Compare commits

...

5 commits

7 changed files with 177 additions and 33 deletions

48
Cargo.lock generated
View file

@ -71,6 +71,24 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "proc-macro2"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.3.23"
@ -128,6 +146,8 @@ dependencies = [
"getopts",
"log 0.3.9",
"rand 0.3.23",
"serde",
"serde_derive",
"serde_json",
]
@ -143,6 +163,17 @@ version = "1.0.152"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
[[package]]
name = "serde_derive"
version = "1.0.192"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.91"
@ -154,6 +185,23 @@ dependencies = [
"serde",
]
[[package]]
name = "syn"
version = "2.0.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "unicode-width"
version = "0.1.9"

View file

@ -12,3 +12,5 @@ fnv = "1.0.0"
float-ord = "0.2.0"
crossbeam = "0.2.5"
serde_json = "*"
serde_derive = "*"
serde = "*"

View file

@ -9,11 +9,17 @@ pub const NUM_COLORS: usize = 5;
pub const COLORS: [Color; NUM_COLORS] = ['r', 'y', 'g', 'b', 'w'];
pub type Value = u32;
// list of values, assumed to be small to large
pub const NUM_VALUES: usize = 5;
pub const VALUES: [Value; NUM_VALUES] = [1, 2, 3, 4, 5];
pub const FINAL_VALUE: Value = 5;
pub enum DeckSpec {
Seed(u32),
Deck(Vec<Card>)
}
pub fn get_count_for_value(value: Value) -> u32 {
match value {
1 => 3,

View file

@ -1,5 +1,8 @@
use crate::game::*;
use serde_json::*;
use std::vec;
use serde_derive::{Deserialize, Serialize};
use crate::game;
fn color_value(color: &Color) -> usize {
COLORS
@ -65,3 +68,44 @@ pub fn json_format(
"actions": actions,
})
}
#[derive(Serialize, Deserialize)]
struct JsonCard {
rank: game::Value,
#[serde(rename = "suitIndex")]
suit_index: usize
}
fn convert_card(
json_card: &JsonCard
) -> Card {
let c: Color = COLORS[json_card.suit_index];
return Card { color: c, value: json_card.rank };
}
pub fn parse_deck_and_players(
game_json: &String
) -> (u32, Vec<Card>) {
let game: serde_json::Value = match serde_json::from_str(game_json) {
Ok(v) => v,
Err(_) => panic!("Invalid file format: Could not parse contents as JSON.")
};
let (players_json, deck_json): (serde_json::Value, serde_json::Value) = match game {
serde_json::Value::Object(m) => (m["players"].clone(), m["deck"].clone()),
_ => panic!("No deck field found")
};
let deck: Vec<JsonCard> = match serde_json::from_value(deck_json) {
Ok(v) => v,
Err(e) => panic!("Could not parse cards from deck entry: {e}")
};
let players: Vec<String> = match serde_json::from_value(players_json) {
Ok(v) => v,
Err(e) => panic!("Coud not parse players form entry: {e}")
};
// The deck is reversed since in our implementation we draw from the end of the deck.
return (players.len() as u32, deck.iter().map(convert_card).rev().collect());
}

View file

@ -19,8 +19,12 @@ mod strategies {
pub mod information;
}
use std::fs;
use getopts::Options;
use std::str::FromStr;
use crate::game::DeckSpec;
use crate::json_output::parse_deck_and_players;
use crate::simulator::simulate_once;
struct SimpleLogger;
impl log::Log for SimpleLogger {
@ -65,7 +69,7 @@ fn main() {
opts.optopt(
"j",
"json-output",
"Pattern for the JSON output file. '%s' will be replaced by the seed.",
"Pattern for the JSON output file. '%s' will be replaced by the seed or input filename.",
"FILE_PATTERN",
);
opts.optopt(
@ -75,6 +79,7 @@ fn main() {
"NTHREADS",
);
opts.optopt("s", "seed", "Seed for PRNG (default random)", "SEED");
opts.optopt("f", "file", "Filename for deck input (default none)", "FNAME");
opts.optopt("p", "nplayers", "Number of players", "NPLAYERS");
opts.optopt(
"g",
@ -142,6 +147,7 @@ fn main() {
let seed = matches
.opt_str("s")
.map(|seed_str| u32::from_str(&seed_str).unwrap());
let filename = matches.opt_str("file");
let progress_info = matches
.opt_str("o")
.map(|freq_str| u32::from_str(&freq_str).unwrap());
@ -152,29 +158,27 @@ fn main() {
let json_output_pattern = matches.opt_str("j");
let json_losses_only = matches.opt_present("losses-only");
sim_games(
let sim_result = sim_games(
n_players,
strategy_str,
seed,
filename,
n_trials,
n_threads,
progress_info,
json_output_pattern,
json_losses_only,
)
.info();
json_losses_only
);
if let Some(v) = sim_result {
v.info();
}
}
fn sim_games(
n_players: u32,
strategy_str: &str,
seed: Option<u32>,
n_trials: u32,
n_threads: u32,
progress_info: Option<u32>,
json_output_pattern: Option<String>,
json_losses_only: bool,
) -> simulator::SimResult {
fn get_game_opts(
n_players: u32
) -> game::GameOptions {
let hand_size = match n_players {
2 => 5,
3 => 5,
@ -184,15 +188,27 @@ fn sim_games(
panic!("There should be 2 to 5 players, not {n_players}");
}
};
let game_opts = game::GameOptions {
return game::GameOptions {
num_players: n_players,
hand_size,
num_hints: 8,
num_lives: 3,
// hanabi rules are a bit ambiguous about whether you can give hints that match 0 cards
// Hanabi rules are a bit ambiguous about whether you can give hints that match 0 cards
allow_empty_hints: false,
};
}
fn sim_games(
n_players: u32,
strategy_str: &str,
seed: Option<u32>,
filename: Option<String>,
n_trials: u32,
n_threads: u32,
progress_info: Option<u32>,
json_output_pattern: Option<String>,
json_losses_only: bool,
) -> Option<simulator::SimResult> {
let strategy_config: Box<dyn strategy::GameStrategyConfig + Sync> = match strategy_str {
"random" => Box::new(strategies::examples::RandomStrategyConfig {
@ -207,16 +223,38 @@ fn sim_games(
panic!("Unexpected strategy argument {strategy_str}");
}
};
simulator::simulate(
&game_opts,
strategy_config,
seed,
n_trials,
n_threads,
progress_info,
json_output_pattern,
json_losses_only,
)
return if let Some(file) = filename {
let contents = fs::read_to_string(&file).expect("Failed to open file");
let (json_n_players, deck) = parse_deck_and_players(&contents);
let game_opts = get_game_opts(json_n_players);
let (_, json_output) = simulate_once(
&game_opts,
strategy_config.initialize(&game_opts),
DeckSpec::Deck(deck),
true
);
let pattern = json_output_pattern.unwrap_or("%-cheat.json".parse().unwrap());
let outfile_name = pattern.replace('%', &file);
let path = std::path::Path::new(&outfile_name);
let outfile = std::fs::File::create(path).unwrap();
serde_json::to_writer(outfile, &json_output.unwrap()).unwrap();
Option::None
} else {
let game_opts = get_game_opts(n_players);
let sim_result = simulator::simulate(
&game_opts,
strategy_config,
seed,
n_trials,
n_threads,
progress_info,
json_output_pattern,
json_losses_only,
);
Option::Some(sim_result)
}
}
fn get_results_table() -> String {
@ -275,12 +313,13 @@ fn get_results_table() -> String {
n_players,
strategy,
Some(seed),
Option::None,
n_trials,
n_threads,
None,
None,
false,
);
).unwrap();
(
format_score(simresult.average_score(), simresult.score_stderr()),
format_percent(

View file

@ -25,10 +25,14 @@ fn new_deck(seed: u32) -> Cards {
pub fn simulate_once(
opts: &GameOptions,
game_strategy: Box<dyn GameStrategy>,
seed: u32,
deck_spec: DeckSpec,
output_json: bool,
) -> (GameState, Option<serde_json::Value>) {
let deck = new_deck(seed);
let deck = match deck_spec {
DeckSpec::Seed(seed) => new_deck(seed),
DeckSpec::Deck(v) => v
};
let mut game = GameState::new(opts, deck.clone());
@ -201,7 +205,7 @@ where
let (game, json_output) = simulate_once(
opts,
strat_config_ref.initialize(opts),
seed,
DeckSpec::Seed(seed),
json_output_pattern_ref.is_some(),
);
let score = game.score();

View file

@ -137,7 +137,8 @@ impl CheatingPlayerStrategy {
}
impl PlayerStrategy for CheatingPlayerStrategy {
fn name(&self) -> String {
String::from("cheat")
let names = ["Alice", "Bob", "Cathy", "Donald", "Emily"];
names[self.me as usize].to_string() + " (Cheater)"
}
fn decide(&mut self, view: &BorrowedGameView) -> TurnChoice {
self.inform_last_player_cards(view);