From 3d318340eb08df1f0fb9db1bb15d927014e0a106 Mon Sep 17 00:00:00 2001 From: Jeff Wu Date: Tue, 29 Mar 2016 22:24:29 -0700 Subject: [PATCH] improve to 24.78 (for 5 players) --- README.md | 19 +++++-- src/info.rs | 3 ++ src/simulator.rs | 2 +- src/strategies/cheating.rs | 15 ++---- src/strategies/information.rs | 98 +++++++++++++++++------------------ 5 files changed, 71 insertions(+), 66 deletions(-) diff --git a/README.md b/README.md index 4663b84..2032025 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,21 @@ # Simulations of Hanabi strategies -Hanabi is an interesting cooperative game, with -[simple rules](https://boardgamegeek.com/article/10670613#10670613). +Hanabi is a cooperative card game of incomplete information. +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: - - https://github.com/rjtobin/HanSim (written for the paper mentioned above) - https://github.com/Quuxplusone/Hanabi @@ -39,8 +48,10 @@ For example, ## Results (sparsely updated) Currently, on seeds 0-9999, we have: +(info strategy is only ran on 1000 seeds) | 2p | 3p | 4p | 5p | ----------|---------|---------|---------|---------| cheating | 24.8600 | 24.9781 | 24.9715 | 24.9583 | +info | 14.676 | 22.19 | 24.516 | 24.68 | diff --git a/src/info.rs b/src/info.rs index c461056..0e626f3 100644 --- a/src/info.rs +++ b/src/info.rs @@ -60,6 +60,9 @@ pub trait CardInfo { } 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 { let f = |card: &Card| { if predicate(card) { 1.0 } else { 0.0 } diff --git a/src/simulator.rs b/src/simulator.rs index b520f55..1d0b20e 100644 --- a/src/simulator.rs +++ b/src/simulator.rs @@ -181,7 +181,7 @@ pub fn simulate( } 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(); info!("Average score: {:?}", average); average diff --git a/src/strategies/cheating.rs b/src/strategies/cheating.rs index 4e702b4..daf45ca 100644 --- a/src/strategies/cheating.rs +++ b/src/strategies/cheating.rs @@ -216,27 +216,20 @@ impl PlayerStrategy for CheatingPlayerStrategy { // Play the best discardable card, according to the ordering induced by comparing // (is in another hand, is dispensable, value) // The higher, the better to discard - let mut discard_card = None; + let mut index = 0; let mut compval = (false, false, 0); - for card in my_cards { + for (i, card) in my_cards.iter().enumerate() { let my_compval = ( view.can_see(card), view.board.is_dispensable(card), card.value, ); if my_compval > compval { - discard_card = Some(card); + index = i; compval = my_compval; } } - if let Some(card) = discard_card { - let index = my_cards.iter().position(|iter_card| { - card == iter_card - }).unwrap(); - TurnChoice::Discard(index) - } else { - panic!("This shouldn't happen! No discardable card"); - } + TurnChoice::Discard(index) } fn update(&mut self, _: &Turn, _: &BorrowedGameView) { } diff --git a/src/strategies/information.rs b/src/strategies/information.rs index 1b39167..6d2075f 100644 --- a/src/strategies/information.rs +++ b/src/strategies/information.rs @@ -1,4 +1,5 @@ use std::collections::{HashMap, HashSet}; +use std::cmp::Ordering; use simulator::*; use game::*; @@ -18,9 +19,6 @@ use game::*; // // 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)] struct ModulusInformation { modulus: u32, @@ -220,9 +218,6 @@ impl InformationStrategyConfig { } impl GameStrategyConfig for InformationStrategyConfig { fn initialize(&self, opts: &GameOptions) -> Box { - if opts.num_players < 4 { - panic!("Information strategy doesn't work with less than 4 players"); - } Box::new(InformationStrategy::new()) } } @@ -278,20 +273,43 @@ impl InformationPlayerStrategy { *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); + (p, card_table, i) + }).collect::>(); + + // 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 add_question(&mut questions, &mut info_remaining, IsPlayable {index: i}) { 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); - if add_question(&mut questions, &mut info_remaining, question) { - return questions; - } + // } + + for &(p, card_table, i) in &augmented_hand_info { + if card_table.is_determined() { + 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 // (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) { return self.get_hint(view); } @@ -695,44 +713,24 @@ impl PlayerStrategy for InformationPlayerStrategy { return TurnChoice::Discard(i); } - // // All cards are plausibly useful. - // // Play the best discardable card, according to the ordering induced by comparing - // // (is in another hand, is dispensable, value) - // // The higher, the better to discard - // let mut discard_card = None; - // let mut compval = (false, false, 0); - // for card in my_cards { - // let my_compval = ( - // view.can_see(card), - // view.board.is_dispensable(card), - // card.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); - // } - // } + // Play the best discardable card + let mut compval = 0.0; + let mut index = 0; + for (i, card_table) in private_info.iter().enumerate() { + let probability_is_seen = card_table.probability_of_predicate(&|card| { + view.can_see(card) + }); + let my_compval = + 20.0 * probability_is_seen + + 10.0 * view.board.probability_is_dispensable(card_table) + + card_table.average_value(); - // let index = my_cards.iter().position(|iter_card| { - // card == iter_card - // }).unwrap(); - // 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) + if my_compval > compval { + compval = my_compval; + index = i; + } } + TurnChoice::Discard(index) } fn update(&mut self, turn: &Turn, view: &BorrowedGameView) {