improvements to strategies API

make strategies its own module
use trait objects so you can mix and match strategies
stop using internal state associated type..
This commit is contained in:
Jeff Wu 2016-03-10 22:26:32 -08:00
parent 958c63e7d0
commit 1ad4f9b825
4 changed files with 187 additions and 133 deletions

View File

@ -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
);
}

73
src/simulator.rs Normal file
View File

@ -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<Strategy>;
}
pub fn simulate_once<'a>(opts: &GameOptions, strat_configs: &Vec<Box<StrategyConfig + 'a>>) -> Score {
let mut game = GameState::new(opts);
assert_eq!(opts.num_players, (strat_configs.len() as u32));
let mut strategies : HashMap<Player, Box<Strategy>> = 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<Box<StrategyConfig + 'a>>, 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<StrategyConfig + 'a>);
}
simulate(opts, &strat_configs, n_trials)
}

View File

@ -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<S: Strategy>(opts: &GameOptions, _: &S) -> Score {
let mut game = GameState::new(opts);
let mut internal_states : HashMap<Player, S::InternalState> = 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<S: Strategy>(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::<f64>();
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) {
}
}

View File

@ -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<Strategy> {
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<Strategy> {
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<Strategy> {
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::<f64>();
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) {
}
}