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:
parent
958c63e7d0
commit
1ad4f9b825
4 changed files with 187 additions and 133 deletions
27
src/main.rs
27
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
|
||||
);
|
||||
}
|
||||
|
|
73
src/simulator.rs
Normal file
73
src/simulator.rs
Normal 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)
|
||||
}
|
|
@ -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) {
|
||||
}
|
||||
}
|
91
src/strategies/examples.rs
Normal file
91
src/strategies/examples.rs
Normal 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) {
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue