diff --git a/src/game.rs b/src/game.rs index a30aade..7cef3c4 100644 --- a/src/game.rs +++ b/src/game.rs @@ -1,7 +1,6 @@ use rand::{self, Rng}; use std::convert::From; -use std::collections::HashSet; use std::collections::HashMap; use std::fmt; @@ -20,13 +19,15 @@ pub const VALUES : [Value; 5] = [1, 2, 3, 4, 5]; pub const VALUE_COUNTS : [(Value, u32); 5] = [(1, 3), (2, 2), (3, 2), (4, 2), (5, 1)]; pub const FINAL_VALUE : Value = 5; +#[derive(Debug)] pub struct Card { pub color: Color, pub value: Value, } -impl fmt::Debug for Card { +impl fmt::Display for Card { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{} {}", self.color, self.value) + let colorchar = self.color.chars().next().unwrap(); + write!(f, "{}{}", colorchar, self.value) } } @@ -61,6 +62,15 @@ impl From> for Pile { Pile(items) } } +impl fmt::Display for Pile where T: fmt::Display { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "["); + for item in &self.0 { + write!(f, "{}, ", item); + } + write!(f, "] ") + } +} pub type Cards = Pile; @@ -69,15 +79,29 @@ pub type CardsInfo = Pile; pub type Player = u32; #[derive(Debug)] -pub enum Hint { - Color, - Value, +pub enum Hinted { + Color(Color), + Value(Value), +} +impl fmt::Display for Hinted { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + &Hinted::Color(color) => { write!(f, "{}", color) } + &Hinted::Value(value) => { write!(f, "{}", value) } + } + } +} + +#[derive(Debug)] +pub struct Hint { + pub player: Player, + pub hinted: Hinted, } // represents the choice a player made in a given turn #[derive(Debug)] pub enum TurnChoice { - Hint, + Hint(Hint), Discard(usize), Play(usize), } @@ -102,9 +126,57 @@ pub struct GameOptions { #[derive(Debug)] pub struct PlayerState { // the player's actual hand - pub hand: Cards, + hand: Cards, // represents what is common knowledge about the player's hand - pub info: CardsInfo, + info: CardsInfo, +} +impl PlayerState { + pub fn new(hand: Cards) -> PlayerState { + let infos = (0..hand.size()).map(|_| { + CardInfo::new() + }).collect::>(); + PlayerState { + hand: hand, + info: CardsInfo::from(infos), + } + } + + pub fn take(&mut self, index: usize) -> (Card, CardInfo) { + let card = self.hand.take(index); + let info = self.info.take(index); + (card, info) + } + + pub fn place(&mut self, card: Card) { + self.hand.place(card); + self.info.place(CardInfo::new()); + } + + pub fn reveal(&mut self, hinted: &Hinted) { + match hinted { + &Hinted::Color(ref color) => { + let mut i = 0; + for card in &self.hand.0 { + self.info.0[i].color_info.mark( + color, + card.color == *color + ); + i += 1; + } + } + &Hinted::Value(ref value) => { + let mut i = 0; + for card in &self.hand.0 { + self.info.0[i].value_info.mark( + value, + card.value == *value + ); + i += 1; + } + } + + } + } } // State of everything except the player's hands @@ -163,14 +235,9 @@ impl GameState { // we can assume the deck is big enough to draw initial hands deck.draw().unwrap() }).collect::>(); - let infos = (0..opts.hand_size).map(|_| { - CardInfo::new() - }).collect::>(); - let state = PlayerState { - hand: Cards::from(raw_hand), - info: CardsInfo::from(infos), - }; - player_states.insert(i, state); + player_states.insert( + i, PlayerState::new(Cards::from(raw_hand)), + ); } let mut fireworks : HashMap = HashMap::new(); @@ -211,7 +278,7 @@ impl GameState { } }; deck.shuffle(); - info!("Created deck: {:?}", deck); + info!("Created deck: {}", deck); deck } @@ -253,11 +320,9 @@ impl GameState { // takes a card from the player's hand, and replaces it if possible fn take_from_hand(&mut self, index: usize) -> Card { let ref mut state = self.player_states.get_mut(&self.board.player).unwrap(); - let card = state.hand.take(index); - state.info.take(index); + let (card, _) = state.take(index); if let Some(new_card) = self.board.deck.draw() { - state.hand.place(new_card); - state.info.place(CardInfo::new()); + state.place(new_card); } card } @@ -269,16 +334,19 @@ impl GameState { } pub fn process_choice(&mut self, choice: &TurnChoice) { + info!("Player {}'s move", self.board.player); match *choice { - TurnChoice::Hint => { + TurnChoice::Hint(ref hint) => { assert!(self.board.hints_remaining > 0); self.board.hints_remaining -= 1; - // TODO: actually inform player of values.. - // nothing to update, really... - // TODO: manage common knowledge + info!("Hint to player {}, about {}", hint.player, hint.hinted); + + let ref mut state = self.player_states.get_mut(&hint.player).unwrap(); + state.reveal(&hint.hinted); } TurnChoice::Discard(index) => { let card = self.take_from_hand(index); + info!("Discard {}, which is {}", index, card); self.board.discard.place(card); self.try_add_hint(); @@ -286,8 +354,8 @@ impl GameState { TurnChoice::Play(index) => { let card = self.take_from_hand(index); - debug!( - "Here! Playing card at {}, which is {:?}", + info!( + "Playing card at {}, which is {}", index, card ); @@ -307,7 +375,7 @@ impl GameState { } else { self.board.discard.place(card); self.board.lives_remaining -= 1; - debug!( + info!( "Removing a life! Lives remaining: {}", self.board.lives_remaining ); diff --git a/src/info.rs b/src/info.rs index 77f05cb..c7d2c22 100644 --- a/src/info.rs +++ b/src/info.rs @@ -5,7 +5,7 @@ use std::hash::Hash; use game::*; // Represents a bit of information about T -trait Info where T: Hash + Eq + Clone { +pub trait Info where T: Hash + Eq + Clone { // get all a-priori possibilities fn get_possibilities() -> Vec; @@ -42,6 +42,14 @@ trait Info where T: Hash + Eq + Clone { fn mark_false(&mut self, value: &T) { self.get_mut_possibility_map().insert(value.clone(), false); } + + fn mark(&mut self, value: &T, info: bool) { + if info { + self.mark_true(value); + } else { + self.mark_false(value); + } + } } #[derive(Debug)] diff --git a/src/main.rs b/src/main.rs index b3aad5c..3b9e8b9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,9 +10,7 @@ mod info; struct SimpleLogger; impl log::Log for SimpleLogger { fn enabled(&self, metadata: &log::LogMetadata) -> bool { - // metadata.level() <= log::LogLevel::Warn - metadata.level() <= log::LogLevel::Info - // metadata.level() <= log::LogLevel::Debug + true } fn log(&self, record: &log::LogRecord) { @@ -24,6 +22,7 @@ impl log::Log for SimpleLogger { fn main() { log::set_logger(|max_log_level| { + // Trace, Debug, Info, Warn, ... max_log_level.set(log::LogLevelFilter::Info); Box::new(SimpleLogger) }); @@ -34,5 +33,8 @@ fn main() { num_hints: 8, num_lives: 3, }; - strategies::simulate(&opts, &strategies::AlwaysPlay, 100); + let n = 1; + // strategies::simulate(&opts, &strategies::AlwaysDiscard, n); + // strategies::simulate(&opts, &strategies::AlwaysPlay, n); + strategies::simulate(&opts, &strategies::RandomStrategy, n); } diff --git a/src/strategies.rs b/src/strategies.rs index 8945960..8d45f0c 100644 --- a/src/strategies.rs +++ b/src/strategies.rs @@ -1,5 +1,6 @@ use game::*; use std::collections::HashMap; +use rand::{self, Rng}; // Trait to implement for any valid Hanabi strategy // State management is done by the simulator, to avoid cheating @@ -10,7 +11,7 @@ pub trait Strategy { fn update(&mut Self::InternalState, &Turn, &GameStateView); } -pub fn simulate_once(opts: &GameOptions, strategy: &S) -> Score { +pub fn simulate_once(opts: &GameOptions, _: &S) -> Score { let mut game = GameState::new(opts); let mut internal_states : HashMap = HashMap::new(); @@ -22,6 +23,7 @@ pub fn simulate_once(opts: &GameOptions, strategy: &S) -> Score { } while !game.is_over() { + debug!("Turn {}", game.board.turn); let player = game.board.player; let choice = { let ref mut internal_state = internal_states.get_mut(&player).unwrap(); @@ -30,7 +32,6 @@ pub fn simulate_once(opts: &GameOptions, strategy: &S) -> Score { game.process_choice(&choice); - info!("Player {:?} decided to {:?}", player, choice); let turn = Turn { player: &player, choice: &choice, @@ -43,7 +44,7 @@ pub fn simulate_once(opts: &GameOptions, strategy: &S) -> Score { } // TODO: do some stuff - info!("State: {:?}", game); + debug!("State: {:?}", game); } game.score() } @@ -61,15 +62,68 @@ pub fn simulate(opts: &GameOptions, strategy: &S, n_trials: u32) -> } // dummy, terrible strategy +#[allow(dead_code)] pub struct AlwaysPlay; impl Strategy for AlwaysPlay { type InternalState = (); - fn initialize(player: &Player, view: &GameStateView) -> () { + fn initialize(_: &Player, _: &GameStateView) -> () { () } - fn decide(_: &mut (), player: &Player, view: &GameStateView) -> TurnChoice { + fn decide(_: &mut (), _: &Player, _: &GameStateView) -> TurnChoice { TurnChoice::Play(0) } - fn update(_: &mut (), turn: &Turn, view: &GameStateView) { + fn update(_: &mut (), _: &Turn, _: &GameStateView) { + } +} + +// dummy, terrible strategy +#[allow(dead_code)] +pub struct AlwaysDiscard; +impl Strategy for AlwaysDiscard { + type InternalState = (); + fn initialize(_: &Player, _: &GameStateView) -> () { + () + } + fn decide(_: &mut (), _: &Player, _: &GameStateView) -> TurnChoice { + TurnChoice::Discard(0) + } + fn update(_: &mut (), _: &Turn, _: &GameStateView) { + } +} + +// dummy, terrible strategy +#[allow(dead_code)] +pub struct RandomStrategy; +impl Strategy for RandomStrategy { + type InternalState = (); + fn initialize(_: &Player, _: &GameStateView) -> () { + () + } + fn decide(_: &mut (), _: &Player, view: &GameStateView) -> TurnChoice { + let p = rand::random::(); + if p < 0.4 { + if view.board.hints_remaining > 0 { + let hinted = { + if rand::random() { + // hint a color + Hinted::Color(rand::thread_rng().choose(&COLORS).unwrap()) + } else { + Hinted::Value(*rand::thread_rng().choose(&VALUES).unwrap()) + } + }; + TurnChoice::Hint(Hint { + player: 0, + hinted: hinted, + }) + } else { + TurnChoice::Discard(0) + } + } else if p < 0.8 { + TurnChoice::Discard(0) + } else { + TurnChoice::Play(0) + } + } + fn update(_: &mut (), _: &Turn, _: &GameStateView) { } }