Merge branch 'rust-2021' into json_output_2021

This commit is contained in:
timotree3 2023-01-19 21:44:15 -05:00
commit a1bfa76b77
13 changed files with 426 additions and 402 deletions

160
Cargo.lock generated
View File

@ -1,91 +1,183 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]] [[package]]
name = "crossbeam" name = "crossbeam"
version = "0.2.8" version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd66663db5a988098a89599d4857919b3acf7f61402e61365acfd3919857b9be"
[[package]] [[package]]
name = "float-ord" name = "float-ord"
version = "0.2.0" version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7bad48618fdb549078c333a7a8528acb57af271d0433bdecd523eb620628364e"
[[package]] [[package]]
name = "fnv" name = "fnv"
version = "1.0.6" version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "fuchsia-cprng"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
[[package]] [[package]]
name = "getopts" name = "getopts"
version = "0.2.14" version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5"
dependencies = [
"unicode-width",
]
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "0.4.3" version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.7" version = "0.2.125"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5916d2ae698f6de9bfb891ad7a8d65c09d232dc58cc4ac433c7da3b2fd84bc2b"
[[package]] [[package]]
name = "log" name = "log"
version = "0.3.5" version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b"
dependencies = [ dependencies = [
"libc 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.17",
]
[[package]]
name = "log"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
] ]
[[package]] [[package]]
name = "rand" name = "rand"
version = "0.3.14" version = "0.3.23"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c"
dependencies = [ dependencies = [
"libc 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", "libc",
"rand 0.4.6",
]
[[package]]
name = "rand"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293"
dependencies = [
"fuchsia-cprng",
"libc",
"rand_core 0.3.1",
"rdrand",
"winapi",
]
[[package]]
name = "rand_core"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
dependencies = [
"rand_core 0.4.2",
]
[[package]]
name = "rand_core"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
[[package]]
name = "rdrand"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
dependencies = [
"rand_core 0.3.1",
] ]
[[package]] [[package]]
name = "rust_hanabi" 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",
"float-ord 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "float-ord",
"fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", "fnv",
"getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", "getopts",
"log 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.9",
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.23",
"serde_json 1.0.38 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json",
] ]
[[package]] [[package]]
name = "ryu" name = "ryu"
version = "0.2.7" version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.88" version = "1.0.152"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.38" version = "1.0.91"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883"
dependencies = [ dependencies = [
"itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "itoa",
"ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", "ryu",
"serde 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)", "serde",
] ]
[metadata] [[package]]
"checksum crossbeam 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "348228ce9f93d20ffc30c18e575f82fa41b9c8bf064806c65d41eba4771595a0" name = "unicode-width"
"checksum float-ord 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7bad48618fdb549078c333a7a8528acb57af271d0433bdecd523eb620628364e" version = "0.1.9"
"checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9047cfbd08a437050b363d35ef160452c5fe8ea5187ae0a624708c91581d685" checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
"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" [[package]]
"checksum log 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "038b5d13189a14e5b6ac384fdb7c691a45ef0885f6d2dddbf422e6c3506b8234" name = "winapi"
"checksum rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "2791d88c6defac799c3f20d74f094ca33b9332612d9aef9078519c82e4fe04a5" version = "0.3.9"
"checksum ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "eb9e9b8cde282a9fe6a42dd4681319bfb63f121b8a8ee9439c6f4107e58a46f7" source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum serde 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)" = "9f301d728f2b94c9a7691c90f07b0b4e8a4517181d9461be94c04bddeb4bd850" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
"checksum serde_json 1.0.38 (registry+https://github.com/rust-lang/crates.io-index)" = "27dce848e7467aa0e2fcaf0a413641499c0b745452aaca1194d24dedde9e13c9" dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

View File

@ -2,12 +2,13 @@
name = "rust_hanabi" name = "rust_hanabi"
version = "0.1.0" version = "0.1.0"
authors = ["Jeff Wu <wuthefwasthat@gmail.com>"] authors = ["Jeff Wu <wuthefwasthat@gmail.com>"]
edition = "2021"
[dependencies] [dependencies]
rand = "*" rand = "0.3.0"
log = "*" log = "0.3.0"
getopts = "*" getopts = "0.2.14"
fnv = "*" fnv = "1.0.0"
float-ord = "*" float-ord = "0.2.0"
crossbeam = "0.2.5" crossbeam = "0.2.5"
serde_json = "*" serde_json = "*"

View File

@ -3,18 +3,14 @@
Hanabi is a cooperative card game of incomplete information. Hanabi is a cooperative card game of incomplete information.
Despite relatively [simple rules](https://boardgamegeek.com/article/10670613#10670613), Despite relatively [simple rules](https://boardgamegeek.com/article/10670613#10670613),
the space of Hanabi strategies is quite interesting. the space of Hanabi strategies is quite interesting.
This project provides a framework for implementing Hanabi strategies in Rust. This project provides a framework for implementing Hanabi strategies in Rust, and also implements extremely strong strategies.
It also explores some implementations, based on ideas from
[this paper](https://d0474d97-a-62cb3a1a-s-sites.googlegroups.com/site/rmgpgrwc/research-papers/Hanabi_final.pdf). The best strategy is based on the "information strategy" from
In particular, it contains an improved version of their "information strategy", [this paper](https://d0474d97-a-62cb3a1a-s-sites.googlegroups.com/site/rmgpgrwc/research-papers/Hanabi_final.pdf). See results ([below](#results)).
which achieves the best results I'm aware of for games with more than 2 players ([see below](#results)). It held state-of-the-art results (from March 2016) until December 2019, when [researchers at Facebook](https://arxiv.org/abs/1912.02318) surpassed it by extending the idea further with explicit search.
Please feel free to contact me about Hanabi strategies, or this framework. Please feel free to contact me about Hanabi strategies, or this framework.
Most similar projects I am aware of:
- https://github.com/rjtobin/HanSim (written for the paper mentioned above)
- https://github.com/Quuxplusone/Hanabi
## Setup ## Setup
Install rust (rustc and cargo), and clone this git repo. Install rust (rustc and cargo), and clone this git repo.
@ -75,3 +71,13 @@ On the first 20000 seeds, we have these scores and win rates (average ± standar
| | 90.59 ± 0.21 % | 98.17 ± 0.09 % | 97.76 ± 0.10 % | 96.42 ± 0.13 % | | | 90.59 ± 0.21 % | 98.17 ± 0.09 % | 97.76 ± 0.10 % | 96.42 ± 0.13 % |
| info | 22.5194 ± 0.0125 | 24.7942 ± 0.0039 | 24.9354 ± 0.0022 | 24.9220 ± 0.0024 | | info | 22.5194 ± 0.0125 | 24.7942 ± 0.0039 | 24.9354 ± 0.0022 | 24.9220 ± 0.0024 |
| | 12.58 ± 0.23 % | 84.46 ± 0.26 % | 95.03 ± 0.15 % | 94.01 ± 0.17 % | | | 12.58 ± 0.23 % | 84.46 ± 0.26 % | 95.03 ± 0.15 % | 94.01 ± 0.17 % |
## Other work
Most similar projects I am aware of:
- https://github.com/rjtobin/HanSim (written for the paper mentioned above which introduces the information strategy)
- https://github.com/Quuxplusone/Hanabi
Some researchers are trying to solve Hanabi using machine learning techniques:
- [Initial paper](https://arxiv.org/abs/1902.00506) from DeepMind and Google Brain researchers. See [this Wall Street Journal coverage](https://www.wsj.com/articles/why-the-card-game-hanabi-is-the-next-big-hurdle-for-artificial-intelligence-11553875351)
- [This paper](https://arxiv.org/abs/1912.02318) from Facebook, code at https://github.com/facebookresearch/Hanabi_SPARTA which includes their machine-learned agent

View File

@ -20,7 +20,7 @@ pub fn get_count_for_value(value: Value) -> u32 {
2 | 3 | 4 => 2, 2 | 3 | 4 => 2,
5 => 1, 5 => 1,
_ => { _ => {
panic!(format!("Unexpected value: {}", value)); panic!("Unexpected value: {value}");
} }
} }
} }
@ -32,10 +32,7 @@ pub struct Card {
} }
impl Card { impl Card {
pub fn new(color: Color, value: Value) -> Card { pub fn new(color: Color, value: Value) -> Card {
Card { Card { color, value }
color: color,
value: value,
}
} }
} }
impl fmt::Display for Card { impl fmt::Display for Card {
@ -61,7 +58,7 @@ impl CardCounts {
counts.insert(Card::new(color, value), 0); counts.insert(Card::new(color, value), 0);
} }
} }
CardCounts { counts: counts } CardCounts { counts }
} }
pub fn get_count(&self, card: &Card) -> u32 { pub fn get_count(&self, card: &Card) -> u32 {
@ -81,16 +78,16 @@ impl CardCounts {
impl fmt::Display for CardCounts { impl fmt::Display for CardCounts {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for &color in COLORS.iter() { for &color in COLORS.iter() {
try!(f.write_str(&format!("{}: ", color,))); write!(f, "{color}: ")?;
for &value in VALUES.iter() { for &value in VALUES.iter() {
let count = self.get_count(&Card::new(color, value)); let count = self.get_count(&Card::new(color, value));
let total = get_count_for_value(value); let total = get_count_for_value(value);
try!(f.write_str(&format!("{}/{} {}s", count, total, value))); write!(f, "{count}/{total} {value}s")?;
if value != FINAL_VALUE { if value != FINAL_VALUE {
try!(f.write_str(", ")); f.write_str(", ")?;
} }
} }
try!(f.write_str("\n")); f.write_str("\n")?;
} }
Ok(()) Ok(())
} }
@ -126,9 +123,7 @@ impl Discard {
} }
impl fmt::Display for Discard { impl fmt::Display for Discard {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// try!(f.write_str(&format!( // write!(f, "{}", self.cards)?;
// "{}", self.cards,
// )));
write!(f, "{}", self.counts) write!(f, "{}", self.counts)
} }
} }
@ -143,10 +138,7 @@ pub struct Firework {
} }
impl Firework { impl Firework {
pub fn new(color: Color) -> Firework { pub fn new(color: Color) -> Firework {
Firework { Firework { color, top: 0 }
color: color,
top: 0,
}
} }
pub fn needed_value(&self) -> Option<Value> { pub fn needed_value(&self) -> Option<Value> {
@ -194,12 +186,12 @@ pub enum Hinted {
} }
impl fmt::Display for Hinted { impl fmt::Display for Hinted {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self { match *self {
&Hinted::Color(color) => { Hinted::Color(color) => {
write!(f, "{}", color) write!(f, "{color}")
} }
&Hinted::Value(value) => { Hinted::Value(value) => {
write!(f, "{}", value) write!(f, "{value}")
} }
} }
} }
@ -282,9 +274,9 @@ impl BoardState {
.collect::<FnvHashMap<_, _>>(); .collect::<FnvHashMap<_, _>>();
BoardState { BoardState {
deck_size: deck_size, deck_size,
total_cards: deck_size, total_cards: deck_size,
fireworks: fireworks, fireworks,
discard: Discard::new(), discard: Discard::new(),
num_players: opts.num_players, num_players: opts.num_players,
hand_size: opts.hand_size, hand_size: opts.hand_size,
@ -340,52 +332,28 @@ impl BoardState {
return value - 1; return value - 1;
} }
} }
return FINAL_VALUE; FINAL_VALUE
} }
// is never going to play, based on discard + fireworks // is never going to play, based on discard + fireworks
pub fn is_dead(&self, card: &Card) -> bool { pub fn is_dead(&self, card: &Card) -> bool {
let firework = self.fireworks.get(&card.color).unwrap(); let firework = self.fireworks.get(&card.color).unwrap();
if firework.complete() { firework.complete()
true || card.value < firework.needed_value().unwrap()
} else { || card.value > self.highest_attainable(card.color)
let needed = firework.needed_value().unwrap();
if card.value < needed {
true
} else {
card.value > self.highest_attainable(card.color)
}
}
} }
// can be discarded without necessarily sacrificing score, based on discard + fireworks // can be discarded without necessarily sacrificing score, based on discard + fireworks
pub fn is_dispensable(&self, card: &Card) -> bool { pub fn is_dispensable(&self, card: &Card) -> bool {
let firework = self.fireworks.get(&card.color).unwrap(); self.is_dead(card) || self.discard.remaining(card) != 1
if firework.complete() {
true
} else {
let needed = firework.needed_value().unwrap();
if card.value < needed {
true
} else {
if card.value > self.highest_attainable(card.color) {
true
} else {
self.discard.remaining(&card) != 1
}
}
}
} }
pub fn get_players(&self) -> Range<Player> { pub fn get_players(&self) -> Range<Player> {
(0..self.num_players) 0..self.num_players
} }
pub fn score(&self) -> Score { pub fn score(&self) -> Score {
self.fireworks self.fireworks.values().map(Firework::score).sum()
.iter()
.map(|(_, firework)| firework.score())
.fold(0, |a, b| a + b)
} }
pub fn discard_size(&self) -> u32 { pub fn discard_size(&self) -> u32 {
@ -408,35 +376,35 @@ impl BoardState {
impl fmt::Display for BoardState { impl fmt::Display for BoardState {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.is_over() { if self.is_over() {
try!(f.write_str(&format!("Turn {} (GAME ENDED):\n", self.turn))); writeln!(f, "Turn {} (GAME ENDED):", self.turn)?;
} else { } else {
try!(f.write_str(&format!( writeln!(f, "Turn {} (Player {}'s turn):", self.turn, self.player)?;
"Turn {} (Player {}'s turn):\n",
self.turn, self.player
)));
} }
try!(f.write_str(&format!("{} cards remaining in deck\n", self.deck_size))); writeln!(f, "{} cards remaining in deck", self.deck_size)?;
if self.deck_size == 0 { if self.deck_size == 0 {
try!(f.write_str(&format!( writeln!(
"Deck is empty. {} turns remaining in game\n", f,
"Deck is empty. {} turns remaining in game",
self.deckless_turns_remaining self.deckless_turns_remaining
))); )?;
} }
try!(f.write_str(&format!( writeln!(
"{}/{} hints remaining\n", f,
"{}/{} hints remaining",
self.hints_remaining, self.hints_total self.hints_remaining, self.hints_total
))); )?;
try!(f.write_str(&format!( writeln!(
"{}/{} lives remaining\n", f,
"{}/{} lives remaining",
self.lives_remaining, self.lives_total self.lives_remaining, self.lives_total
))); )?;
try!(f.write_str("Fireworks:\n")); f.write_str("Fireworks:\n")?;
for &color in COLORS.iter() { for &color in COLORS.iter() {
try!(f.write_str(&format!(" {}\n", self.get_firework(color)))); writeln!(f, " {}", self.get_firework(color))?;
} }
try!(f.write_str("Discard:\n")); f.write_str("Discard:\n")?;
try!(f.write_str(&format!("{}\n", self.discard))); writeln!(f, "{}\n", self.discard)?;
Ok(()) Ok(())
} }
@ -445,7 +413,7 @@ impl fmt::Display for BoardState {
// complete game view of a given player // complete game view of a given player
pub trait GameView { pub trait GameView {
fn me(&self) -> Player; fn me(&self) -> Player;
fn get_hand(&self, &Player) -> &Cards; fn get_hand(&self, player: &Player) -> &Cards;
fn get_board(&self) -> &BoardState; fn get_board(&self) -> &BoardState;
fn my_hand_size(&self) -> usize; fn my_hand_size(&self) -> usize;
@ -461,8 +429,7 @@ pub trait GameView {
fn has_card(&self, player: &Player, card: &Card) -> bool { fn has_card(&self, player: &Player, card: &Card) -> bool {
self.get_hand(player) self.get_hand(player)
.iter() .iter()
.position(|other_card| card == other_card) .any(|other_card| card == other_card)
.is_some()
} }
fn get_other_players(&self) -> Vec<Player> { fn get_other_players(&self) -> Vec<Player> {
@ -475,12 +442,12 @@ pub trait GameView {
fn can_see(&self, card: &Card) -> bool { fn can_see(&self, card: &Card) -> bool {
self.get_other_players() self.get_other_players()
.iter() .iter()
.any(|player| self.has_card(&player, card)) .any(|player| self.has_card(player, card))
} }
fn someone_else_can_play(&self) -> bool { fn someone_else_can_play(&self) -> bool {
self.get_other_players().iter().any(|player| { self.get_other_players().iter().any(|player| {
self.get_hand(&player) self.get_hand(player)
.iter() .iter()
.any(|card| self.get_board().is_playable(card)) .any(|card| self.get_board().is_playable(card))
}) })
@ -534,9 +501,9 @@ impl OwnedGameView {
.collect::<FnvHashMap<_, _>>(); .collect::<FnvHashMap<_, _>>();
OwnedGameView { OwnedGameView {
player: borrowed_view.player.clone(), player: borrowed_view.player,
hand_size: borrowed_view.hand_size, hand_size: borrowed_view.hand_size,
other_hands: other_hands, other_hands,
board: (*borrowed_view.board).clone(), board: (*borrowed_view.board).clone(),
} }
} }
@ -582,22 +549,22 @@ pub struct GameState {
} }
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 {
try!(f.write_str("\n")); f.write_str("\n")?;
try!(f.write_str("======\n")); f.write_str("======\n")?;
try!(f.write_str("Hands:\n")); f.write_str("Hands:\n")?;
try!(f.write_str("======\n")); f.write_str("======\n")?;
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))); write!(f, "player {player}:")?;
for (_i, card) in hand.iter() { for (_i, card) in hand.iter() {
try!(f.write_str(&format!(" {}", card))); write!(f, " {card}")?;
} }
try!(f.write_str(&"\n")); f.write_str("\n")?;
} }
try!(f.write_str("======\n")); f.write_str("======\n")?;
try!(f.write_str("Board:\n")); f.write_str("Board:\n")?;
try!(f.write_str("======\n")); f.write_str("======\n")?;
try!(f.write_str(&format!("{}", self.board))); write!(f, "{}", self.board)?;
Ok(()) Ok(())
} }
} }
@ -654,9 +621,9 @@ impl GameState {
} }
} }
BorrowedGameView { BorrowedGameView {
player: player, player,
hand_size: self.hands.get(&player).unwrap().len(), hand_size: self.hands.get(&player).unwrap().len(),
other_hands: other_hands, other_hands,
board: &self.board, board: &self.board,
} }
} }
@ -669,27 +636,19 @@ impl GameState {
// 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. let hand = &mut self.hands.get_mut(&self.board.player).unwrap();
// Can we have this look nicer? let card = hand.remove(index).1;
let result = {
let ref mut hand = self.hands.get_mut(&self.board.player).unwrap();
hand.remove(index).1
};
self.update_player_hand(); self.update_player_hand();
result card
} }
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. let hand = &mut self.hands.get_mut(&self.board.player).unwrap();
// Can we have this look nicer? if (hand.len() as u32) < self.board.hand_size {
{ if let Some(new_card) = self.deck.pop() {
let ref mut hand = self.hands.get_mut(&self.board.player).unwrap(); self.board.deck_size -= 1;
if (hand.len() as u32) < self.board.hand_size { debug!("Drew new card, {}", new_card.1);
if let Some(new_card) = self.deck.pop() { hand.push(new_card);
self.board.deck_size -= 1;
debug!("Drew new card, {}", new_card.1);
hand.push(new_card);
}
} }
} }
self.update_player_hand(); self.update_player_hand();
@ -706,9 +665,10 @@ impl GameState {
self.board.hints_remaining -= 1; self.board.hints_remaining -= 1;
debug!("Hint to player {}, about {}", hint.player, hint.hinted); debug!("Hint to player {}, about {}", hint.player, hint.hinted);
assert!( assert_ne!(
self.board.player != hint.player, self.board.player, hint.player,
format!("Player {} gave a hint to himself", hint.player) "Player {} gave a hint to himself",
hint.player
); );
let hand = self.hands.get(&hint.player).unwrap(); let hand = self.hands.get(&hint.player).unwrap();
@ -767,9 +727,9 @@ impl GameState {
} }
}; };
let turn_record = TurnRecord { let turn_record = TurnRecord {
player: self.board.player.clone(), player: self.board.player,
result: turn_result, result: turn_result,
choice: choice, choice,
}; };
self.board.turn_history.push(turn_record.clone()); self.board.turn_history.push(turn_record.clone());

View File

@ -1,12 +1,12 @@
use std::cmp::Eq; use std::cmp::Eq;
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::convert::From; use std::convert::From;
use std::fmt; use std::fmt::{self, Write};
use std::hash::Hash; use std::hash::Hash;
use std::ops::{Index, IndexMut}; use std::ops::{Index, IndexMut};
use std::slice; use std::slice;
use game::*; use crate::game::*;
// trait representing information about a card // trait representing information about a card
pub trait CardInfo { pub trait CardInfo {
@ -33,7 +33,7 @@ pub trait CardInfo {
// get probability weight for the card // get probability weight for the card
#[allow(unused_variables)] #[allow(unused_variables)]
fn get_weight(&self, card: &Card) -> f32 { fn get_weight(&self, card: &Card) -> f32 {
1 as f32 1.0
} }
fn get_weighted_possibilities(&self) -> Vec<(Card, f32)> { fn get_weighted_possibilities(&self) -> Vec<(Card, f32)> {
@ -49,11 +49,11 @@ pub trait CardInfo {
fn total_weight(&self) -> f32 { fn total_weight(&self) -> f32 {
self.get_possibilities() self.get_possibilities()
.iter() .iter()
.map(|card| self.get_weight(&card)) .map(|card| self.get_weight(card))
.fold(0.0, |a, b| a + b) .fold(0.0, |a, b| a + b)
} }
fn weighted_score<T>(&self, score_fn: &Fn(&Card) -> T) -> f32 fn weighted_score<T>(&self, score_fn: &dyn Fn(&Card) -> T) -> f32
where where
f32: From<T>, f32: From<T>,
{ {
@ -72,7 +72,7 @@ pub trait CardInfo {
self.weighted_score(&|card| card.value as f32) self.weighted_score(&|card| card.value as f32)
} }
fn probability_of_predicate(&self, predicate: &Fn(&Card) -> bool) -> f32 { fn probability_of_predicate(&self, predicate: &dyn Fn(&Card) -> bool) -> f32 {
let f = |card: &Card| { let f = |card: &Card| {
if predicate(card) { if predicate(card) {
1.0 1.0
@ -149,7 +149,7 @@ where
fn get_possibilities(&self) -> Vec<T> { fn get_possibilities(&self) -> Vec<T> {
self.get_possibility_set() self.get_possibility_set()
.iter() .iter()
.map(|t| t.clone()) .copied()
.collect::<Vec<T>>() .collect::<Vec<T>>()
} }
@ -160,14 +160,14 @@ where
fn initialize() -> HashSet<T> { fn initialize() -> HashSet<T> {
Self::get_all_possibilities() Self::get_all_possibilities()
.iter() .iter()
.map(|val| val.clone()) .copied()
.collect::<HashSet<_>>() .collect::<HashSet<_>>()
} }
fn mark_true(&mut self, value: T) { fn mark_true(&mut self, value: T) {
let possible = self.get_mut_possibility_set(); let possible = self.get_mut_possibility_set();
possible.clear(); possible.clear();
possible.insert(value.clone()); possible.insert(value);
} }
fn mark_false(&mut self, value: T) { fn mark_false(&mut self, value: T) {
@ -268,7 +268,7 @@ impl fmt::Display for SimpleCardInfo {
//} //}
for &value in &VALUES { for &value in &VALUES {
if self.value_info.is_possible(value) { if self.value_info.is_possible(value) {
string.push_str(&format!("{}", value)); write!(string, "{value}").unwrap();
} }
} }
f.pad(&string) f.pad(&string)
@ -301,10 +301,10 @@ impl CardPossibilityTable {
pub fn decrement_weight(&mut self, card: &Card) { pub fn decrement_weight(&mut self, card: &Card) {
let remove = { let remove = {
let weight = self.possible.get_mut(card).expect(&format!( let weight = self
"Decrementing weight for impossible card: {}", .possible
card .get_mut(card)
)); .unwrap_or_else(|| panic!("Decrementing weight for impossible card: {card}"));
*weight -= 1; *weight -= 1;
*weight == 0 *weight == 0
}; };
@ -368,7 +368,7 @@ impl<'a> From<&'a CardCounts> for CardPossibilityTable {
} }
} }
} }
CardPossibilityTable { possible: possible } CardPossibilityTable { possible }
} }
} }
impl CardInfo for CardPossibilityTable { impl CardInfo for CardPossibilityTable {
@ -380,11 +380,7 @@ impl CardInfo for CardPossibilityTable {
self.possible.contains_key(card) self.possible.contains_key(card)
} }
fn get_possibilities(&self) -> Vec<Card> { fn get_possibilities(&self) -> Vec<Card> {
let mut cards = self let mut cards = self.possible.keys().cloned().collect::<Vec<_>>();
.possible
.keys()
.map(|card| card.clone())
.collect::<Vec<_>>();
cards.sort(); cards.sort();
cards cards
} }
@ -405,7 +401,7 @@ impl CardInfo for CardPossibilityTable {
impl fmt::Display for CardPossibilityTable { impl fmt::Display for CardPossibilityTable {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for (card, weight) in &self.possible { for (card, weight) in &self.possible {
try!(f.write_str(&format!("{} {}, ", weight, card))); write!(f, "{weight} {card}, ")?;
} }
Ok(()) Ok(())
} }
@ -424,20 +420,18 @@ where
{ {
pub fn new(hand_size: u32) -> Self { pub fn new(hand_size: u32) -> Self {
let hand_info = (0..hand_size).map(|_| T::new()).collect::<Vec<_>>(); let hand_info = (0..hand_size).map(|_| T::new()).collect::<Vec<_>>();
HandInfo { HandInfo { hand_info }
hand_info: hand_info,
}
} }
// update for hint to me // update for hint to me
pub fn update_for_hint(&mut self, hinted: &Hinted, matches: &Vec<bool>) { pub fn update_for_hint(&mut self, hinted: &Hinted, matches: &[bool]) {
match hinted { match *hinted {
&Hinted::Color(color) => { Hinted::Color(color) => {
for (card_info, &matched) in self.hand_info.iter_mut().zip(matches.iter()) { for (card_info, &matched) in self.hand_info.iter_mut().zip(matches.iter()) {
card_info.mark_color(color, matched); card_info.mark_color(color, matched);
} }
} }
&Hinted::Value(value) => { Hinted::Value(value) => {
for (card_info, &matched) in self.hand_info.iter_mut().zip(matches.iter()) { for (card_info, &matched) in self.hand_info.iter_mut().zip(matches.iter()) {
card_info.mark_value(value, matched); card_info.mark_value(value, matched);
} }

View File

@ -1,4 +1,4 @@
use game::*; use crate::game::*;
use serde_json::*; use serde_json::*;
fn color_value(color: &Color) -> usize { fn color_value(color: &Color) -> usize {

View File

@ -36,7 +36,7 @@ impl log::Log for SimpleLogger {
} }
fn print_usage(program: &str, opts: Options) { fn print_usage(program: &str, opts: Options) {
print!("{}", opts.usage(&format!("Usage: {} [options]", program))); print!("{}", opts.usage(&format!("Usage: {program} [options]")));
} }
fn main() { fn main() {
@ -102,7 +102,7 @@ fn main() {
Ok(m) => m, Ok(m) => m,
Err(f) => { Err(f) => {
print_usage(&program, opts); print_usage(&program, opts);
panic!(f.to_string()) panic!("{}", f)
} }
}; };
if matches.opt_present("h") { if matches.opt_present("h") {
@ -118,7 +118,8 @@ fn main() {
return print!("{}", get_results_table()); return print!("{}", get_results_table());
} }
let log_level_str: &str = &matches.opt_str("l").unwrap_or("info".to_string()); let l_opt = matches.opt_str("l");
let log_level_str = l_opt.as_deref().unwrap_or("info");
let log_level = match log_level_str { let log_level = match log_level_str {
"trace" => log::LogLevelFilter::Trace, "trace" => log::LogLevelFilter::Trace,
"debug" => log::LogLevelFilter::Debug, "debug" => log::LogLevelFilter::Debug,
@ -127,7 +128,7 @@ fn main() {
"error" => log::LogLevelFilter::Error, "error" => log::LogLevelFilter::Error,
_ => { _ => {
print_usage(&program, opts); print_usage(&program, opts);
panic!("Unexpected log level argument {}", log_level_str); panic!("Unexpected log level argument {log_level_str}");
} }
}; };
@ -137,21 +138,20 @@ fn main() {
}) })
.unwrap(); .unwrap();
let n_trials = u32::from_str(&matches.opt_str("n").unwrap_or("1".to_string())).unwrap(); let n_trials = u32::from_str(matches.opt_str("n").as_deref().unwrap_or("1")).unwrap();
let seed = matches let seed = matches
.opt_str("s") .opt_str("s")
.map(|seed_str| u32::from_str(&seed_str).unwrap()); .map(|seed_str| u32::from_str(&seed_str).unwrap());
let progress_info = matches let progress_info = matches
.opt_str("o") .opt_str("o")
.map(|freq_str| u32::from_str(&freq_str).unwrap()); .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").as_deref().unwrap_or("1")).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 strategy_str: &str = g_opt.as_deref().unwrap_or("cheat");
let json_output_pattern = matches.opt_str("j"); let json_output_pattern = matches.opt_str("j");
let json_losses_only = matches.opt_present("losses-only"); let json_losses_only = matches.opt_present("losses-only");
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( sim_games(
n_players, n_players,
strategy_str, strategy_str,
@ -181,30 +181,30 @@ fn sim_games(
4 => 4, 4 => 4,
5 => 4, 5 => 4,
_ => { _ => {
panic!("There should be 2 to 5 players, not {}", n_players); panic!("There should be 2 to 5 players, not {n_players}");
} }
}; };
let game_opts = game::GameOptions { let game_opts = game::GameOptions {
num_players: n_players, num_players: n_players,
hand_size: hand_size, hand_size,
num_hints: 8, num_hints: 8,
num_lives: 3, 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, allow_empty_hints: false,
}; };
let strategy_config: Box<strategy::GameStrategyConfig + Sync> = match strategy_str { let strategy_config: Box<dyn strategy::GameStrategyConfig + Sync> = match strategy_str {
"random" => Box::new(strategies::examples::RandomStrategyConfig { "random" => Box::new(strategies::examples::RandomStrategyConfig {
hint_probability: 0.4, hint_probability: 0.4,
play_probability: 0.2, play_probability: 0.2,
}) as Box<strategy::GameStrategyConfig + Sync>, }) as Box<dyn strategy::GameStrategyConfig + Sync>,
"cheat" => Box::new(strategies::cheating::CheatingStrategyConfig::new()) "cheat" => Box::new(strategies::cheating::CheatingStrategyConfig::new())
as Box<strategy::GameStrategyConfig + Sync>, as Box<dyn strategy::GameStrategyConfig + Sync>,
"info" => Box::new(strategies::information::InformationStrategyConfig::new()) "info" => Box::new(strategies::information::InformationStrategyConfig::new())
as Box<strategy::GameStrategyConfig + Sync>, as Box<dyn strategy::GameStrategyConfig + Sync>,
_ => { _ => {
panic!("Unexpected strategy argument {}", strategy_str); panic!("Unexpected strategy argument {strategy_str}");
} }
}; };
simulator::simulate( simulator::simulate(
@ -227,19 +227,18 @@ fn get_results_table() -> String {
let n_threads = 8; let n_threads = 8;
let intro = format!( let intro = format!(
"On the first {} seeds, we have these scores and win rates (average ± standard error):\n\n", "On the first {n_trials} seeds, we have these scores and win rates (average ± standard error):\n\n"
n_trials
); );
let format_name = |x| format!(" {:7} ", x); let format_name = |x| format!(" {x:7} ");
let format_players = |x| format!(" {}p ", x); let format_players = |x| format!(" {x}p ");
let format_percent = |x, stderr| format!(" {:05.2} ± {:.2} % ", x, stderr); let format_percent = |x, stderr| format!(" {x:05.2} ± {stderr:.2} % ");
let format_score = |x, stderr| format!(" {:07.4} ± {:.4} ", x, stderr); let format_score = |x, stderr| format!(" {x:07.4} ± {stderr:.4} ");
let space = String::from(" "); let space = String::from(" ");
let dashes = String::from("---------"); let dashes = String::from("---------");
let dashes_long = String::from("------------------"); let dashes_long = String::from("------------------");
type TwoLines = (String, String); type TwoLines = (String, String);
fn make_twolines( fn make_twolines(
player_nums: &Vec<u32>, player_nums: &[u32],
head: TwoLines, head: TwoLines,
make_block: &dyn Fn(u32) -> TwoLines, make_block: &dyn Fn(u32) -> TwoLines,
) -> TwoLines { ) -> TwoLines {
@ -259,14 +258,12 @@ fn get_results_table() -> String {
} }
fn concat_twolines(body: Vec<TwoLines>) -> String { fn concat_twolines(body: Vec<TwoLines>) -> String {
body.into_iter().fold(String::default(), |output, (a, b)| { body.into_iter().fold(String::default(), |output, (a, b)| {
(output + &a + "\n" + &b + "\n") output + &a + "\n" + &b + "\n"
}) })
} }
let header = make_twolines( let header = make_twolines(&player_nums, (space.clone(), dashes), &|n_players| {
&player_nums, (format_players(n_players), dashes_long.clone())
(space.clone(), dashes.clone()), });
&|n_players| (format_players(n_players), dashes_long.clone()),
);
let mut body = strategies let mut body = strategies
.iter() .iter()
.map(|strategy| { .map(|strategy| {
@ -319,7 +316,7 @@ time cargo run --release -- --write-results-table
let readme_init = { let readme_init = {
let parts = readme_contents.splitn(2, separator).collect::<Vec<_>>(); let parts = readme_contents.splitn(2, separator).collect::<Vec<_>>();
if parts.len() != 2 { if parts.len() != 2 {
panic!("{} has been modified in the Results section!", readme); panic!("{readme} has been modified in the Results section!");
} }
parts[0] parts[0]
}; };

View File

@ -1,11 +1,10 @@
use crossbeam;
use fnv::FnvHashMap; use fnv::FnvHashMap;
use rand::{self, Rng, SeedableRng}; use rand::{self, Rng, SeedableRng};
use std::fmt; use std::fmt;
use game::*; use crate::game::*;
use json_output::*; use crate::json_output::*;
use strategy::*; use crate::strategy::*;
fn new_deck(seed: u32) -> Cards { fn new_deck(seed: u32) -> Cards {
let mut deck: Cards = Cards::new(); let mut deck: Cards = Cards::new();
@ -25,7 +24,7 @@ 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<dyn GameStrategy>,
seed: u32, seed: u32,
output_json: bool, output_json: bool,
) -> (GameState, Option<serde_json::Value>) { ) -> (GameState, Option<serde_json::Value>) {
@ -41,7 +40,7 @@ pub fn simulate_once(
game_strategy.initialize(player, &game.get_view(player)), game_strategy.initialize(player, &game.get_view(player)),
) )
}) })
.collect::<FnvHashMap<Player, Box<PlayerStrategy>>>(); .collect::<FnvHashMap<Player, Box<dyn PlayerStrategy>>>();
let mut actions = Vec::new(); let mut actions = Vec::new();
@ -55,7 +54,7 @@ pub fn simulate_once(
debug!("{}", game); debug!("{}", game);
let choice = { let choice = {
let mut 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 { if output_json {
@ -75,7 +74,7 @@ pub fn simulate_once(
let turn = game.process_choice(choice); let turn = game.process_choice(choice);
for player in game.get_players() { for player in game.get_players() {
let mut strategy = strategies.get_mut(&player).unwrap(); let strategy = strategies.get_mut(&player).unwrap();
strategy.update(&turn, &game.get_view(player)); strategy.update(&turn, &game.get_view(player));
} }
} }
@ -112,14 +111,14 @@ impl Histogram {
fn insert_many(&mut self, val: Score, count: u32) { fn insert_many(&mut self, val: Score, count: u32) {
let new_count = self.get_count(&val) + count; let new_count = self.get_count(&val) + count;
self.hist.insert(val, new_count); self.hist.insert(val, new_count);
self.sum += val * (count as u32); self.sum += val * count;
self.total_count += count; self.total_count += count;
} }
pub fn insert(&mut self, val: Score) { pub fn insert(&mut self, val: Score) {
self.insert_many(val, 1); self.insert_many(val, 1);
} }
pub fn get_count(&self, val: &Score) -> u32 { pub fn get_count(&self, val: &Score) -> u32 {
*self.hist.get(&val).unwrap_or(&0) *self.hist.get(val).unwrap_or(&0)
} }
pub fn percentage_with(&self, val: &Score) -> f32 { pub fn percentage_with(&self, val: &Score) -> f32 {
self.get_count(val) as f32 / self.total_count as f32 self.get_count(val) as f32 / self.total_count as f32
@ -149,7 +148,7 @@ impl fmt::Display for Histogram {
let mut keys = self.hist.keys().collect::<Vec<_>>(); let mut keys = self.hist.keys().collect::<Vec<_>>();
keys.sort(); keys.sort();
for val in keys { for val in keys {
try!(f.write_str(&format!("\n{}: {}", val, self.get_count(val),))); write!(f, "\n{}: {}", val, self.get_count(val))?;
} }
Ok(()) Ok(())
} }
@ -239,11 +238,11 @@ where
lives_histogram.merge(thread_lives_histogram); lives_histogram.merge(thread_lives_histogram);
} }
non_perfect_seeds.sort(); non_perfect_seeds.sort_unstable();
SimResult { SimResult {
scores: score_histogram, scores: score_histogram,
lives: lives_histogram, lives: lives_histogram,
non_perfect_seed: non_perfect_seeds.get(0).cloned(), non_perfect_seed: non_perfect_seeds.first().cloned(),
} }
}) })
} }

View File

@ -2,8 +2,8 @@ use fnv::{FnvHashMap, FnvHashSet};
use std::cell::RefCell; use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
use game::*; use crate::game::*;
use strategy::*; use crate::strategy::*;
// strategy that explicitly cheats by using Rc/RefCell // strategy that explicitly cheats by using Rc/RefCell
// serves as a reference point for other strategies // serves as a reference point for other strategies
@ -25,7 +25,7 @@ impl CheatingStrategyConfig {
} }
} }
impl GameStrategyConfig for CheatingStrategyConfig { impl GameStrategyConfig for CheatingStrategyConfig {
fn initialize(&self, _: &GameOptions) -> Box<GameStrategy> { fn initialize(&self, _: &GameOptions) -> Box<dyn GameStrategy> {
Box::new(CheatingStrategy::new()) Box::new(CheatingStrategy::new())
} }
} }
@ -42,7 +42,7 @@ impl CheatingStrategy {
} }
} }
impl GameStrategy for CheatingStrategy { impl GameStrategy for CheatingStrategy {
fn initialize(&self, player: Player, view: &BorrowedGameView) -> Box<PlayerStrategy> { fn initialize(&self, player: Player, view: &BorrowedGameView) -> Box<dyn PlayerStrategy> {
for (&player, &hand) in &view.other_hands { for (&player, &hand) in &view.other_hands {
self.player_hands_cheat self.player_hands_cheat
.borrow_mut() .borrow_mut()
@ -95,7 +95,7 @@ impl CheatingPlayerStrategy {
fn hand_play_value(&self, view: &BorrowedGameView, hand: &Cards) -> u32 { fn hand_play_value(&self, view: &BorrowedGameView, hand: &Cards) -> u32 {
hand.iter() hand.iter()
.map(|card| self.card_play_value(view, card)) .map(|card| self.card_play_value(view, card))
.fold(0, |a, b| a + b) .sum()
} }
// how badly do we need to play a particular card // how badly do we need to play a particular card
@ -106,13 +106,11 @@ impl CheatingPlayerStrategy {
let my_hand_value = self.hand_play_value(view, my_hand); let my_hand_value = self.hand_play_value(view, my_hand);
for player in view.board.get_players() { for player in view.board.get_players() {
if player != self.me { if player != self.me && view.has_card(&player, card) {
if view.has_card(&player, card) { let their_hand_value = self.hand_play_value(view, hands.get(&player).unwrap());
let their_hand_value = self.hand_play_value(view, hands.get(&player).unwrap()); // they can play this card, and have less urgent plays than i do
// they can play this card, and have less urgent plays than i do if their_hand_value < my_hand_value {
if their_hand_value < my_hand_value { return 10 - (card.value as i32);
return 10 - (card.value as i32);
}
} }
} }
} }
@ -134,7 +132,7 @@ impl CheatingPlayerStrategy {
} }
set.insert(card.clone()); set.insert(card.clone());
} }
return None; None
} }
} }
impl PlayerStrategy for CheatingPlayerStrategy { impl PlayerStrategy for CheatingPlayerStrategy {
@ -152,7 +150,7 @@ impl PlayerStrategy for CheatingPlayerStrategy {
.filter(|&(_, card)| view.board.is_playable(card)) .filter(|&(_, card)| view.board.is_playable(card))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
if playable_cards.len() > 0 { if !playable_cards.is_empty() {
// play the best playable card // play the best playable card
// the higher the play_score, the better to play // the higher the play_score, the better to play
let mut index = 0; let mut index = 0;
@ -184,10 +182,8 @@ impl PlayerStrategy for CheatingPlayerStrategy {
// hinting is better than discarding dead cards // hinting is better than discarding dead cards
// (probably because it stalls the deck-drawing). // (probably because it stalls the deck-drawing).
if view.board.hints_remaining > 0 { if view.board.hints_remaining > 0 && view.someone_else_can_play() {
if view.someone_else_can_play() { return self.throwaway_hint(view);
return self.throwaway_hint(view);
}
} }
// if anything is totally useless, discard it // if anything is totally useless, discard it

View File

@ -1,6 +1,6 @@
use game::*; use crate::game::*;
use crate::strategy::*;
use rand::{self, Rng}; use rand::{self, Rng};
use strategy::*;
// dummy, terrible strategy, as an example // dummy, terrible strategy, as an example
#[derive(Clone)] #[derive(Clone)]
@ -10,7 +10,7 @@ pub struct RandomStrategyConfig {
} }
impl GameStrategyConfig for RandomStrategyConfig { impl GameStrategyConfig for RandomStrategyConfig {
fn initialize(&self, _: &GameOptions) -> Box<GameStrategy> { fn initialize(&self, _: &GameOptions) -> Box<dyn GameStrategy> {
Box::new(RandomStrategy { Box::new(RandomStrategy {
hint_probability: self.hint_probability, hint_probability: self.hint_probability,
play_probability: self.play_probability, play_probability: self.play_probability,
@ -23,7 +23,7 @@ pub struct RandomStrategy {
play_probability: f64, play_probability: f64,
} }
impl GameStrategy for RandomStrategy { impl GameStrategy for RandomStrategy {
fn initialize(&self, player: Player, _: &BorrowedGameView) -> Box<PlayerStrategy> { fn initialize(&self, player: Player, _: &BorrowedGameView) -> Box<dyn PlayerStrategy> {
Box::new(RandomStrategyPlayer { Box::new(RandomStrategyPlayer {
hint_probability: self.hint_probability, hint_probability: self.hint_probability,
play_probability: self.play_probability, play_probability: self.play_probability,
@ -51,7 +51,7 @@ impl PlayerStrategy for RandomStrategyPlayer {
if view.board.hints_remaining > 0 { if view.board.hints_remaining > 0 {
let hint_player = view.board.player_to_left(&self.me); let hint_player = view.board.player_to_left(&self.me);
let hint_card = rand::thread_rng() let hint_card = rand::thread_rng()
.choose(&view.get_hand(&hint_player)) .choose(view.get_hand(&hint_player))
.unwrap(); .unwrap();
let hinted = { let hinted = {
if rand::random() { if rand::random() {
@ -63,7 +63,7 @@ impl PlayerStrategy for RandomStrategyPlayer {
}; };
TurnChoice::Hint(Hint { TurnChoice::Hint(Hint {
player: hint_player, player: hint_player,
hinted: hinted, hinted,
}) })
} else { } else {
TurnChoice::Discard(0) TurnChoice::Discard(0)

View File

@ -1,5 +1,5 @@
use game::*; use crate::game::*;
use helpers::*; use crate::helpers::*;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ModulusInformation { pub struct ModulusInformation {
@ -9,10 +9,7 @@ pub struct ModulusInformation {
impl ModulusInformation { impl ModulusInformation {
pub fn new(modulus: u32, value: u32) -> Self { pub fn new(modulus: u32, value: u32) -> Self {
assert!(value < modulus); assert!(value < modulus);
ModulusInformation { ModulusInformation { modulus, value }
modulus: modulus,
value: value,
}
} }
pub fn none() -> Self { pub fn none() -> Self {
@ -21,7 +18,7 @@ impl ModulusInformation {
pub fn combine(&mut self, other: Self, max_modulus: u32) { pub fn combine(&mut self, other: Self, max_modulus: u32) {
assert!(other.modulus <= self.info_remaining(max_modulus)); assert!(other.modulus <= self.info_remaining(max_modulus));
self.value = self.value + self.modulus * other.value; self.value += self.modulus * other.value;
self.modulus = std::cmp::min(max_modulus, self.modulus * other.modulus); self.modulus = std::cmp::min(max_modulus, self.modulus * other.modulus);
assert!(self.value < self.modulus); assert!(self.value < self.modulus);
} }
@ -45,7 +42,7 @@ impl ModulusInformation {
let original_modulus = self.modulus; let original_modulus = self.modulus;
let original_value = self.value; let original_value = self.value;
let value = self.value % modulus; let value = self.value % modulus;
self.value = self.value / modulus; self.value /= modulus;
// `self.modulus` is the largest number such that // `self.modulus` is the largest number such that
// `value + (self.modulus - 1) * modulus < original_modulus`. // `value + (self.modulus - 1) * modulus < original_modulus`.
// TODO: find an explanation of why this makes everything work out // TODO: find an explanation of why this makes everything work out
@ -80,9 +77,14 @@ pub trait Question {
// how much info does this question ask for? // how much info does this question ask for?
fn info_amount(&self) -> u32; fn info_amount(&self) -> u32;
// get the answer to this question, given cards // get the answer to this question, given cards
fn answer(&self, &Cards, &BoardState) -> u32; fn answer(&self, hand: &Cards, board: &BoardState) -> u32;
// process the answer to this question, updating card info // process the answer to this question, updating card info
fn acknowledge_answer(&self, value: u32, &mut HandInfo<CardPossibilityTable>, &BoardState); fn acknowledge_answer(
&self,
value: u32,
hand_info: &mut HandInfo<CardPossibilityTable>,
board: &BoardState,
);
fn answer_info(&self, hand: &Cards, board: &BoardState) -> ModulusInformation { fn answer_info(&self, hand: &Cards, board: &BoardState) -> ModulusInformation {
ModulusInformation::new(self.info_amount(), self.answer(hand, board)) ModulusInformation::new(self.info_amount(), self.answer(hand, board))
@ -100,11 +102,11 @@ pub trait Question {
} }
pub trait PublicInformation: Clone { pub trait PublicInformation: Clone {
fn get_player_info(&self, &Player) -> HandInfo<CardPossibilityTable>; fn get_player_info(&self, player: &Player) -> HandInfo<CardPossibilityTable>;
fn set_player_info(&mut self, &Player, HandInfo<CardPossibilityTable>); fn set_player_info(&mut self, player: &Player, hand_info: HandInfo<CardPossibilityTable>);
fn new(&BoardState) -> Self; fn new(board: &BoardState) -> Self;
fn set_board(&mut self, &BoardState); fn set_board(&mut self, board: &BoardState);
/// If we store more state than just `HandInfo<CardPossibilityTable>`s, update it after `set_player_info` has been called. /// If we store more state than just `HandInfo<CardPossibilityTable>`s, update it after `set_player_info` has been called.
fn update_other_info(&mut self) {} fn update_other_info(&mut self) {}
@ -122,17 +124,17 @@ pub trait PublicInformation: Clone {
/// before the entire "hat value" calculation. /// before the entire "hat value" calculation.
fn ask_question( fn ask_question(
&self, &self,
&Player, player: &Player,
&HandInfo<CardPossibilityTable>, hand_info: &HandInfo<CardPossibilityTable>,
total_info: u32, total_info: u32,
) -> Option<Box<Question>>; ) -> Option<Box<dyn Question>>;
fn ask_question_wrapper( fn ask_question_wrapper(
&self, &self,
player: &Player, player: &Player,
hand_info: &HandInfo<CardPossibilityTable>, hand_info: &HandInfo<CardPossibilityTable>,
total_info: u32, total_info: u32,
) -> Option<Box<Question>> { ) -> Option<Box<dyn Question>> {
assert!(total_info > 0); assert!(total_info > 0);
if total_info == 1 { if total_info == 1 {
None None
@ -205,7 +207,7 @@ pub trait PublicInformation: Clone {
.map(|player| { .map(|player| {
let mut hand_info = self.get_player_info(player); let mut hand_info = self.get_player_info(player);
let info = self.get_hat_info_for_player(player, &mut hand_info, total_info, view); let info = self.get_hat_info_for_player(player, &mut hand_info, total_info, view);
(info, (player.clone(), hand_info)) (info, (*player, hand_info))
}) })
.unzip(); .unzip();
self.set_player_infos(new_player_hands); self.set_player_infos(new_player_hands);
@ -231,7 +233,7 @@ pub trait PublicInformation: Clone {
let mut hand_info = self.get_player_info(&player); let mut hand_info = self.get_player_info(&player);
let player_info = let player_info =
self.get_hat_info_for_player(&player, &mut hand_info, info.modulus, view); self.get_hat_info_for_player(&player, &mut hand_info, info.modulus, view);
(player_info, (player.clone(), hand_info)) (player_info, (player, hand_info))
}) })
.unzip(); .unzip();
for other_info in other_infos { for other_info in other_infos {
@ -251,7 +253,7 @@ pub trait PublicInformation: Clone {
fn get_private_info(&self, view: &OwnedGameView) -> HandInfo<CardPossibilityTable> { fn get_private_info(&self, view: &OwnedGameView) -> HandInfo<CardPossibilityTable> {
let mut info = self.get_player_info(&view.player); let mut info = self.get_player_info(&view.player);
for card_table in info.iter_mut() { for card_table in info.iter_mut() {
for (_, hand) in &view.other_hands { for hand in view.other_hands.values() {
for card in hand { for card in hand {
card_table.decrement_weight_if_possible(card); card_table.decrement_weight_if_possible(card);
} }

View File

@ -2,10 +2,10 @@ use float_ord::*;
use fnv::{FnvHashMap, FnvHashSet}; use fnv::{FnvHashMap, FnvHashSet};
use std::cmp::Ordering; use std::cmp::Ordering;
use game::*; use crate::game::*;
use helpers::*; use crate::helpers::*;
use strategies::hat_helpers::*; use crate::strategies::hat_helpers::*;
use strategy::*; use crate::strategy::*;
// TODO: use random extra information - i.e. when casting up and down, // TODO: use random extra information - i.e. when casting up and down,
// we sometimes have 2 choices of value to choose // we sometimes have 2 choices of value to choose
@ -23,7 +23,7 @@ impl Question for CardHasProperty {
2 2
} }
fn answer(&self, hand: &Cards, board: &BoardState) -> u32 { fn answer(&self, hand: &Cards, board: &BoardState) -> u32 {
let ref card = hand[self.index]; let card = &hand[self.index];
if (self.property)(board, card) { if (self.property)(board, card) {
1 1
} else { } else {
@ -36,17 +36,15 @@ impl Question for CardHasProperty {
hand_info: &mut HandInfo<CardPossibilityTable>, hand_info: &mut HandInfo<CardPossibilityTable>,
board: &BoardState, board: &BoardState,
) { ) {
let ref mut card_table = hand_info[self.index]; let card_table = &mut hand_info[self.index];
let possible = card_table.get_possibilities(); let possible = card_table.get_possibilities();
for card in &possible { for card in &possible {
if (self.property)(board, card) { if (self.property)(board, card) {
if answer == 0 { if answer == 0 {
card_table.mark_false(card); card_table.mark_false(card);
} }
} else { } else if answer == 1 {
if answer == 1 { card_table.mark_false(card);
card_table.mark_false(card);
}
} }
} }
} }
@ -72,7 +70,7 @@ fn q_is_dead(index: usize) -> CardHasProperty {
/// It's named that way because the `info_amount` grows additively with the `info_amount`s of /// It's named that way because the `info_amount` grows additively with the `info_amount`s of
/// the questions in `l`. /// the questions in `l`.
struct AdditiveComboQuestion { struct AdditiveComboQuestion {
questions: Vec<Box<Question>>, questions: Vec<Box<dyn Question>>,
} }
impl Question for AdditiveComboQuestion { impl Question for AdditiveComboQuestion {
fn info_amount(&self) -> u32 { fn info_amount(&self) -> u32 {
@ -134,7 +132,7 @@ impl CardPossibilityPartition {
let mut partition = FnvHashMap::default(); let mut partition = FnvHashMap::default();
let mut n_partitions = 0; let mut n_partitions = 0;
let has_dead = card_table.probability_is_dead(&board) != 0.0; let has_dead = card_table.probability_is_dead(board) != 0.0;
// TODO: group things of different colors and values? // TODO: group things of different colors and values?
let mut effective_max = max_n_partitions; let mut effective_max = max_n_partitions;
@ -161,21 +159,21 @@ impl CardPossibilityPartition {
n_partitions += 1; n_partitions += 1;
} }
// let mut s : String = "Partition: |".to_string(); // let mut s: String = "Partition: |".to_string();
// for i in 0..n_partitions { // for i in 0..n_partitions {
// for (card, block) in partition.iter() { // for (card, block) in partition.iter() {
// if *block == i { // if *block == i {
// s = s + &format!(" {}", card); // s = s + &format!(" {}", card);
// } // }
// } // }
// s = s + &format!(" |"); // s.push_str(" |");
// } // }
// debug!("{}", s); // debug!("{}", s);
CardPossibilityPartition { CardPossibilityPartition {
index: index, index,
n_partitions: n_partitions, n_partitions,
partition: partition, partition,
} }
} }
} }
@ -184,8 +182,8 @@ impl Question for CardPossibilityPartition {
self.n_partitions self.n_partitions
} }
fn answer(&self, hand: &Cards, _: &BoardState) -> u32 { fn answer(&self, hand: &Cards, _: &BoardState) -> u32 {
let ref card = hand[self.index]; let card = &hand[self.index];
*self.partition.get(&card).unwrap() *self.partition.get(card).unwrap()
} }
fn acknowledge_answer( fn acknowledge_answer(
&self, &self,
@ -193,7 +191,7 @@ impl Question for CardPossibilityPartition {
hand_info: &mut HandInfo<CardPossibilityTable>, hand_info: &mut HandInfo<CardPossibilityTable>,
_: &BoardState, _: &BoardState,
) { ) {
let ref mut card_table = hand_info[self.index]; let card_table = &mut hand_info[self.index];
let possible = card_table.get_possibilities(); let possible = card_table.get_possibilities();
for card in &possible { for card in &possible {
if *self.partition.get(card).unwrap() != answer { if *self.partition.get(card).unwrap() != answer {
@ -220,10 +218,7 @@ impl MyPublicInformation {
fn get_other_players_starting_after(&self, player: Player) -> Vec<Player> { fn get_other_players_starting_after(&self, player: Player) -> Vec<Player> {
let n = self.board.num_players; let n = self.board.num_players;
(0..n - 1) (0..n - 1).map(|i| (player + 1 + i) % n).collect()
.into_iter()
.map(|i| (player + 1 + i) % n)
.collect()
} }
// Returns the number of ways to hint the player. // Returns the number of ways to hint the player.
@ -232,7 +227,7 @@ impl MyPublicInformation {
// - it is public that there are at least two colors // - it is public that there are at least two colors
// - it is public that there are at least two numbers // - it is public that there are at least two numbers
let ref info = self.hand_info[&player]; let info = &self.hand_info[&player];
let may_be_all_one_color = COLORS let may_be_all_one_color = COLORS
.iter() .iter()
@ -242,11 +237,11 @@ impl MyPublicInformation {
.iter() .iter()
.any(|value| info.iter().all(|card| card.can_be_value(*value))); .any(|value| info.iter().all(|card| card.can_be_value(*value)));
return if !may_be_all_one_color && !may_be_all_one_number { if !may_be_all_one_color && !may_be_all_one_number {
4 4
} else { } else {
3 3
}; }
} }
fn get_hint_index_score(&self, card_table: &CardPossibilityTable) -> i32 { fn get_hint_index_score(&self, card_table: &CardPossibilityTable) -> i32 {
@ -264,7 +259,7 @@ impl MyPublicInformation {
if !card_table.value_determined() { if !card_table.value_determined() {
score += 1; score += 1;
} }
return score; score
} }
fn get_index_for_hint(&self, player: &Player) -> usize { fn get_index_for_hint(&self, player: &Player) -> usize {
@ -276,7 +271,7 @@ impl MyPublicInformation {
(-score, i) (-score, i)
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
scores.sort(); scores.sort_unstable();
scores[0].1 scores[0].1
} }
@ -392,12 +387,12 @@ impl MyPublicInformation {
.into_iter() .into_iter()
.map(|hinted| Hint { .map(|hinted| Hint {
player: hint_player, player: hint_player,
hinted: hinted, hinted,
}) })
.collect() .collect()
} }
fn decode_hint_choice(&self, hint: &Hint, result: &Vec<bool>) -> ModulusInformation { fn decode_hint_choice(&self, hint: &Hint, result: &[bool]) -> ModulusInformation {
let hinter = self.board.player; let hinter = self.board.player;
let info_per_player: Vec<_> = self let info_per_player: Vec<_> = self
@ -411,10 +406,7 @@ impl MyPublicInformation {
let player_amt = (n + hint.player - hinter - 1) % n; let player_amt = (n + hint.player - hinter - 1) % n;
let amt_from_prev_players = info_per_player let amt_from_prev_players: u32 = info_per_player.iter().take(player_amt as usize).sum();
.iter()
.take(player_amt as usize)
.fold(0, |a, b| a + b);
let hint_info_we_can_give_to_this_player = info_per_player[player_amt as usize]; let hint_info_we_can_give_to_this_player = info_per_player[player_amt as usize];
let card_index = self.get_index_for_hint(&hint.player); let card_index = self.get_index_for_hint(&hint.player);
@ -427,17 +419,15 @@ impl MyPublicInformation {
} else { } else {
2 2
} }
} else if result[card_index] {
match hint.hinted {
Hinted::Value(_) => 0,
Hinted::Color(_) => 1,
}
} else { } else {
if result[card_index] { match hint.hinted {
match hint.hinted { Hinted::Value(_) => 2,
Hinted::Value(_) => 0, Hinted::Color(_) => 3,
Hinted::Color(_) => 1,
}
} else {
match hint.hinted {
Hinted::Value(_) => 2,
Hinted::Color(_) => 3,
}
} }
}; };
@ -446,12 +436,12 @@ impl MyPublicInformation {
ModulusInformation::new(total_info, hint_value) ModulusInformation::new(total_info, hint_value)
} }
fn update_from_hint_choice(&mut self, hint: &Hint, matches: &Vec<bool>, view: &OwnedGameView) { fn update_from_hint_choice(&mut self, hint: &Hint, matches: &[bool], view: &OwnedGameView) {
let info = self.decode_hint_choice(hint, matches); let info = self.decode_hint_choice(hint, matches);
self.update_from_hat_sum(info, view); self.update_from_hat_sum(info, view);
} }
fn update_from_hint_matches(&mut self, hint: &Hint, matches: &Vec<bool>) { fn update_from_hint_matches(&mut self, hint: &Hint, matches: &[bool]) {
let info = self.get_player_info_mut(&hint.player); let info = self.get_player_info_mut(&hint.player);
info.update_for_hint(&hint.hinted, matches); info.update_for_hint(&hint.hinted, matches);
} }
@ -466,10 +456,10 @@ impl MyPublicInformation {
// Does another player have a playable card, but doesn't know it? // Does another player have a playable card, but doesn't know it?
view.get_other_players().iter().any(|player| { view.get_other_players().iter().any(|player| {
let has_playable_card = view let has_playable_card = view
.get_hand(&player) .get_hand(player)
.iter() .iter()
.any(|card| view.get_board().is_playable(card)); .any(|card| view.get_board().is_playable(card));
has_playable_card && !self.knows_playable_card(&player) has_playable_card && !self.knows_playable_card(player)
}) })
} }
@ -508,7 +498,7 @@ impl MyPublicInformation {
info.remove(index); info.remove(index);
// push *before* incrementing public counts // push *before* incrementing public counts
if info.len() < new_view.hand_size(&player) { if info.len() < new_view.hand_size(player) {
info.push(new_card_table); info.push(new_card_table);
} }
} }
@ -536,7 +526,7 @@ impl PublicInformation for MyPublicInformation {
}) })
.collect::<FnvHashMap<_, _>>(); .collect::<FnvHashMap<_, _>>();
MyPublicInformation { MyPublicInformation {
hand_info: hand_info, hand_info,
card_counts: CardCounts::new(), card_counts: CardCounts::new(),
board: board.clone(), board: board.clone(),
} }
@ -563,7 +553,7 @@ impl PublicInformation for MyPublicInformation {
_me: &Player, _me: &Player,
hand_info: &HandInfo<CardPossibilityTable>, hand_info: &HandInfo<CardPossibilityTable>,
total_info: u32, total_info: u32,
) -> Option<Box<Question>> { ) -> Option<Box<dyn Question>> {
// Changing anything inside this function will not break the information transfer // Changing anything inside this function will not break the information transfer
// mechanisms! // mechanisms!
@ -571,10 +561,10 @@ impl PublicInformation for MyPublicInformation {
.iter() .iter()
.cloned() .cloned()
.enumerate() .enumerate()
.filter_map(|(i, card_table)| { .map(|(i, card_table)| {
let p_play = card_table.probability_is_playable(&self.board); let p_play = card_table.probability_is_playable(&self.board);
let p_dead = card_table.probability_is_dead(&self.board); let p_dead = card_table.probability_is_dead(&self.board);
Some((i, p_play, p_dead)) (i, p_play, p_dead)
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let know_playable_card = augmented_hand_info_raw let know_playable_card = augmented_hand_info_raw
@ -587,15 +577,7 @@ impl PublicInformation for MyPublicInformation {
// We don't need to find out anything about cards that are determined or dead. // We don't need to find out anything about cards that are determined or dead.
let augmented_hand_info = augmented_hand_info_raw let augmented_hand_info = augmented_hand_info_raw
.into_iter() .into_iter()
.filter(|&(i, _, p_dead)| { .filter(|&(i, _, p_dead)| p_dead != 1.0 && !hand_info[i].is_determined())
if p_dead == 1.0 {
false
} else if hand_info[i].is_determined() {
false
} else {
true
}
})
.collect::<Vec<_>>(); .collect::<Vec<_>>();
if !know_playable_card { if !know_playable_card {
@ -637,7 +619,7 @@ impl PublicInformation for MyPublicInformation {
to_ask.sort_by_key(|&(ask_dead, _, p_yes)| (ask_dead, FloatOrd(p_yes))); to_ask.sort_by_key(|&(ask_dead, _, p_yes)| (ask_dead, FloatOrd(p_yes)));
let questions = to_ask let questions = to_ask
.into_iter() .into_iter()
.map(|(ask_dead, i, _)| -> Box<Question> { .map(|(ask_dead, i, _)| -> Box<dyn Question> {
if ask_dead { if ask_dead {
Box::new(q_is_dead(i)) Box::new(q_is_dead(i))
} else { } else {
@ -645,7 +627,7 @@ impl PublicInformation for MyPublicInformation {
} }
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
if questions.len() > 0 { if !questions.is_empty() {
return Some(Box::new(AdditiveComboQuestion { questions })); return Some(Box::new(AdditiveComboQuestion { questions }));
} }
} }
@ -657,14 +639,14 @@ impl PublicInformation for MyPublicInformation {
.cloned() .cloned()
.collect::<Vec<_>>(); .collect::<Vec<_>>();
ask_play.sort_by_key(|&(i, p_play, _)| (ask_play_score(p_play), i)); ask_play.sort_by_key(|&(i, p_play, _)| (ask_play_score(p_play), i));
if let Some(&(i, _, _)) = ask_play.get(0) { if let Some(&(i, _, _)) = ask_play.first() {
return Some(Box::new(q_is_playable(i))); return Some(Box::new(q_is_playable(i)));
} }
let mut ask_partition = augmented_hand_info; let mut ask_partition = augmented_hand_info;
// sort by probability of death (lowest first), then by index // sort by probability of death (lowest first), then by index
ask_partition.sort_by_key(|&(i, _, p_death)| (FloatOrd(p_death), i)); ask_partition.sort_by_key(|&(i, _, p_death)| (FloatOrd(p_death), i));
if let Some(&(i, _, _)) = ask_partition.get(0) { if let Some(&(i, _, _)) = ask_partition.first() {
let question = CardPossibilityPartition::new(i, total_info, &hand_info[i], &self.board); let question = CardPossibilityPartition::new(i, total_info, &hand_info[i], &self.board);
Some(Box::new(question)) Some(Box::new(question))
} else { } else {
@ -681,7 +663,7 @@ impl InformationStrategyConfig {
} }
} }
impl GameStrategyConfig for InformationStrategyConfig { impl GameStrategyConfig for InformationStrategyConfig {
fn initialize(&self, _: &GameOptions) -> Box<GameStrategy> { fn initialize(&self, _: &GameOptions) -> Box<dyn GameStrategy> {
Box::new(InformationStrategy::new()) Box::new(InformationStrategy::new())
} }
} }
@ -694,7 +676,7 @@ impl InformationStrategy {
} }
} }
impl GameStrategy for InformationStrategy { impl GameStrategy for InformationStrategy {
fn initialize(&self, player: Player, view: &BorrowedGameView) -> Box<PlayerStrategy> { fn initialize(&self, player: Player, view: &BorrowedGameView) -> Box<dyn PlayerStrategy> {
Box::new(InformationPlayerStrategy { Box::new(InformationPlayerStrategy {
me: player, me: player,
public_info: MyPublicInformation::new(view.board), public_info: MyPublicInformation::new(view.board),
@ -728,10 +710,8 @@ impl InformationPlayerStrategy {
let mut num_with = 1; let mut num_with = 1;
if view.board.deck_size > 0 { if view.board.deck_size > 0 {
for player in view.board.get_players() { for player in view.board.get_players() {
if player != self.me { if player != self.me && view.has_card(&player, card) {
if view.has_card(&player, card) { num_with += 1;
num_with += 1;
}
} }
} }
} }
@ -743,27 +723,30 @@ impl InformationPlayerStrategy {
board: &BoardState, board: &BoardState,
hand: &HandInfo<CardPossibilityTable>, hand: &HandInfo<CardPossibilityTable>,
) -> Vec<usize> { ) -> Vec<usize> {
use std::collections::hash_map::Entry::{Occupied, Vacant};
let mut useless: FnvHashSet<usize> = FnvHashSet::default(); let mut useless: FnvHashSet<usize> = FnvHashSet::default();
let mut seen: FnvHashMap<Card, usize> = FnvHashMap::default(); 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(board) == 1.0 { if card_table.probability_is_dead(board) == 1.0 {
useless.insert(i); useless.insert(i);
} else { } else if let Some(card) = card_table.get_card() {
if let Some(card) = card_table.get_card() { match seen.entry(card) {
if seen.contains_key(&card) { Occupied(e) => {
// found a duplicate card // found a duplicate card
useless.insert(i); useless.insert(i);
useless.insert(*seen.get(&card).unwrap()); useless.insert(*e.get());
} else { }
seen.insert(card, i); Vacant(e) => {
e.insert(i);
} }
} }
} }
} }
let mut useless_vec: Vec<usize> = useless.into_iter().collect(); let mut useless_vec: Vec<usize> = useless.into_iter().collect();
useless_vec.sort(); useless_vec.sort_unstable();
return useless_vec; useless_vec
} }
// how good is it to give this hint to this player? // how good is it to give this hint to this player?
@ -774,8 +757,8 @@ impl InformationPlayerStrategy {
let hint_player = &hint.player; let hint_player = &hint.player;
let hinted = &hint.hinted; let hinted = &hint.hinted;
let hand = view.get_hand(&hint_player); let hand = view.get_hand(hint_player);
let mut hand_info = self.public_info.get_player_info(&hint_player); let mut hand_info = self.public_info.get_player_info(hint_player);
let mut goodness = 1.0; let mut goodness = 1.0;
for (i, card_table) in hand_info.iter_mut().enumerate() { for (i, card_table) in hand_info.iter_mut().enumerate() {
@ -794,9 +777,8 @@ impl InformationPlayerStrategy {
let new_weight = card_table.total_weight(); let new_weight = card_table.total_weight();
assert!(new_weight <= old_weight); assert!(new_weight <= old_weight);
let bonus = { let bonus = {
if card_table.is_determined() { if card_table.is_determined() || card_table.probability_is_dead(&view.board) == 1.0
2 {
} else if card_table.probability_is_dead(&view.board) == 1.0 {
2 2
} else { } else {
1 1
@ -821,12 +803,10 @@ impl InformationPlayerStrategy {
hint_options.sort_by(|h1, h2| h2.0.partial_cmp(&h1.0).unwrap_or(Ordering::Equal)); hint_options.sort_by(|h1, h2| h2.0.partial_cmp(&h1.0).unwrap_or(Ordering::Equal));
if hint_options.len() == 0 { if hint_options.is_empty() {
// NOTE: Technically possible, but never happens // NOTE: Technically possible, but never happens
} else { } else if hint_options.len() > 1 {
if hint_options.len() > 1 { debug!("Choosing amongst hint options: {:?}", hint_options);
debug!("Choosing amongst hint options: {:?}", hint_options);
}
} }
hint_options.remove(0).1 hint_options.remove(0).1
} }
@ -868,7 +848,7 @@ impl InformationPlayerStrategy {
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
playable_cards.sort_by_key(|&(i, play_score)| (FloatOrd(-play_score), i)); playable_cards.sort_by_key(|&(i, play_score)| (FloatOrd(-play_score), i));
if let Some(&(play_index, _)) = playable_cards.get(0) { if let Some(&(play_index, _)) = playable_cards.first() {
return TurnChoice::Play(play_index); return TurnChoice::Play(play_index);
} }
@ -894,7 +874,7 @@ impl InformationPlayerStrategy {
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
if risky_playable_cards.len() > 0 { if !risky_playable_cards.is_empty() {
risky_playable_cards risky_playable_cards
.sort_by(|c1, c2| c2.2.partial_cmp(&c1.2).unwrap_or(Ordering::Equal)); .sort_by(|c1, c2| c2.2.partial_cmp(&c1.2).unwrap_or(Ordering::Equal));
@ -910,23 +890,20 @@ impl InformationPlayerStrategy {
let useless_indices = self.find_useless_cards(&view.board, &private_info); let useless_indices = self.find_useless_cards(&view.board, &private_info);
// NOTE When changing this, make sure to keep the "discard" branch of update() up to date! // NOTE When changing this, make sure to keep the "discard" branch of update() up to date!
let will_hint = let will_hint = if view.board.hints_remaining > 0
if view.board.hints_remaining > 0 && public_info.someone_else_needs_hint(view) { && public_info.someone_else_needs_hint(view)
true {
} else if view.board.discard_size() <= discard_threshold && useless_indices.len() > 0 { true
false } else if view.board.discard_size() <= discard_threshold && !useless_indices.is_empty() {
} false
// hinting is better than discarding dead cards // hinting is better than discarding dead cards
// (probably because it stalls the deck-drawing). // (probably because it stalls the deck-drawing).
else if view.board.hints_remaining > 0 && view.someone_else_can_play() { } else if view.board.hints_remaining > 0 && view.someone_else_can_play() {
true true
} else if view.board.hints_remaining > 4 { } else {
true // this being false is the only case in which we discard a potentially useful card.
} view.board.hints_remaining > 4
// this is the only case in which we discard a potentially useful card. };
else {
false
};
if will_hint { if will_hint {
let hint_set = public_info.get_hint(view); let hint_set = public_info.get_hint(view);
@ -942,7 +919,7 @@ impl InformationPlayerStrategy {
if public_useless_indices.len() > 1 { if public_useless_indices.len() > 1 {
let info = public_info.get_hat_sum(public_useless_indices.len() as u32, view); let info = public_info.get_hat_sum(public_useless_indices.len() as u32, view);
return TurnChoice::Discard(public_useless_indices[info.value as usize]); return TurnChoice::Discard(public_useless_indices[info.value as usize]);
} else if useless_indices.len() > 0 { } else if !useless_indices.is_empty() {
// TODO: have opponents infer that i knew a card was useless // TODO: have opponents infer that i knew a card was useless
// TODO: after that, potentially prefer useless indices that arent public // TODO: after that, potentially prefer useless indices that arent public
return TurnChoice::Discard(useless_indices[0]); return TurnChoice::Discard(useless_indices[0]);
@ -1026,7 +1003,7 @@ impl PlayerStrategy for InformationPlayerStrategy {
} }
fn update(&mut self, turn_record: &TurnRecord, view: &BorrowedGameView) { fn update(&mut self, turn_record: &TurnRecord, view: &BorrowedGameView) {
let hint_matches = if let &TurnResult::Hint(ref matches) = &turn_record.result { let hint_matches = if let TurnResult::Hint(matches) = &turn_record.result {
Some(matches) Some(matches)
} else { } else {
None None
@ -1042,7 +1019,7 @@ impl PlayerStrategy for InformationPlayerStrategy {
} }
match turn_record.choice { match turn_record.choice {
TurnChoice::Hint(ref hint) => { TurnChoice::Hint(ref hint) => {
if let &TurnResult::Hint(ref matches) = &turn_record.result { if let TurnResult::Hint(matches) = &turn_record.result {
self.public_info.update_from_hint_matches(hint, matches); self.public_info.update_from_hint_matches(hint, matches);
} else { } else {
panic!( panic!(
@ -1052,7 +1029,7 @@ impl PlayerStrategy for InformationPlayerStrategy {
} }
} }
TurnChoice::Discard(index) => { TurnChoice::Discard(index) => {
if let &TurnResult::Discard(ref card) = &turn_record.result { if let TurnResult::Discard(card) = &turn_record.result {
self.public_info.update_from_discard_or_play_result( self.public_info.update_from_discard_or_play_result(
view, view,
&turn_record.player, &turn_record.player,
@ -1067,7 +1044,7 @@ impl PlayerStrategy for InformationPlayerStrategy {
} }
} }
TurnChoice::Play(index) => { TurnChoice::Play(index) => {
if let &TurnResult::Play(ref card, _) = &turn_record.result { if let TurnResult::Play(card, _) = &turn_record.result {
self.public_info.update_from_discard_or_play_result( self.public_info.update_from_discard_or_play_result(
view, view,
&turn_record.player, &turn_record.player,

View File

@ -1,4 +1,4 @@
use game::*; use crate::game::*;
// Traits to implement for any valid Hanabi strategy // Traits to implement for any valid Hanabi strategy
@ -11,20 +11,20 @@ pub trait PlayerStrategy {
fn name(&self) -> String; 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, view: &BorrowedGameView) -> TurnChoice;
// A function to update internal state after other players' turns. // A function to update internal state after other players' turns.
// Given what happened last turn, and the new state. // Given what happened last turn, and the new state.
fn update(&mut self, &TurnRecord, &BorrowedGameView); fn update(&mut self, turn_record: &TurnRecord, view: &BorrowedGameView);
} }
// Represents the overall strategy for a game // Represents the overall strategy for a game
// Shouldn't do much, except store configuration parameters and // Shouldn't do much, except store configuration parameters and
// possibility initialize some shared randomness between players // possibility initialize some shared randomness between players
pub trait GameStrategy { pub trait GameStrategy {
fn initialize(&self, Player, &BorrowedGameView) -> Box<PlayerStrategy>; fn initialize(&self, me: Player, view: &BorrowedGameView) -> Box<dyn PlayerStrategy>;
} }
// Represents configuration for a strategy. // Represents configuration for a strategy.
// Acts as a factory for game strategies, so we can play many rounds // Acts as a factory for game strategies, so we can play many rounds
pub trait GameStrategyConfig { pub trait GameStrategyConfig {
fn initialize(&self, &GameOptions) -> Box<GameStrategy>; fn initialize(&self, opts: &GameOptions) -> Box<dyn GameStrategy>;
} }