diff --git a/src/main.rs b/src/main.rs index df6b4b4..3b87b44 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,10 @@ extern crate rand; extern crate log; mod game; -mod strategies; +mod simulator; +mod strategies { + pub mod examples; +} mod info; #[allow(unused_imports)] @@ -35,7 +38,23 @@ fn main() { num_lives: 3, }; let n = 1; - // strategies::simulate(&opts, &strategies::AlwaysDiscard, n); - // strategies::simulate(&opts, &strategies::AlwaysPlay, n); - strategies::simulate(&opts, &strategies::RandomStrategy, n); + // simulator::simulate(&opts, &strategies::examples::AlwaysDiscard, n); + // simulator::simulate_symmetric(&opts, strategies::examples::AlwaysPlayConfig, n); + // simulator::simulate( + // &opts, + // &vec![ + // Box::new(strategies::examples::AlwaysPlayConfig), + // Box::new(strategies::examples::AlwaysPlayConfig), + // Box::new(strategies::examples::AlwaysPlayConfig), + // Box::new(strategies::examples::AlwaysPlayConfig), + // ], + // n); + simulator::simulate_symmetric( + &opts, + strategies::examples::RandomStrategyConfig { + hint_probability: 0.4, + play_probability: 0.2, + }, + n + ); } diff --git a/src/simulator.rs b/src/simulator.rs new file mode 100644 index 0000000..43e4810 --- /dev/null +++ b/src/simulator.rs @@ -0,0 +1,73 @@ +use game::*; +use std::collections::HashMap; + +// Trait to implement for any valid Hanabi strategy +// State management is done by the simulator, to avoid cheating +pub trait Strategy { + fn decide(&mut self, &Player, &GameStateView) -> TurnChoice; + fn update(&mut self, &Turn, &GameStateView); +} +pub trait StrategyConfig { + fn initialize(&self, &Player, &GameStateView) -> Box; +} + +pub fn simulate_once<'a>(opts: &GameOptions, strat_configs: &Vec>) -> Score { + let mut game = GameState::new(opts); + + assert_eq!(opts.num_players, (strat_configs.len() as u32)); + + let mut strategies : HashMap> = HashMap::new(); + let mut i = 0; + for player in game.get_players() { + strategies.insert( + player, + (*strat_configs[i]).initialize(&player, &game.get_view(player)), + ); + i += 1; + } + + while !game.is_over() { + debug!("Turn {}", game.board.turn); + let player = game.board.player; + let choice = { + let mut strategy = strategies.get_mut(&player).unwrap(); + strategy.decide(&player, &game.get_view(player)) + }; + + game.process_choice(&choice); + + let turn = Turn { + player: &player, + choice: &choice, + }; + + for player in game.get_players() { + let mut strategy = strategies.get_mut(&player).unwrap(); + strategy.update(&turn, &game.get_view(player)); + } + + // TODO: do some stuff + debug!("State:\n{}", game); + } + game.score() +} + +pub fn simulate<'a>(opts: &GameOptions, strat_configs: &Vec>, n_trials: u32) -> f32 { + let mut total_score = 0; + for _ in 0..n_trials { + let score = simulate_once(&opts, strat_configs); + info!("Scored: {:?}", score); + total_score += score; + } + let average: f32 = (total_score as f32) / (n_trials as f32); + info!("Average score: {:?}", average); + average +} + +pub fn simulate_symmetric<'a, S: StrategyConfig + Clone + 'a>(opts: &GameOptions, strat_config: S, n_trials: u32) -> f32 { + let mut strat_configs = Vec::new(); + for _ in 0..opts.num_players { + strat_configs.push(Box::new(strat_config.clone()) as Box); + } + simulate(opts, &strat_configs, n_trials) +} diff --git a/src/strategies.rs b/src/strategies.rs deleted file mode 100644 index 3bca303..0000000 --- a/src/strategies.rs +++ /dev/null @@ -1,129 +0,0 @@ -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 -pub trait Strategy { - type InternalState; - fn initialize(&Player, &GameStateView) -> Self::InternalState; - fn decide(&mut Self::InternalState, &Player, &GameStateView) -> TurnChoice; - fn update(&mut Self::InternalState, &Turn, &GameStateView); -} - -pub fn simulate_once(opts: &GameOptions, _: &S) -> Score { - let mut game = GameState::new(opts); - - let mut internal_states : HashMap = HashMap::new(); - for player in game.get_players() { - internal_states.insert( - player, - S::initialize(&player, &game.get_view(player)), - ); - } - - 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(); - S::decide(internal_state, &player, &game.get_view(player)) - }; - - game.process_choice(&choice); - - let turn = Turn { - player: &player, - choice: &choice, - }; - - for player in game.get_players() { - let ref mut internal_state = internal_states.get_mut(&player).unwrap(); - - S::update(internal_state, &turn, &game.get_view(player)); - } - - // TODO: do some stuff - debug!("State:\n{}", game); - } - game.score() -} - -pub fn simulate(opts: &GameOptions, strategy: &S, n_trials: u32) -> f32 { - let mut total_score = 0; - for _ in 0..n_trials { - let score = simulate_once(&opts, strategy); - info!("Scored: {:?}", score); - total_score += score; - } - let average: f32 = (total_score as f32) / (n_trials as f32); - info!("Average score: {:?}", average); - average -} - -// dummy, terrible strategy -#[allow(dead_code)] -pub struct AlwaysPlay; -impl Strategy for AlwaysPlay { - type InternalState = (); - fn initialize(_: &Player, _: &GameStateView) -> () { - () - } - fn decide(_: &mut (), _: &Player, _: &GameStateView) -> TurnChoice { - TurnChoice::Play(0) - } - 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 (), me: &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: view.board.player_to_left(&me), - hinted: hinted, - }) - } else { - TurnChoice::Discard(0) - } - } else if p < 0.8 { - TurnChoice::Discard(0) - } else { - TurnChoice::Play(0) - } - } - fn update(_: &mut (), _: &Turn, _: &GameStateView) { - } -} diff --git a/src/strategies/examples.rs b/src/strategies/examples.rs new file mode 100644 index 0000000..e0e9e15 --- /dev/null +++ b/src/strategies/examples.rs @@ -0,0 +1,91 @@ +use simulator::*; +use game::*; +use rand::{self, Rng}; + +// dummy, terrible strategy +#[allow(dead_code)] +#[derive(Clone)] +pub struct AlwaysPlayConfig; +impl StrategyConfig for AlwaysPlayConfig { + fn initialize(&self, _: &Player, _: &GameStateView) -> Box { + Box::new(AlwaysPlay) + } +} +pub struct AlwaysPlay; +impl Strategy for AlwaysPlay { + fn decide(&mut self, _: &Player, _: &GameStateView) -> TurnChoice { + TurnChoice::Play(0) + } + fn update(&mut self, _: &Turn, _: &GameStateView) { + } +} + +// dummy, terrible strategy +#[allow(dead_code)] +#[derive(Clone)] +pub struct AlwaysDiscardConfig; +impl StrategyConfig for AlwaysDiscardConfig { + fn initialize(&self, _: &Player, _: &GameStateView) -> Box { + Box::new(AlwaysDiscard) + } +} +pub struct AlwaysDiscard; +impl Strategy for AlwaysDiscard { + fn decide(&mut self, _: &Player, _: &GameStateView) -> TurnChoice { + TurnChoice::Discard(0) + } + fn update(&mut self, _: &Turn, _: &GameStateView) { + } +} + + +// dummy, terrible strategy +#[allow(dead_code)] +#[derive(Clone)] +pub struct RandomStrategyConfig { + pub hint_probability: f64, + pub play_probability: f64, +} + +impl StrategyConfig for RandomStrategyConfig { + fn initialize(&self, _: &Player, _: &GameStateView) -> Box { + Box::new(RandomStrategy { + hint_probability: self.hint_probability, + play_probability: self.play_probability, + }) + } +} +pub struct RandomStrategy { + pub hint_probability: f64, + pub play_probability: f64, +} + +impl Strategy for RandomStrategy { + fn decide(&mut self, me: &Player, view: &GameStateView) -> TurnChoice { + let p = rand::random::(); + if p < self.hint_probability { + 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: view.board.player_to_left(&me), + hinted: hinted, + }) + } else { + TurnChoice::Discard(0) + } + } else if p < self.hint_probability + self.play_probability { + TurnChoice::Play(0) + } else { + TurnChoice::Discard(0) + } + } + fn update(&mut self, _: &Turn, _: &GameStateView) { + } +}