diff --git a/README.md b/README.md index 2918a0d..a4ad2ec 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ This strategy achieves the best results I am aware of for n > 2 (see below). Please contact me if: - You know of other interesting/good strategy ideas! -- Have questions about the framework +- Have questions about the framework or existing strategies Some similar projects I am aware of: - https://github.com/rjtobin/HanSim (written for the paper mentioned above) @@ -30,20 +30,33 @@ Usage: target/debug/rust_hanabi [options] Options: -l, --loglevel LOGLEVEL - Log level, one of 'trace', 'debug', 'info', 'warn', and 'error' + Log level, one of 'trace', 'debug', 'info', 'warn', + and 'error' -n, --ntrials NTRIALS - Number of games to simulate + Number of games to simulate (default 1) -t, --nthreads NTHREADS - Number of threads to use for simulation - -s, --seed SEED Seed for PRNG + Number of threads to use for simulation (default 1) + -s, --seed SEED Seed for PRNG (default random) -p, --nplayers NPLAYERS Number of players + -g, --strategy STRATEGY + Which strategy to use. One of 'random', 'cheat', and + 'info' -h, --help Print this help menu ``` For example, -`cargo run -- -n 10000 -s 0 -t 2 -p 3` +``` +cargo run -- -n 10000 -s 0 -t 2 -p 5 -g cheat +``` + +Or, if the simulation is slow (as the info strategy is), + +``` +cargo build --release +time ./target/release/rust_hanabi -n 10000 -s 0 -t 2 -p 5 -g info +``` ## Results diff --git a/src/cards.rs b/src/cards.rs index 3eba9b5..4dc4e10 100644 --- a/src/cards.rs +++ b/src/cards.rs @@ -1,19 +1,16 @@ use std::collections::HashMap; use std::fmt; -pub type Color = &'static str; -pub const COLORS: [Color; 5] = ["red", "yellow", "green", "blue", "white"]; -pub fn display_color(color: Color) -> char { - color.chars().next().unwrap() -} +pub type Color = char; +pub const COLORS: [Color; 5] = ['r', 'y', 'g', 'b', 'w']; pub type Value = u32; // list of values, assumed to be small to large pub const VALUES : [Value; 5] = [1, 2, 3, 4, 5]; pub const FINAL_VALUE : Value = 5; -pub fn get_count_for_value(value: &Value) -> u32 { - match *value { +pub fn get_count_for_value(value: Value) -> u32 { + match value { 1 => 3, 2 | 3 | 4 => 2, 5 => 1, @@ -33,7 +30,7 @@ impl Card { } impl fmt::Display for Card { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}{}", display_color(self.color), self.value) + write!(f, "{}{}", self.color, self.value) } } @@ -46,9 +43,9 @@ pub struct CardCounts { impl CardCounts { pub fn new() -> CardCounts { let mut counts = HashMap::new(); - for color in COLORS.iter() { - for value in VALUES.iter() { - counts.insert(Card::new(*color, *value), 0); + for &color in COLORS.iter() { + for &value in VALUES.iter() { + counts.insert(Card::new(color, value), 0); } } CardCounts { @@ -62,7 +59,7 @@ impl CardCounts { pub fn remaining(&self, card: &Card) -> u32 { let count = self.get_count(card); - get_count_for_value(&card.value) - count + get_count_for_value(card.value) - count } pub fn increment(&mut self, card: &Card) { @@ -72,17 +69,17 @@ impl CardCounts { } impl fmt::Display for CardCounts { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - for color in COLORS.iter() { + for &color in COLORS.iter() { try!(f.write_str(&format!( - "{}: ", display_color(color), + "{}: ", color, ))); - for value in VALUES.iter() { - let count = self.get_count(&Card::new(color, *value)); + for &value in VALUES.iter() { + let count = self.get_count(&Card::new(color, value)); let total = get_count_for_value(value); try!(f.write_str(&format!( "{}/{} {}s", count, total, value ))); - if *value != FINAL_VALUE { + if value != FINAL_VALUE { try!(f.write_str(", ")); } } diff --git a/src/game.rs b/src/game.rs index d6ec3aa..faa6db4 100644 --- a/src/game.rs +++ b/src/game.rs @@ -116,16 +116,16 @@ impl PlayerState { pub fn reveal(&mut self, hinted: &Hinted) -> Vec { match hinted { - &Hinted::Color(ref color) => { + &Hinted::Color(color) => { self.hand_info_iter_mut().map(|(card, info)| { - let matches = card.color == *color; + let matches = card.color == color; info.mark_color(color, matches); matches }).collect::>() } - &Hinted::Value(ref value) => { + &Hinted::Value(value) => { self.hand_info_iter_mut().map(|(card, info)| { - let matches = card.value == *value; + let matches = card.value == value; info.mark_value(value, matches); matches }).collect::>() @@ -137,11 +137,11 @@ impl PlayerState { fn new_deck(seed: u32) -> Cards { let mut deck: Cards = Cards::new(); - for color in COLORS.iter() { - for value in VALUES.iter() { + for &color in COLORS.iter() { + for &value in VALUES.iter() { let count = get_count_for_value(value); for _ in 0..count { - deck.push(Card::new(color, value.clone())); + deck.push(Card::new(color, value)); } } }; @@ -181,7 +181,7 @@ pub struct BoardState { impl BoardState { pub fn new(opts: &GameOptions, seed: u32) -> BoardState { let mut fireworks : HashMap = HashMap::new(); - for color in COLORS.iter() { + for &color in COLORS.iter() { fireworks.insert(color, Firework::new(color)); } let deck = new_deck(seed); @@ -213,34 +213,34 @@ impl BoardState { } } - pub fn get_firework(&self, color: &Color) -> &Firework { - self.fireworks.get(color).unwrap() + pub fn get_firework(&self, color: Color) -> &Firework { + self.fireworks.get(&color).unwrap() } - fn get_firework_mut(&mut self, color: &Color) -> &mut Firework { - self.fireworks.get_mut(color).unwrap() + fn get_firework_mut(&mut self, color: Color) -> &mut Firework { + self.fireworks.get_mut(&color).unwrap() } // returns whether a card would place on a firework pub fn is_playable(&self, card: &Card) -> bool { - Some(card.value) == self.get_firework(&card.color).desired_value() + Some(card.value) == self.get_firework(card.color).desired_value() } // best possible value we can get for firework of that color, // based on looking at discard + fireworks - fn highest_attainable(&self, color: &Color) -> Value { - let firework = self.fireworks.get(color).unwrap(); + fn highest_attainable(&self, color: Color) -> Value { + let firework = self.fireworks.get(&color).unwrap(); if firework.complete() { return FINAL_VALUE; } let desired = firework.desired_value().unwrap(); - for value in VALUES.iter() { - if *value < desired { + for &value in VALUES.iter() { + if value < desired { // already have these cards continue } - let needed_card = Card::new(color, value.clone()); + let needed_card = Card::new(color, value); if self.discard.has_all(&needed_card) { // already discarded all of these return value - 1; @@ -251,7 +251,7 @@ impl BoardState { // is never going to play, based on discard + fireworks 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() { true } else { @@ -259,14 +259,14 @@ impl BoardState { if card.value < desired { true } else { - card.value > self.highest_attainable(&card.color) + card.value > self.highest_attainable(card.color) } } } // can be discarded without necessarily sacrificing score, based on discard + fireworks pub fn is_dispensable(&self, card: &Card) -> bool { - let firework = self.fireworks.get(card.color).unwrap(); + let firework = self.fireworks.get(&card.color).unwrap(); if firework.complete() { true } else { @@ -274,7 +274,7 @@ impl BoardState { if card.value < desired { true } else { - if card.value > self.highest_attainable(&card.color) { + if card.value > self.highest_attainable(card.color) { true } else { self.discard.remaining(&card) != 1 @@ -342,7 +342,7 @@ impl fmt::Display for BoardState { "{}/{} lives remaining\n", self.lives_remaining, self.lives_total ))); try!(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)))); } try!(f.write_str("Discard:\n")); @@ -597,7 +597,7 @@ impl GameState { let playable = self.board.is_playable(&card); if playable { { - let firework = self.board.get_firework_mut(&card.color); + let firework = self.board.get_firework_mut(card.color); debug!("Successfully played {}!", card); firework.place(&card); } diff --git a/src/info.rs b/src/info.rs index e415931..70379f8 100644 --- a/src/info.rs +++ b/src/info.rs @@ -9,20 +9,11 @@ use game::BoardState; // trait representing information about a card pub trait CardInfo { - // get all a-priori possibilities - fn get_all_possibilities(&self) -> Vec { - let mut v = Vec::new(); - for &color in COLORS.iter() { - for &value in VALUES.iter() { - v.push(Card::new(color, value)); - } - } - v - } // whether the card is possible fn is_possible(&self, card: &Card) -> bool; // mark all current possibilities for the card + // this should generally be overridden, for efficiency fn get_possibilities(&self) -> Vec { let mut v = Vec::new(); for &color in COLORS.iter() { @@ -89,16 +80,16 @@ pub trait CardInfo { } // mark a whole color as false - fn mark_color_false(&mut self, color: &Color); + fn mark_color_false(&mut self, color: Color); // mark a color as correct - fn mark_color_true(&mut self, color: &Color) { - for other_color in COLORS.iter() { + fn mark_color_true(&mut self, color: Color) { + for &other_color in COLORS.iter() { if other_color != color { self.mark_color_false(other_color); } } } - fn mark_color(&mut self, color: &Color, is_color: bool) { + fn mark_color(&mut self, color: Color, is_color: bool) { if is_color { self.mark_color_true(color); } else { @@ -107,16 +98,16 @@ pub trait CardInfo { } // mark a whole value as false - fn mark_value_false(&mut self, value: &Value); + fn mark_value_false(&mut self, value: Value); // mark a value as correct - fn mark_value_true(&mut self, value: &Value) { - for other_value in VALUES.iter() { + fn mark_value_true(&mut self, value: Value) { + for &other_value in VALUES.iter() { if other_value != value { self.mark_value_false(other_value); } } } - fn mark_value(&mut self, value: &Value, is_value: bool) { + fn mark_value(&mut self, value: Value, is_value: bool) { if is_value { self.mark_value_true(value); } else { @@ -127,7 +118,7 @@ pub trait CardInfo { // Represents hinted information about possible values of type T -pub trait Info where T: Hash + Eq + Clone { +pub trait Info where T: Hash + Eq + Clone + Copy { // get all a-priori possibilities fn get_all_possibilities() -> Vec; @@ -141,8 +132,8 @@ pub trait Info where T: Hash + Eq + Clone { self.get_possibility_set().iter().map(|t| t.clone()).collect::>() } - fn is_possible(&self, value: &T) -> bool { - self.get_possibility_set().contains(value) + fn is_possible(&self, value: T) -> bool { + self.get_possibility_set().contains(&value) } fn initialize() -> HashSet { @@ -153,17 +144,17 @@ pub trait Info where T: Hash + Eq + Clone { possible_map } - fn mark_true(&mut self, value: &T) { + fn mark_true(&mut self, value: T) { let possible = self.get_mut_possibility_set(); possible.clear(); possible.insert(value.clone()); } - fn mark_false(&mut self, value: &T) { - self.get_mut_possibility_set().remove(value); + fn mark_false(&mut self, value: T) { + self.get_mut_possibility_set().remove(&value); } - fn mark(&mut self, value: &T, info: bool) { + fn mark(&mut self, value: T, info: bool) { if info { self.mark_true(value); } else { @@ -220,30 +211,30 @@ impl CardInfo for SimpleCardInfo { v } fn is_possible(&self, card: &Card) -> bool { - self.color_info.is_possible(&card.color) && - self.value_info.is_possible(&card.value) + self.color_info.is_possible(card.color) && + self.value_info.is_possible(card.value) } - fn mark_color_false(&mut self, color: &Color) { + fn mark_color_false(&mut self, color: Color) { self.color_info.mark_false(color); } - fn mark_value_false(&mut self, value: &Value) { + fn mark_value_false(&mut self, value: Value) { self.value_info.mark_false(value); } } impl fmt::Display for SimpleCardInfo { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut string = String::new(); - for color in &COLORS { + for &color in &COLORS { if self.color_info.is_possible(color) { - string.push(display_color(color)); + string.push(color); } } // while string.len() < COLORS.len() + 1 { string.push(' '); //} - for value in &VALUES { + for &value in &VALUES { if self.value_info.is_possible(value) { string.push_str(&format!("{}", value)); } @@ -346,15 +337,15 @@ impl CardInfo for CardPossibilityTable { cards.sort(); cards } - fn mark_color_false(&mut self, color: &Color) { + fn mark_color_false(&mut self, color: Color) { for &value in VALUES.iter() { self.mark_false(&Card::new(color, value)); } } - fn mark_value_false(&mut self, value: &Value) { + fn mark_value_false(&mut self, value: Value) { for &color in COLORS.iter() { - self.mark_false(&Card::new(color, value.clone())); + self.mark_false(&Card::new(color, value)); } } fn get_weight(&self, card: &Card) -> f32 { diff --git a/src/strategies/information.rs b/src/strategies/information.rs index 2348896..5657796 100644 --- a/src/strategies/information.rs +++ b/src/strategies/information.rs @@ -27,7 +27,7 @@ impl ModulusInformation { self.modulus = self.modulus * other.modulus; } - pub fn emit(&mut self, modulus: u32) -> Self { + pub fn split(&mut self, modulus: u32) -> Self { assert!(self.modulus >= modulus); assert!(self.modulus % modulus == 0); let original_modulus = self.modulus; @@ -68,16 +68,17 @@ trait Question { fn info_amount(&self) -> u32; // get the answer to this question, given cards fn answer(&self, &Cards, &OwnedGameView) -> u32; + // process the answer to this question, updating card info + fn acknowledge_answer( + &self, value: u32, &mut Vec, &OwnedGameView + ); + fn answer_info(&self, hand: &Cards, view: &OwnedGameView) -> ModulusInformation { ModulusInformation::new( self.info_amount(), self.answer(hand, view) ) } - // process the answer to this question, updating card info - fn acknowledge_answer( - &self, value: u32, &mut Vec, &OwnedGameView - ); fn acknowledge_answer_info( &self, @@ -353,7 +354,7 @@ impl InformationPlayerStrategy { { let view = &self.last_view; for question in questions { - let answer_info = hint.emit(question.info_amount()); + let answer_info = hint.split(question.info_amount()); question.acknowledge_answer_info(answer_info, &mut hand_info, view); } } @@ -487,12 +488,12 @@ impl InformationPlayerStrategy { let mut info = self.get_player_public_info_mut(&hint.player); let zip_iter = info.iter_mut().zip(matches); match hint.hinted { - Hinted::Color(ref color) => { + Hinted::Color(color) => { for (card_info, matched) in zip_iter { card_info.mark_color(color, *matched); } } - Hinted::Value(ref value) => { + Hinted::Value(value) => { for (card_info, matched) in zip_iter { card_info.mark_value(value, *matched); } @@ -520,6 +521,9 @@ impl InformationPlayerStrategy { } } + // TODO: decrement weight counts for fully determined cards, + // ahead of time + // note: other_player could be player, as well // in particular, we will decrement the newly drawn card for other_player in view.board.get_players() { @@ -574,6 +578,16 @@ impl InformationPlayerStrategy { fn get_hint(&self) -> TurnChoice { let view = &self.last_view; + + // Can give up to 3(n-1) hints + // For any given player with at least 4 cards, and index i, there are at least 3 hints that can be given. + // 0. a value hint on card i + // 1. a color hint on card i + // 2. any hint not involving card i + + // TODO: make it so space of hints is larger when there is + // knowledge about the cards? + let total_info = 3 * (view.board.num_players - 1); let hint_info = self.get_hint_sum_info(total_info, view); @@ -587,13 +601,6 @@ impl InformationPlayerStrategy { let card_index = self.get_index_for_hint(self.get_player_public_info(&hint_player), view); let hint_card = &hand[card_index]; - // For any given player with at least 4 cards, and index i, there are at least 3 hints that can be given. - // 0. a value hint on card i - // 1. a color hint on card i - // 2. any hint not involving card i - // - // for 4 players, can give 6 distinct hints - let hinted = match hint_type { 0 => {