Merge pull request #16 from timotree3/json_output_2021
Updated JSON output for hanab.live
This commit is contained in:
commit
2d1a6163a5
11 changed files with 246 additions and 17 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,2 +1,5 @@
|
||||||
target
|
target
|
||||||
*.sw*
|
*.sw*
|
||||||
|
|
||||||
|
# Developers may wish to generate JSON replays of bot games and store them in /replays/
|
||||||
|
/replays/
|
||||||
|
|
30
Cargo.lock
generated
30
Cargo.lock
generated
|
@ -41,6 +41,12 @@ dependencies = [
|
||||||
"unicode-width",
|
"unicode-width",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itoa"
|
||||||
|
version = "1.0.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.125"
|
version = "0.2.125"
|
||||||
|
@ -122,6 +128,30 @@ dependencies = [
|
||||||
"getopts",
|
"getopts",
|
||||||
"log 0.3.9",
|
"log 0.3.9",
|
||||||
"rand 0.3.23",
|
"rand 0.3.23",
|
||||||
|
"serde_json",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ryu"
|
||||||
|
version = "1.0.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.152"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_json"
|
||||||
|
version = "1.0.91"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883"
|
||||||
|
dependencies = [
|
||||||
|
"itoa",
|
||||||
|
"ryu",
|
||||||
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -11,3 +11,4 @@ getopts = "0.2.14"
|
||||||
fnv = "1.0.0"
|
fnv = "1.0.0"
|
||||||
float-ord = "0.2.0"
|
float-ord = "0.2.0"
|
||||||
crossbeam = "0.2.5"
|
crossbeam = "0.2.5"
|
||||||
|
serde_json = "*"
|
||||||
|
|
60
src/game.rs
60
src/game.rs
|
@ -368,7 +368,9 @@ 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 {
|
||||||
|
@ -522,12 +524,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 {
|
||||||
|
@ -538,7 +556,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();
|
||||||
write!(f, "player {player}:")?;
|
write!(f, "player {player}:")?;
|
||||||
for card in hand.iter() {
|
for (_i, card) in hand.iter() {
|
||||||
write!(f, " {card}")?;
|
write!(f, " {card}")?;
|
||||||
}
|
}
|
||||||
f.write_str("\n")?;
|
f.write_str("\n")?;
|
||||||
|
@ -552,7 +570,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 = (0..opts.num_players)
|
let hands = (0..opts.num_players)
|
||||||
|
@ -567,8 +587,17 @@ impl GameState {
|
||||||
(player, hand)
|
(player, hand)
|
||||||
})
|
})
|
||||||
.collect::<FnvHashMap<_, _>>();
|
.collect::<FnvHashMap<_, _>>();
|
||||||
|
let unannotated_hands = hands
|
||||||
|
.iter()
|
||||||
|
.map(|(player, hand)| (*player, strip_annotations(hand)))
|
||||||
|
.collect::<FnvHashMap<_, _>>();
|
||||||
|
|
||||||
GameState { hands, board, deck }
|
GameState {
|
||||||
|
hands,
|
||||||
|
unannotated_hands,
|
||||||
|
board,
|
||||||
|
deck,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_players(&self) -> Range<Player> {
|
pub fn get_players(&self) -> Range<Player> {
|
||||||
|
@ -586,7 +615,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);
|
||||||
}
|
}
|
||||||
|
@ -599,10 +628,18 @@ impl GameState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn update_player_hand(&mut self) {
|
||||||
|
let player = self.board.player;
|
||||||
|
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 {
|
||||||
let hand = &mut self.hands.get_mut(&self.board.player).unwrap();
|
let hand = &mut self.hands.get_mut(&self.board.player).unwrap();
|
||||||
hand.remove(index)
|
let card = hand.remove(index).1;
|
||||||
|
self.update_player_hand();
|
||||||
|
card
|
||||||
}
|
}
|
||||||
|
|
||||||
fn replenish_hand(&mut self) {
|
fn replenish_hand(&mut self) {
|
||||||
|
@ -610,10 +647,11 @@ impl GameState {
|
||||||
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 {
|
||||||
|
@ -637,11 +675,11 @@ impl GameState {
|
||||||
let results = match hint.hinted {
|
let results = match hint.hinted {
|
||||||
Hinted::Color(color) => hand
|
Hinted::Color(color) => hand
|
||||||
.iter()
|
.iter()
|
||||||
.map(|card| card.color == color)
|
.map(|(_i, card)| card.color == color)
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
Hinted::Value(value) => hand
|
Hinted::Value(value) => hand
|
||||||
.iter()
|
.iter()
|
||||||
.map(|card| card.value == value)
|
.map(|(_i, card)| card.value == value)
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
};
|
};
|
||||||
if !self.board.allow_empty_hints {
|
if !self.board.allow_empty_hints {
|
||||||
|
|
67
src/json_output.rs
Normal file
67
src/json_output.rs
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
use crate::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,
|
||||||
|
"suitIndex": color_value(&card.color),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn action_clue(hint: &Hint) -> serde_json::Value {
|
||||||
|
match hint.hinted {
|
||||||
|
Hinted::Color(color) => {
|
||||||
|
json!({
|
||||||
|
"type": 2,
|
||||||
|
"target": hint.player,
|
||||||
|
"value": color_value(&color),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Hinted::Value(value) => {
|
||||||
|
json!({
|
||||||
|
"type": 3,
|
||||||
|
"target": hint.player,
|
||||||
|
"value": value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn action_play((i, _card): &AnnotatedCard) -> serde_json::Value {
|
||||||
|
json!({
|
||||||
|
"type": 0,
|
||||||
|
"target": i,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn action_discard((i, _card): &AnnotatedCard) -> serde_json::Value {
|
||||||
|
json!({
|
||||||
|
"type": 1,
|
||||||
|
"target": i,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn json_format(
|
||||||
|
deck: &Cards,
|
||||||
|
actions: &Vec<serde_json::Value>,
|
||||||
|
players: &Vec<String>,
|
||||||
|
) -> serde_json::Value {
|
||||||
|
json!({
|
||||||
|
"options": {
|
||||||
|
"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,
|
||||||
|
})
|
||||||
|
}
|
33
src/main.rs
33
src/main.rs
|
@ -5,9 +5,11 @@ extern crate crossbeam;
|
||||||
extern crate float_ord;
|
extern crate float_ord;
|
||||||
extern crate fnv;
|
extern crate fnv;
|
||||||
extern crate rand;
|
extern crate rand;
|
||||||
|
extern crate serde_json;
|
||||||
|
|
||||||
mod game;
|
mod game;
|
||||||
mod helpers;
|
mod helpers;
|
||||||
|
mod json_output;
|
||||||
mod simulator;
|
mod simulator;
|
||||||
mod strategy;
|
mod strategy;
|
||||||
mod strategies {
|
mod strategies {
|
||||||
|
@ -60,6 +62,12 @@ fn main() {
|
||||||
"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(
|
opts.optopt(
|
||||||
"t",
|
"t",
|
||||||
"nthreads",
|
"nthreads",
|
||||||
|
@ -85,6 +93,11 @@ fn main() {
|
||||||
"write-results-table",
|
"write-results-table",
|
||||||
"Update the results table in README.md",
|
"Update the results table in README.md",
|
||||||
);
|
);
|
||||||
|
opts.optflag(
|
||||||
|
"",
|
||||||
|
"losses-only",
|
||||||
|
"When saving JSON outputs, save lost games only",
|
||||||
|
);
|
||||||
let matches = match opts.parse(&args[1..]) {
|
let matches = match opts.parse(&args[1..]) {
|
||||||
Ok(m) => m,
|
Ok(m) => m,
|
||||||
Err(f) => {
|
Err(f) => {
|
||||||
|
@ -136,6 +149,8 @@ fn main() {
|
||||||
let n_players = u32::from_str(matches.opt_str("p").as_deref().unwrap_or("4")).unwrap();
|
let n_players = u32::from_str(matches.opt_str("p").as_deref().unwrap_or("4")).unwrap();
|
||||||
let g_opt = matches.opt_str("g");
|
let g_opt = matches.opt_str("g");
|
||||||
let strategy_str: &str = g_opt.as_deref().unwrap_or("cheat");
|
let strategy_str: &str = g_opt.as_deref().unwrap_or("cheat");
|
||||||
|
let json_output_pattern = matches.opt_str("j");
|
||||||
|
let json_losses_only = matches.opt_present("losses-only");
|
||||||
|
|
||||||
sim_games(
|
sim_games(
|
||||||
n_players,
|
n_players,
|
||||||
|
@ -144,6 +159,8 @@ fn main() {
|
||||||
n_trials,
|
n_trials,
|
||||||
n_threads,
|
n_threads,
|
||||||
progress_info,
|
progress_info,
|
||||||
|
json_output_pattern,
|
||||||
|
json_losses_only,
|
||||||
)
|
)
|
||||||
.info();
|
.info();
|
||||||
}
|
}
|
||||||
|
@ -155,6 +172,8 @@ fn sim_games(
|
||||||
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>,
|
||||||
|
json_losses_only: bool,
|
||||||
) -> simulator::SimResult {
|
) -> simulator::SimResult {
|
||||||
let hand_size = match n_players {
|
let hand_size = match n_players {
|
||||||
2 => 5,
|
2 => 5,
|
||||||
|
@ -195,6 +214,8 @@ fn sim_games(
|
||||||
n_trials,
|
n_trials,
|
||||||
n_threads,
|
n_threads,
|
||||||
progress_info,
|
progress_info,
|
||||||
|
json_output_pattern,
|
||||||
|
json_losses_only,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -250,8 +271,16 @@ fn get_results_table() -> String {
|
||||||
&player_nums,
|
&player_nums,
|
||||||
(format_name(strategy), space.clone()),
|
(format_name(strategy), space.clone()),
|
||||||
&|n_players| {
|
&|n_players| {
|
||||||
let simresult =
|
let simresult = sim_games(
|
||||||
sim_games(n_players, strategy, Some(seed), n_trials, n_threads, None);
|
n_players,
|
||||||
|
strategy,
|
||||||
|
Some(seed),
|
||||||
|
n_trials,
|
||||||
|
n_threads,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
false,
|
||||||
|
);
|
||||||
(
|
(
|
||||||
format_score(simresult.average_score(), simresult.score_stderr()),
|
format_score(simresult.average_score(), simresult.score_stderr()),
|
||||||
format_percent(
|
format_percent(
|
||||||
|
|
|
@ -3,6 +3,7 @@ use rand::{self, Rng, SeedableRng};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use crate::game::*;
|
use crate::game::*;
|
||||||
|
use crate::json_output::*;
|
||||||
use crate::strategy::*;
|
use crate::strategy::*;
|
||||||
|
|
||||||
fn new_deck(seed: u32) -> Cards {
|
fn new_deck(seed: u32) -> Cards {
|
||||||
|
@ -25,10 +26,11 @@ pub fn simulate_once(
|
||||||
opts: &GameOptions,
|
opts: &GameOptions,
|
||||||
game_strategy: Box<dyn GameStrategy>,
|
game_strategy: Box<dyn 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
|
let mut strategies = game
|
||||||
.get_players()
|
.get_players()
|
||||||
|
@ -40,6 +42,8 @@ pub fn simulate_once(
|
||||||
})
|
})
|
||||||
.collect::<FnvHashMap<Player, Box<dyn PlayerStrategy>>>();
|
.collect::<FnvHashMap<Player, Box<dyn PlayerStrategy>>>();
|
||||||
|
|
||||||
|
let mut actions = Vec::new();
|
||||||
|
|
||||||
while !game.is_over() {
|
while !game.is_over() {
|
||||||
let player = game.board.player;
|
let player = game.board.player;
|
||||||
|
|
||||||
|
@ -53,6 +57,19 @@ pub fn simulate_once(
|
||||||
let strategy = strategies.get_mut(&player).unwrap();
|
let 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);
|
||||||
|
|
||||||
|
@ -65,7 +82,16 @@ pub fn simulate_once(
|
||||||
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)]
|
||||||
|
@ -135,6 +161,8 @@ 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>,
|
||||||
|
json_losses_only: bool,
|
||||||
) -> SimResult
|
) -> SimResult
|
||||||
where
|
where
|
||||||
T: GameStrategyConfig + Sync,
|
T: GameStrategyConfig + Sync,
|
||||||
|
@ -142,6 +170,7 @@ where
|
||||||
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 {
|
||||||
|
@ -169,13 +198,27 @@ where
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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 {
|
if score != PERFECT_SCORE {
|
||||||
non_perfect_seeds.push(seed);
|
non_perfect_seeds.push(seed);
|
||||||
}
|
}
|
||||||
|
if let Some(file_pattern) = json_output_pattern_ref {
|
||||||
|
if !(score == PERFECT_SCORE && json_losses_only) {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
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,12 @@ 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.play_probability {
|
if p < self.play_probability {
|
||||||
|
|
|
@ -991,6 +991,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 crate::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, view: &BorrowedGameView) -> TurnChoice;
|
fn decide(&mut self, view: &BorrowedGameView) -> TurnChoice;
|
||||||
|
|
Loading…
Reference in a new issue