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;
|
extern crate log;
|
||||||
|
|
||||||
mod game;
|
mod game;
|
||||||
mod strategies;
|
mod simulator;
|
||||||
|
mod strategies {
|
||||||
|
pub mod examples;
|
||||||
|
}
|
||||||
mod info;
|
mod info;
|
||||||
|
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
|
@ -35,7 +38,23 @@ fn main() {
|
||||||
num_lives: 3,
|
num_lives: 3,
|
||||||
};
|
};
|
||||||
let n = 1;
|
let n = 1;
|
||||||
// strategies::simulate(&opts, &strategies::AlwaysDiscard, n);
|
// simulator::simulate(&opts, &strategies::examples::AlwaysDiscard, n);
|
||||||
// strategies::simulate(&opts, &strategies::AlwaysPlay, n);
|
// simulator::simulate_symmetric(&opts, strategies::examples::AlwaysPlayConfig, n);
|
||||||
strategies::simulate(&opts, &strategies::RandomStrategy, 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