Second attempt at outputting JSON for hanabi.live
We don't support any notes yet. Inside our GameState, we annotate each card with its index in the original (reverse) deck. In order to support per-card notes, we should probably share those annotations with the players, and refactor the player code around them.
This commit is contained in:
parent
7f384cc15d
commit
180d8c5f4e
10 changed files with 228 additions and 29 deletions
30
Cargo.lock
generated
30
Cargo.lock
generated
|
@ -18,6 +18,11 @@ name = "getopts"
|
||||||
version = "0.2.14"
|
version = "0.2.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itoa"
|
||||||
|
version = "0.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.7"
|
version = "0.2.7"
|
||||||
|
@ -49,6 +54,27 @@ dependencies = [
|
||||||
"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)",
|
||||||
|
"serde_json 1.0.38 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ryu"
|
||||||
|
version = "0.2.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.88"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_json"
|
||||||
|
version = "1.0.38"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"serde 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[metadata]
|
[metadata]
|
||||||
|
@ -56,6 +82,10 @@ dependencies = [
|
||||||
"checksum float-ord 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7bad48618fdb549078c333a7a8528acb57af271d0433bdecd523eb620628364e"
|
"checksum float-ord 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7bad48618fdb549078c333a7a8528acb57af271d0433bdecd523eb620628364e"
|
||||||
"checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3"
|
"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 itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1306f3464951f30e30d12373d31c79fbd52d236e5e896fd92f96ec7babbbe60b"
|
||||||
"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"
|
||||||
"checksum rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "2791d88c6defac799c3f20d74f094ca33b9332612d9aef9078519c82e4fe04a5"
|
"checksum rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "2791d88c6defac799c3f20d74f094ca33b9332612d9aef9078519c82e4fe04a5"
|
||||||
|
"checksum ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "eb9e9b8cde282a9fe6a42dd4681319bfb63f121b8a8ee9439c6f4107e58a46f7"
|
||||||
|
"checksum serde 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)" = "9f301d728f2b94c9a7691c90f07b0b4e8a4517181d9461be94c04bddeb4bd850"
|
||||||
|
"checksum serde_json 1.0.38 (registry+https://github.com/rust-lang/crates.io-index)" = "27dce848e7467aa0e2fcaf0a413641499c0b745452aaca1194d24dedde9e13c9"
|
||||||
|
|
|
@ -10,3 +10,4 @@ getopts = "*"
|
||||||
fnv = "*"
|
fnv = "*"
|
||||||
float-ord = "*"
|
float-ord = "*"
|
||||||
crossbeam = "0.2.5"
|
crossbeam = "0.2.5"
|
||||||
|
serde_json = "*"
|
||||||
|
|
65
src/game.rs
65
src/game.rs
|
@ -389,7 +389,7 @@ impl BoardState {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_over(&self) -> bool {
|
pub fn is_over(&self) -> bool {
|
||||||
(self.lives_remaining == 0) || (self.deckless_turns_remaining == 0)
|
(self.lives_remaining == 0) || (self.deckless_turns_remaining == 0) || (self.score() == PERFECT_SCORE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl fmt::Display for BoardState {
|
impl fmt::Display for BoardState {
|
||||||
|
@ -541,12 +541,28 @@ impl GameView for OwnedGameView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Internally, every card is annotated with its index in the deck in order to
|
||||||
|
// generate easy-to-interpret JSON output. These annotations are stripped off
|
||||||
|
// when passing GameViews to strategies.
|
||||||
|
//
|
||||||
|
// TODO: Maybe we should give strategies access to the annotations as well?
|
||||||
|
// This could simplify code like in InformationPlayerStrategy::update_public_info_for_discard_or_play.
|
||||||
|
// Also, this would let a strategy publish "notes" on cards more easily.
|
||||||
|
pub type AnnotatedCard = (usize, Card);
|
||||||
|
pub type AnnotatedCards = Vec<AnnotatedCard>;
|
||||||
|
|
||||||
|
fn strip_annotations(cards: &AnnotatedCards) -> Cards {
|
||||||
|
cards.iter().map(|(_i, card)| { card.clone() }).collect()
|
||||||
|
}
|
||||||
|
|
||||||
// complete game state (known to nobody!)
|
// complete game state (known to nobody!)
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct GameState {
|
pub struct GameState {
|
||||||
pub hands: FnvHashMap<Player, Cards>,
|
pub hands: FnvHashMap<Player, AnnotatedCards>,
|
||||||
|
// used to construct BorrowedGameViews
|
||||||
|
pub unannotated_hands: FnvHashMap<Player, Cards>,
|
||||||
pub board: BoardState,
|
pub board: BoardState,
|
||||||
pub deck: Cards,
|
pub deck: AnnotatedCards,
|
||||||
}
|
}
|
||||||
impl fmt::Display for GameState {
|
impl fmt::Display for GameState {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
@ -557,7 +573,7 @@ impl fmt::Display for GameState {
|
||||||
for player in self.board.get_players() {
|
for player in self.board.get_players() {
|
||||||
let hand = &self.hands.get(&player).unwrap();
|
let hand = &self.hands.get(&player).unwrap();
|
||||||
try!(f.write_str(&format!("player {}:", player)));
|
try!(f.write_str(&format!("player {}:", player)));
|
||||||
for card in hand.iter() {
|
for (_i, card) in hand.iter() {
|
||||||
try!(f.write_str(&format!(" {}", card)));
|
try!(f.write_str(&format!(" {}", card)));
|
||||||
}
|
}
|
||||||
try!(f.write_str(&"\n"));
|
try!(f.write_str(&"\n"));
|
||||||
|
@ -571,7 +587,9 @@ impl fmt::Display for GameState {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GameState {
|
impl GameState {
|
||||||
pub fn new(opts: &GameOptions, mut deck: Cards) -> GameState {
|
pub fn new(opts: &GameOptions, deck: Cards) -> GameState {
|
||||||
|
// We enumerate the cards in reverse order since they'll be drawn from the back of the deck.
|
||||||
|
let mut deck: AnnotatedCards = deck.into_iter().rev().enumerate().rev().collect();
|
||||||
let mut board = BoardState::new(opts, deck.len() as u32);
|
let mut board = BoardState::new(opts, deck.len() as u32);
|
||||||
|
|
||||||
let hands =
|
let hands =
|
||||||
|
@ -583,11 +601,15 @@ impl GameState {
|
||||||
}).collect::<Vec<_>>();
|
}).collect::<Vec<_>>();
|
||||||
(player, hand)
|
(player, hand)
|
||||||
}).collect::<FnvHashMap<_, _>>();
|
}).collect::<FnvHashMap<_, _>>();
|
||||||
|
let unannotated_hands = hands.iter().map(|(player, hand)| {
|
||||||
|
(player.clone(), strip_annotations(hand))
|
||||||
|
}).collect::<FnvHashMap<_, _>>();
|
||||||
|
|
||||||
GameState {
|
GameState {
|
||||||
hands: hands,
|
hands,
|
||||||
board: board,
|
unannotated_hands,
|
||||||
deck: deck,
|
board,
|
||||||
|
deck,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -606,7 +628,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 = FnvHashMap::default();
|
let mut other_hands = FnvHashMap::default();
|
||||||
for (&other_player, hand) in &self.hands {
|
for (&other_player, hand) in &self.unannotated_hands {
|
||||||
if player != other_player {
|
if player != other_player {
|
||||||
other_hands.insert(other_player, hand);
|
other_hands.insert(other_player, hand);
|
||||||
}
|
}
|
||||||
|
@ -619,22 +641,39 @@ impl GameState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn update_player_hand(&mut self) {
|
||||||
|
let player = self.board.player.clone();
|
||||||
|
self.unannotated_hands.insert(player, strip_annotations(self.hands.get(&player).unwrap()));
|
||||||
|
}
|
||||||
|
|
||||||
// takes a card from the player's hand, and replaces it if possible
|
// takes a card from the player's hand, and replaces it if possible
|
||||||
fn take_from_hand(&mut self, index: usize) -> Card {
|
fn take_from_hand(&mut self, index: usize) -> Card {
|
||||||
|
// FIXME this code looks like it's awfully contorted in order to please the borrow checker.
|
||||||
|
// Can we have this look nicer?
|
||||||
|
let result =
|
||||||
|
{
|
||||||
let ref mut hand = self.hands.get_mut(&self.board.player).unwrap();
|
let ref mut hand = self.hands.get_mut(&self.board.player).unwrap();
|
||||||
hand.remove(index)
|
hand.remove(index).1
|
||||||
|
};
|
||||||
|
self.update_player_hand();
|
||||||
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
fn replenish_hand(&mut self) {
|
fn replenish_hand(&mut self) {
|
||||||
|
// FIXME this code looks like it's awfully contorted in order to please the borrow checker.
|
||||||
|
// Can we have this look nicer?
|
||||||
|
{
|
||||||
let ref mut hand = self.hands.get_mut(&self.board.player).unwrap();
|
let ref mut hand = self.hands.get_mut(&self.board.player).unwrap();
|
||||||
if (hand.len() as u32) < self.board.hand_size {
|
if (hand.len() as u32) < self.board.hand_size {
|
||||||
if let Some(new_card) = self.deck.pop() {
|
if let Some(new_card) = self.deck.pop() {
|
||||||
self.board.deck_size -= 1;
|
self.board.deck_size -= 1;
|
||||||
debug!("Drew new card, {}", new_card);
|
debug!("Drew new card, {}", new_card.1);
|
||||||
hand.push(new_card);
|
hand.push(new_card);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.update_player_hand();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn process_choice(&mut self, choice: TurnChoice) -> TurnRecord {
|
pub fn process_choice(&mut self, choice: TurnChoice) -> TurnRecord {
|
||||||
let turn_result = {
|
let turn_result = {
|
||||||
|
@ -651,10 +690,10 @@ impl GameState {
|
||||||
let hand = self.hands.get(&hint.player).unwrap();
|
let hand = self.hands.get(&hint.player).unwrap();
|
||||||
let results = match hint.hinted {
|
let results = match hint.hinted {
|
||||||
Hinted::Color(color) => {
|
Hinted::Color(color) => {
|
||||||
hand.iter().map(|card| { card.color == color }).collect::<Vec<_>>()
|
hand.iter().map(|(_i, card)| { card.color == color }).collect::<Vec<_>>()
|
||||||
}
|
}
|
||||||
Hinted::Value(value) => {
|
Hinted::Value(value) => {
|
||||||
hand.iter().map(|card| { card.value == value }).collect::<Vec<_>>()
|
hand.iter().map(|(_i, card)| { card.value == value }).collect::<Vec<_>>()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if !self.board.allow_empty_hints {
|
if !self.board.allow_empty_hints {
|
||||||
|
|
56
src/json_output.rs
Normal file
56
src/json_output.rs
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
use game::*;
|
||||||
|
use serde_json::*;
|
||||||
|
|
||||||
|
fn color_value(color: &Color) -> usize {
|
||||||
|
COLORS.iter().position(|&card_color| &card_color == color).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn card_to_json(card: &Card) -> serde_json::Value {
|
||||||
|
json!({
|
||||||
|
"rank": card.value,
|
||||||
|
"suit": color_value(&card.color),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn action_clue(hint: &Hint) -> serde_json::Value {
|
||||||
|
json!({
|
||||||
|
"type": 0,
|
||||||
|
"target": hint.player,
|
||||||
|
"clue": match hint.hinted {
|
||||||
|
Hinted::Value(value) => { json!({
|
||||||
|
"type": 0,
|
||||||
|
"value": value,
|
||||||
|
}) }
|
||||||
|
Hinted::Color(color) => { json!({
|
||||||
|
"type": 1,
|
||||||
|
"value": color_value(&color),
|
||||||
|
}) }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn action_play((i, _card): &AnnotatedCard) -> serde_json::Value {
|
||||||
|
json!({
|
||||||
|
"type": 1,
|
||||||
|
"target": i,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn action_discard((i, _card): &AnnotatedCard) -> serde_json::Value {
|
||||||
|
json!({
|
||||||
|
"type": 2,
|
||||||
|
"target": i,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn json_format(deck: &Cards, actions: &Vec<serde_json::Value>, players: &Vec<String>) -> serde_json::Value {
|
||||||
|
json!({
|
||||||
|
"variant": "No Variant",
|
||||||
|
"players": players,
|
||||||
|
"first_player": 0,
|
||||||
|
"notes": players.iter().map(|_player| {json!([])}).collect::<Vec<_>>(), // TODO add notes
|
||||||
|
// The deck is reversed since in our implementation we draw from the end of the deck.
|
||||||
|
"deck": deck.iter().rev().map(card_to_json).collect::<Vec<serde_json::Value>>(),
|
||||||
|
"actions": actions,
|
||||||
|
})
|
||||||
|
}
|
25
src/main.rs
25
src/main.rs
|
@ -5,9 +5,11 @@ extern crate rand;
|
||||||
extern crate crossbeam;
|
extern crate crossbeam;
|
||||||
extern crate fnv;
|
extern crate fnv;
|
||||||
extern crate float_ord;
|
extern crate float_ord;
|
||||||
|
extern crate serde_json;
|
||||||
|
|
||||||
mod helpers;
|
mod helpers;
|
||||||
mod game;
|
mod game;
|
||||||
|
mod json_output;
|
||||||
mod simulator;
|
mod simulator;
|
||||||
mod strategy;
|
mod strategy;
|
||||||
mod strategies {
|
mod strategies {
|
||||||
|
@ -53,6 +55,9 @@ fn main() {
|
||||||
opts.optopt("o", "output",
|
opts.optopt("o", "output",
|
||||||
"Number of games after which to print an update",
|
"Number of games after which to print an update",
|
||||||
"OUTPUT_FREQ");
|
"OUTPUT_FREQ");
|
||||||
|
opts.optopt("j", "json-output",
|
||||||
|
"Pattern for the JSON output file. '%s' will be replaced by the seed.",
|
||||||
|
"FILE_PATTERN");
|
||||||
opts.optopt("t", "nthreads",
|
opts.optopt("t", "nthreads",
|
||||||
"Number of threads to use for simulation (default 1)",
|
"Number of threads to use for simulation (default 1)",
|
||||||
"NTHREADS");
|
"NTHREADS");
|
||||||
|
@ -113,14 +118,24 @@ fn main() {
|
||||||
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 json_output_pattern = matches.opt_str("j");
|
||||||
|
|
||||||
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());
|
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();
|
sim_games(n_players, strategy_str, seed, n_trials, n_threads, progress_info, json_output_pattern).info();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sim_games(n_players: u32, strategy_str: &str, seed: Option<u32>, n_trials: u32, n_threads: u32, progress_info: Option<u32>)
|
fn sim_games(
|
||||||
-> simulator::SimResult {
|
n_players: u32,
|
||||||
|
strategy_str: &str,
|
||||||
|
seed: Option<u32>,
|
||||||
|
n_trials: u32,
|
||||||
|
n_threads: u32,
|
||||||
|
progress_info: Option<u32>,
|
||||||
|
json_output_pattern: Option<String>,
|
||||||
|
) -> simulator::SimResult {
|
||||||
let hand_size = match n_players {
|
let hand_size = match n_players {
|
||||||
2 => 5,
|
2 => 5,
|
||||||
3 => 5,
|
3 => 5,
|
||||||
|
@ -157,7 +172,7 @@ fn sim_games(n_players: u32, strategy_str: &str, seed: Option<u32>, n_trials: u3
|
||||||
panic!("Unexpected strategy argument {}", strategy_str);
|
panic!("Unexpected strategy argument {}", strategy_str);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
simulator::simulate(&game_opts, strategy_config, seed, n_trials, n_threads, progress_info)
|
simulator::simulate(&game_opts, strategy_config, seed, n_trials, n_threads, progress_info, json_output_pattern)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_results_table() -> String {
|
fn get_results_table() -> String {
|
||||||
|
@ -193,7 +208,7 @@ fn get_results_table() -> String {
|
||||||
&|n_players| (format_players(n_players), dashes_long.clone()));
|
&|n_players| (format_players(n_players), dashes_long.clone()));
|
||||||
let mut body = strategies.iter().map(|strategy| {
|
let mut body = strategies.iter().map(|strategy| {
|
||||||
make_twolines(&player_nums, (format_name(strategy), space.clone()), &|n_players| {
|
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);
|
let simresult = sim_games(n_players, strategy, Some(seed), n_trials, n_threads, None, None);
|
||||||
(
|
(
|
||||||
format_score(simresult.average_score(), simresult.score_stderr()),
|
format_score(simresult.average_score(), simresult.score_stderr()),
|
||||||
format_percent(simresult.percent_perfect(), simresult.percent_perfect_stderr())
|
format_percent(simresult.percent_perfect(), simresult.percent_perfect_stderr())
|
||||||
|
|
|
@ -5,6 +5,7 @@ use crossbeam;
|
||||||
|
|
||||||
use game::*;
|
use game::*;
|
||||||
use strategy::*;
|
use strategy::*;
|
||||||
|
use json_output::*;
|
||||||
|
|
||||||
fn new_deck(seed: u32) -> Cards {
|
fn new_deck(seed: u32) -> Cards {
|
||||||
let mut deck: Cards = Cards::new();
|
let mut deck: Cards = Cards::new();
|
||||||
|
@ -22,19 +23,24 @@ fn new_deck(seed: u32) -> Cards {
|
||||||
deck
|
deck
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn simulate_once(
|
pub fn simulate_once(
|
||||||
opts: &GameOptions,
|
opts: &GameOptions,
|
||||||
game_strategy: Box<GameStrategy>,
|
game_strategy: Box<GameStrategy>,
|
||||||
seed: u32,
|
seed: u32,
|
||||||
) -> GameState {
|
output_json: bool,
|
||||||
|
) -> (GameState, Option<serde_json::Value>) {
|
||||||
|
|
||||||
let deck = new_deck(seed);
|
let deck = new_deck(seed);
|
||||||
|
|
||||||
let mut game = GameState::new(opts, deck);
|
let mut game = GameState::new(opts, deck.clone());
|
||||||
|
|
||||||
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::<FnvHashMap<Player, Box<PlayerStrategy>>>();
|
}).collect::<FnvHashMap<Player, Box<PlayerStrategy>>>();
|
||||||
|
|
||||||
|
let mut actions = Vec::new();
|
||||||
|
|
||||||
while !game.is_over() {
|
while !game.is_over() {
|
||||||
let player = game.board.player;
|
let player = game.board.player;
|
||||||
|
|
||||||
|
@ -49,6 +55,22 @@ pub fn simulate_once(
|
||||||
let mut strategy = strategies.get_mut(&player).unwrap();
|
let mut strategy = strategies.get_mut(&player).unwrap();
|
||||||
strategy.decide(&game.get_view(player))
|
strategy.decide(&game.get_view(player))
|
||||||
};
|
};
|
||||||
|
if output_json {
|
||||||
|
actions.push(match choice {
|
||||||
|
TurnChoice::Hint(ref hint) => {
|
||||||
|
action_clue(hint)
|
||||||
|
}
|
||||||
|
TurnChoice::Play(index) => {
|
||||||
|
let card = &game.hands[&player][index];
|
||||||
|
action_play(card)
|
||||||
|
}
|
||||||
|
TurnChoice::Discard(index) => {
|
||||||
|
let card = &game.hands[&player][index];
|
||||||
|
action_discard(card)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
let turn = game.process_choice(choice);
|
let turn = game.process_choice(choice);
|
||||||
|
|
||||||
|
@ -56,13 +78,20 @@ pub fn simulate_once(
|
||||||
let mut strategy = strategies.get_mut(&player).unwrap();
|
let mut strategy = strategies.get_mut(&player).unwrap();
|
||||||
strategy.update(&turn, &game.get_view(player));
|
strategy.update(&turn, &game.get_view(player));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
debug!("");
|
debug!("");
|
||||||
debug!("=======================================================");
|
debug!("=======================================================");
|
||||||
debug!("Final state:\n{}", game);
|
debug!("Final state:\n{}", game);
|
||||||
debug!("SCORE: {:?}", game.score());
|
debug!("SCORE: {:?}", game.score());
|
||||||
game
|
let json_output = if output_json {
|
||||||
|
let player_names = game.get_players().map(|player| {
|
||||||
|
strategies[&player].name()
|
||||||
|
}).collect();
|
||||||
|
Some(json_format(&deck, &actions, &player_names))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
(game, json_output)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -134,12 +163,14 @@ 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>,
|
||||||
|
json_output_pattern: Option<String>,
|
||||||
) -> SimResult
|
) -> SimResult
|
||||||
where T: GameStrategyConfig + Sync {
|
where T: GameStrategyConfig + Sync {
|
||||||
|
|
||||||
let first_seed = first_seed_opt.unwrap_or_else(|| 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;
|
||||||
|
let json_output_pattern_ref = &json_output_pattern;
|
||||||
crossbeam::scope(|scope| {
|
crossbeam::scope(|scope| {
|
||||||
let mut join_handles = Vec::new();
|
let mut join_handles = Vec::new();
|
||||||
for i in 0..n_threads {
|
for i in 0..n_threads {
|
||||||
|
@ -164,11 +195,23 @@ pub fn simulate<T: ?Sized>(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let game = simulate_once(&opts, strat_config_ref.initialize(&opts), seed);
|
let (game, json_output) = simulate_once(&opts,
|
||||||
|
strat_config_ref.initialize(&opts),
|
||||||
|
seed,
|
||||||
|
json_output_pattern_ref.is_some());
|
||||||
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(seed); }
|
if score != PERFECT_SCORE { non_perfect_seeds.push(seed); }
|
||||||
|
match json_output_pattern_ref {
|
||||||
|
Some(file_pattern) => {
|
||||||
|
let file_pattern = file_pattern.clone().replace("%s", &seed.to_string());
|
||||||
|
let path = std::path::Path::new(&file_pattern);
|
||||||
|
let file = std::fs::File::create(path).unwrap();
|
||||||
|
serde_json::to_writer(file, &json_output.unwrap()).unwrap();
|
||||||
|
}
|
||||||
|
None => { }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if progress_info.is_some() {
|
if progress_info.is_some() {
|
||||||
info!("Thread {} done", i);
|
info!("Thread {} done", i);
|
||||||
|
|
|
@ -136,6 +136,9 @@ impl CheatingPlayerStrategy {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl PlayerStrategy for CheatingPlayerStrategy {
|
impl PlayerStrategy for CheatingPlayerStrategy {
|
||||||
|
fn name(&self) -> String {
|
||||||
|
String::from("cheat")
|
||||||
|
}
|
||||||
fn decide(&mut self, view: &BorrowedGameView) -> TurnChoice {
|
fn decide(&mut self, view: &BorrowedGameView) -> TurnChoice {
|
||||||
self.inform_last_player_cards(view);
|
self.inform_last_player_cards(view);
|
||||||
|
|
||||||
|
|
|
@ -39,6 +39,9 @@ pub struct RandomStrategyPlayer {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlayerStrategy for RandomStrategyPlayer {
|
impl PlayerStrategy for RandomStrategyPlayer {
|
||||||
|
fn name(&self) -> String {
|
||||||
|
format!("random(hint={}, play={})", self.hint_probability, self.play_probability)
|
||||||
|
}
|
||||||
fn decide(&mut self, view: &BorrowedGameView) -> TurnChoice {
|
fn decide(&mut self, view: &BorrowedGameView) -> TurnChoice {
|
||||||
let p = rand::random::<f64>();
|
let p = rand::random::<f64>();
|
||||||
if p < self.hint_probability {
|
if p < self.hint_probability {
|
||||||
|
|
|
@ -913,6 +913,10 @@ impl InformationPlayerStrategy {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlayerStrategy for InformationPlayerStrategy {
|
impl PlayerStrategy for InformationPlayerStrategy {
|
||||||
|
fn name(&self) -> String {
|
||||||
|
String::from("info")
|
||||||
|
}
|
||||||
|
|
||||||
fn decide(&mut self, _: &BorrowedGameView) -> TurnChoice {
|
fn decide(&mut self, _: &BorrowedGameView) -> TurnChoice {
|
||||||
let mut public_info = self.public_info.clone();
|
let mut public_info = self.public_info.clone();
|
||||||
let turn_choice = self.decide_wrapped(&mut public_info);
|
let turn_choice = self.decide_wrapped(&mut public_info);
|
||||||
|
|
|
@ -4,6 +4,11 @@ use game::*;
|
||||||
|
|
||||||
// Represents the strategy of a given player
|
// Represents the strategy of a given player
|
||||||
pub trait PlayerStrategy {
|
pub trait PlayerStrategy {
|
||||||
|
// A function returning the name of a strategy.
|
||||||
|
// This is a method of PlayerStrategy rather than GameStrategyConfig
|
||||||
|
// so that the name may incorporate useful information that's specific
|
||||||
|
// to this player instance.
|
||||||
|
fn name(&self) -> String;
|
||||||
// A function to decide what to do on the player's turn.
|
// A function to decide what to do on the player's turn.
|
||||||
// Given a BorrowedGameView, outputs their choice.
|
// Given a BorrowedGameView, outputs their choice.
|
||||||
fn decide(&mut self, &BorrowedGameView) -> TurnChoice;
|
fn decide(&mut self, &BorrowedGameView) -> TurnChoice;
|
||||||
|
|
Loading…
Reference in a new issue