Merge branch 'rust-2021' into json_output_2021
This commit is contained in:
commit
a1bfa76b77
160
Cargo.lock
generated
160
Cargo.lock
generated
@ -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"
|
||||||
|
11
Cargo.toml
11
Cargo.toml
@ -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 = "*"
|
||||||
|
24
README.md
24
README.md
@ -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
|
||||||
|
192
src/game.rs
192
src/game.rs
@ -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());
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
59
src/main.rs
59
src/main.rs
@ -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]
|
||||||
};
|
};
|
||||||
|
@ -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(),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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>;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user