improve to 24.78 (for 5 players)

This commit is contained in:
Jeff Wu 2016-03-29 22:24:29 -07:00
parent e0239dead0
commit 3d318340eb
5 changed files with 71 additions and 66 deletions

View File

@ -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 |

View File

@ -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 }

View File

@ -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

View File

@ -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) {
} }

View File

@ -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) {