Compare commits
5 commits
main
...
deck-input
Author | SHA1 | Date | |
---|---|---|---|
a85e095b0d | |||
b6986def06 | |||
1b7ff133be | |||
d99a71a0ea | |||
2fc5a84fdc |
7 changed files with 177 additions and 33 deletions
48
Cargo.lock
generated
48
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -12,3 +12,5 @@ fnv = "1.0.0"
|
|||
float-ord = "0.2.0"
|
||||
crossbeam = "0.2.5"
|
||||
serde_json = "*"
|
||||
serde_derive = "*"
|
||||
serde = "*"
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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());
|
||||
}
|
97
src/main.rs
97
src/main.rs
|
@ -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(
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue