improvements, cleanup, readme
This commit is contained in:
parent
e36700d93f
commit
107d585b19
4 changed files with 62 additions and 26 deletions
46
README.md
Normal file
46
README.md
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
# Simulations of Hanabi strategies
|
||||||
|
|
||||||
|
Hanabi is an interesting cooperative game, with
|
||||||
|
[simple rules](https://boardgamegeek.com/article/10670613#10670613).
|
||||||
|
|
||||||
|
I plan on reimplementing strategies with ideas from [this paper](https://d0474d97-a-62cb3a1a-s-sites.googlegroups.com/site/rmgpgrwc/research-papers/Hanabi_final.pdf).
|
||||||
|
|
||||||
|
Some similar projects I am aware of:
|
||||||
|
|
||||||
|
- https://github.com/rjtobin/HanSim (written for the paper mentioned above)
|
||||||
|
- https://github.com/Quuxplusone/Hanabi
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
Install rust/rustc and cargo, and change the options in main.rs appropriately.
|
||||||
|
|
||||||
|
`cargo run -- -h`
|
||||||
|
|
||||||
|
```
|
||||||
|
Usage: target/debug/rust_hanabi [options]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-l, --loglevel LOGLEVEL
|
||||||
|
Log level, one of 'trace', 'debug', 'info', 'warn', and 'error'
|
||||||
|
-n, --ntrials NTRIALS
|
||||||
|
Number of games to simulate
|
||||||
|
-t, --nthreads NTHREADS
|
||||||
|
Number of threads to use for simulation
|
||||||
|
-s, --seed SEED Seed for PRNG
|
||||||
|
-p, --nplayers NPLAYERS
|
||||||
|
Number of players
|
||||||
|
-h, --help Print this help menu
|
||||||
|
```
|
||||||
|
|
||||||
|
For example,
|
||||||
|
|
||||||
|
`cargo run -- -n 10000 -s 0 -t 2 -p 3`
|
||||||
|
|
||||||
|
## Results (sparsely updated)
|
||||||
|
|
||||||
|
Currently, on seeds 0-9999, we have:
|
||||||
|
|
||||||
|
| 2p | 3p | 4p | 5p |
|
||||||
|
----------|---------|---------|---------|---------|
|
||||||
|
cheating | 24.8600 | 24.9781 | 24.9715 | 24.9583 |
|
||||||
|
|
|
@ -463,6 +463,10 @@ impl BoardState {
|
||||||
self.deck.len() as u32
|
self.deck.len() as u32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn discard_size(&self) -> u32 {
|
||||||
|
self.discard.cards.len() as u32
|
||||||
|
}
|
||||||
|
|
||||||
pub fn player_to_left(&self, player: &Player) -> Player {
|
pub fn player_to_left(&self, player: &Player) -> Player {
|
||||||
(player + 1) % self.num_players
|
(player + 1) % self.num_players
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,7 +42,7 @@ fn main() {
|
||||||
opts.optopt("l", "loglevel", "Log level, one of 'trace', 'debug', 'info', 'warn', and 'error'", "LOGLEVEL");
|
opts.optopt("l", "loglevel", "Log level, one of 'trace', 'debug', 'info', 'warn', and 'error'", "LOGLEVEL");
|
||||||
opts.optopt("n", "ntrials", "Number of games to simulate", "NTRIALS");
|
opts.optopt("n", "ntrials", "Number of games to simulate", "NTRIALS");
|
||||||
opts.optopt("t", "nthreads", "Number of threads to use for simulation", "NTHREADS");
|
opts.optopt("t", "nthreads", "Number of threads to use for simulation", "NTHREADS");
|
||||||
opts.optopt("s", "seed", "Seed for PRNG (can only be used with n=1)", "SEED");
|
opts.optopt("s", "seed", "Seed for PRNG", "SEED");
|
||||||
opts.optopt("p", "nplayers", "Number of players", "NPLAYERS");
|
opts.optopt("p", "nplayers", "Number of players", "NPLAYERS");
|
||||||
opts.optflag("h", "help", "Print this help menu");
|
opts.optflag("h", "help", "Print this help menu");
|
||||||
let matches = match opts.parse(&args[1..]) {
|
let matches = match opts.parse(&args[1..]) {
|
||||||
|
|
|
@ -91,6 +91,8 @@ impl CheatingPlayerStrategy {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !view.board.is_dispensable(card) {
|
if !view.board.is_dispensable(card) {
|
||||||
|
value += 20 - card.value;
|
||||||
|
} else if view.board.is_playable(card) {
|
||||||
value += 10 - card.value;
|
value += 10 - card.value;
|
||||||
} else {
|
} else {
|
||||||
value += 1;
|
value += 1;
|
||||||
|
@ -111,15 +113,15 @@ impl CheatingPlayerStrategy {
|
||||||
if view.has_card(&player, card) {
|
if view.has_card(&player, card) {
|
||||||
let their_hand_value = self.hand_play_value(view, states.get(&player).unwrap());
|
let their_hand_value = self.hand_play_value(view, states.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 1;
|
return 10 - (card.value as i32)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// there are no hints
|
// there are no hints
|
||||||
// maybe value 5s more?
|
// maybe value 5s more?
|
||||||
5 + (5 - (card.value as i32))
|
20 - (card.value as i32)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_useless_card(&self, view: &GameStateView, hand: &Cards) -> Option<usize> {
|
fn find_useless_card(&self, view: &GameStateView, hand: &Cards) -> Option<usize> {
|
||||||
|
@ -161,17 +163,7 @@ impl PlayerStrategy for CheatingPlayerStrategy {
|
||||||
view.board.is_playable(card)
|
view.board.is_playable(card)
|
||||||
}).collect::<Vec<_>>();
|
}).collect::<Vec<_>>();
|
||||||
|
|
||||||
let mut should_play = true;
|
if playable_cards.len() > 0 {
|
||||||
if playable_cards.len() == 0 {
|
|
||||||
should_play = false;
|
|
||||||
}
|
|
||||||
// if (playable_cards.len() == 1) &&
|
|
||||||
// (view.board.deck_size() == 1) &&
|
|
||||||
// (view.board.hints_remaining > 1) {
|
|
||||||
// return self.throwaway_hint(view);
|
|
||||||
// }
|
|
||||||
|
|
||||||
if should_play {
|
|
||||||
// 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 play_card = None;
|
let mut play_card = None;
|
||||||
|
@ -195,10 +187,10 @@ impl PlayerStrategy for CheatingPlayerStrategy {
|
||||||
// we would not reach the final countdown round
|
// we would not reach the final countdown round
|
||||||
// e.g. 50 total, 25 to play, 20 in hand
|
// e.g. 50 total, 25 to play, 20 in hand
|
||||||
let discard_threshold =
|
let discard_threshold =
|
||||||
view.board.total_cards as usize
|
view.board.total_cards
|
||||||
- (COLORS.len() * VALUES.len())
|
- (COLORS.len() * VALUES.len()) as u32
|
||||||
- (view.board.num_players * view.board.hand_size) as usize;
|
- (view.board.num_players * view.board.hand_size);
|
||||||
if view.board.discard.cards.len() <= discard_threshold {
|
if view.board.discard_size() <= discard_threshold {
|
||||||
// if anything is totally useless, discard it
|
// if anything is totally useless, discard it
|
||||||
if let Some(i) = self.find_useless_card(view, my_cards) {
|
if let Some(i) = self.find_useless_card(view, my_cards) {
|
||||||
return TurnChoice::Discard(i);
|
return TurnChoice::Discard(i);
|
||||||
|
@ -207,7 +199,7 @@ 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 > 1 {
|
if view.board.hints_remaining > 0 {
|
||||||
if self.someone_else_can_play(view) {
|
if self.someone_else_can_play(view) {
|
||||||
return self.throwaway_hint(view);
|
return self.throwaway_hint(view);
|
||||||
}
|
}
|
||||||
|
@ -236,12 +228,6 @@ impl PlayerStrategy for CheatingPlayerStrategy {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(card) = discard_card {
|
if let Some(card) = discard_card {
|
||||||
if view.board.hints_remaining > 0 {
|
|
||||||
if !view.can_see(card) {
|
|
||||||
return self.throwaway_hint(view);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let index = my_cards.iter().position(|iter_card| {
|
let index = my_cards.iter().position(|iter_card| {
|
||||||
card == iter_card
|
card == iter_card
|
||||||
}).unwrap();
|
}).unwrap();
|
||||||
|
|
Loading…
Reference in a new issue