improve to 24.78 (for 5 players)
This commit is contained in:
parent
e0239dead0
commit
3d318340eb
19
README.md
19
README.md
@ -1,12 +1,21 @@
|
|||||||
# Simulations of Hanabi strategies
|
# Simulations of Hanabi strategies
|
||||||
|
|
||||||
Hanabi is an interesting cooperative game, with
|
Hanabi is a cooperative card game of incomplete information.
|
||||||
[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.
|
||||||
|
|
||||||
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).
|
This repository provides a framework for implementing Hanabi 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).
|
||||||
|
|
||||||
|
In particular, it contains a variant of their "information strategy", with some improvements.
|
||||||
|
This strategy achieves the best results I am aware of (see below), for n > 3.
|
||||||
|
|
||||||
|
Please contact me if:
|
||||||
|
- You know of other interesting/good strategy ideas!
|
||||||
|
- Have questions about the framework
|
||||||
|
|
||||||
Some similar projects I am aware of:
|
Some similar projects I am aware of:
|
||||||
|
|
||||||
- https://github.com/rjtobin/HanSim (written for the paper mentioned above)
|
- https://github.com/rjtobin/HanSim (written for the paper mentioned above)
|
||||||
- https://github.com/Quuxplusone/Hanabi
|
- https://github.com/Quuxplusone/Hanabi
|
||||||
|
|
||||||
@ -39,8 +48,10 @@ For example,
|
|||||||
## Results (sparsely updated)
|
## Results (sparsely updated)
|
||||||
|
|
||||||
Currently, on seeds 0-9999, we have:
|
Currently, on seeds 0-9999, we have:
|
||||||
|
(info strategy is only ran on 1000 seeds)
|
||||||
|
|
||||||
| 2p | 3p | 4p | 5p |
|
| 2p | 3p | 4p | 5p |
|
||||||
----------|---------|---------|---------|---------|
|
----------|---------|---------|---------|---------|
|
||||||
cheating | 24.8600 | 24.9781 | 24.9715 | 24.9583 |
|
cheating | 24.8600 | 24.9781 | 24.9715 | 24.9583 |
|
||||||
|
info | 14.676 | 22.19 | 24.516 | 24.68 |
|
||||||
|
|
||||||
|
@ -60,6 +60,9 @@ pub trait CardInfo {
|
|||||||
}
|
}
|
||||||
total_score / total_weight
|
total_score / total_weight
|
||||||
}
|
}
|
||||||
|
fn average_value(&self) -> 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: &Fn(&Card) -> bool) -> f32 {
|
||||||
let f = |card: &Card| {
|
let f = |card: &Card| {
|
||||||
if predicate(card) { 1.0 } else { 0.0 }
|
if predicate(card) { 1.0 } else { 0.0 }
|
||||||
|
@ -181,7 +181,7 @@ pub fn simulate<T: ?Sized>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let percentage = (n_trials - non_perfect_seeds.len() as u32) as f32 / n_trials as f32;
|
let percentage = (n_trials - non_perfect_seeds.len() as u32) as f32 / n_trials as f32;
|
||||||
info!("Percentage perfect: {:?}%", percentage);
|
info!("Percentage perfect: {:?}%", percentage * 100.0);
|
||||||
let average = histogram.average();
|
let average = histogram.average();
|
||||||
info!("Average score: {:?}", average);
|
info!("Average score: {:?}", average);
|
||||||
average
|
average
|
||||||
|
@ -216,27 +216,20 @@ impl PlayerStrategy for CheatingPlayerStrategy {
|
|||||||
// Play the best discardable card, according to the ordering induced by comparing
|
// Play the best discardable card, according to the ordering induced by comparing
|
||||||
// (is in another hand, is dispensable, value)
|
// (is in another hand, is dispensable, value)
|
||||||
// The higher, the better to discard
|
// The higher, the better to discard
|
||||||
let mut discard_card = None;
|
let mut index = 0;
|
||||||
let mut compval = (false, false, 0);
|
let mut compval = (false, false, 0);
|
||||||
for card in my_cards {
|
for (i, card) in my_cards.iter().enumerate() {
|
||||||
let my_compval = (
|
let my_compval = (
|
||||||
view.can_see(card),
|
view.can_see(card),
|
||||||
view.board.is_dispensable(card),
|
view.board.is_dispensable(card),
|
||||||
card.value,
|
card.value,
|
||||||
);
|
);
|
||||||
if my_compval > compval {
|
if my_compval > compval {
|
||||||
discard_card = Some(card);
|
index = i;
|
||||||
compval = my_compval;
|
compval = my_compval;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(card) = discard_card {
|
TurnChoice::Discard(index)
|
||||||
let index = my_cards.iter().position(|iter_card| {
|
|
||||||
card == iter_card
|
|
||||||
}).unwrap();
|
|
||||||
TurnChoice::Discard(index)
|
|
||||||
} else {
|
|
||||||
panic!("This shouldn't happen! No discardable card");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
fn update(&mut self, _: &Turn, _: &BorrowedGameView) {
|
fn update(&mut self, _: &Turn, _: &BorrowedGameView) {
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
use simulator::*;
|
use simulator::*;
|
||||||
use game::*;
|
use game::*;
|
||||||
@ -18,9 +19,6 @@ use game::*;
|
|||||||
//
|
//
|
||||||
// for 4 players, can give 6 distinct hints
|
// for 4 players, can give 6 distinct hints
|
||||||
|
|
||||||
// TODO: currently, you need to be very careful due to
|
|
||||||
// answers changing from the old view to the new view
|
|
||||||
|
|
||||||
#[derive(Debug,Clone)]
|
#[derive(Debug,Clone)]
|
||||||
struct ModulusInformation {
|
struct ModulusInformation {
|
||||||
modulus: u32,
|
modulus: u32,
|
||||||
@ -220,9 +218,6 @@ impl InformationStrategyConfig {
|
|||||||
}
|
}
|
||||||
impl GameStrategyConfig for InformationStrategyConfig {
|
impl GameStrategyConfig for InformationStrategyConfig {
|
||||||
fn initialize(&self, opts: &GameOptions) -> Box<GameStrategy> {
|
fn initialize(&self, opts: &GameOptions) -> Box<GameStrategy> {
|
||||||
if opts.num_players < 4 {
|
|
||||||
panic!("Information strategy doesn't work with less than 4 players");
|
|
||||||
}
|
|
||||||
Box::new(InformationStrategy::new())
|
Box::new(InformationStrategy::new())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -278,20 +273,43 @@ impl InformationPlayerStrategy {
|
|||||||
*info_remaining <= 1
|
*info_remaining <= 1
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i, card_table) in hand_info.iter().enumerate() {
|
let mut augmented_hand_info = hand_info.iter().enumerate().map(|(i, card_table)| {
|
||||||
let p = view.get_board().probability_is_playable(card_table);
|
let p = view.get_board().probability_is_playable(card_table);
|
||||||
|
(p, card_table, i)
|
||||||
|
}).collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// sort by probability of play, then by index
|
||||||
|
augmented_hand_info.sort_by(|&(p1, card_table1, i1), &(p2, card_table2, i2)| {
|
||||||
|
let result = p1.partial_cmp(&p2);
|
||||||
|
if result == None || result == Some(Ordering::Equal) {
|
||||||
|
i1.cmp(&i2)
|
||||||
|
} else {
|
||||||
|
result.unwrap()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// let known_playable = augmented_hand_info[0].0 == 1.0;
|
||||||
|
// // if there is a card that is definitely playable, don't ask about playability
|
||||||
|
// if !known_playable {
|
||||||
|
for &(p, card_table, i) in &augmented_hand_info {
|
||||||
if (p != 0.0) && (p != 1.0) {
|
if (p != 0.0) && (p != 1.0) {
|
||||||
if add_question(&mut questions, &mut info_remaining, IsPlayable {index: i}) {
|
if add_question(&mut questions, &mut info_remaining, IsPlayable {index: i}) {
|
||||||
return questions;
|
return questions;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (i, card_table) in hand_info.iter().enumerate() {
|
// }
|
||||||
if !card_table.is_determined() {
|
|
||||||
let question = CardPossibilityPartition::new(i, info_remaining, card_table, view);
|
for &(p, card_table, i) in &augmented_hand_info {
|
||||||
if add_question(&mut questions, &mut info_remaining, question) {
|
if card_table.is_determined() {
|
||||||
return questions;
|
continue;
|
||||||
}
|
}
|
||||||
|
if view.get_board().probability_is_dead(card_table) == 1.0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let question = CardPossibilityPartition::new(i, info_remaining, card_table, view);
|
||||||
|
if add_question(&mut questions, &mut info_remaining, question) {
|
||||||
|
return questions;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -684,7 +702,7 @@ impl PlayerStrategy for InformationPlayerStrategy {
|
|||||||
|
|
||||||
// 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.get_hint(view);
|
return self.get_hint(view);
|
||||||
}
|
}
|
||||||
@ -695,44 +713,24 @@ impl PlayerStrategy for InformationPlayerStrategy {
|
|||||||
return TurnChoice::Discard(i);
|
return TurnChoice::Discard(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
// // All cards are plausibly useful.
|
// Play the best discardable card
|
||||||
// // Play the best discardable card, according to the ordering induced by comparing
|
let mut compval = 0.0;
|
||||||
// // (is in another hand, is dispensable, value)
|
let mut index = 0;
|
||||||
// // The higher, the better to discard
|
for (i, card_table) in private_info.iter().enumerate() {
|
||||||
// let mut discard_card = None;
|
let probability_is_seen = card_table.probability_of_predicate(&|card| {
|
||||||
// let mut compval = (false, false, 0);
|
view.can_see(card)
|
||||||
// for card in my_cards {
|
});
|
||||||
// let my_compval = (
|
let my_compval =
|
||||||
// view.can_see(card),
|
20.0 * probability_is_seen
|
||||||
// view.board.is_dispensable(card),
|
+ 10.0 * view.board.probability_is_dispensable(card_table)
|
||||||
// card.value,
|
+ card_table.average_value();
|
||||||
// );
|
|
||||||
// if my_compval > compval {
|
|
||||||
// discard_card = Some(card);
|
|
||||||
// compval = my_compval;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// 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| {
|
if my_compval > compval {
|
||||||
// card == iter_card
|
compval = my_compval;
|
||||||
// }).unwrap();
|
index = i;
|
||||||
// TurnChoice::Discard(index)
|
}
|
||||||
// } else {
|
|
||||||
// panic!("This shouldn't happen! No discardable card");
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
if view.board.hints_remaining > 0 {
|
|
||||||
self.get_hint(view)
|
|
||||||
} else {
|
|
||||||
TurnChoice::Discard(0)
|
|
||||||
}
|
}
|
||||||
|
TurnChoice::Discard(index)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, turn: &Turn, view: &BorrowedGameView) {
|
fn update(&mut self, turn: &Turn, view: &BorrowedGameView) {
|
||||||
|
Loading…
Reference in New Issue
Block a user