Merge pull request #9 from felixbauckholt/determinism

Determinism
This commit is contained in:
Felix Bauckholt 2019-03-07 20:56:05 +01:00 committed by GitHub
commit 92aa0e703a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 187 additions and 76 deletions

7
Cargo.lock generated
View file

@ -3,6 +3,11 @@ name = "crossbeam"
version = "0.2.8" version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "fnv"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]] [[package]]
name = "getopts" name = "getopts"
version = "0.2.14" version = "0.2.14"
@ -34,6 +39,7 @@ name = "rust_hanabi"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"crossbeam 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
"getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", "getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
@ -41,6 +47,7 @@ dependencies = [
[metadata] [metadata]
"checksum crossbeam 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "348228ce9f93d20ffc30c18e575f82fa41b9c8bf064806c65d41eba4771595a0" "checksum crossbeam 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "348228ce9f93d20ffc30c18e575f82fa41b9c8bf064806c65d41eba4771595a0"
"checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3"
"checksum getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9047cfbd08a437050b363d35ef160452c5fe8ea5187ae0a624708c91581d685" "checksum getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9047cfbd08a437050b363d35ef160452c5fe8ea5187ae0a624708c91581d685"
"checksum libc 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "4870ef6725dde13394134e587e4ab4eca13cb92e916209a31c851b49131d3c75" "checksum libc 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "4870ef6725dde13394134e587e4ab4eca13cb92e916209a31c851b49131d3c75"
"checksum log 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "038b5d13189a14e5b6ac384fdb7c691a45ef0885f6d2dddbf422e6c3506b8234" "checksum log 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "038b5d13189a14e5b6ac384fdb7c691a45ef0885f6d2dddbf422e6c3506b8234"

View file

@ -7,4 +7,5 @@ authors = ["Jeff Wu <wuthefwasthat@gmail.com>"]
rand = "*" rand = "*"
log = "*" log = "*"
getopts = "*" getopts = "*"
fnv = "*"
crossbeam = "0.2.5" crossbeam = "0.2.5"

View file

@ -55,25 +55,23 @@ Some examples:
- [A cheating strategy](src/strategies/cheating.rs), using `Rc<RefCell<_>>` - [A cheating strategy](src/strategies/cheating.rs), using `Rc<RefCell<_>>`
- [The information strategy](src/strategies/information.rs)! - [The information strategy](src/strategies/information.rs)!
## Results ## Results (auto-generated)
On seeds 0-9999, we have these average scores and win rates:
| | 2p | 3p | 4p | 5p |
|-------|---------|---------|---------|---------|
|cheat | 24.8600 | 24.9781 | 24.9715 | 24.9570 |
| | 90.52 % | 98.12 % | 97.74 % | 96.57 % |
|info | 22.3386 | 24.7322 | 24.8921 | 24.8996 |
| | 09.86 % | 80.75 % | 91.58 % | 92.11 % |
To reproduce: To reproduce:
``` ```
n=10000 # number of rounds to simulate time cargo run --release -- --results-table
t=4 # number of threads
for strategy in info cheat; do
for p in $(seq 2 5); do
time cargo run --release -- -n $n -s 0 -t $t -p $p -g $strategy;
done
done
``` ```
To update this file:
```
time cargo run --release -- --write-results-table
```
On the first 20000 seeds, we have these average scores and win rates:
| | 2p | 3p | 4p | 5p |
|---------|---------|---------|---------|---------|
| cheat | 24.8594 | 24.9785 | 24.9720 | 24.9557 |
| | 90.59 % | 98.17 % | 97.76 % | 96.42 % |
| info | 22.3249 | 24.7278 | 24.8919 | 24.8961 |
| | 09.81 % | 80.54 % | 91.67 % | 91.90 % |

View file

@ -1,4 +1,4 @@
use std::collections::HashMap; use fnv::FnvHashMap;
use std::fmt; use std::fmt;
use std::ops::Range; use std::ops::Range;
@ -46,11 +46,11 @@ impl fmt::Debug for Card {
#[derive(Debug,Clone)] #[derive(Debug,Clone)]
pub struct CardCounts { pub struct CardCounts {
counts: HashMap<Card, u32>, counts: FnvHashMap<Card, u32>,
} }
impl CardCounts { impl CardCounts {
pub fn new() -> CardCounts { pub fn new() -> CardCounts {
let mut counts = HashMap::new(); let mut counts = FnvHashMap::default();
for &color in COLORS.iter() { for &color in COLORS.iter() {
for &value in VALUES.iter() { for &value in VALUES.iter() {
counts.insert(Card::new(color, value), 0); counts.insert(Card::new(color, value), 0);
@ -248,7 +248,7 @@ pub struct BoardState {
pub deck_size: u32, pub deck_size: u32,
pub total_cards: u32, pub total_cards: u32,
pub discard: Discard, pub discard: Discard,
pub fireworks: HashMap<Color, Firework>, pub fireworks: FnvHashMap<Color, Firework>,
pub num_players: u32, pub num_players: u32,
@ -271,7 +271,7 @@ impl BoardState {
pub fn new(opts: &GameOptions, deck_size: u32) -> BoardState { pub fn new(opts: &GameOptions, deck_size: u32) -> BoardState {
let fireworks = COLORS.iter().map(|&color| { let fireworks = COLORS.iter().map(|&color| {
(color, Firework::new(color)) (color, Firework::new(color))
}).collect::<HashMap<_, _>>(); }).collect::<FnvHashMap<_, _>>();
BoardState { BoardState {
deck_size: deck_size, deck_size: deck_size,
@ -479,7 +479,7 @@ pub struct BorrowedGameView<'a> {
pub player: Player, pub player: Player,
pub hand_size: usize, pub hand_size: usize,
// the cards of the other players, as well as the information they have // the cards of the other players, as well as the information they have
pub other_hands: HashMap<Player, &'a Cards>, pub other_hands: FnvHashMap<Player, &'a Cards>,
// board state // board state
pub board: &'a BoardState, pub board: &'a BoardState,
} }
@ -506,7 +506,7 @@ pub struct OwnedGameView {
pub player: Player, pub player: Player,
pub hand_size: usize, pub hand_size: usize,
// the cards of the other players, as well as the information they have // the cards of the other players, as well as the information they have
pub other_hands: HashMap<Player, Cards>, pub other_hands: FnvHashMap<Player, Cards>,
// board state // board state
pub board: BoardState, pub board: BoardState,
} }
@ -515,7 +515,7 @@ impl OwnedGameView {
let other_hands = borrowed_view.other_hands.iter() let other_hands = borrowed_view.other_hands.iter()
.map(|(&other_player, &player_state)| { .map(|(&other_player, &player_state)| {
(other_player, player_state.clone()) (other_player, player_state.clone())
}).collect::<HashMap<_, _>>(); }).collect::<FnvHashMap<_, _>>();
OwnedGameView { OwnedGameView {
player: borrowed_view.player.clone(), player: borrowed_view.player.clone(),
@ -544,7 +544,7 @@ impl GameView for OwnedGameView {
// complete game state (known to nobody!) // complete game state (known to nobody!)
#[derive(Debug)] #[derive(Debug)]
pub struct GameState { pub struct GameState {
pub hands: HashMap<Player, Cards>, pub hands: FnvHashMap<Player, Cards>,
pub board: BoardState, pub board: BoardState,
pub deck: Cards, pub deck: Cards,
} }
@ -582,7 +582,7 @@ impl GameState {
deck.pop().unwrap() deck.pop().unwrap()
}).collect::<Vec<_>>(); }).collect::<Vec<_>>();
(player, hand) (player, hand)
}).collect::<HashMap<_, _>>(); }).collect::<FnvHashMap<_, _>>();
GameState { GameState {
hands: hands, hands: hands,
@ -605,7 +605,7 @@ impl GameState {
// get the game state view of a particular player // get the game state view of a particular player
pub fn get_view(&self, player: Player) -> BorrowedGameView { pub fn get_view(&self, player: Player) -> BorrowedGameView {
let mut other_hands = HashMap::new(); let mut other_hands = FnvHashMap::default();
for (&other_player, hand) in &self.hands { for (&other_player, hand) in &self.hands {
if player != other_player { if player != other_player {
other_hands.insert(other_player, hand); other_hands.insert(other_player, hand);

View file

@ -3,6 +3,7 @@ extern crate getopts;
extern crate log; extern crate log;
extern crate rand; extern crate rand;
extern crate crossbeam; extern crate crossbeam;
extern crate fnv;
mod helpers; mod helpers;
mod game; mod game;
@ -64,6 +65,10 @@ fn main() {
"STRATEGY"); "STRATEGY");
opts.optflag("h", "help", opts.optflag("h", "help",
"Print this help menu"); "Print this help menu");
opts.optflag("", "results-table",
"Print a table of results for each strategy");
opts.optflag("", "write-results-table",
"Update the results table in README.md");
let matches = match opts.parse(&args[1..]) { let matches = match opts.parse(&args[1..]) {
Ok(m) => { m } Ok(m) => { m }
Err(f) => { Err(f) => {
@ -77,6 +82,12 @@ fn main() {
if !matches.free.is_empty() { if !matches.free.is_empty() {
return print_usage(&program, opts); return print_usage(&program, opts);
} }
if matches.opt_present("write-results-table") {
return write_results_table();
}
if matches.opt_present("results-table") {
return print!("{}", get_results_table());
}
let log_level_str : &str = &matches.opt_str("l").unwrap_or("info".to_string()); let log_level_str : &str = &matches.opt_str("l").unwrap_or("info".to_string());
let log_level = match log_level_str { let log_level = match log_level_str {
@ -96,15 +107,18 @@ fn main() {
Box::new(SimpleLogger) Box::new(SimpleLogger)
}).unwrap(); }).unwrap();
let n = u32::from_str(&matches.opt_str("n").unwrap_or("1".to_string())).unwrap(); let n_trials = u32::from_str(&matches.opt_str("n").unwrap_or("1".to_string())).unwrap();
let seed = matches.opt_str("s").map(|seed_str| { u32::from_str(&seed_str).unwrap() }); let seed = matches.opt_str("s").map(|seed_str| { u32::from_str(&seed_str).unwrap() });
let progress_info = matches.opt_str("o").map(|freq_str| { u32::from_str(&freq_str).unwrap() }); let progress_info = matches.opt_str("o").map(|freq_str| { u32::from_str(&freq_str).unwrap() });
let n_threads = u32::from_str(&matches.opt_str("t").unwrap_or("1".to_string())).unwrap(); let n_threads = u32::from_str(&matches.opt_str("t").unwrap_or("1".to_string())).unwrap();
let n_players = u32::from_str(&matches.opt_str("p").unwrap_or("4".to_string())).unwrap(); let n_players = u32::from_str(&matches.opt_str("p").unwrap_or("4".to_string())).unwrap();
let strategy_str : &str = &matches.opt_str("g").unwrap_or("cheat".to_string());
sim_games(n_players, strategy_str, seed, n_trials, n_threads, progress_info).info();
}
fn sim_games(n_players: u32, strategy_str: &str, seed: Option<u32>, n_trials: u32, n_threads: u32, progress_info: Option<u32>)
-> simulator::SimResult {
let hand_size = match n_players { let hand_size = match n_players {
2 => 5, 2 => 5,
3 => 5, 3 => 5,
@ -122,7 +136,6 @@ fn main() {
allow_empty_hints: false, allow_empty_hints: false,
}; };
let strategy_str : &str = &matches.opt_str("g").unwrap_or("cheat".to_string());
let strategy_config : Box<strategy::GameStrategyConfig + Sync> = match strategy_str { let strategy_config : Box<strategy::GameStrategyConfig + Sync> = match strategy_str {
"random" => { "random" => {
Box::new(strategies::examples::RandomStrategyConfig { Box::new(strategies::examples::RandomStrategyConfig {
@ -139,9 +152,75 @@ fn main() {
as Box<strategy::GameStrategyConfig + Sync> as Box<strategy::GameStrategyConfig + Sync>
}, },
_ => { _ => {
print_usage(&program, opts);
panic!("Unexpected strategy argument {}", strategy_str); panic!("Unexpected strategy argument {}", strategy_str);
}, },
}; };
simulator::simulate(&game_opts, strategy_config, seed, n, n_threads, progress_info); simulator::simulate(&game_opts, strategy_config, seed, n_trials, n_threads, progress_info)
}
fn get_results_table() -> String {
let strategies = ["cheat", "info"];
let player_nums = (2..=5).collect::<Vec<_>>();
let seed = 0;
let n_trials = 1;
let n_threads = 8;
let intro = format!("On the first {} seeds, we have these average scores and win rates:\n\n", n_trials);
let format_name = |x| format!(" {:7} ", x);
let format_players = |x| format!(" {}p ", x);
let format_percent = |x| format!(" {:05.2} % ", x);
let format_score = |x| format!(" {:07.4} ", x);
let space = String::from(" ");
let dashes = String::from("---------");
type TwoLines = (String, String);
fn make_twolines(player_nums: &Vec<u32>, head: TwoLines, make_block: &dyn Fn(u32) -> TwoLines) -> TwoLines {
let mut blocks = player_nums.iter().cloned().map(make_block).collect::<Vec<_>>();
blocks.insert(0, head);
fn combine(items: Vec<String>) -> String {
items.iter().fold(String::from("|"), |init, next| { init + next + "|" })
}
let (a, b): (Vec<_>, Vec<_>) = blocks.into_iter().unzip();
(combine(a), combine(b))
}
fn concat_twolines(body: Vec<TwoLines>) -> String {
body.into_iter().fold(String::default(), |output, (a, b)| (output + &a + "\n" + &b + "\n"))
}
let header = make_twolines(&player_nums, (space.clone(), dashes.clone()), &|n_players| (format_players(n_players), dashes.clone()));
let mut body = strategies.iter().map(|strategy| {
make_twolines(&player_nums, (format_name(strategy), space.clone()), &|n_players| {
let simresult = sim_games(n_players, strategy, Some(seed), n_trials, n_threads, None);
(format_score(simresult.average_score()), format_percent(simresult.percent_perfect()))
})
}).collect::<Vec<_>>();
body.insert(0, header);
intro + &concat_twolines(body)
}
fn write_results_table() {
let separator = r#"
## Results (auto-generated)
To reproduce:
```
time cargo run --release -- --results-table
```
To update this file:
```
time cargo run --release -- --write-results-table
```
"#;
let readme = "README.md";
let readme_contents = std::fs::read_to_string(readme).unwrap();
let readme_init = {
let parts = readme_contents.splitn(2, separator).collect::<Vec<_>>();
if parts.len() != 2 {
panic!("{} has been modified in the Results section!", readme);
}
parts[0]
};
let table = get_results_table();
let new_readme_contents = String::from(readme_init) + separator + &table;
std::fs::write(readme, new_readme_contents).unwrap();
} }

View file

@ -1,5 +1,5 @@
use rand::{self, Rng, SeedableRng}; use rand::{self, Rng, SeedableRng};
use std::collections::HashMap; use fnv::FnvHashMap;
use std::fmt; use std::fmt;
use crossbeam; use crossbeam;
@ -25,17 +25,15 @@ fn new_deck(seed: u32) -> Cards {
pub fn simulate_once( pub fn simulate_once(
opts: &GameOptions, opts: &GameOptions,
game_strategy: Box<GameStrategy>, game_strategy: Box<GameStrategy>,
seed_opt: Option<u32>, seed: u32,
) -> GameState { ) -> GameState {
let seed = seed_opt.unwrap_or(rand::thread_rng().next_u32());
let deck = new_deck(seed); let deck = new_deck(seed);
let mut game = GameState::new(opts, deck); let mut game = GameState::new(opts, deck);
let mut strategies = game.get_players().map(|player| { let mut strategies = game.get_players().map(|player| {
(player, game_strategy.initialize(player, &game.get_view(player))) (player, game_strategy.initialize(player, &game.get_view(player)))
}).collect::<HashMap<Player, Box<PlayerStrategy>>>(); }).collect::<FnvHashMap<Player, Box<PlayerStrategy>>>();
while !game.is_over() { while !game.is_over() {
let player = game.board.player; let player = game.board.player;
@ -68,15 +66,15 @@ pub fn simulate_once(
} }
#[derive(Debug)] #[derive(Debug)]
struct Histogram { pub struct Histogram {
pub hist: HashMap<Score, u32>, pub hist: FnvHashMap<Score, u32>,
pub sum: Score, pub sum: Score,
pub total_count: u32, pub total_count: u32,
} }
impl Histogram { impl Histogram {
pub fn new() -> Histogram { pub fn new() -> Histogram {
Histogram { Histogram {
hist: HashMap::new(), hist: FnvHashMap::default(),
sum: 0, sum: 0,
total_count: 0, total_count: 0,
} }
@ -125,9 +123,10 @@ pub fn simulate<T: ?Sized>(
n_trials: u32, n_trials: u32,
n_threads: u32, n_threads: u32,
progress_info: Option<u32>, progress_info: Option<u32>,
) where T: GameStrategyConfig + Sync { ) -> SimResult
where T: GameStrategyConfig + Sync {
let first_seed = first_seed_opt.unwrap_or(rand::thread_rng().next_u32()); let first_seed = first_seed_opt.unwrap_or_else(|| rand::thread_rng().next_u32());
let strat_config_ref = &strat_config; let strat_config_ref = &strat_config;
crossbeam::scope(|scope| { crossbeam::scope(|scope| {
@ -154,11 +153,11 @@ pub fn simulate<T: ?Sized>(
); );
} }
} }
let game = simulate_once(&opts, strat_config_ref.initialize(&opts), Some(seed)); let game = simulate_once(&opts, strat_config_ref.initialize(&opts), seed);
let score = game.score(); let score = game.score();
lives_histogram.insert(game.board.lives_remaining); lives_histogram.insert(game.board.lives_remaining);
score_histogram.insert(score); score_histogram.insert(score);
if score != PERFECT_SCORE { non_perfect_seeds.push((score, seed)); } if score != PERFECT_SCORE { non_perfect_seeds.push(seed); }
} }
if progress_info.is_some() { if progress_info.is_some() {
info!("Thread {} done", i); info!("Thread {} done", i);
@ -167,7 +166,7 @@ pub fn simulate<T: ?Sized>(
})); }));
} }
let mut non_perfect_seeds : Vec<(Score,u32)> = Vec::new(); let mut non_perfect_seeds : Vec<u32> = Vec::new();
let mut score_histogram = Histogram::new(); let mut score_histogram = Histogram::new();
let mut lives_histogram = Histogram::new(); let mut lives_histogram = Histogram::new();
for join_handle in join_handles { for join_handle in join_handles {
@ -177,17 +176,44 @@ pub fn simulate<T: ?Sized>(
lives_histogram.merge(thread_lives_histogram); lives_histogram.merge(thread_lives_histogram);
} }
info!("Score histogram:\n{}", score_histogram);
non_perfect_seeds.sort(); non_perfect_seeds.sort();
// info!("Seeds with non-perfect score: {:?}", non_perfect_seeds); SimResult {
if non_perfect_seeds.len() > 0 { scores: score_histogram,
info!("Example seed with non-perfect score: {}", lives: lives_histogram,
non_perfect_seeds.get(0).unwrap().1); non_perfect_seed: non_perfect_seeds.get(0).cloned(),
} }
info!("Percentage perfect: {:?}%", score_histogram.percentage_with(&PERFECT_SCORE) * 100.0);
info!("Average score: {:?}", score_histogram.average());
info!("Average lives: {:?}", lives_histogram.average());
}) })
} }
pub struct SimResult {
pub scores: Histogram,
pub lives: Histogram,
pub non_perfect_seed: Option<u32>,
}
impl SimResult {
pub fn percent_perfect(&self) -> f32 {
self.scores.percentage_with(&PERFECT_SCORE) * 100.0
}
pub fn average_score(&self) -> f32 {
self.scores.average()
}
pub fn average_lives(&self) -> f32 {
self.lives.average()
}
pub fn info(&self) {
info!("Score histogram:\n{}", self.scores);
// info!("Seeds with non-perfect score: {:?}", non_perfect_seeds);
if let Some(seed) = self.non_perfect_seed {
info!("Example seed with non-perfect score: {}", seed);
}
info!("Percentage perfect: {:?}%", self.percent_perfect());
info!("Average score: {:?}", self.average_score());
info!("Average lives: {:?}", self.average_lives());
}
}

View file

@ -1,6 +1,6 @@
use std::rc::Rc; use std::rc::Rc;
use std::cell::{RefCell}; use std::cell::{RefCell};
use std::collections::{HashMap, HashSet}; use fnv::{FnvHashMap, FnvHashSet};
use strategy::*; use strategy::*;
use game::*; use game::*;
@ -31,13 +31,13 @@ impl GameStrategyConfig for CheatingStrategyConfig {
} }
pub struct CheatingStrategy { pub struct CheatingStrategy {
player_hands_cheat: Rc<RefCell<HashMap<Player, Cards>>>, player_hands_cheat: Rc<RefCell<FnvHashMap<Player, Cards>>>,
} }
impl CheatingStrategy { impl CheatingStrategy {
pub fn new() -> CheatingStrategy { pub fn new() -> CheatingStrategy {
CheatingStrategy { CheatingStrategy {
player_hands_cheat: Rc::new(RefCell::new(HashMap::new())), player_hands_cheat: Rc::new(RefCell::new(FnvHashMap::default())),
} }
} }
} }
@ -56,7 +56,7 @@ impl GameStrategy for CheatingStrategy {
} }
pub struct CheatingPlayerStrategy { pub struct CheatingPlayerStrategy {
player_hands_cheat: Rc<RefCell<HashMap<Player, Cards>>>, player_hands_cheat: Rc<RefCell<FnvHashMap<Player, Cards>>>,
me: Player, me: Player,
} }
impl CheatingPlayerStrategy { impl CheatingPlayerStrategy {
@ -120,7 +120,7 @@ impl CheatingPlayerStrategy {
} }
fn find_useless_card(&self, view: &BorrowedGameView, hand: &Cards) -> Option<usize> { fn find_useless_card(&self, view: &BorrowedGameView, hand: &Cards) -> Option<usize> {
let mut set: HashSet<Card> = HashSet::new(); let mut set: FnvHashSet<Card> = FnvHashSet::default();
for (i, card) in hand.iter().enumerate() { for (i, card) in hand.iter().enumerate() {
if view.board.is_dead(card) { if view.board.is_dead(card) {

View file

@ -1,4 +1,4 @@
use std::collections::{HashMap, HashSet}; use fnv::{FnvHashMap, FnvHashSet};
use std::cmp::Ordering; use std::cmp::Ordering;
use strategy::*; use strategy::*;
@ -185,14 +185,14 @@ impl Question for AdditiveComboQuestion {
struct CardPossibilityPartition { struct CardPossibilityPartition {
index: usize, index: usize,
n_partitions: u32, n_partitions: u32,
partition: HashMap<Card, u32>, partition: FnvHashMap<Card, u32>,
} }
impl CardPossibilityPartition { impl CardPossibilityPartition {
fn new( fn new(
index: usize, max_n_partitions: u32, card_table: &CardPossibilityTable, view: &OwnedGameView index: usize, max_n_partitions: u32, card_table: &CardPossibilityTable, view: &OwnedGameView
) -> CardPossibilityPartition { ) -> CardPossibilityPartition {
let mut cur_block = 0; let mut cur_block = 0;
let mut partition = HashMap::new(); let mut partition = FnvHashMap::default();
let mut n_partitions = 0; let mut n_partitions = 0;
let has_dead = card_table.probability_is_dead(&view.board) != 0.0; let has_dead = card_table.probability_is_dead(&view.board) != 0.0;
@ -288,7 +288,7 @@ impl GameStrategy for InformationStrategy {
view.board.get_players().map(|player| { view.board.get_players().map(|player| {
let hand_info = HandInfo::new(view.board.hand_size); let hand_info = HandInfo::new(view.board.hand_size);
(player, hand_info) (player, hand_info)
}).collect::<HashMap<_,_>>(); }).collect::<FnvHashMap<_,_>>();
Box::new(InformationPlayerStrategy { Box::new(InformationPlayerStrategy {
me: player, me: player,
@ -301,7 +301,7 @@ impl GameStrategy for InformationStrategy {
pub struct InformationPlayerStrategy { pub struct InformationPlayerStrategy {
me: Player, me: Player,
public_info: HashMap<Player, HandInfo<CardPossibilityTable>>, public_info: FnvHashMap<Player, HandInfo<CardPossibilityTable>>,
public_counts: CardCounts, // what any newly drawn card should be public_counts: CardCounts, // what any newly drawn card should be
last_view: OwnedGameView, // the view on the previous turn last_view: OwnedGameView, // the view on the previous turn
} }
@ -550,8 +550,8 @@ impl InformationPlayerStrategy {
} }
fn find_useless_cards(&self, view: &OwnedGameView, hand: &HandInfo<CardPossibilityTable>) -> Vec<usize> { fn find_useless_cards(&self, view: &OwnedGameView, hand: &HandInfo<CardPossibilityTable>) -> Vec<usize> {
let mut useless: HashSet<usize> = HashSet::new(); let mut useless: FnvHashSet<usize> = FnvHashSet::default();
let mut seen: HashMap<Card, usize> = HashMap::new(); let mut seen: FnvHashMap<Card, usize> = FnvHashMap::default();
for (i, card_table) in hand.iter().enumerate() { for (i, card_table) in hand.iter().enumerate() {
if card_table.probability_is_dead(view.get_board()) == 1.0 { if card_table.probability_is_dead(view.get_board()) == 1.0 {
@ -792,7 +792,7 @@ impl InformationPlayerStrategy {
(0 .. n - 1).into_iter().map(|i| { (player + 1 + i) % n }).collect() (0 .. n - 1).into_iter().map(|i| { (player + 1 + i) % n }).collect()
} }
fn get_best_hint_of_options(&self, hint_player: Player, hint_option_set: HashSet<Hinted>) -> Hinted { fn get_best_hint_of_options(&self, hint_player: Player, hint_option_set: FnvHashSet<Hinted>) -> Hinted {
let view = &self.last_view; let view = &self.last_view;
// using hint goodness barely helps // using hint goodness barely helps
@ -864,7 +864,7 @@ impl InformationPlayerStrategy {
2 => { 2 => {
// NOTE: this doesn't do that much better than just hinting // NOTE: this doesn't do that much better than just hinting
// the first thing that doesn't match the hint_card // the first thing that doesn't match the hint_card
let mut hint_option_set = HashSet::new(); let mut hint_option_set = FnvHashSet::default();
for card in hand { for card in hand {
if card.color != hint_card.color { if card.color != hint_card.color {
hint_option_set.insert(Hinted::Color(card.color)); hint_option_set.insert(Hinted::Color(card.color));
@ -889,7 +889,7 @@ impl InformationPlayerStrategy {
} }
2 => { 2 => {
// Any value hint for a card other than the first // Any value hint for a card other than the first
let mut hint_option_set = HashSet::new(); let mut hint_option_set = FnvHashSet::default();
for card in hand { for card in hand {
if card.value != hint_card.value { if card.value != hint_card.value {
hint_option_set.insert(Hinted::Value(card.value)); hint_option_set.insert(Hinted::Value(card.value));
@ -899,7 +899,7 @@ impl InformationPlayerStrategy {
} }
3 => { 3 => {
// Any color hint for a card other than the first // Any color hint for a card other than the first
let mut hint_option_set = HashSet::new(); let mut hint_option_set = FnvHashSet::default();
for card in hand { for card in hand {
if card.color != hint_card.color { if card.color != hint_card.color {
hint_option_set.insert(Hinted::Color(card.color)); hint_option_set.insert(Hinted::Color(card.color));