diff --git a/README.md b/README.md index 8caab42..d314ada 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ cargo run -- -s 222 -p 5 -g info -l debug | less ## Strategies -To write a strategy, you simply [implement a few traits](src/simulator.rs). +To write a strategy, you simply [implement a few traits](src/strategy.rs). The framework is designed to take advantage of Rust's ownership system so that you *can't cheat*, without using stuff like `Cell` or `Arc` or `Mutex`. diff --git a/src/game.rs b/src/game.rs index 7fcc5d5..d72ec1b 100644 --- a/src/game.rs +++ b/src/game.rs @@ -23,7 +23,7 @@ pub fn get_count_for_value(value: Value) -> u32 { } } -#[derive(Debug,Clone,PartialEq,Eq,Hash,Ord,PartialOrd)] +#[derive(Clone,PartialEq,Eq,Hash,Ord,PartialOrd)] pub struct Card { pub color: Color, pub value: Value, @@ -38,6 +38,11 @@ impl fmt::Display for Card { write!(f, "{}{}", self.color, self.value) } } +impl fmt::Debug for Card { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}{}", self.color, self.value) + } +} #[derive(Debug,Clone)] pub struct CardCounts { @@ -217,11 +222,12 @@ pub enum TurnResult { // represents a turn taken in the game #[derive(Debug,Clone)] -pub struct Turn { +pub struct TurnRecord { pub player: Player, pub choice: TurnChoice, pub result: TurnResult, } +pub type TurnHistory = Vec; // represents possible settings for the game pub struct GameOptions { @@ -248,6 +254,7 @@ pub struct BoardState { // which turn is it? pub turn: u32, + pub turn_history: TurnHistory, // // whose turn is it? pub player: Player, pub hand_size: u32, @@ -257,7 +264,6 @@ pub struct BoardState { pub allow_empty_hints: bool, pub lives_total: u32, pub lives_remaining: u32, - pub turn_history: Vec, // only relevant when deck runs out pub deckless_turns_remaining: u32, } @@ -628,7 +634,7 @@ impl GameState { } } - pub fn process_choice(&mut self, choice: TurnChoice) -> Turn { + pub fn process_choice(&mut self, choice: TurnChoice) -> TurnRecord { let turn_result = { match choice { TurnChoice::Hint(ref hint) => { @@ -649,8 +655,9 @@ impl GameState { hand.iter().map(|card| { card.value == value }).collect::>() } }; - if (!self.board.allow_empty_hints) && (results.iter().all(|matched| !matched)) { - panic!("Tried hinting an empty hint"); + if !self.board.allow_empty_hints { + assert!(results.iter().any(|matched| *matched), + "Tried hinting an empty hint"); } TurnResult::Hint(results) @@ -693,12 +700,12 @@ impl GameState { } } }; - let turn = Turn { + let turn_record = TurnRecord { player: self.board.player.clone(), result: turn_result, choice: choice, }; - self.board.turn_history.push(turn.clone()); + self.board.turn_history.push(turn_record.clone()); self.replenish_hand(); @@ -712,6 +719,6 @@ impl GameState { }; assert_eq!((self.board.turn - 1) % self.board.num_players, self.board.player); - turn + turn_record } } diff --git a/src/main.rs b/src/main.rs index 167969f..5069fd3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,7 @@ extern crate crossbeam; mod helpers; mod game; mod simulator; +mod strategy; mod strategies { pub mod examples; pub mod cheating; @@ -122,20 +123,20 @@ fn main() { }; let strategy_str : &str = &matches.opt_str("g").unwrap_or("cheat".to_string()); - let strategy_config : Box = match strategy_str { + let strategy_config : Box = match strategy_str { "random" => { Box::new(strategies::examples::RandomStrategyConfig { hint_probability: 0.4, play_probability: 0.2, - }) as Box + }) as Box }, "cheat" => { Box::new(strategies::cheating::CheatingStrategyConfig::new()) - as Box + as Box }, "info" => { Box::new(strategies::information::InformationStrategyConfig::new()) - as Box + as Box }, _ => { print_usage(&program, opts); diff --git a/src/simulator.rs b/src/simulator.rs index 9387194..2ebf36a 100644 --- a/src/simulator.rs +++ b/src/simulator.rs @@ -4,46 +4,21 @@ use std::fmt; use crossbeam; use game::*; - -// Traits to implement for any valid Hanabi strategy - -// Represents the strategy of a given player -pub trait PlayerStrategy { - // A function to decide what to do on the player's turn. - // Given a BorrowedGameView, outputs their choice. - fn decide(&mut self, &BorrowedGameView) -> TurnChoice; - // A function to update internal state after other players' turns. - // Given what happened last turn, and the new state. - fn update(&mut self, &Turn, &BorrowedGameView); -} -// Represents the overall strategy for a game -// Shouldn't do much, except store configuration parameters and -// possibility initialize some shared randomness between players -pub trait GameStrategy { - fn initialize(&self, Player, &BorrowedGameView) -> Box; -} - -// Represents configuration for a strategy. -// Acts as a factory for game strategies, so we can play many rounds -pub trait GameStrategyConfig { - fn initialize(&self, &GameOptions) -> Box; -} +use strategy::*; fn new_deck(seed: u32) -> Cards { let mut deck: Cards = Cards::new(); for &color in COLORS.iter() { for &value in VALUES.iter() { - let count = get_count_for_value(value); - for _ in 0..count { + for _ in 0..get_count_for_value(value) { deck.push(Card::new(color, value)); } } }; rand::ChaChaRng::from_seed(&[seed]).shuffle(&mut deck[..]); - - trace!("Created deck: {:?}", deck); + debug!("Deck: {:?}", deck); deck } @@ -59,7 +34,7 @@ pub fn simulate_once( let mut game = GameState::new(opts, deck); let mut strategies = game.get_players().map(|player| { - (player, game_strategy.initialize(player.clone(), &game.get_view(player))) + (player, game_strategy.initialize(player, &game.get_view(player))) }).collect::>>(); while !game.is_over() { diff --git a/src/strategies/cheating.rs b/src/strategies/cheating.rs index b7010bb..6f64406 100644 --- a/src/strategies/cheating.rs +++ b/src/strategies/cheating.rs @@ -2,7 +2,7 @@ use std::rc::Rc; use std::cell::{RefCell}; use std::collections::{HashMap, HashSet}; -use simulator::*; +use strategy::*; use game::*; // strategy that explicitly cheats by using Rc/RefCell @@ -208,6 +208,6 @@ impl PlayerStrategy for CheatingPlayerStrategy { } TurnChoice::Discard(index) } - fn update(&mut self, _: &Turn, _: &BorrowedGameView) { + fn update(&mut self, _: &TurnRecord, _: &BorrowedGameView) { } } diff --git a/src/strategies/examples.rs b/src/strategies/examples.rs index 7d3b827..60c7aad 100644 --- a/src/strategies/examples.rs +++ b/src/strategies/examples.rs @@ -1,4 +1,4 @@ -use simulator::*; +use strategy::*; use game::*; use rand::{self, Rng}; @@ -66,6 +66,6 @@ impl PlayerStrategy for RandomStrategyPlayer { TurnChoice::Discard(0) } } - fn update(&mut self, _: &Turn, _: &BorrowedGameView) { + fn update(&mut self, _: &TurnRecord, _: &BorrowedGameView) { } } diff --git a/src/strategies/information.rs b/src/strategies/information.rs index 6980636..8ea5680 100644 --- a/src/strategies/information.rs +++ b/src/strategies/information.rs @@ -1,12 +1,14 @@ use std::collections::{HashMap, HashSet}; use std::cmp::Ordering; -use simulator::*; +use strategy::*; use game::*; use helpers::*; // TODO: use random extra information - i.e. when casting up and down, // we sometimes have 2 choices of value to choose +// TODO: guess very aggressively at very end of game (first, see whether +// situation ever occurs) #[derive(Debug,Clone)] struct ModulusInformation { @@ -39,8 +41,7 @@ impl ModulusInformation { self.modulus = self.modulus / modulus; let value = self.value / self.modulus; self.value = self.value - value * self.modulus; - trace!("orig value {}, orig modulus {}, self.value {}, self.modulus {}, value {}, modulus {}", - original_value, original_modulus, self.value, self.modulus, value, modulus); + assert!(original_modulus == modulus * self.modulus); assert!(original_value == value * self.modulus + self.value); Self::new(modulus, value) } @@ -303,8 +304,6 @@ impl InformationPlayerStrategy { return questions; } } - } else { - debug!("Something known playable"); } let mut ask_partition = augmented_hand_info.iter() @@ -354,10 +353,8 @@ impl InformationPlayerStrategy { assert!(player != &self.me); let hand_info = self.get_player_public_info(player); let questions = Self::get_questions(total_info, view, hand_info); - trace!("Getting hint for player {}, questions {:?}", player, questions.len()); let mut answer = Self::answer_questions(&questions, view.get_hand(player), view); answer.cast_up(total_info); - trace!("Resulting answer {:?}", answer); answer } @@ -379,11 +376,8 @@ impl InformationPlayerStrategy { let hand_info = self.get_my_public_info(); Self::get_questions(hint.modulus, view, hand_info) }; - trace!("{}: Questions {:?}", self.me, questions.len()); let product = questions.iter().fold(1, |a, ref b| a * b.info_amount()); - trace!("{}: Product {}, hint: {:?}", self.me, product, hint); hint.cast_down(product); - trace!("{}: Inferred for myself {:?}", self.me, hint); let me = self.me.clone(); let mut hand_info = self.take_public_info(&me); @@ -404,14 +398,12 @@ impl InformationPlayerStrategy { let view = &self.last_view; view.board.get_players() }; - trace!("{}: inferring for myself, starting with {:?}", self.me, hint); for player in players { if (player != hinter) && (player != self.me) { { let view = &self.last_view; let hint_info = self.get_hint_info_for_player(&player, hint.modulus, view); hint.subtract(&hint_info); - trace!("{}: subtracted for {}, now {:?}", self.me, player, hint); } // *take* instead of borrowing mutably, because of borrow rules... @@ -856,20 +848,20 @@ impl PlayerStrategy for InformationPlayerStrategy { TurnChoice::Discard(index) } - fn update(&mut self, turn: &Turn, view: &BorrowedGameView) { - match turn.choice { + fn update(&mut self, turn_record: &TurnRecord, view: &BorrowedGameView) { + match turn_record.choice { TurnChoice::Hint(ref hint) => { - if let &TurnResult::Hint(ref matches) = &turn.result { + if let &TurnResult::Hint(ref matches) = &turn_record.result { self.infer_from_hint(hint, matches); self.update_public_info_for_hint(hint, matches); } else { panic!("Got turn choice {:?}, but turn result {:?}", - turn.choice, turn.result); + turn_record.choice, turn_record.result); } } TurnChoice::Discard(index) => { let known_useless_indices = self.find_useless_cards( - &self.last_view, &self.get_player_public_info(&turn.player) + &self.last_view, &self.get_player_public_info(&turn_record.player) ); if known_useless_indices.len() > 1 { @@ -881,19 +873,19 @@ impl PlayerStrategy for InformationPlayerStrategy { )); } - if let &TurnResult::Discard(ref card) = &turn.result { - self.update_public_info_for_discard_or_play(view, &turn.player, index, card); + if let &TurnResult::Discard(ref card) = &turn_record.result { + self.update_public_info_for_discard_or_play(view, &turn_record.player, index, card); } else { panic!("Got turn choice {:?}, but turn result {:?}", - turn.choice, turn.result); + turn_record.choice, turn_record.result); } } TurnChoice::Play(index) => { - if let &TurnResult::Play(ref card, _) = &turn.result { - self.update_public_info_for_discard_or_play(view, &turn.player, index, card); + if let &TurnResult::Play(ref card, _) = &turn_record.result { + self.update_public_info_for_discard_or_play(view, &turn_record.player, index, card); } else { panic!("Got turn choice {:?}, but turn result {:?}", - turn.choice, turn.result); + turn_record.choice, turn_record.result); } } } diff --git a/src/strategy.rs b/src/strategy.rs new file mode 100644 index 0000000..8ef667f --- /dev/null +++ b/src/strategy.rs @@ -0,0 +1,26 @@ +use game::*; + +// Traits to implement for any valid Hanabi strategy + +// Represents the strategy of a given player +pub trait PlayerStrategy { + // A function to decide what to do on the player's turn. + // Given a BorrowedGameView, outputs their choice. + fn decide(&mut self, &BorrowedGameView) -> TurnChoice; + // A function to update internal state after other players' turns. + // Given what happened last turn, and the new state. + fn update(&mut self, &TurnRecord, &BorrowedGameView); +} +// Represents the overall strategy for a game +// Shouldn't do much, except store configuration parameters and +// possibility initialize some shared randomness between players +pub trait GameStrategy { + fn initialize(&self, Player, &BorrowedGameView) -> Box; +} + +// Represents configuration for a strategy. +// Acts as a factory for game strategies, so we can play many rounds +pub trait GameStrategyConfig { + fn initialize(&self, &GameOptions) -> Box; +} +