From b8ea5d4ad5e1bf75943c3ce9f3a3e710781a7f1f Mon Sep 17 00:00:00 2001 From: Jeff Wu Date: Sun, 26 May 2019 23:59:42 -0700 Subject: [PATCH 01/10] bragging --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1802d63..653bcb2 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ This project provides a framework for implementing Hanabi strategies in Rust. 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 an improved version of their "information strategy", -which achieves the best results I'm aware of for games with more than 2 players ([see below](#results)). +which achieves state-of-the-art results for games with more than 2 players ([see below](#results)). Please feel free to contact me about Hanabi strategies, or this framework. @@ -15,6 +15,8 @@ Most similar projects I am aware of: - https://github.com/rjtobin/HanSim (written for the paper mentioned above) - https://github.com/Quuxplusone/Hanabi +This repository has been cited in [this paper](https://arxiv.org/abs/1902.00506) from DeepMind and Google Brain, and is briefly discussed in [this article](https://www.wsj.com/articles/why-the-card-game-hanabi-is-the-next-big-hurdle-for-artificial-intelligence-11553875351) from the Wall Street Journal! + ## Setup Install rust (rustc and cargo), and clone this git repo. From cf65b0e158bedda26ee8a85b40dde940bbf0a0f1 Mon Sep 17 00:00:00 2001 From: Jeff Wu Date: Mon, 9 Dec 2019 10:12:31 -0800 Subject: [PATCH 02/10] updates for FAIR work --- README.md | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 653bcb2..0ee7365 100644 --- a/README.md +++ b/README.md @@ -3,20 +3,14 @@ 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. -This project provides a framework for implementing Hanabi strategies in Rust. -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 an improved version of their "information strategy", -which achieves state-of-the-art results for games with more than 2 players ([see below](#results)). +This project provides a framework for implementing Hanabi strategies in Rust, and also implements extremely strong strategies. + +The best strategy is based on the "information strategy" from +[this paper](https://d0474d97-a-62cb3a1a-s-sites.googlegroups.com/site/rmgpgrwc/research-papers/Hanabi_final.pdf). See results ([below](#results)). +It held state-of-the-art results (from March 2016) until December 2019, when [researchers at Facebook](https://arxiv.org/abs/1912.02318) surpassed it by extending the idea further with explicit search. Please feel free to contact me about Hanabi strategies, or this framework. -Most similar projects I am aware of: -- https://github.com/rjtobin/HanSim (written for the paper mentioned above) -- https://github.com/Quuxplusone/Hanabi - -This repository has been cited in [this paper](https://arxiv.org/abs/1902.00506) from DeepMind and Google Brain, and is briefly discussed in [this article](https://www.wsj.com/articles/why-the-card-game-hanabi-is-the-next-big-hurdle-for-artificial-intelligence-11553875351) from the Wall Street Journal! - ## Setup Install rust (rustc and cargo), and clone this git repo. @@ -77,3 +71,13 @@ On the first 20000 seeds, we have these scores and win rates (average ± standar | | 90.59 ± 0.21 % | 98.17 ± 0.09 % | 97.76 ± 0.10 % | 96.42 ± 0.13 % | | info | 22.5194 ± 0.0125 | 24.7942 ± 0.0039 | 24.9354 ± 0.0022 | 24.9220 ± 0.0024 | | | 12.58 ± 0.23 % | 84.46 ± 0.26 % | 95.03 ± 0.15 % | 94.01 ± 0.17 % | + +## Other work + +Most similar projects I am aware of: +- https://github.com/rjtobin/HanSim (written for the paper mentioned above which introduces the information strategy) +- https://github.com/Quuxplusone/Hanabi + +Some researchers are trying to solve Hanabi using machine learning techniques: +- [Initial paper](https://arxiv.org/abs/1902.00506) from DeepMind and Google Brain researchers. See [this Wall Street Journal coverage](https://www.wsj.com/articles/why-the-card-game-hanabi-is-the-next-big-hurdle-for-artificial-intelligence-11553875351) +- [This paper](https://arxiv.org/abs/1912.02318) from Facebook, code at https://github.com/facebookresearch/Hanabi_SPARTA which includes their machine-learned agent From c9b21fa047f2da1b4e18f634a27a20b88572061a Mon Sep 17 00:00:00 2001 From: phimuemue Date: Tue, 5 Jul 2022 20:05:13 +0200 Subject: [PATCH 03/10] Compile on rust 1.61, resolve warnings, fix some lints (#12) * Fix getopts version The pinned version does not compile anymore because of mutable aliasing: * https://github.com/rust-lang/getopts/pull/61 * https://github.com/rust-lang/getopts/issues/110 This was achieved by temporarily setting the getopts version to "0.2.21" in Cargo.toml, and running `cargo check`. Note that this also converts the Cargo.lock to a new format. * Fix warning: Instead of deprecated macro try!, use question mark operator * Fix warning: Avoid anonymous parameters * Fix warning: Use dyn on trait objects * Fix warning: Avoid unneeded mutability * Fix warning: Avoid redundant format in panic or assert * Fix lint: Avoid redundant field names in initializers * Fix lint: Avoid redundant clone * Fix lint: Avoid literal cast * Fix lint: Collapse if/else where applicable I left some if/else branches in place, if there was a certain symmetry between the branches. * Fix lint: Avoid needless borrow I left some if/else branches in place, if there was a certain symmetry between the branches. * Fix lint: Use cloned instead of custom closure * Fix lint: Avoid unneeded trait bound * Fix lint: Avoid unneeded trait bound (2) avoid redundant clone * Fix lint: Use &[T] instead of &Vec * Fix lint: Avoid & on each pattern * Fix lint: Avoid manual assign * Fix lint: Use implicit return * Fix lint: Merge if/else branches with same value I left one complicated branch in place. * Fix lint: Use is_empty instead of comparing len against 0 * Fix lint: Use sum instead of fold * Fix lint: Avoid clone on Copy types --- Cargo.lock | 45 +++++++----- src/game.rs | 126 ++++++++++++++++------------------ src/helpers.rs | 34 ++++----- src/main.rs | 16 ++--- src/simulator.rs | 16 ++--- src/strategies/cheating.rs | 28 ++++---- src/strategies/examples.rs | 8 +-- src/strategies/hat_helpers.rs | 28 ++++---- src/strategies/information.rs | 93 ++++++++++++------------- src/strategy.rs | 8 +-- 10 files changed, 198 insertions(+), 204 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e063b68..7f6b6db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,61 +1,72 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + [[package]] name = "crossbeam" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "348228ce9f93d20ffc30c18e575f82fa41b9c8bf064806c65d41eba4771595a0" [[package]] name = "float-ord" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bad48618fdb549078c333a7a8528acb57af271d0433bdecd523eb620628364e" [[package]] name = "fnv" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" [[package]] name = "getopts" -version = "0.2.14" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" +dependencies = [ + "unicode-width", +] [[package]] name = "libc" version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4870ef6725dde13394134e587e4ab4eca13cb92e916209a31c851b49131d3c75" [[package]] name = "log" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "038b5d13189a14e5b6ac384fdb7c691a45ef0885f6d2dddbf422e6c3506b8234" dependencies = [ - "libc 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", ] [[package]] name = "rand" version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2791d88c6defac799c3f20d74f094ca33b9332612d9aef9078519c82e4fe04a5" dependencies = [ - "libc 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", + "libc", ] [[package]] name = "rust_hanabi" version = "0.1.0" dependencies = [ - "crossbeam 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "float-ord 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam", + "float-ord", + "fnv", + "getopts", + "log", + "rand", ] -[metadata] -"checksum crossbeam 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "348228ce9f93d20ffc30c18e575f82fa41b9c8bf064806c65d41eba4771595a0" -"checksum float-ord 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7bad48618fdb549078c333a7a8528acb57af271d0433bdecd523eb620628364e" -"checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" -"checksum getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9047cfbd08a437050b363d35ef160452c5fe8ea5187ae0a624708c91581d685" -"checksum libc 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "4870ef6725dde13394134e587e4ab4eca13cb92e916209a31c851b49131d3c75" -"checksum log 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "038b5d13189a14e5b6ac384fdb7c691a45ef0885f6d2dddbf422e6c3506b8234" -"checksum rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "2791d88c6defac799c3f20d74f094ca33b9332612d9aef9078519c82e4fe04a5" +[[package]] +name = "unicode-width" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" diff --git a/src/game.rs b/src/game.rs index e973a3c..1b21f11 100644 --- a/src/game.rs +++ b/src/game.rs @@ -19,7 +19,7 @@ pub fn get_count_for_value(value: Value) -> u32 { 1 => 3, 2 | 3 | 4 => 2, 5 => 1, - _ => { panic!(format!("Unexpected value: {}", value)); } + _ => { panic!("Unexpected value: {}", value); } } } @@ -30,7 +30,7 @@ pub struct Card { } impl Card { pub fn new(color: Color, value: Value) -> Card { - Card { color: color, value: value } + Card { color, value } } } impl fmt::Display for Card { @@ -57,7 +57,7 @@ impl CardCounts { } } CardCounts { - counts: counts, + counts, } } @@ -78,20 +78,20 @@ impl CardCounts { impl fmt::Display for CardCounts { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { for &color in COLORS.iter() { - try!(f.write_str(&format!( + f.write_str(&format!( "{}: ", color, - ))); + ))?; 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!( + f.write_str(&format!( "{}/{} {}s", count, total, value - ))); + ))?; if value != FINAL_VALUE { - try!(f.write_str(", ")); + f.write_str(", ")?; } } - try!(f.write_str("\n")); + f.write_str("\n")?; } Ok(()) } @@ -127,9 +127,9 @@ impl Discard { } impl fmt::Display for Discard { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - // try!(f.write_str(&format!( + // f.write_str(&format!( // "{}", self.cards, - // ))); + // ))?; write!(f, "{}", self.counts) } } @@ -145,7 +145,7 @@ pub struct Firework { impl Firework { pub fn new(color: Color) -> Firework { Firework { - color: color, + color, top: 0, } } @@ -192,8 +192,8 @@ pub enum Hinted { impl fmt::Display for Hinted { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - &Hinted::Color(color) => { write!(f, "{}", color) } - &Hinted::Value(value) => { write!(f, "{}", value) } + Hinted::Color(color) => { write!(f, "{}", color) } + Hinted::Value(value) => { write!(f, "{}", value) } } } } @@ -274,9 +274,9 @@ impl BoardState { }).collect::>(); BoardState { - deck_size: deck_size, + deck_size, total_cards: deck_size, - fireworks: fireworks, + fireworks, discard: Discard::new(), num_players: opts.num_players, hand_size: opts.hand_size, @@ -332,7 +332,7 @@ impl BoardState { return value - 1; } } - return FINAL_VALUE; + FINAL_VALUE } // is never going to play, based on discard + fireworks @@ -357,24 +357,20 @@ impl BoardState { true } else { let needed = firework.needed_value().unwrap(); - if card.value < needed { + if card.value < needed || card.value > self.highest_attainable(card.color) { true } else { - if card.value > self.highest_attainable(card.color) { - true - } else { - self.discard.remaining(&card) != 1 - } + self.discard.remaining(card) != 1 } } } pub fn get_players(&self) -> Range { - (0..self.num_players) + 0..self.num_players } pub fn score(&self) -> Score { - self.fireworks.iter().map(|(_, firework)| firework.score()).fold(0, |a, b| a + b) + self.fireworks.iter().map(|(_, firework)| firework.score()).sum() } pub fn discard_size(&self) -> u32 { @@ -395,35 +391,35 @@ impl BoardState { impl fmt::Display for BoardState { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if self.is_over() { - try!(f.write_str(&format!( + f.write_str(&format!( "Turn {} (GAME ENDED):\n", self.turn - ))); + ))?; } else { - try!(f.write_str(&format!( + f.write_str(&format!( "Turn {} (Player {}'s turn):\n", self.turn, self.player - ))); + ))?; } - try!(f.write_str(&format!( + f.write_str(&format!( "{} cards remaining in deck\n", self.deck_size - ))); + ))?; if self.deck_size == 0 { - try!(f.write_str(&format!( + f.write_str(&format!( "Deck is empty. {} turns remaining in game\n", self.deckless_turns_remaining - ))); + ))?; } - try!(f.write_str(&format!( + f.write_str(&format!( "{}/{} hints remaining\n", self.hints_remaining, self.hints_total - ))); - try!(f.write_str(&format!( + ))?; + f.write_str(&format!( "{}/{} lives remaining\n", self.lives_remaining, self.lives_total - ))); - try!(f.write_str("Fireworks:\n")); + ))?; + f.write_str("Fireworks:\n")?; for &color in COLORS.iter() { - try!(f.write_str(&format!(" {}\n", self.get_firework(color)))); + f.write_str(&format!(" {}\n", self.get_firework(color)))?; } - try!(f.write_str("Discard:\n")); - try!(f.write_str(&format!("{}\n", self.discard))); + f.write_str("Discard:\n")?; + f.write_str(&format!("{}\n", self.discard))?; Ok(()) } @@ -432,7 +428,7 @@ impl fmt::Display for BoardState { // complete game view of a given player pub trait GameView { fn me(&self) -> Player; - fn get_hand(&self, &Player) -> &Cards; + fn get_hand(&self, player: &Player) -> &Cards; fn get_board(&self) -> &BoardState; fn my_hand_size(&self) -> usize; @@ -459,13 +455,13 @@ pub trait GameView { fn can_see(&self, card: &Card) -> bool { self.get_other_players().iter().any(|player| { - self.has_card(&player, card) + self.has_card(player, card) }) } fn someone_else_can_play(&self) -> bool { self.get_other_players().iter().any(|player| { - self.get_hand(&player).iter().any(|card| { + self.get_hand(player).iter().any(|card| { self.get_board().is_playable(card) }) }) @@ -518,9 +514,9 @@ impl OwnedGameView { }).collect::>(); OwnedGameView { - player: borrowed_view.player.clone(), + player: borrowed_view.player, hand_size: borrowed_view.hand_size, - other_hands: other_hands, + other_hands, board: (*borrowed_view.board).clone(), } } @@ -550,22 +546,22 @@ pub struct GameState { } impl fmt::Display for GameState { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - try!(f.write_str("\n")); - try!(f.write_str("======\n")); - try!(f.write_str("Hands:\n")); - try!(f.write_str("======\n")); + f.write_str("\n")?; + f.write_str("======\n")?; + f.write_str("Hands:\n")?; + f.write_str("======\n")?; for player in self.board.get_players() { let hand = &self.hands.get(&player).unwrap(); - try!(f.write_str(&format!("player {}:", player))); + f.write_str(&format!("player {}:", player))?; for card in hand.iter() { - try!(f.write_str(&format!(" {}", card))); + f.write_str(&format!(" {}", card))?; } - try!(f.write_str(&"\n")); + f.write_str("\n")?; } - try!(f.write_str("======\n")); - try!(f.write_str("Board:\n")); - try!(f.write_str("======\n")); - try!(f.write_str(&format!("{}", self.board))); + f.write_str("======\n")?; + f.write_str("Board:\n")?; + f.write_str("======\n")?; + f.write_str(&format!("{}", self.board))?; Ok(()) } } @@ -585,9 +581,9 @@ impl GameState { }).collect::>(); GameState { - hands: hands, - board: board, - deck: deck, + hands, + board, + deck, } } @@ -612,9 +608,9 @@ impl GameState { } } BorrowedGameView { - player: player, + player, hand_size: self.hands.get(&player).unwrap().len(), - other_hands: other_hands, + other_hands, board: &self.board, } } @@ -646,7 +642,7 @@ impl GameState { debug!("Hint to player {}, about {}", hint.player, hint.hinted); assert!(self.board.player != hint.player, - format!("Player {} gave a hint to himself", hint.player)); + "Player {} gave a hint to himself", hint.player); let hand = self.hands.get(&hint.player).unwrap(); let results = match hint.hinted { @@ -703,9 +699,9 @@ impl GameState { } }; let turn_record = TurnRecord { - player: self.board.player.clone(), + player: self.board.player, result: turn_result, - choice: choice, + choice, }; self.board.turn_history.push(turn_record.clone()); diff --git a/src/helpers.rs b/src/helpers.rs index 43aa3b7..1306e78 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -33,7 +33,7 @@ pub trait CardInfo { // get probability weight for the card #[allow(unused_variables)] fn get_weight(&self, card: &Card) -> f32 { - 1 as f32 + 1. } fn get_weighted_possibilities(&self) -> Vec<(Card, f32)> { @@ -46,11 +46,11 @@ pub trait CardInfo { fn total_weight(&self) -> f32 { self.get_possibilities().iter() - .map(|card| self.get_weight(&card)) + .map(|card| self.get_weight(card)) .fold(0.0, |a, b| a+b) } - fn weighted_score(&self, score_fn: &Fn(&Card) -> T) -> f32 + fn weighted_score(&self, score_fn: &dyn Fn(&Card) -> T) -> f32 where f32: From { let mut total_score = 0.; @@ -68,7 +68,7 @@ pub trait CardInfo { self.weighted_score(&|card| card.value as f32 ) } - fn probability_of_predicate(&self, predicate: &Fn(&Card) -> bool) -> f32 { + fn probability_of_predicate(&self, predicate: &dyn Fn(&Card) -> bool) -> f32 { let f = |card: &Card| { if predicate(card) { 1.0 } else { 0.0 } }; @@ -126,7 +126,7 @@ pub trait CardInfo { // Represents hinted information about possible values of type T -pub trait Info where T: Hash + Eq + Clone + Copy { +pub trait Info where T: Hash + Eq + Clone { // get all a-priori possibilities fn get_all_possibilities() -> Vec; @@ -137,7 +137,7 @@ pub trait Info where T: Hash + Eq + Clone + Copy { // get what is now possible fn get_possibilities(&self) -> Vec { - self.get_possibility_set().iter().map(|t| t.clone()).collect::>() + self.get_possibility_set().iter().cloned().collect::>() } fn is_possible(&self, value: T) -> bool { @@ -146,13 +146,13 @@ pub trait Info where T: Hash + Eq + Clone + Copy { fn initialize() -> HashSet { Self::get_all_possibilities().iter() - .map(|val| val.clone()).collect::>() + .cloned().collect::>() } fn mark_true(&mut self, value: T) { let possible = self.get_mut_possibility_set(); possible.clear(); - possible.insert(value.clone()); + possible.insert(value); } fn mark_false(&mut self, value: T) { @@ -328,7 +328,7 @@ impl <'a> From<&'a CardCounts> for CardPossibilityTable { } } CardPossibilityTable { - possible: possible, + possible, } } } @@ -341,7 +341,7 @@ impl CardInfo for CardPossibilityTable { self.possible.contains_key(card) } fn get_possibilities(&self) -> Vec { - let mut cards = self.possible.keys().map(|card| {card.clone() }).collect::>(); + let mut cards = self.possible.keys().cloned().collect::>(); cards.sort(); cards } @@ -363,7 +363,7 @@ impl CardInfo for CardPossibilityTable { impl fmt::Display for CardPossibilityTable { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { for (card, weight) in &self.possible { - try!(f.write_str(&format!("{} {}, ", weight, card))); + f.write_str(&format!("{} {}, ", weight, card))?; } Ok(()) } @@ -377,21 +377,21 @@ impl HandInfo where T: CardInfo { pub fn new(hand_size: u32) -> Self { let hand_info = (0..hand_size).map(|_| T::new()).collect::>(); HandInfo { - hand_info: hand_info, + hand_info, } } // update for hint to me - pub fn update_for_hint(&mut self, hinted: &Hinted, matches: &Vec) { + pub fn update_for_hint(&mut self, hinted: &Hinted, matches: &[bool]) { match hinted { - &Hinted::Color(color) => { + Hinted::Color(color) => { for (card_info, &matched) in self.hand_info.iter_mut().zip(matches.iter()) { - card_info.mark_color(color, matched); + card_info.mark_color(*color, matched); } } - &Hinted::Value(value) => { + Hinted::Value(value) => { for (card_info, &matched) in self.hand_info.iter_mut().zip(matches.iter()) { - card_info.mark_value(value, matched); + card_info.mark_value(*value, matched); } } } diff --git a/src/main.rs b/src/main.rs index 1568470..aa74cdb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -75,7 +75,7 @@ fn main() { Ok(m) => { m } Err(f) => { print_usage(&program, opts); - panic!(f.to_string()) + panic!("{}", f) } }; if matches.opt_present("h") { @@ -131,27 +131,27 @@ fn sim_games(n_players: u32, strategy_str: &str, seed: Option, n_trials: u3 let game_opts = game::GameOptions { num_players: n_players, - hand_size: hand_size, + hand_size, num_hints: 8, num_lives: 3, // hanabi rules are a bit ambiguous about whether you can give hints that match 0 cards allow_empty_hints: false, }; - let strategy_config : Box = match strategy_str { + let strategy_config : Box = match strategy_str { "random" => { Box::new(strategies::examples::RandomStrategyConfig { hint_probability: 0.4, play_probability: 0.2, - }) as Box + }) as Box }, "cheat" => { Box::new(strategies::cheating::CheatingStrategyConfig::new()) - as Box + as Box }, "info" => { Box::new(strategies::information::InformationStrategyConfig::new()) - as Box + as Box }, _ => { panic!("Unexpected strategy argument {}", strategy_str); @@ -176,7 +176,7 @@ fn get_results_table() -> String { let dashes = String::from("---------"); let dashes_long = String::from("------------------"); type TwoLines = (String, String); - fn make_twolines(player_nums: &Vec, head: TwoLines, make_block: &dyn Fn(u32) -> TwoLines) -> TwoLines { + fn make_twolines(player_nums: &[u32], head: TwoLines, make_block: &dyn Fn(u32) -> TwoLines) -> TwoLines { let mut blocks = player_nums.iter().cloned().map(make_block).collect::>(); blocks.insert(0, head); fn combine(items: Vec) -> String { @@ -189,7 +189,7 @@ fn get_results_table() -> String { body.into_iter().fold(String::default(), |output, (a, b)| (output + &a + "\n" + &b + "\n")) } let header = make_twolines(&player_nums, - (space.clone(), dashes.clone()), + (space.clone(), dashes), &|n_players| (format_players(n_players), dashes_long.clone())); let mut body = strategies.iter().map(|strategy| { make_twolines(&player_nums, (format_name(strategy), space.clone()), &|n_players| { diff --git a/src/simulator.rs b/src/simulator.rs index cf96183..0c51065 100644 --- a/src/simulator.rs +++ b/src/simulator.rs @@ -24,7 +24,7 @@ fn new_deck(seed: u32) -> Cards { pub fn simulate_once( opts: &GameOptions, - game_strategy: Box, + game_strategy: Box, seed: u32, ) -> GameState { let deck = new_deck(seed); @@ -33,7 +33,7 @@ pub fn simulate_once( let mut strategies = game.get_players().map(|player| { (player, game_strategy.initialize(player, &game.get_view(player))) - }).collect::>>(); + }).collect::>>(); while !game.is_over() { let player = game.board.player; @@ -46,14 +46,14 @@ pub fn simulate_once( let choice = { - let mut strategy = strategies.get_mut(&player).unwrap(); + let strategy = strategies.get_mut(&player).unwrap(); strategy.decide(&game.get_view(player)) }; let turn = game.process_choice(choice); for player in game.get_players() { - let mut strategy = strategies.get_mut(&player).unwrap(); + let strategy = strategies.get_mut(&player).unwrap(); strategy.update(&turn, &game.get_view(player)); } @@ -89,7 +89,7 @@ impl Histogram { self.insert_many(val, 1); } pub fn get_count(&self, val: &Score) -> u32 { - *self.hist.get(&val).unwrap_or(&0) + *self.hist.get(val).unwrap_or(&0) } pub fn percentage_with(&self, val: &Score) -> f32 { self.get_count(val) as f32 / self.total_count as f32 @@ -119,9 +119,9 @@ impl fmt::Display for Histogram { let mut keys = self.hist.keys().collect::>(); keys.sort(); for val in keys { - try!(f.write_str(&format!( + f.write_str(&format!( "\n{}: {}", val, self.get_count(val), - ))); + ))?; } Ok(()) } @@ -164,7 +164,7 @@ pub fn simulate( ); } } - let game = simulate_once(&opts, strat_config_ref.initialize(&opts), seed); + let game = simulate_once(opts, strat_config_ref.initialize(opts), seed); let score = game.score(); lives_histogram.insert(game.board.lives_remaining); score_histogram.insert(score); diff --git a/src/strategies/cheating.rs b/src/strategies/cheating.rs index c71bfd5..9ca8c8b 100644 --- a/src/strategies/cheating.rs +++ b/src/strategies/cheating.rs @@ -25,7 +25,7 @@ impl CheatingStrategyConfig { } } impl GameStrategyConfig for CheatingStrategyConfig { - fn initialize(&self, _: &GameOptions) -> Box { + fn initialize(&self, _: &GameOptions) -> Box { Box::new(CheatingStrategy::new()) } } @@ -42,7 +42,7 @@ impl CheatingStrategy { } } impl GameStrategy for CheatingStrategy { - fn initialize(&self, player: Player, view: &BorrowedGameView) -> Box { + fn initialize(&self, player: Player, view: &BorrowedGameView) -> Box { for (&player, &hand) in &view.other_hands { self.player_hands_cheat.borrow_mut().insert( player, hand.clone() @@ -93,7 +93,7 @@ impl CheatingPlayerStrategy { // given a hand of cards, represents how badly it will need to play things fn hand_play_value(&self, view: &BorrowedGameView, hand: &Cards) -> u32 { - hand.iter().map(|card| self.card_play_value(view, card)).fold(0, |a,b| a+b) + hand.iter().map(|card| self.card_play_value(view, card)).sum() } // how badly do we need to play a particular card @@ -104,13 +104,11 @@ impl CheatingPlayerStrategy { let my_hand_value = self.hand_play_value(view, my_hand); for player in view.board.get_players() { - if player != self.me { - if view.has_card(&player, card) { - let their_hand_value = self.hand_play_value(view, hands.get(&player).unwrap()); - // they can play this card, and have less urgent plays than i do - if their_hand_value < my_hand_value { - return 10 - (card.value as i32) - } + if player != self.me && view.has_card(&player, card) { + let their_hand_value = self.hand_play_value(view, hands.get(&player).unwrap()); + // they can play this card, and have less urgent plays than i do + if their_hand_value < my_hand_value { + return 10 - (card.value as i32) } } } @@ -132,7 +130,7 @@ impl CheatingPlayerStrategy { } set.insert(card.clone()); } - return None + None } } impl PlayerStrategy for CheatingPlayerStrategy { @@ -145,7 +143,7 @@ impl PlayerStrategy for CheatingPlayerStrategy { view.board.is_playable(card) }).collect::>(); - if playable_cards.len() > 0 { + if !playable_cards.is_empty() { // play the best playable card // the higher the play_score, the better to play let mut index = 0; @@ -178,10 +176,8 @@ impl PlayerStrategy for CheatingPlayerStrategy { // hinting is better than discarding dead cards // (probably because it stalls the deck-drawing). - if view.board.hints_remaining > 0 { - if view.someone_else_can_play() { - return self.throwaway_hint(view); - } + if view.board.hints_remaining > 0 && view.someone_else_can_play() { + return self.throwaway_hint(view); } // if anything is totally useless, discard it diff --git a/src/strategies/examples.rs b/src/strategies/examples.rs index 60c7aad..a524b75 100644 --- a/src/strategies/examples.rs +++ b/src/strategies/examples.rs @@ -10,7 +10,7 @@ pub struct RandomStrategyConfig { } impl GameStrategyConfig for RandomStrategyConfig { - fn initialize(&self, _: &GameOptions) -> Box { + fn initialize(&self, _: &GameOptions) -> Box { Box::new(RandomStrategy { hint_probability: self.hint_probability, play_probability: self.play_probability, @@ -23,7 +23,7 @@ pub struct RandomStrategy { play_probability: f64, } impl GameStrategy for RandomStrategy { - fn initialize(&self, player: Player, _: &BorrowedGameView) -> Box { + fn initialize(&self, player: Player, _: &BorrowedGameView) -> Box { Box::new(RandomStrategyPlayer { hint_probability: self.hint_probability, play_probability: self.play_probability, @@ -44,7 +44,7 @@ impl PlayerStrategy for RandomStrategyPlayer { if p < self.hint_probability { if view.board.hints_remaining > 0 { let hint_player = view.board.player_to_left(&self.me); - let hint_card = rand::thread_rng().choose(&view.get_hand(&hint_player)).unwrap(); + let hint_card = rand::thread_rng().choose(view.get_hand(&hint_player)).unwrap(); let hinted = { if rand::random() { // hint a color @@ -55,7 +55,7 @@ impl PlayerStrategy for RandomStrategyPlayer { }; TurnChoice::Hint(Hint { player: hint_player, - hinted: hinted, + hinted, }) } else { TurnChoice::Discard(0) diff --git a/src/strategies/hat_helpers.rs b/src/strategies/hat_helpers.rs index 7550a5b..4f2c921 100644 --- a/src/strategies/hat_helpers.rs +++ b/src/strategies/hat_helpers.rs @@ -10,8 +10,8 @@ impl ModulusInformation { pub fn new(modulus: u32, value: u32) -> Self { assert!(value < modulus); ModulusInformation { - modulus: modulus, - value: value, + modulus, + value, } } @@ -21,7 +21,7 @@ impl ModulusInformation { pub fn combine(&mut self, other: Self, max_modulus: u32) { assert!(other.modulus <= self.info_remaining(max_modulus)); - self.value = self.value + self.modulus * other.value; + self.value += self.modulus * other.value; self.modulus = std::cmp::min(max_modulus, self.modulus * other.modulus); assert!(self.value < self.modulus); } @@ -45,7 +45,7 @@ impl ModulusInformation { let original_modulus = self.modulus; let original_value = self.value; let value = self.value % modulus; - self.value = self.value / modulus; + self.value /= modulus; // `self.modulus` is the largest number such that // `value + (self.modulus - 1) * modulus < original_modulus`. // TODO: find an explanation of why this makes everything work out @@ -80,10 +80,10 @@ pub trait Question { // how much info does this question ask for? fn info_amount(&self) -> u32; // get the answer to this question, given cards - fn answer(&self, &Cards, &BoardState) -> u32; + fn answer(&self, hand: &Cards, board: &BoardState) -> u32; // process the answer to this question, updating card info fn acknowledge_answer( - &self, value: u32, &mut HandInfo, &BoardState + &self, value: u32, hand_info: &mut HandInfo, board: &BoardState ); fn answer_info(&self, hand: &Cards, board: &BoardState) -> ModulusInformation { @@ -105,11 +105,11 @@ pub trait Question { } pub trait PublicInformation: Clone { - fn get_player_info(&self, &Player) -> HandInfo; - fn set_player_info(&mut self, &Player, HandInfo); + fn get_player_info(&self, player: &Player) -> HandInfo; + fn set_player_info(&mut self, player: &Player, hand_info: HandInfo); - fn new(&BoardState) -> Self; - fn set_board(&mut self, &BoardState); + fn new(board: &BoardState) -> Self; + fn set_board(&mut self, board: &BoardState); /// If we store more state than just `HandInfo`s, update it after `set_player_info` has been called. fn update_other_info(&mut self) { @@ -126,10 +126,10 @@ pub trait PublicInformation: Clone { /// /// Note that `self` does not reflect the answers to previous questions; it reflects the state /// before the entire "hat value" calculation. - fn ask_question(&self, &Player, &HandInfo, total_info: u32) -> Option>; + fn ask_question(&self, player: &Player, hand_info: &HandInfo, total_info: u32) -> Option>; fn ask_question_wrapper(&self, player: &Player, hand_info: &HandInfo, total_info: u32) - -> Option> + -> Option> { assert!(total_info > 0); if total_info == 1 { @@ -191,7 +191,7 @@ pub trait PublicInformation: Clone { let (infos, new_player_hands): (Vec<_>, Vec<_>) = view.get_other_players().iter().map(|player| { let mut hand_info = self.get_player_info(player); let info = self.get_hat_info_for_player(player, &mut hand_info, total_info, view); - (info, (player.clone(), hand_info)) + (info, (*player, hand_info)) }).unzip(); self.set_player_infos(new_player_hands); infos.into_iter().fold( @@ -213,7 +213,7 @@ pub trait PublicInformation: Clone { }).map(|player| { let mut hand_info = self.get_player_info(&player); let player_info = self.get_hat_info_for_player(&player, &mut hand_info, info.modulus, view); - (player_info, (player.clone(), hand_info)) + (player_info, (player, hand_info)) }).unzip(); for other_info in other_infos { info.subtract(&other_info); diff --git a/src/strategies/information.rs b/src/strategies/information.rs index e28951d..53b47bf 100644 --- a/src/strategies/information.rs +++ b/src/strategies/information.rs @@ -58,7 +58,7 @@ fn q_is_dead(index: usize) -> CardHasProperty { /// It's named that way because the `info_amount` grows additively with the `info_amount`s of /// the questions in `l`. struct AdditiveComboQuestion { - questions: Vec>, + questions: Vec>, } impl Question for AdditiveComboQuestion { fn info_amount(&self) -> u32 { @@ -113,7 +113,7 @@ impl CardPossibilityPartition { let mut partition = FnvHashMap::default(); let mut n_partitions = 0; - let has_dead = card_table.probability_is_dead(&board) != 0.0; + let has_dead = card_table.probability_is_dead(board) != 0.0; // TODO: group things of different colors and values? let mut effective_max = max_n_partitions; @@ -152,9 +152,9 @@ impl CardPossibilityPartition { // debug!("{}", s); CardPossibilityPartition { - index: index, - n_partitions: n_partitions, - partition: partition, + index, + n_partitions, + partition, } } } @@ -162,7 +162,7 @@ impl Question for CardPossibilityPartition { fn info_amount(&self) -> u32 { self.n_partitions } fn answer(&self, hand: &Cards, _: &BoardState) -> u32 { let ref card = hand[self.index]; - *self.partition.get(&card).unwrap() + *self.partition.get(card).unwrap() } fn acknowledge_answer( &self, @@ -220,7 +220,7 @@ impl MyPublicInformation { }) }); - return if !may_be_all_one_color && !may_be_all_one_number { 4 } else { 3 } + if !may_be_all_one_color && !may_be_all_one_number { 4 } else { 3 } } fn get_hint_index_score(&self, card_table: &CardPossibilityTable) -> i32 { @@ -238,7 +238,7 @@ impl MyPublicInformation { if !card_table.value_determined() { score += 1; } - return score; + score } fn get_index_for_hint(&self, player: &Player) -> usize { @@ -355,12 +355,12 @@ impl MyPublicInformation { hint_option_set.into_iter().collect::>().into_iter().map(|hinted| { Hint { player: hint_player, - hinted: hinted, + hinted, } }).collect() } - fn decode_hint_choice(&self, hint: &Hint, result: &Vec) -> ModulusInformation { + fn decode_hint_choice(&self, hint: &Hint, result: &[bool]) -> ModulusInformation { let hinter = self.board.player; let info_per_player: Vec<_> = self.get_other_players_starting_after(hinter).into_iter().map( @@ -372,7 +372,7 @@ impl MyPublicInformation { let player_amt = (n + hint.player - hinter - 1) % n; - let amt_from_prev_players = info_per_player.iter().take(player_amt as usize).fold(0, |a, b| a + b); + let amt_from_prev_players = info_per_player.iter().take(player_amt as usize).sum::(); let hint_info_we_can_give_to_this_player = info_per_player[player_amt as usize]; let card_index = self.get_index_for_hint(&hint.player); @@ -405,12 +405,12 @@ impl MyPublicInformation { ModulusInformation::new(total_info, hint_value) } - fn update_from_hint_choice(&mut self, hint: &Hint, matches: &Vec, view: &OwnedGameView) { + fn update_from_hint_choice(&mut self, hint: &Hint, matches: &[bool], view: &OwnedGameView) { let info = self.decode_hint_choice(hint, matches); self.update_from_hat_sum(info, view); } - fn update_from_hint_matches(&mut self, hint: &Hint, matches: &Vec) { + fn update_from_hint_matches(&mut self, hint: &Hint, matches: &[bool]) { let info = self.get_player_info_mut(&hint.player); info.update_for_hint(&hint.hinted, matches); } @@ -424,10 +424,10 @@ impl MyPublicInformation { fn someone_else_needs_hint(&self, view: &OwnedGameView) -> bool { // Does another player have a playable card, but doesn't know it? view.get_other_players().iter().any(|player| { - let has_playable_card = view.get_hand(&player).iter().any(|card| { + let has_playable_card = view.get_hand(player).iter().any(|card| { view.get_board().is_playable(card) }); - has_playable_card && !self.knows_playable_card(&player) + has_playable_card && !self.knows_playable_card(player) }) } @@ -466,7 +466,7 @@ impl MyPublicInformation { info.remove(index); // push *before* incrementing public counts - if info.len() < new_view.hand_size(&player) { + if info.len() < new_view.hand_size(player) { info.push(new_card_table); } } @@ -491,7 +491,7 @@ impl PublicInformation for MyPublicInformation { (player, hand_info) }).collect::>(); MyPublicInformation { - hand_info: hand_info, + hand_info, card_counts: CardCounts::new(), board: board.clone(), } @@ -518,7 +518,7 @@ impl PublicInformation for MyPublicInformation { _me: &Player, hand_info: &HandInfo, total_info: u32, - ) -> Option> { + ) -> Option> { // Changing anything inside this function will not break the information transfer // mechanisms! @@ -532,8 +532,7 @@ impl PublicInformation for MyPublicInformation { // We don't need to find out anything about cards that are determined or dead. let augmented_hand_info = augmented_hand_info_raw.into_iter().filter(|&(i, _, p_dead)| { - if p_dead == 1.0 { false } - else if hand_info[i].is_determined() { false } + if p_dead == 1.0 || hand_info[i].is_determined() { false } else { true } }).collect::>(); @@ -565,11 +564,11 @@ impl PublicInformation for MyPublicInformation { // only matters if we find a playable/dead card, and conditional on that, it's better // to find out about as many non-playable/non-dead cards as possible. to_ask.sort_by_key(|&(ask_dead, _, p_yes)| {(ask_dead, FloatOrd(p_yes))}); - let questions = to_ask.into_iter().map(|(ask_dead, i, _)| -> Box { + let questions = to_ask.into_iter().map(|(ask_dead, i, _)| -> Box { if ask_dead { Box::new(q_is_dead(i)) } else { Box::new(q_is_playable(i)) } }).collect::>(); - if questions.len() > 0 { + if !questions.is_empty() { return Some(Box::new(AdditiveComboQuestion { questions })) } } @@ -607,7 +606,7 @@ impl InformationStrategyConfig { } } impl GameStrategyConfig for InformationStrategyConfig { - fn initialize(&self, _: &GameOptions) -> Box { + fn initialize(&self, _: &GameOptions) -> Box { Box::new(InformationStrategy::new()) } } @@ -620,7 +619,7 @@ impl InformationStrategy { } } impl GameStrategy for InformationStrategy { - fn initialize(&self, player: Player, view: &BorrowedGameView) -> Box { + fn initialize(&self, player: Player, view: &BorrowedGameView) -> Box { Box::new(InformationPlayerStrategy { me: player, public_info: MyPublicInformation::new(view.board), @@ -650,10 +649,8 @@ impl InformationPlayerStrategy { let mut num_with = 1; if view.board.deck_size > 0 { for player in view.board.get_players() { - if player != self.me { - if view.has_card(&player, card) { - num_with += 1; - } + if player != self.me && view.has_card(&player, card) { + num_with += 1; } } } @@ -667,21 +664,19 @@ impl InformationPlayerStrategy { for (i, card_table) in hand.iter().enumerate() { if card_table.probability_is_dead(board) == 1.0 { useless.insert(i); - } else { - if let Some(card) = card_table.get_card() { - if seen.contains_key(&card) { - // found a duplicate card - useless.insert(i); - useless.insert(*seen.get(&card).unwrap()); - } else { - seen.insert(card, i); - } + } else if let Some(card) = card_table.get_card() { + if seen.contains_key(&card) { + // found a duplicate card + useless.insert(i); + useless.insert(*seen.get(&card).unwrap()); + } else { + seen.insert(card, i); } } } let mut useless_vec : Vec = useless.into_iter().collect(); useless_vec.sort(); - return useless_vec; + useless_vec } // how good is it to give this hint to this player? @@ -692,8 +687,8 @@ impl InformationPlayerStrategy { let hint_player = &hint.player; let hinted = &hint.hinted; - let hand = view.get_hand(&hint_player); - let mut hand_info = self.public_info.get_player_info(&hint_player); + let hand = view.get_hand(hint_player); + let mut hand_info = self.public_info.get_player_info(hint_player); let mut goodness = 1.0; for (i, card_table) in hand_info.iter_mut().enumerate() { @@ -716,9 +711,7 @@ impl InformationPlayerStrategy { let new_weight = card_table.total_weight(); assert!(new_weight <= old_weight); let bonus = { - if card_table.is_determined() { - 2 - } else if card_table.probability_is_dead(&view.board) == 1.0 { + if card_table.is_determined() || card_table.probability_is_dead(&view.board) == 1.0 { 2 } else { 1 @@ -744,12 +737,10 @@ impl InformationPlayerStrategy { h2.0.partial_cmp(&h1.0).unwrap_or(Ordering::Equal) }); - if hint_options.len() == 0 { + if hint_options.is_empty() { // NOTE: Technically possible, but never happens - } else { - if hint_options.len() > 1 { - debug!("Choosing amongst hint options: {:?}", hint_options); - } + } else if hint_options.len() > 1 { + debug!("Choosing amongst hint options: {:?}", hint_options); } hint_options.remove(0).1 } @@ -809,7 +800,7 @@ impl InformationPlayerStrategy { (i, card_table, p) }).collect::>(); - if risky_playable_cards.len() > 0 { + if !risky_playable_cards.is_empty() { risky_playable_cards.sort_by(|c1, c2| { c2.2.partial_cmp(&c1.2).unwrap_or(Ordering::Equal) }); @@ -827,7 +818,7 @@ impl InformationPlayerStrategy { // NOTE When changing this, make sure to keep the "discard" branch of update() up to date! let will_hint = if view.board.hints_remaining > 0 && public_info.someone_else_needs_hint(view) { true } - else if view.board.discard_size() <= discard_threshold && useless_indices.len() > 0 { false } + else if view.board.discard_size() <= discard_threshold && !useless_indices.is_empty() { false } // hinting is better than discarding dead cards // (probably because it stalls the deck-drawing). else if view.board.hints_remaining > 0 && view.someone_else_can_play() { true } @@ -849,7 +840,7 @@ impl InformationPlayerStrategy { if public_useless_indices.len() > 1 { let info = public_info.get_hat_sum(public_useless_indices.len() as u32, view); return TurnChoice::Discard(public_useless_indices[info.value as usize]); - } else if useless_indices.len() > 0 { + } else if !useless_indices.is_empty() { // TODO: have opponents infer that i knew a card was useless // TODO: after that, potentially prefer useless indices that arent public return TurnChoice::Discard(useless_indices[0]); diff --git a/src/strategy.rs b/src/strategy.rs index 8ef667f..c3823ee 100644 --- a/src/strategy.rs +++ b/src/strategy.rs @@ -6,21 +6,21 @@ use game::*; pub trait PlayerStrategy { // A function to decide what to do on the player's turn. // Given a BorrowedGameView, outputs their choice. - fn decide(&mut self, &BorrowedGameView) -> TurnChoice; + fn decide(&mut self, view: &BorrowedGameView) -> TurnChoice; // A function to update internal state after other players' turns. // Given what happened last turn, and the new state. - fn update(&mut self, &TurnRecord, &BorrowedGameView); + fn update(&mut self, turn_record: &TurnRecord, view: &BorrowedGameView); } // Represents the overall strategy for a game // Shouldn't do much, except store configuration parameters and // possibility initialize some shared randomness between players pub trait GameStrategy { - fn initialize(&self, Player, &BorrowedGameView) -> Box; + fn initialize(&self, player: Player, view: &BorrowedGameView) -> Box; } // Represents configuration for a strategy. // Acts as a factory for game strategies, so we can play many rounds pub trait GameStrategyConfig { - fn initialize(&self, &GameOptions) -> Box; + fn initialize(&self, opts: &GameOptions) -> Box; } From 4bab09ba2bf33d3d73cec625f04018ddadbc286a Mon Sep 17 00:00:00 2001 From: timotree3 Date: Thu, 19 Jan 2023 20:31:32 -0500 Subject: [PATCH 04/10] Run `cargo fmt` --- src/game.rs | 202 +++++++------- src/helpers.rs | 178 ++++++++---- src/main.rs | 236 ++++++++++------ src/simulator.rs | 73 ++--- src/strategies/cheating.rs | 44 +-- src/strategies/examples.rs | 9 +- src/strategies/hat_helpers.rs | 89 +++--- src/strategies/information.rs | 502 +++++++++++++++++++++------------- src/strategy.rs | 1 - 9 files changed, 812 insertions(+), 522 deletions(-) diff --git a/src/game.rs b/src/game.rs index 1b21f11..e2b8915 100644 --- a/src/game.rs +++ b/src/game.rs @@ -11,19 +11,21 @@ pub const COLORS: [Color; NUM_COLORS] = ['r', 'y', 'g', 'b', 'w']; pub type Value = u32; // list of values, assumed to be small to large pub const NUM_VALUES: usize = 5; -pub const VALUES : [Value; NUM_VALUES] = [1, 2, 3, 4, 5]; -pub const FINAL_VALUE : Value = 5; +pub const VALUES: [Value; NUM_VALUES] = [1, 2, 3, 4, 5]; +pub const FINAL_VALUE: Value = 5; pub fn get_count_for_value(value: Value) -> u32 { match value { - 1 => 3, + 1 => 3, 2 | 3 | 4 => 2, - 5 => 1, - _ => { panic!("Unexpected value: {}", value); } + 5 => 1, + _ => { + panic!("Unexpected value: {}", value); + } } } -#[derive(Clone,PartialEq,Eq,Hash,Ord,PartialOrd)] +#[derive(Clone, PartialEq, Eq, Hash, Ord, PartialOrd)] pub struct Card { pub color: Color, pub value: Value, @@ -44,7 +46,7 @@ impl fmt::Debug for Card { } } -#[derive(Debug,Clone,Eq,PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq)] pub struct CardCounts { counts: FnvHashMap, } @@ -56,9 +58,7 @@ impl CardCounts { counts.insert(Card::new(color, value), 0); } } - CardCounts { - counts, - } + CardCounts { counts } } pub fn get_count(&self, card: &Card) -> u32 { @@ -78,15 +78,11 @@ impl CardCounts { impl fmt::Display for CardCounts { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { for &color in COLORS.iter() { - f.write_str(&format!( - "{}: ", color, - ))?; + f.write_str(&format!("{}: ", color,))?; for &value in VALUES.iter() { let count = self.get_count(&Card::new(color, value)); let total = get_count_for_value(value); - f.write_str(&format!( - "{}/{} {}s", count, total, value - ))?; + f.write_str(&format!("{}/{} {}s", count, total, value))?; if value != FINAL_VALUE { f.write_str(", ")?; } @@ -99,7 +95,7 @@ impl fmt::Display for CardCounts { pub type Cards = Vec; -#[derive(Debug,Clone,Eq,PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq)] pub struct Discard { pub cards: Cards, counts: CardCounts, @@ -137,21 +133,22 @@ impl fmt::Display for Discard { pub type Score = u32; pub const PERFECT_SCORE: Score = (NUM_COLORS * NUM_VALUES) as u32; -#[derive(Debug,Clone,Eq,PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq)] pub struct Firework { pub color: Color, pub top: Value, } impl Firework { pub fn new(color: Color) -> Firework { - Firework { - color, - top: 0, - } + Firework { color, top: 0 } } pub fn needed_value(&self) -> Option { - if self.complete() { None } else { Some(self.top + 1) } + if self.complete() { + None + } else { + Some(self.top + 1) + } } pub fn score(&self) -> Score { @@ -184,7 +181,7 @@ impl fmt::Display for Firework { } } -#[derive(Debug,Clone,Hash,PartialEq,Eq)] +#[derive(Debug, Clone, Hash, PartialEq, Eq)] pub enum Hinted { Color(Color), Value(Value), @@ -192,20 +189,24 @@ pub enum Hinted { impl fmt::Display for Hinted { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - Hinted::Color(color) => { write!(f, "{}", color) } - Hinted::Value(value) => { write!(f, "{}", value) } + Hinted::Color(color) => { + write!(f, "{}", color) + } + Hinted::Value(value) => { + write!(f, "{}", value) + } } } } -#[derive(Debug,Clone,Eq,PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq)] pub struct Hint { pub player: Player, pub hinted: Hinted, } // represents the choice a player made in a given turn -#[derive(Debug,Clone,Eq,PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq)] pub enum TurnChoice { Hint(Hint), Discard(usize), // index of card to discard @@ -213,7 +214,7 @@ pub enum TurnChoice { } // represents what happened in a turn -#[derive(Debug,Clone,Eq,PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq)] pub enum TurnResult { Hint(Vec), // vector of whether each was in the hint Discard(Card), // card discarded @@ -221,7 +222,7 @@ pub enum TurnResult { } // represents a turn taken in the game -#[derive(Debug,Clone,Eq,PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq)] pub struct TurnRecord { pub player: Player, pub choice: TurnChoice, @@ -243,7 +244,7 @@ pub struct GameOptions { // State of everything except the player's hands // Is all completely common knowledge -#[derive(Debug,Clone,Eq,PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq)] pub struct BoardState { pub deck_size: u32, pub total_cards: u32, @@ -269,9 +270,10 @@ pub struct BoardState { } impl BoardState { pub fn new(opts: &GameOptions, deck_size: u32) -> BoardState { - let fireworks = COLORS.iter().map(|&color| { - (color, Firework::new(color)) - }).collect::>(); + let fireworks = COLORS + .iter() + .map(|&color| (color, Firework::new(color))) + .collect::>(); BoardState { deck_size, @@ -324,7 +326,7 @@ impl BoardState { for &value in VALUES.iter() { if value < needed { // already have these cards - continue + continue; } let needed_card = Card::new(color, value); if self.discard.has_all(&needed_card) { @@ -370,7 +372,10 @@ impl BoardState { } pub fn score(&self) -> Score { - self.fireworks.iter().map(|(_, firework)| firework.score()).sum() + self.fireworks + .iter() + .map(|(_, firework)| firework.score()) + .sum() } pub fn discard_size(&self) -> u32 { @@ -391,28 +396,28 @@ impl BoardState { impl fmt::Display for BoardState { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if self.is_over() { - f.write_str(&format!( - "Turn {} (GAME ENDED):\n", self.turn - ))?; + f.write_str(&format!("Turn {} (GAME ENDED):\n", self.turn))?; } else { f.write_str(&format!( - "Turn {} (Player {}'s turn):\n", self.turn, self.player + "Turn {} (Player {}'s turn):\n", + self.turn, self.player ))?; } - f.write_str(&format!( - "{} cards remaining in deck\n", self.deck_size - ))?; + f.write_str(&format!("{} cards remaining in deck\n", self.deck_size))?; if self.deck_size == 0 { f.write_str(&format!( - "Deck is empty. {} turns remaining in game\n", self.deckless_turns_remaining + "Deck is empty. {} turns remaining in game\n", + self.deckless_turns_remaining ))?; } f.write_str(&format!( - "{}/{} hints remaining\n", self.hints_remaining, self.hints_total + "{}/{} hints remaining\n", + self.hints_remaining, self.hints_total ))?; f.write_str(&format!( - "{}/{} lives remaining\n", self.lives_remaining, self.lives_total + "{}/{} lives remaining\n", + self.lives_remaining, self.lives_total ))?; f.write_str("Fireworks:\n")?; for &color in COLORS.iter() { @@ -442,28 +447,30 @@ pub trait GameView { } fn has_card(&self, player: &Player, card: &Card) -> bool { - self.get_hand(player).iter().position(|other_card| { - card == other_card - }).is_some() + self.get_hand(player) + .iter() + .position(|other_card| card == other_card) + .is_some() } fn get_other_players(&self) -> Vec { - self.get_board().get_players().filter(|&player| { - player != self.me() - }).collect() + self.get_board() + .get_players() + .filter(|&player| player != self.me()) + .collect() } fn can_see(&self, card: &Card) -> bool { - self.get_other_players().iter().any(|player| { - self.has_card(player, card) - }) + self.get_other_players() + .iter() + .any(|player| self.has_card(player, card)) } fn someone_else_can_play(&self) -> bool { self.get_other_players().iter().any(|player| { - self.get_hand(player).iter().any(|card| { - self.get_board().is_playable(card) - }) + self.get_hand(player) + .iter() + .any(|card| self.get_board().is_playable(card)) }) } } @@ -479,7 +486,7 @@ pub struct BorrowedGameView<'a> { // board state pub board: &'a BoardState, } -impl <'a> GameView for BorrowedGameView<'a> { +impl<'a> GameView for BorrowedGameView<'a> { fn me(&self) -> Player { self.player } @@ -508,10 +515,11 @@ pub struct OwnedGameView { } impl OwnedGameView { pub fn clone_from(borrowed_view: &BorrowedGameView) -> OwnedGameView { - let other_hands = borrowed_view.other_hands.iter() - .map(|(&other_player, &player_state)| { - (other_player, player_state.clone()) - }).collect::>(); + let other_hands = borrowed_view + .other_hands + .iter() + .map(|(&other_player, &player_state)| (other_player, player_state.clone())) + .collect::>(); OwnedGameView { player: borrowed_view.player, @@ -570,21 +578,20 @@ impl GameState { pub fn new(opts: &GameOptions, mut deck: Cards) -> GameState { let mut board = BoardState::new(opts, deck.len() as u32); - let hands = - (0..opts.num_players).map(|player| { - let hand = (0..opts.hand_size).map(|_| { - // we can assume the deck is big enough to draw initial hands - board.deck_size -= 1; - deck.pop().unwrap() - }).collect::>(); + let hands = (0..opts.num_players) + .map(|player| { + let hand = (0..opts.hand_size) + .map(|_| { + // we can assume the deck is big enough to draw initial hands + board.deck_size -= 1; + deck.pop().unwrap() + }) + .collect::>(); (player, hand) - }).collect::>(); + }) + .collect::>(); - GameState { - hands, - board, - deck, - } + GameState { hands, board, deck } } pub fn get_players(&self) -> Range { @@ -636,26 +643,35 @@ impl GameState { let turn_result = { match choice { TurnChoice::Hint(ref hint) => { - assert!(self.board.hints_remaining > 0, - "Tried to hint with no hints remaining"); + assert!( + self.board.hints_remaining > 0, + "Tried to hint with no hints remaining" + ); self.board.hints_remaining -= 1; debug!("Hint to player {}, about {}", hint.player, hint.hinted); - assert!(self.board.player != hint.player, - "Player {} gave a hint to himself", hint.player); + assert!( + self.board.player != hint.player, + "Player {} gave a hint to himself", + hint.player + ); let hand = self.hands.get(&hint.player).unwrap(); let results = match hint.hinted { - Hinted::Color(color) => { - hand.iter().map(|card| { card.color == color }).collect::>() - } - Hinted::Value(value) => { - hand.iter().map(|card| { card.value == value }).collect::>() - } + Hinted::Color(color) => hand + .iter() + .map(|card| card.color == color) + .collect::>(), + Hinted::Value(value) => hand + .iter() + .map(|card| card.value == value) + .collect::>(), }; if !self.board.allow_empty_hints { - assert!(results.iter().any(|matched| *matched), - "Tried hinting an empty hint"); + assert!( + results.iter().any(|matched| *matched), + "Tried hinting an empty hint" + ); } TurnResult::Hint(results) @@ -671,10 +687,7 @@ impl GameState { TurnChoice::Play(index) => { let card = self.take_from_hand(index); - debug!( - "Playing card at position {}, which is {}", - index, card - ); + debug!("Playing card at position {}, which is {}", index, card); let playable = self.board.is_playable(&card); if playable { { @@ -715,7 +728,10 @@ impl GameState { let cur = self.board.player; self.board.player_to_left(&cur) }; - assert_eq!((self.board.turn - 1) % self.board.num_players, self.board.player); + assert_eq!( + (self.board.turn - 1) % self.board.num_players, + self.board.player + ); turn_record } diff --git a/src/helpers.rs b/src/helpers.rs index 1306e78..800756e 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -1,9 +1,9 @@ use std::cmp::Eq; use std::collections::{HashMap, HashSet}; -use std::fmt; -use std::ops::{Index,IndexMut}; -use std::hash::Hash; use std::convert::From; +use std::fmt; +use std::hash::Hash; +use std::ops::{Index, IndexMut}; use std::slice; use game::*; @@ -37,21 +37,25 @@ pub trait CardInfo { } fn get_weighted_possibilities(&self) -> Vec<(Card, f32)> { - self.get_possibilities().into_iter() + self.get_possibilities() + .into_iter() .map(|card| { let weight = self.get_weight(&card); (card, weight) - }).collect::>() + }) + .collect::>() } fn total_weight(&self) -> f32 { - self.get_possibilities().iter() + self.get_possibilities() + .iter() .map(|card| self.get_weight(card)) - .fold(0.0, |a, b| a+b) + .fold(0.0, |a, b| a + b) } fn weighted_score(&self, score_fn: &dyn Fn(&Card) -> T) -> f32 - where f32: From + where + f32: From, { let mut total_score = 0.; let mut total_weight = 0.; @@ -65,12 +69,16 @@ pub trait CardInfo { } fn average_value(&self) -> f32 { - self.weighted_score(&|card| card.value as f32 ) + self.weighted_score(&|card| card.value as f32) } fn probability_of_predicate(&self, predicate: &dyn Fn(&Card) -> bool) -> f32 { let f = |card: &Card| { - if predicate(card) { 1.0 } else { 0.0 } + if predicate(card) { + 1.0 + } else { + 0.0 + } }; self.weighted_score(&f) } @@ -124,9 +132,11 @@ 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, +{ // get all a-priori possibilities fn get_all_possibilities() -> Vec; @@ -137,7 +147,10 @@ pub trait Info where T: Hash + Eq + Clone { // get what is now possible fn get_possibilities(&self) -> Vec { - self.get_possibility_set().iter().cloned().collect::>() + self.get_possibility_set() + .iter() + .cloned() + .collect::>() } fn is_possible(&self, value: T) -> bool { @@ -145,8 +158,10 @@ pub trait Info where T: Hash + Eq + Clone { } fn initialize() -> HashSet { - Self::get_all_possibilities().iter() - .cloned().collect::>() + Self::get_all_possibilities() + .iter() + .cloned() + .collect::>() } fn mark_true(&mut self, value: T) { @@ -160,35 +175,55 @@ pub trait Info where T: Hash + Eq + Clone { } fn mark(&mut self, value: T, info: bool) { - if info { self.mark_true(value); } else { self.mark_false(value); } + if info { + self.mark_true(value); + } else { + self.mark_false(value); + } } } -#[derive(Debug,Clone)] +#[derive(Debug, Clone)] pub struct ColorInfo(HashSet); impl ColorInfo { - pub fn new() -> ColorInfo { ColorInfo(ColorInfo::initialize()) } + pub fn new() -> ColorInfo { + ColorInfo(ColorInfo::initialize()) + } } impl Info for ColorInfo { - fn get_all_possibilities() -> Vec { COLORS.to_vec() } - fn get_possibility_set(&self) -> &HashSet { &self.0 } - fn get_mut_possibility_set(&mut self) -> &mut HashSet { &mut self.0 } + fn get_all_possibilities() -> Vec { + COLORS.to_vec() + } + fn get_possibility_set(&self) -> &HashSet { + &self.0 + } + fn get_mut_possibility_set(&mut self) -> &mut HashSet { + &mut self.0 + } } -#[derive(Debug,Clone)] +#[derive(Debug, Clone)] pub struct ValueInfo(HashSet); impl ValueInfo { - pub fn new() -> ValueInfo { ValueInfo(ValueInfo::initialize()) } + pub fn new() -> ValueInfo { + ValueInfo(ValueInfo::initialize()) + } } impl Info for ValueInfo { - fn get_all_possibilities() -> Vec { VALUES.to_vec() } - fn get_possibility_set(&self) -> &HashSet { &self.0 } - fn get_mut_possibility_set(&mut self) -> &mut HashSet { &mut self.0 } + fn get_all_possibilities() -> Vec { + VALUES.to_vec() + } + fn get_possibility_set(&self) -> &HashSet { + &self.0 + } + fn get_mut_possibility_set(&mut self) -> &mut HashSet { + &mut self.0 + } } // represents information only of the form: // this color is/isn't possible, this value is/isn't possible -#[derive(Debug,Clone)] +#[derive(Debug, Clone)] pub struct SimpleCardInfo { pub color_info: ColorInfo, pub value_info: ValueInfo, @@ -211,13 +246,10 @@ 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) { self.color_info.mark_false(color); - } fn mark_value_false(&mut self, value: Value) { self.value_info.mark_false(value); @@ -246,7 +278,7 @@ impl fmt::Display for SimpleCardInfo { // Can represent information of the form: // this card is/isn't possible // also, maintains integer weights for the cards -#[derive(Clone,Debug,Eq,PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct CardPossibilityTable { possible: HashMap, } @@ -269,9 +301,10 @@ impl CardPossibilityTable { pub fn decrement_weight(&mut self, card: &Card) { let remove = { - let weight = - self.possible.get_mut(card) - .expect(&format!("Decrementing weight for impossible card: {}", card)); + let weight = self.possible.get_mut(card).expect(&format!( + "Decrementing weight for impossible card: {}", + card + )); *weight -= 1; *weight == 0 }; @@ -295,27 +328,35 @@ impl CardPossibilityTable { pub fn color_determined(&self) -> bool { self.get_possibilities() - .iter().map(|card| card.color) + .iter() + .map(|card| card.color) .collect::>() - .len() == 1 + .len() + == 1 } pub fn value_determined(&self) -> bool { self.get_possibilities() - .iter().map(|card| card.value) + .iter() + .map(|card| card.value) .collect::>() - .len() == 1 + .len() + == 1 } pub fn can_be_color(&self, color: Color) -> bool { - self.get_possibilities().into_iter().any(|card| card.color == color) + self.get_possibilities() + .into_iter() + .any(|card| card.color == color) } pub fn can_be_value(&self, value: Value) -> bool { - self.get_possibilities().into_iter().any(|card| card.value == value) + self.get_possibilities() + .into_iter() + .any(|card| card.value == value) } } -impl <'a> From<&'a CardCounts> for CardPossibilityTable { +impl<'a> From<&'a CardCounts> for CardPossibilityTable { fn from(counts: &'a CardCounts) -> CardPossibilityTable { let mut possible = HashMap::new(); for &color in COLORS.iter() { @@ -327,9 +368,7 @@ impl <'a> From<&'a CardCounts> for CardPossibilityTable { } } } - CardPossibilityTable { - possible, - } + CardPossibilityTable { possible } } } impl CardInfo for CardPossibilityTable { @@ -349,7 +388,6 @@ impl CardInfo for CardPossibilityTable { for &value in VALUES.iter() { self.mark_false(&Card::new(color, value)); } - } fn mark_value_false(&mut self, value: Value) { for &color in COLORS.iter() { @@ -369,16 +407,20 @@ impl fmt::Display for CardPossibilityTable { } } -#[derive(Clone,Eq,PartialEq)] -pub struct HandInfo where T: CardInfo { - pub hand_info: Vec +#[derive(Clone, Eq, PartialEq)] +pub struct HandInfo +where + T: CardInfo, +{ + pub hand_info: Vec, } -impl HandInfo where T: CardInfo { +impl HandInfo +where + T: CardInfo, +{ pub fn new(hand_size: u32) -> Self { let hand_info = (0..hand_size).map(|_| T::new()).collect::>(); - HandInfo { - hand_info, - } + HandInfo { hand_info } } // update for hint to me @@ -397,19 +439,35 @@ impl HandInfo where T: CardInfo { } } - pub fn remove(&mut self, index: usize) -> T { self.hand_info.remove(index) } - pub fn push(&mut self, card_info: T) { self.hand_info.push(card_info) } - pub fn iter_mut(&mut self) -> slice::IterMut { self.hand_info.iter_mut() } - pub fn iter(&self) -> slice::Iter { self.hand_info.iter() } - pub fn len(&self) -> usize { self.hand_info.len() } + pub fn remove(&mut self, index: usize) -> T { + self.hand_info.remove(index) + } + pub fn push(&mut self, card_info: T) { + self.hand_info.push(card_info) + } + pub fn iter_mut(&mut self) -> slice::IterMut { + self.hand_info.iter_mut() + } + pub fn iter(&self) -> slice::Iter { + self.hand_info.iter() + } + pub fn len(&self) -> usize { + self.hand_info.len() + } } -impl Index for HandInfo where T: CardInfo { +impl Index for HandInfo +where + T: CardInfo, +{ type Output = T; fn index(&self, index: usize) -> &T { &self.hand_info[index] } } -impl IndexMut for HandInfo where T: CardInfo { +impl IndexMut for HandInfo +where + T: CardInfo, +{ fn index_mut(&mut self, index: usize) -> &mut T { &mut self.hand_info[index] } diff --git a/src/main.rs b/src/main.rs index aa74cdb..69e03ed 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,18 +1,18 @@ extern crate getopts; #[macro_use] extern crate log; -extern crate rand; extern crate crossbeam; -extern crate fnv; extern crate float_ord; +extern crate fnv; +extern crate rand; -mod helpers; mod game; +mod helpers; mod simulator; mod strategy; mod strategies { - pub mod examples; pub mod cheating; + pub mod examples; mod hat_helpers; pub mod information; } @@ -33,46 +33,60 @@ impl log::Log for SimpleLogger { } } - fn print_usage(program: &str, opts: Options) { print!("{}", opts.usage(&format!("Usage: {} [options]", program))); } - fn main() { let args: Vec = std::env::args().collect(); let program = args[0].clone(); let mut opts = Options::new(); - opts.optopt("l", "loglevel", - "Log level, one of 'trace', 'debug', 'info', 'warn', and 'error'", - "LOGLEVEL"); - opts.optopt("n", "ntrials", - "Number of games to simulate (default 1)", - "NTRIALS"); - opts.optopt("o", "output", - "Number of games after which to print an update", - "OUTPUT_FREQ"); - opts.optopt("t", "nthreads", - "Number of threads to use for simulation (default 1)", - "NTHREADS"); - opts.optopt("s", "seed", - "Seed for PRNG (default random)", - "SEED"); - opts.optopt("p", "nplayers", - "Number of players", - "NPLAYERS"); - opts.optopt("g", "strategy", - "Which strategy to use. One of 'random', 'cheat', and 'info'", - "STRATEGY"); - opts.optflag("h", "help", - "Print this help menu"); - opts.optflag("", "results-table", - "Print a table of results for each strategy"); - opts.optflag("", "write-results-table", - "Update the results table in README.md"); + opts.optopt( + "l", + "loglevel", + "Log level, one of 'trace', 'debug', 'info', 'warn', and 'error'", + "LOGLEVEL", + ); + opts.optopt( + "n", + "ntrials", + "Number of games to simulate (default 1)", + "NTRIALS", + ); + opts.optopt( + "o", + "output", + "Number of games after which to print an update", + "OUTPUT_FREQ", + ); + opts.optopt( + "t", + "nthreads", + "Number of threads to use for simulation (default 1)", + "NTHREADS", + ); + opts.optopt("s", "seed", "Seed for PRNG (default random)", "SEED"); + opts.optopt("p", "nplayers", "Number of players", "NPLAYERS"); + opts.optopt( + "g", + "strategy", + "Which strategy to use. One of 'random', 'cheat', and 'info'", + "STRATEGY", + ); + opts.optflag("h", "help", "Print this help menu"); + opts.optflag( + "", + "results-table", + "Print a table of results for each strategy", + ); + opts.optflag( + "", + "write-results-table", + "Update the results table in README.md", + ); let matches = match opts.parse(&args[1..]) { - Ok(m) => { m } + Ok(m) => m, Err(f) => { print_usage(&program, opts); panic!("{}", f) @@ -91,14 +105,14 @@ fn main() { return print!("{}", get_results_table()); } - let log_level_str : &str = &matches.opt_str("l").unwrap_or("info".to_string()); + let log_level_str: &str = &matches.opt_str("l").unwrap_or("info".to_string()); let log_level = match log_level_str { - "trace" => { log::LogLevelFilter::Trace } - "debug" => { log::LogLevelFilter::Debug } - "info" => { log::LogLevelFilter::Info } - "warn" => { log::LogLevelFilter::Warn } - "error" => { log::LogLevelFilter::Error } - _ => { + "trace" => log::LogLevelFilter::Trace, + "debug" => log::LogLevelFilter::Debug, + "info" => log::LogLevelFilter::Info, + "warn" => log::LogLevelFilter::Warn, + "error" => log::LogLevelFilter::Error, + _ => { print_usage(&program, opts); panic!("Unexpected log level argument {}", log_level_str); } @@ -107,26 +121,47 @@ fn main() { log::set_logger(|max_log_level| { max_log_level.set(log_level); Box::new(SimpleLogger) - }).unwrap(); + }) + .unwrap(); let n_trials = u32::from_str(&matches.opt_str("n").unwrap_or("1".to_string())).unwrap(); - let seed = matches.opt_str("s").map(|seed_str| { u32::from_str(&seed_str).unwrap() }); - let progress_info = matches.opt_str("o").map(|freq_str| { u32::from_str(&freq_str).unwrap() }); + let seed = matches + .opt_str("s") + .map(|seed_str| u32::from_str(&seed_str).unwrap()); + let progress_info = matches + .opt_str("o") + .map(|freq_str| u32::from_str(&freq_str).unwrap()); let n_threads = u32::from_str(&matches.opt_str("t").unwrap_or("1".to_string())).unwrap(); let n_players = u32::from_str(&matches.opt_str("p").unwrap_or("4".to_string())).unwrap(); - let strategy_str : &str = &matches.opt_str("g").unwrap_or("cheat".to_string()); + let strategy_str: &str = &matches.opt_str("g").unwrap_or("cheat".to_string()); - sim_games(n_players, strategy_str, seed, n_trials, n_threads, progress_info).info(); + sim_games( + n_players, + strategy_str, + seed, + n_trials, + n_threads, + progress_info, + ) + .info(); } -fn sim_games(n_players: u32, strategy_str: &str, seed: Option, n_trials: u32, n_threads: u32, progress_info: Option) - -> simulator::SimResult { +fn sim_games( + n_players: u32, + strategy_str: &str, + seed: Option, + n_trials: u32, + n_threads: u32, + progress_info: Option, +) -> simulator::SimResult { let hand_size = match n_players { 2 => 5, 3 => 5, 4 => 4, 5 => 4, - _ => { panic!("There should be 2 to 5 players, not {}", n_players); } + _ => { + panic!("There should be 2 to 5 players, not {}", n_players); + } }; let game_opts = game::GameOptions { @@ -138,26 +173,27 @@ fn sim_games(n_players: u32, strategy_str: &str, seed: Option, n_trials: u3 allow_empty_hints: false, }; - let strategy_config : Box = match strategy_str { - "random" => { - Box::new(strategies::examples::RandomStrategyConfig { - hint_probability: 0.4, - play_probability: 0.2, - }) as Box - }, - "cheat" => { - Box::new(strategies::cheating::CheatingStrategyConfig::new()) - as Box - }, - "info" => { - Box::new(strategies::information::InformationStrategyConfig::new()) - as Box - }, + let strategy_config: Box = match strategy_str { + "random" => Box::new(strategies::examples::RandomStrategyConfig { + hint_probability: 0.4, + play_probability: 0.2, + }) as Box, + "cheat" => Box::new(strategies::cheating::CheatingStrategyConfig::new()) + as Box, + "info" => Box::new(strategies::information::InformationStrategyConfig::new()) + as Box, _ => { panic!("Unexpected strategy argument {}", strategy_str); - }, + } }; - simulator::simulate(&game_opts, strategy_config, seed, n_trials, n_threads, progress_info) + simulator::simulate( + &game_opts, + strategy_config, + seed, + n_trials, + n_threads, + progress_info, + ) } fn get_results_table() -> String { @@ -167,39 +203,65 @@ fn get_results_table() -> String { let n_trials = 20000; let n_threads = 8; - let intro = format!("On the first {} seeds, we have these scores and win rates (average ± standard error):\n\n", n_trials); - let format_name = |x| format!(" {:7} ", x); - let format_players = |x| format!(" {}p ", x); + let intro = format!( + "On the first {} seeds, we have these scores and win rates (average ± standard error):\n\n", + n_trials + ); + let format_name = |x| format!(" {:7} ", x); + let format_players = |x| format!(" {}p ", x); let format_percent = |x, stderr| format!(" {:05.2} ± {:.2} % ", x, stderr); - let format_score = |x, stderr| format!(" {:07.4} ± {:.4} ", x, stderr); - let space = String::from(" "); - let dashes = String::from("---------"); - let dashes_long = String::from("------------------"); + let format_score = |x, stderr| format!(" {:07.4} ± {:.4} ", x, stderr); + let space = String::from(" "); + let dashes = String::from("---------"); + let dashes_long = String::from("------------------"); type TwoLines = (String, String); - fn make_twolines(player_nums: &[u32], head: TwoLines, make_block: &dyn Fn(u32) -> TwoLines) -> TwoLines { - let mut blocks = player_nums.iter().cloned().map(make_block).collect::>(); + fn make_twolines( + player_nums: &[u32], + head: TwoLines, + make_block: &dyn Fn(u32) -> TwoLines, + ) -> TwoLines { + let mut blocks = player_nums + .iter() + .cloned() + .map(make_block) + .collect::>(); blocks.insert(0, head); fn combine(items: Vec) -> String { - items.iter().fold(String::from("|"), |init, next| { init + next + "|" }) + items + .iter() + .fold(String::from("|"), |init, next| init + next + "|") } let (a, b): (Vec<_>, Vec<_>) = blocks.into_iter().unzip(); (combine(a), combine(b)) } fn concat_twolines(body: Vec) -> String { - body.into_iter().fold(String::default(), |output, (a, b)| (output + &a + "\n" + &b + "\n")) + body.into_iter().fold(String::default(), |output, (a, b)| { + (output + &a + "\n" + &b + "\n") + }) } - let header = make_twolines(&player_nums, - (space.clone(), dashes), - &|n_players| (format_players(n_players), dashes_long.clone())); - let mut body = strategies.iter().map(|strategy| { - make_twolines(&player_nums, (format_name(strategy), space.clone()), &|n_players| { - let simresult = sim_games(n_players, strategy, Some(seed), n_trials, n_threads, None); - ( - format_score(simresult.average_score(), simresult.score_stderr()), - format_percent(simresult.percent_perfect(), simresult.percent_perfect_stderr()) + let header = make_twolines(&player_nums, (space.clone(), dashes), &|n_players| { + (format_players(n_players), dashes_long.clone()) + }); + let mut body = strategies + .iter() + .map(|strategy| { + make_twolines( + &player_nums, + (format_name(strategy), space.clone()), + &|n_players| { + let simresult = + sim_games(n_players, strategy, Some(seed), n_trials, n_threads, None); + ( + format_score(simresult.average_score(), simresult.score_stderr()), + format_percent( + simresult.percent_perfect(), + simresult.percent_perfect_stderr(), + ), + ) + }, ) }) - }).collect::>(); + .collect::>(); body.insert(0, header); intro + &concat_twolines(body) } diff --git a/src/simulator.rs b/src/simulator.rs index 0c51065..fdcd105 100644 --- a/src/simulator.rs +++ b/src/simulator.rs @@ -1,7 +1,7 @@ -use rand::{self, Rng, SeedableRng}; -use fnv::FnvHashMap; -use std::fmt; use crossbeam; +use fnv::FnvHashMap; +use rand::{self, Rng, SeedableRng}; +use std::fmt; use game::*; use strategy::*; @@ -15,7 +15,7 @@ fn new_deck(seed: u32) -> Cards { deck.push(Card::new(color, value)); } } - }; + } rand::ChaChaRng::from_seed(&[seed]).shuffle(&mut deck[..]); debug!("Deck: {:?}", deck); @@ -23,17 +23,23 @@ fn new_deck(seed: u32) -> Cards { } pub fn simulate_once( - opts: &GameOptions, - game_strategy: Box, - seed: u32, - ) -> GameState { + opts: &GameOptions, + game_strategy: Box, + seed: u32, +) -> GameState { let deck = new_deck(seed); let mut game = GameState::new(opts, deck); - let mut strategies = game.get_players().map(|player| { - (player, game_strategy.initialize(player, &game.get_view(player))) - }).collect::>>(); + let mut strategies = game + .get_players() + .map(|player| { + ( + player, + game_strategy.initialize(player, &game.get_view(player)), + ) + }) + .collect::>>(); while !game.is_over() { let player = game.board.player; @@ -44,7 +50,6 @@ pub fn simulate_once( debug!("======================================================="); debug!("{}", game); - let choice = { let strategy = strategies.get_mut(&player).unwrap(); strategy.decide(&game.get_view(player)) @@ -56,7 +61,6 @@ pub fn simulate_once( let strategy = strategies.get_mut(&player).unwrap(); strategy.update(&turn, &game.get_view(player)); } - } debug!(""); debug!("======================================================="); @@ -119,24 +123,23 @@ impl fmt::Display for Histogram { let mut keys = self.hist.keys().collect::>(); keys.sort(); for val in keys { - f.write_str(&format!( - "\n{}: {}", val, self.get_count(val), - ))?; + f.write_str(&format!("\n{}: {}", val, self.get_count(val),))?; } Ok(()) } } pub fn simulate( - opts: &GameOptions, - strat_config: Box, - first_seed_opt: Option, - n_trials: u32, - n_threads: u32, - progress_info: Option, - ) -> SimResult - where T: GameStrategyConfig + Sync { - + opts: &GameOptions, + strat_config: Box, + first_seed_opt: Option, + n_trials: u32, + n_threads: u32, + progress_info: Option, +) -> SimResult +where + T: GameStrategyConfig + Sync, +{ let first_seed = first_seed_opt.unwrap_or_else(|| rand::thread_rng().next_u32()); let strat_config_ref = &strat_config; @@ -144,7 +147,7 @@ pub fn simulate( let mut join_handles = Vec::new(); for i in 0..n_threads { let start = first_seed + ((n_trials * i) / n_threads); - let end = first_seed + ((n_trials * (i+1)) / n_threads); + let end = first_seed + ((n_trials * (i + 1)) / n_threads); join_handles.push(scope.spawn(move || { if progress_info.is_some() { info!("Thread {} spawned: seeds {} to {}", i, start, end); @@ -156,10 +159,13 @@ pub fn simulate( for seed in start..end { if let Some(progress_info_frequency) = progress_info { - if (seed > start) && ((seed-start) % progress_info_frequency == 0) { + if (seed > start) && ((seed - start) % progress_info_frequency == 0) { info!( "Thread {}, Trials: {}, Stats so far: {} score, {} lives, {}% win", - i, seed-start, score_histogram.average(), lives_histogram.average(), + i, + seed - start, + score_histogram.average(), + lives_histogram.average(), score_histogram.percentage_with(&PERFECT_SCORE) * 100.0 ); } @@ -168,7 +174,9 @@ pub fn simulate( let score = game.score(); lives_histogram.insert(game.board.lives_remaining); score_histogram.insert(score); - if score != PERFECT_SCORE { non_perfect_seeds.push(seed); } + if score != PERFECT_SCORE { + non_perfect_seeds.push(seed); + } } if progress_info.is_some() { info!("Thread {} done", i); @@ -177,11 +185,12 @@ pub fn simulate( })); } - let mut non_perfect_seeds : Vec = Vec::new(); + let mut non_perfect_seeds: Vec = Vec::new(); let mut score_histogram = Histogram::new(); let mut lives_histogram = Histogram::new(); for join_handle in join_handles { - let (thread_non_perfect_seeds, thread_score_histogram, thread_lives_histogram) = join_handle.join(); + let (thread_non_perfect_seeds, thread_score_histogram, thread_lives_histogram) = + join_handle.join(); non_perfect_seeds.extend(thread_non_perfect_seeds.iter()); score_histogram.merge(thread_score_histogram); lives_histogram.merge(thread_lives_histogram); @@ -209,7 +218,7 @@ impl SimResult { pub fn percent_perfect_stderr(&self) -> f32 { let pp = self.percent_perfect() / 100.0; - let stdev = (pp*(1.0 - pp) / ((self.scores.total_count - 1) as f32)).sqrt(); + let stdev = (pp * (1.0 - pp) / ((self.scores.total_count - 1) as f32)).sqrt(); stdev * 100.0 } diff --git a/src/strategies/cheating.rs b/src/strategies/cheating.rs index 9ca8c8b..00a4da2 100644 --- a/src/strategies/cheating.rs +++ b/src/strategies/cheating.rs @@ -1,9 +1,9 @@ -use std::rc::Rc; -use std::cell::{RefCell}; use fnv::{FnvHashMap, FnvHashSet}; +use std::cell::RefCell; +use std::rc::Rc; -use strategy::*; use game::*; +use strategy::*; // strategy that explicitly cheats by using Rc/RefCell // serves as a reference point for other strategies @@ -44,9 +44,9 @@ impl CheatingStrategy { impl GameStrategy for CheatingStrategy { fn initialize(&self, player: Player, view: &BorrowedGameView) -> Box { for (&player, &hand) in &view.other_hands { - self.player_hands_cheat.borrow_mut().insert( - player, hand.clone() - ); + self.player_hands_cheat + .borrow_mut() + .insert(player, hand.clone()); } Box::new(CheatingPlayerStrategy { player_hands_cheat: self.player_hands_cheat.clone(), @@ -64,9 +64,9 @@ impl CheatingPlayerStrategy { fn inform_last_player_cards(&self, view: &BorrowedGameView) { let next = view.board.player_to_right(&self.me); let their_hand = *view.other_hands.get(&next).unwrap(); - self.player_hands_cheat.borrow_mut().insert( - next, their_hand.clone() - ); + self.player_hands_cheat + .borrow_mut() + .insert(next, their_hand.clone()); } // give a throwaway hint - we only do this when we have nothing to do @@ -75,7 +75,7 @@ impl CheatingPlayerStrategy { let hint_card = &view.get_hand(&hint_player).first().unwrap(); TurnChoice::Hint(Hint { player: hint_player, - hinted: Hinted::Value(hint_card.value) + hinted: Hinted::Value(hint_card.value), }) } @@ -93,12 +93,14 @@ impl CheatingPlayerStrategy { // given a hand of cards, represents how badly it will need to play things fn hand_play_value(&self, view: &BorrowedGameView, hand: &Cards) -> u32 { - hand.iter().map(|card| self.card_play_value(view, card)).sum() + hand.iter() + .map(|card| self.card_play_value(view, card)) + .sum() } // how badly do we need to play a particular card fn get_play_score(&self, view: &BorrowedGameView, card: &Card) -> i32 { - let hands = self.player_hands_cheat.borrow(); + let hands = self.player_hands_cheat.borrow(); let my_hand = hands.get(&self.me).unwrap(); let my_hand_value = self.hand_play_value(view, my_hand); @@ -108,7 +110,7 @@ impl CheatingPlayerStrategy { let their_hand_value = self.hand_play_value(view, hands.get(&player).unwrap()); // they can play this card, and have less urgent plays than i do if their_hand_value < my_hand_value { - return 10 - (card.value as i32) + return 10 - (card.value as i32); } } } @@ -139,9 +141,11 @@ impl PlayerStrategy for CheatingPlayerStrategy { let hands = self.player_hands_cheat.borrow(); let my_hand = hands.get(&self.me).unwrap(); - let playable_cards = my_hand.iter().enumerate().filter(|&(_, card)| { - view.board.is_playable(card) - }).collect::>(); + let playable_cards = my_hand + .iter() + .enumerate() + .filter(|&(_, card)| view.board.is_playable(card)) + .collect::>(); if !playable_cards.is_empty() { // play the best playable card @@ -156,15 +160,14 @@ impl PlayerStrategy for CheatingPlayerStrategy { play_score = score; } } - return TurnChoice::Play(index) + return TurnChoice::Play(index); } // discard threshold is how many cards we're willing to discard // such that if we only played, // we would not reach the final countdown round // e.g. 50 total, 25 to play, 20 in hand - let discard_threshold = - view.board.total_cards + let discard_threshold = view.board.total_cards - (COLORS.len() * VALUES.len()) as u32 - (view.board.num_players * view.board.hand_size); if view.board.discard_size() <= discard_threshold { @@ -204,6 +207,5 @@ impl PlayerStrategy for CheatingPlayerStrategy { } TurnChoice::Discard(index) } - fn update(&mut self, _: &TurnRecord, _: &BorrowedGameView) { - } + fn update(&mut self, _: &TurnRecord, _: &BorrowedGameView) {} } diff --git a/src/strategies/examples.rs b/src/strategies/examples.rs index a524b75..5b5f15f 100644 --- a/src/strategies/examples.rs +++ b/src/strategies/examples.rs @@ -1,6 +1,6 @@ -use strategy::*; use game::*; use rand::{self, Rng}; +use strategy::*; // dummy, terrible strategy, as an example #[derive(Clone)] @@ -44,7 +44,9 @@ impl PlayerStrategy for RandomStrategyPlayer { if p < self.hint_probability { if view.board.hints_remaining > 0 { let hint_player = view.board.player_to_left(&self.me); - let hint_card = rand::thread_rng().choose(view.get_hand(&hint_player)).unwrap(); + let hint_card = rand::thread_rng() + .choose(view.get_hand(&hint_player)) + .unwrap(); let hinted = { if rand::random() { // hint a color @@ -66,6 +68,5 @@ impl PlayerStrategy for RandomStrategyPlayer { TurnChoice::Discard(0) } } - fn update(&mut self, _: &TurnRecord, _: &BorrowedGameView) { - } + fn update(&mut self, _: &TurnRecord, _: &BorrowedGameView) {} } diff --git a/src/strategies/hat_helpers.rs b/src/strategies/hat_helpers.rs index 4f2c921..f4d94e0 100644 --- a/src/strategies/hat_helpers.rs +++ b/src/strategies/hat_helpers.rs @@ -1,7 +1,7 @@ use game::*; use helpers::*; -#[derive(Debug,Clone)] +#[derive(Debug, Clone)] pub struct ModulusInformation { pub modulus: u32, pub value: u32, @@ -9,10 +9,7 @@ pub struct ModulusInformation { impl ModulusInformation { pub fn new(modulus: u32, value: u32) -> Self { assert!(value < modulus); - ModulusInformation { - modulus, - value, - } + ModulusInformation { modulus, value } } pub fn none() -> Self { @@ -83,21 +80,21 @@ pub trait Question { fn answer(&self, hand: &Cards, board: &BoardState) -> u32; // process the answer to this question, updating card info fn acknowledge_answer( - &self, value: u32, hand_info: &mut HandInfo, board: &BoardState + &self, + value: u32, + hand_info: &mut HandInfo, + board: &BoardState, ); fn answer_info(&self, hand: &Cards, board: &BoardState) -> ModulusInformation { - ModulusInformation::new( - self.info_amount(), - self.answer(hand, board) - ) + ModulusInformation::new(self.info_amount(), self.answer(hand, board)) } fn acknowledge_answer_info( &self, answer: ModulusInformation, hand_info: &mut HandInfo, - board: &BoardState + board: &BoardState, ) { assert!(self.info_amount() == answer.modulus); self.acknowledge_answer(answer.value, hand_info, board); @@ -112,8 +109,7 @@ pub trait PublicInformation: Clone { fn set_board(&mut self, board: &BoardState); /// If we store more state than just `HandInfo`s, update it after `set_player_info` has been called. - fn update_other_info(&mut self) { - } + fn update_other_info(&mut self) {} fn agrees_with(&self, other: Self) -> bool; @@ -126,11 +122,19 @@ pub trait PublicInformation: Clone { /// /// Note that `self` does not reflect the answers to previous questions; it reflects the state /// before the entire "hat value" calculation. - fn ask_question(&self, player: &Player, hand_info: &HandInfo, total_info: u32) -> Option>; + fn ask_question( + &self, + player: &Player, + hand_info: &HandInfo, + total_info: u32, + ) -> Option>; - fn ask_question_wrapper(&self, player: &Player, hand_info: &HandInfo, total_info: u32) - -> Option> - { + fn ask_question_wrapper( + &self, + player: &Player, + hand_info: &HandInfo, + total_info: u32, + ) -> Option> { assert!(total_info > 0); if total_info == 1 { None @@ -138,8 +142,11 @@ pub trait PublicInformation: Clone { let result = self.ask_question(player, hand_info, total_info); if let Some(ref question) = result { if question.info_amount() > total_info { - panic!("ask_question returned question with info_amount = {} > total_info = {}!", - question.info_amount(), total_info); + panic!( + "ask_question returned question with info_amount = {} > total_info = {}!", + question.info_amount(), + total_info + ); } if question.info_amount() == 1 { panic!("ask_question returned a trivial question!"); @@ -157,11 +164,17 @@ pub trait PublicInformation: Clone { } fn get_hat_info_for_player( - &self, player: &Player, hand_info: &mut HandInfo, total_info: u32, view: &OwnedGameView + &self, + player: &Player, + hand_info: &mut HandInfo, + total_info: u32, + view: &OwnedGameView, ) -> ModulusInformation { assert!(player != &view.player); let mut answer_info = ModulusInformation::none(); - while let Some(question) = self.ask_question_wrapper(player, hand_info, answer_info.info_remaining(total_info)) { + while let Some(question) = + self.ask_question_wrapper(player, hand_info, answer_info.info_remaining(total_info)) + { let new_answer_info = question.answer_info(view.get_hand(player), view.get_board()); question.acknowledge_answer_info(new_answer_info.clone(), hand_info, view.get_board()); answer_info.combine(new_answer_info, total_info); @@ -188,18 +201,22 @@ pub trait PublicInformation: Clone { /// `self.get_hat_sum(total_info, view)` tells us which choice to take, and at the same time /// mutates `self` to simulate the choice becoming common knowledge. fn get_hat_sum(&mut self, total_info: u32, view: &OwnedGameView) -> ModulusInformation { - let (infos, new_player_hands): (Vec<_>, Vec<_>) = view.get_other_players().iter().map(|player| { - let mut hand_info = self.get_player_info(player); - let info = self.get_hat_info_for_player(player, &mut hand_info, total_info, view); - (info, (*player, hand_info)) - }).unzip(); + let (infos, new_player_hands): (Vec<_>, Vec<_>) = view + .get_other_players() + .iter() + .map(|player| { + let mut hand_info = self.get_player_info(player); + let info = self.get_hat_info_for_player(player, &mut hand_info, total_info, view); + (info, (*player, hand_info)) + }) + .unzip(); self.set_player_infos(new_player_hands); infos.into_iter().fold( ModulusInformation::new(total_info, 0), |mut sum_info, info| { sum_info.add(&info); sum_info - } + }, ) } @@ -208,13 +225,17 @@ pub trait PublicInformation: Clone { /// from that fact. fn update_from_hat_sum(&mut self, mut info: ModulusInformation, view: &OwnedGameView) { let info_source = view.board.player; - let (other_infos, mut new_player_hands): (Vec<_>, Vec<_>) = view.get_other_players().into_iter().filter(|player| { - *player != info_source - }).map(|player| { - let mut hand_info = self.get_player_info(&player); - let player_info = self.get_hat_info_for_player(&player, &mut hand_info, info.modulus, view); - (player_info, (player, hand_info)) - }).unzip(); + let (other_infos, mut new_player_hands): (Vec<_>, Vec<_>) = view + .get_other_players() + .into_iter() + .filter(|player| *player != info_source) + .map(|player| { + let mut hand_info = self.get_player_info(&player); + let player_info = + self.get_hat_info_for_player(&player, &mut hand_info, info.modulus, view); + (player_info, (player, hand_info)) + }) + .unzip(); for other_info in other_infos { info.subtract(&other_info); } diff --git a/src/strategies/information.rs b/src/strategies/information.rs index 53b47bf..baea99a 100644 --- a/src/strategies/information.rs +++ b/src/strategies/information.rs @@ -1,11 +1,11 @@ +use float_ord::*; use fnv::{FnvHashMap, FnvHashSet}; use std::cmp::Ordering; -use float_ord::*; -use strategy::*; use game::*; use helpers::*; use strategies::hat_helpers::*; +use strategy::*; // TODO: use random extra information - i.e. when casting up and down, // we sometimes have 2 choices of value to choose @@ -14,17 +14,21 @@ use strategies::hat_helpers::*; type PropertyPredicate = fn(&BoardState, &Card) -> bool; -struct CardHasProperty -{ +struct CardHasProperty { index: usize, property: PropertyPredicate, } -impl Question for CardHasProperty -{ - fn info_amount(&self) -> u32 { 2 } +impl Question for CardHasProperty { + fn info_amount(&self) -> u32 { + 2 + } fn answer(&self, hand: &Cards, board: &BoardState) -> u32 { let ref card = hand[self.index]; - if (self.property)(board, card) { 1 } else { 0 } + if (self.property)(board, card) { + 1 + } else { + 0 + } } fn acknowledge_answer( &self, @@ -36,18 +40,28 @@ impl Question for CardHasProperty let possible = card_table.get_possibilities(); for card in &possible { if (self.property)(board, card) { - if answer == 0 { card_table.mark_false(card); } + if answer == 0 { + card_table.mark_false(card); + } } else { - if answer == 1 { card_table.mark_false(card); } + if answer == 1 { + card_table.mark_false(card); + } } } } } fn q_is_playable(index: usize) -> CardHasProperty { - CardHasProperty {index, property: |board, card| board.is_playable(card)} + CardHasProperty { + index, + property: |board, card| board.is_playable(card), + } } fn q_is_dead(index: usize) -> CardHasProperty { - CardHasProperty {index, property: |board, card| board.is_dead(card)} + CardHasProperty { + index, + property: |board, card| board.is_dead(card), + } } /// For some list of questions l, the question `AdditiveComboQuestion { questions : l }` asks: @@ -62,7 +76,11 @@ struct AdditiveComboQuestion { } impl Question for AdditiveComboQuestion { fn info_amount(&self) -> u32 { - self.questions.iter().map(|q| { q.info_amount() - 1 }).sum::() + 1 + self.questions + .iter() + .map(|q| q.info_amount() - 1) + .sum::() + + 1 } fn answer(&self, hand: &Cards, board: &BoardState) -> u32 { let mut toadd = 1; @@ -88,7 +106,7 @@ impl Question for AdditiveComboQuestion { answer -= 1; for q in &self.questions { if answer < q.info_amount() - 1 { - q.acknowledge_answer(answer+1, hand_info, board); + q.acknowledge_answer(answer + 1, hand_info, board); return; } else { q.acknowledge_answer(0, hand_info, board); @@ -107,7 +125,10 @@ struct CardPossibilityPartition { } impl CardPossibilityPartition { fn new( - index: usize, max_n_partitions: u32, card_table: &CardPossibilityTable, board: &BoardState + index: usize, + max_n_partitions: u32, + card_table: &CardPossibilityTable, + board: &BoardState, ) -> CardPossibilityPartition { let mut cur_block = 0; let mut partition = FnvHashMap::default(); @@ -159,7 +180,9 @@ impl CardPossibilityPartition { } } impl Question for CardPossibilityPartition { - fn info_amount(&self) -> u32 { self.n_partitions } + fn info_amount(&self) -> u32 { + self.n_partitions + } fn answer(&self, hand: &Cards, _: &BoardState) -> u32 { let ref card = hand[self.index]; *self.partition.get(card).unwrap() @@ -180,11 +203,11 @@ impl Question for CardPossibilityPartition { } } -#[derive(Eq,PartialEq,Clone)] +#[derive(Eq, PartialEq, Clone)] struct MyPublicInformation { hand_info: FnvHashMap>, card_counts: CardCounts, // what any newly drawn card should be - board: BoardState, // TODO: maybe we should store an appropriately lifetimed reference? + board: BoardState, // TODO: maybe we should store an appropriately lifetimed reference? } impl MyPublicInformation { @@ -197,7 +220,10 @@ impl MyPublicInformation { fn get_other_players_starting_after(&self, player: Player) -> Vec { let n = self.board.num_players; - (0 .. n - 1).into_iter().map(|i| { (player + 1 + i) % n }).collect() + (0..n - 1) + .into_iter() + .map(|i| (player + 1 + i) % n) + .collect() } // Returns the number of ways to hint the player. @@ -208,19 +234,19 @@ impl MyPublicInformation { let ref info = self.hand_info[&player]; - let may_be_all_one_color = COLORS.iter().any(|color| { - info.iter().all(|card| { - card.can_be_color(*color) - }) - }); + let may_be_all_one_color = COLORS + .iter() + .any(|color| info.iter().all(|card| card.can_be_color(*color))); - let may_be_all_one_number = VALUES.iter().any(|value| { - info.iter().all(|card| { - card.can_be_value(*value) - }) - }); + let may_be_all_one_number = VALUES + .iter() + .any(|value| info.iter().all(|card| card.can_be_value(*value))); - if !may_be_all_one_color && !may_be_all_one_number { 4 } else { 3 } + if !may_be_all_one_color && !may_be_all_one_number { + 4 + } else { + 3 + } } fn get_hint_index_score(&self, card_table: &CardPossibilityTable) -> i32 { @@ -242,10 +268,14 @@ impl MyPublicInformation { } fn get_index_for_hint(&self, player: &Player) -> usize { - let mut scores = self.hand_info[player].iter().enumerate().map(|(i, card_table)| { - let score = self.get_hint_index_score(card_table); - (-score, i) - }).collect::>(); + let mut scores = self.hand_info[player] + .iter() + .enumerate() + .map(|(i, card_table)| { + let score = self.get_hint_index_score(card_table); + (-score, i) + }) + .collect::>(); scores.sort(); scores[0].1 } @@ -266,14 +296,18 @@ impl MyPublicInformation { // knowledge about the cards? let hinter = view.player; - let info_per_player: Vec<_> = self.get_other_players_starting_after(hinter).into_iter().map( - |player| { self.get_info_per_player(player) } - ).collect(); + let info_per_player: Vec<_> = self + .get_other_players_starting_after(hinter) + .into_iter() + .map(|player| self.get_info_per_player(player)) + .collect(); let total_info = info_per_player.iter().sum(); // FIXME explain and clean up - let card_indices: Vec<_> = self.get_other_players_starting_after(hinter).into_iter().map( - |player| { self.get_index_for_hint(&player) } - ).collect(); + let card_indices: Vec<_> = self + .get_other_players_starting_after(hinter) + .into_iter() + .map(|player| self.get_index_for_hint(&player)) + .collect(); let hint_info = self.get_hat_sum(total_info, view); @@ -352,53 +386,60 @@ impl MyPublicInformation { } } }; - hint_option_set.into_iter().collect::>().into_iter().map(|hinted| { - Hint { + hint_option_set + .into_iter() + .collect::>() + .into_iter() + .map(|hinted| Hint { player: hint_player, hinted, - } - }).collect() + }) + .collect() } fn decode_hint_choice(&self, hint: &Hint, result: &[bool]) -> ModulusInformation { let hinter = self.board.player; - let info_per_player: Vec<_> = self.get_other_players_starting_after(hinter).into_iter().map( - |player| { self.get_info_per_player(player) } - ).collect(); + let info_per_player: Vec<_> = self + .get_other_players_starting_after(hinter) + .into_iter() + .map(|player| self.get_info_per_player(player)) + .collect(); let total_info = info_per_player.iter().sum(); let n = self.board.num_players; let player_amt = (n + hint.player - hinter - 1) % n; - let amt_from_prev_players = info_per_player.iter().take(player_amt as usize).sum::(); + let amt_from_prev_players = info_per_player + .iter() + .take(player_amt as usize) + .sum::(); let hint_info_we_can_give_to_this_player = info_per_player[player_amt as usize]; let card_index = self.get_index_for_hint(&hint.player); - let hint_type = - if hint_info_we_can_give_to_this_player == 3 { - if result[card_index] { - match hint.hinted { - Hinted::Value(_) => 0, - Hinted::Color(_) => 1, - } - } else { - 2 + let hint_type = if hint_info_we_can_give_to_this_player == 3 { + if result[card_index] { + match hint.hinted { + Hinted::Value(_) => 0, + Hinted::Color(_) => 1, } } else { - if result[card_index] { - match hint.hinted { - Hinted::Value(_) => 0, - Hinted::Color(_) => 1, - } - } else { - match hint.hinted { - Hinted::Value(_) => 2, - Hinted::Color(_) => 3, - } + 2 + } + } else { + if result[card_index] { + match hint.hinted { + Hinted::Value(_) => 0, + Hinted::Color(_) => 1, } - }; + } else { + match hint.hinted { + Hinted::Value(_) => 2, + Hinted::Color(_) => 3, + } + } + }; let hint_value = amt_from_prev_players + hint_type; @@ -416,17 +457,18 @@ impl MyPublicInformation { } fn knows_playable_card(&self, player: &Player) -> bool { - self.hand_info[player].iter().any(|table| { - table.probability_is_playable(&self.board) == 1.0 - }) + self.hand_info[player] + .iter() + .any(|table| table.probability_is_playable(&self.board) == 1.0) } fn someone_else_needs_hint(&self, view: &OwnedGameView) -> bool { // Does another player have a playable card, but doesn't know it? view.get_other_players().iter().any(|player| { - let has_playable_card = view.get_hand(player).iter().any(|card| { - view.get_board().is_playable(card) - }); + let has_playable_card = view + .get_hand(player) + .iter() + .any(|card| view.get_board().is_playable(card)); has_playable_card && !self.knows_playable_card(player) }) } @@ -457,7 +499,7 @@ impl MyPublicInformation { new_view: &BorrowedGameView, player: &Player, index: usize, - card: &Card + card: &Card, ) { let new_card_table = CardPossibilityTable::from(&self.card_counts); { @@ -486,10 +528,13 @@ impl MyPublicInformation { impl PublicInformation for MyPublicInformation { fn new(board: &BoardState) -> Self { - let hand_info = board.get_players().map(|player| { - let hand_info = HandInfo::new(board.hand_size); - (player, hand_info) - }).collect::>(); + let hand_info = board + .get_players() + .map(|player| { + let hand_info = HandInfo::new(board.hand_size); + (player, hand_info) + }) + .collect::>(); MyPublicInformation { hand_info, card_counts: CardCounts::new(), @@ -522,32 +567,56 @@ impl PublicInformation for MyPublicInformation { // Changing anything inside this function will not break the information transfer // mechanisms! - let augmented_hand_info_raw = hand_info.iter().cloned().enumerate().filter_map(|(i, card_table)| { - let p_play = card_table.probability_is_playable(&self.board); - let p_dead = card_table.probability_is_dead(&self.board); - Some((i, p_play, p_dead)) - }).collect::>(); - let know_playable_card = augmented_hand_info_raw.iter().any(|&(_, p_play, _)| p_play == 1.0); - let know_dead_card = augmented_hand_info_raw.iter().any(|&(_, _, p_dead)| p_dead == 1.0); + let augmented_hand_info_raw = hand_info + .iter() + .cloned() + .enumerate() + .filter_map(|(i, card_table)| { + let p_play = card_table.probability_is_playable(&self.board); + let p_dead = card_table.probability_is_dead(&self.board); + Some((i, p_play, p_dead)) + }) + .collect::>(); + let know_playable_card = augmented_hand_info_raw + .iter() + .any(|&(_, p_play, _)| p_play == 1.0); + let know_dead_card = augmented_hand_info_raw + .iter() + .any(|&(_, _, p_dead)| p_dead == 1.0); // We don't need to find out anything about cards that are determined or dead. - let augmented_hand_info = augmented_hand_info_raw.into_iter().filter(|&(i, _, p_dead)| { - if p_dead == 1.0 || hand_info[i].is_determined() { false } - else { true } - }).collect::>(); + let augmented_hand_info = augmented_hand_info_raw + .into_iter() + .filter(|&(i, _, p_dead)| { + if p_dead == 1.0 || hand_info[i].is_determined() { + false + } else { + true + } + }) + .collect::>(); if !know_playable_card { // Vector of tuples (ask_dead, i, p_yes), where ask_dead=false means we'll // ask if the card at i is playable, and ask_dead=true means we ask if the card at i is // dead. p_yes is the probability the answer is nonzero. - let mut to_ask: Vec<(bool, usize, f32)> = augmented_hand_info.iter().filter_map(|&(i, p_play, _)| { - if p_play == 0.0 { None } - else { Some((false, i, p_play)) } - }).collect(); + let mut to_ask: Vec<(bool, usize, f32)> = augmented_hand_info + .iter() + .filter_map(|&(i, p_play, _)| { + if p_play == 0.0 { + None + } else { + Some((false, i, p_play)) + } + }) + .collect(); if !know_dead_card { to_ask.extend(augmented_hand_info.iter().filter_map(|&(i, _, p_dead)| { - if p_dead == 0.0 { None } - else { Some((true, i, p_dead)) } + if p_dead == 0.0 { + None + } else { + Some((true, i, p_dead)) + } })); } @@ -555,7 +624,7 @@ impl PublicInformation for MyPublicInformation { if to_ask.len() > combo_question_capacity { // The questions don't fit into an AdditiveComboQuestion. // Sort by type (ask_dead=false first), then by p_yes (bigger first) - to_ask.sort_by_key(|&(ask_dead, _, p_yes)| {(ask_dead, FloatOrd(-p_yes))}); + to_ask.sort_by_key(|&(ask_dead, _, p_yes)| (ask_dead, FloatOrd(-p_yes))); to_ask.truncate(combo_question_capacity); } @@ -563,20 +632,28 @@ impl PublicInformation for MyPublicInformation { // better to put lower-probability-of-playability/death cards first: The difference // only matters if we find a playable/dead card, and conditional on that, it's better // to find out about as many non-playable/non-dead cards as possible. - to_ask.sort_by_key(|&(ask_dead, _, p_yes)| {(ask_dead, FloatOrd(p_yes))}); - let questions = to_ask.into_iter().map(|(ask_dead, i, _)| -> Box { - if ask_dead { Box::new(q_is_dead(i)) } - else { Box::new(q_is_playable(i)) } - }).collect::>(); + to_ask.sort_by_key(|&(ask_dead, _, p_yes)| (ask_dead, FloatOrd(p_yes))); + let questions = to_ask + .into_iter() + .map(|(ask_dead, i, _)| -> Box { + if ask_dead { + Box::new(q_is_dead(i)) + } else { + Box::new(q_is_playable(i)) + } + }) + .collect::>(); if !questions.is_empty() { - return Some(Box::new(AdditiveComboQuestion { questions })) + return Some(Box::new(AdditiveComboQuestion { questions })); } } - let ask_play_score = |p_play: f32| FloatOrd((p_play-0.7).abs()); - let mut ask_play = augmented_hand_info.iter().filter(|&&(_, p_play, _)| { - ask_play_score(p_play) < FloatOrd(0.2) - }).cloned().collect::>(); + let ask_play_score = |p_play: f32| FloatOrd((p_play - 0.7).abs()); + let mut ask_play = augmented_hand_info + .iter() + .filter(|&&(_, p_play, _)| ask_play_score(p_play) < FloatOrd(0.2)) + .cloned() + .collect::>(); ask_play.sort_by_key(|&(i, p_play, _)| (ask_play_score(p_play), i)); if let Some(&(i, _, _)) = ask_play.get(0) { return Some(Box::new(q_is_playable(i))); @@ -584,9 +661,7 @@ impl PublicInformation for MyPublicInformation { let mut ask_partition = augmented_hand_info; // sort by probability of death (lowest first), then by index - ask_partition.sort_by_key(|&(i, _, p_death)| { - (FloatOrd(p_death), i) - }); + ask_partition.sort_by_key(|&(i, _, p_death)| (FloatOrd(p_death), i)); if let Some(&(i, _, _)) = ask_partition.get(0) { let question = CardPossibilityPartition::new(i, total_info, &hand_info[i], &self.board); Some(Box::new(question)) @@ -596,8 +671,6 @@ impl PublicInformation for MyPublicInformation { } } - - pub struct InformationStrategyConfig; impl InformationStrategyConfig { @@ -640,8 +713,12 @@ pub struct InformationPlayerStrategy { impl InformationPlayerStrategy { // how badly do we need to play a particular card - fn get_average_play_score(&self, view: &OwnedGameView, card_table: &CardPossibilityTable) -> f32 { - let f = |card: &Card| { self.get_play_score(view, card) }; + fn get_average_play_score( + &self, + view: &OwnedGameView, + card_table: &CardPossibilityTable, + ) -> f32 { + let f = |card: &Card| self.get_play_score(view, card); card_table.weighted_score(&f) } @@ -657,7 +734,11 @@ impl InformationPlayerStrategy { (10.0 - card.value as f32) / (num_with as f32) } - fn find_useless_cards(&self, board: &BoardState, hand: &HandInfo) -> Vec { + fn find_useless_cards( + &self, + board: &BoardState, + hand: &HandInfo, + ) -> Vec { let mut useless: FnvHashSet = FnvHashSet::default(); let mut seen: FnvHashMap = FnvHashMap::default(); @@ -674,7 +755,7 @@ impl InformationPlayerStrategy { } } } - let mut useless_vec : Vec = useless.into_iter().collect(); + let mut useless_vec: Vec = useless.into_iter().collect(); useless_vec.sort(); useless_vec } @@ -701,17 +782,14 @@ impl InformationPlayerStrategy { } let old_weight = card_table.total_weight(); match *hinted { - Hinted::Color(color) => { - card_table.mark_color(color, color == card.color) - } - Hinted::Value(value) => { - card_table.mark_value(value, value == card.value) - } + Hinted::Color(color) => card_table.mark_color(color, color == card.color), + Hinted::Value(value) => card_table.mark_value(value, value == card.value), }; let new_weight = card_table.total_weight(); assert!(new_weight <= old_weight); let bonus = { - if card_table.is_determined() || card_table.probability_is_dead(&view.board) == 1.0 { + if card_table.is_determined() || card_table.probability_is_dead(&view.board) == 1.0 + { 2 } else { 1 @@ -729,13 +807,12 @@ impl InformationPlayerStrategy { let view = &self.last_view; // using hint goodness barely helps - let mut hint_options = hints.into_iter().map(|hint| { - (self.hint_goodness(&hint, view), hint) - }).collect::>(); + let mut hint_options = hints + .into_iter() + .map(|hint| (self.hint_goodness(&hint, view), hint)) + .collect::>(); - hint_options.sort_by(|h1, h2| { - h2.0.partial_cmp(&h1.0).unwrap_or(Ordering::Equal) - }); + hint_options.sort_by(|h1, h2| h2.0.partial_cmp(&h1.0).unwrap_or(Ordering::Equal)); if hint_options.is_empty() { // NOTE: Technically possible, but never happens @@ -756,7 +833,7 @@ impl InformationPlayerStrategy { let me = &view.player; for player in view.board.get_players() { - let hand_info = public_info.get_player_info(&player); + let hand_info = public_info.get_player_info(&player); debug!("Current state of hand_info for {}:", player); for (i, card_table) in hand_info.iter().enumerate() { debug!(" Card {}: {}", i, card_table); @@ -771,39 +848,46 @@ impl InformationPlayerStrategy { // If possible, play the best playable card // the higher the play_score, the better to play - let mut playable_cards = private_info.iter().enumerate().filter_map(|(i, card_table)| { - if card_table.probability_is_playable(&view.board) != 1.0 { return None; } - Some((i, self.get_average_play_score(view, card_table))) - }).collect::>(); + let mut playable_cards = private_info + .iter() + .enumerate() + .filter_map(|(i, card_table)| { + if card_table.probability_is_playable(&view.board) != 1.0 { + return None; + } + Some((i, self.get_average_play_score(view, card_table))) + }) + .collect::>(); playable_cards.sort_by_key(|&(i, play_score)| (FloatOrd(-play_score), i)); if let Some(&(play_index, _)) = playable_cards.get(0) { - return TurnChoice::Play(play_index) + return TurnChoice::Play(play_index); } - let discard_threshold = - view.board.total_cards + let discard_threshold = view.board.total_cards - (COLORS.len() * VALUES.len()) as u32 - (view.board.num_players * view.board.hand_size); // make a possibly risky play // TODO: consider removing this, if we improve information transfer - if view.board.lives_remaining > 1 && - view.board.discard_size() <= discard_threshold - { - let mut risky_playable_cards = private_info.iter().enumerate().filter(|&(_, card_table)| { - // card is either playable or dead - card_table.probability_of_predicate(&|card| { - view.board.is_playable(card) || view.board.is_dead(card) - }) == 1.0 - }).map(|(i, card_table)| { - let p = card_table.probability_is_playable(&view.board); - (i, card_table, p) - }).collect::>(); + if view.board.lives_remaining > 1 && view.board.discard_size() <= discard_threshold { + let mut risky_playable_cards = private_info + .iter() + .enumerate() + .filter(|&(_, card_table)| { + // card is either playable or dead + card_table.probability_of_predicate(&|card| { + view.board.is_playable(card) || view.board.is_dead(card) + }) == 1.0 + }) + .map(|(i, card_table)| { + let p = card_table.probability_is_playable(&view.board); + (i, card_table, p) + }) + .collect::>(); if !risky_playable_cards.is_empty() { - risky_playable_cards.sort_by(|c1, c2| { - c2.2.partial_cmp(&c1.2).unwrap_or(Ordering::Equal) - }); + risky_playable_cards + .sort_by(|c1, c2| c2.2.partial_cmp(&c1.2).unwrap_or(Ordering::Equal)); let maybe_play = risky_playable_cards[0]; if maybe_play.2 > 0.75 { @@ -812,19 +896,29 @@ impl InformationPlayerStrategy { } } - let public_useless_indices = self.find_useless_cards(&view.board, &public_info.get_player_info(me)); + let public_useless_indices = + self.find_useless_cards(&view.board, &public_info.get_player_info(me)); let useless_indices = self.find_useless_cards(&view.board, &private_info); // NOTE When changing this, make sure to keep the "discard" branch of update() up to date! - let will_hint = - if view.board.hints_remaining > 0 && public_info.someone_else_needs_hint(view) { true } - else if view.board.discard_size() <= discard_threshold && !useless_indices.is_empty() { false } - // hinting is better than discarding dead cards - // (probably because it stalls the deck-drawing). - else if view.board.hints_remaining > 0 && view.someone_else_can_play() { true } - else if view.board.hints_remaining > 4 { true } - // this is the only case in which we discard a potentially useful card. - else { false }; + let will_hint = if view.board.hints_remaining > 0 + && public_info.someone_else_needs_hint(view) + { + true + } else if view.board.discard_size() <= discard_threshold && !useless_indices.is_empty() { + false + } + // hinting is better than discarding dead cards + // (probably because it stalls the deck-drawing). + else if view.board.hints_remaining > 0 && view.someone_else_can_play() { + true + } else if view.board.hints_remaining > 4 { + true + } + // this is the only case in which we discard a potentially useful card. + else { + false + }; if will_hint { let hint_set = public_info.get_hint(view); @@ -847,16 +941,18 @@ impl InformationPlayerStrategy { } // Make the least risky discard. - let mut cards_by_discard_value = private_info.iter().enumerate().map(|(i, card_table)| { - let probability_is_seen = card_table.probability_of_predicate(&|card| { - view.can_see(card) - }); - let compval = - 20.0 * probability_is_seen - + 10.0 * card_table.probability_is_dispensable(&view.board) - + card_table.average_value(); - (i, compval) - }).collect::>(); + let mut cards_by_discard_value = private_info + .iter() + .enumerate() + .map(|(i, card_table)| { + let probability_is_seen = + card_table.probability_of_predicate(&|card| view.can_see(card)); + let compval = 20.0 * probability_is_seen + + 10.0 * card_table.probability_is_dispensable(&view.board) + + card_table.average_value(); + (i, compval) + }) + .collect::>(); cards_by_discard_value.sort_by_key(|&(i, compval)| (FloatOrd(-compval), i)); let (index, _) = cards_by_discard_value[0]; TurnChoice::Discard(index) @@ -876,13 +972,15 @@ impl InformationPlayerStrategy { hint_matches: Option<&Vec>, ) { match turn_choice { - TurnChoice::Hint(ref hint) => { + TurnChoice::Hint(ref hint) => { let matches = hint_matches.unwrap(); - self.public_info.update_from_hint_choice(hint, matches, &self.last_view); + self.public_info + .update_from_hint_choice(hint, matches, &self.last_view); } TurnChoice::Discard(index) => { let known_useless_indices = self.find_useless_cards( - &self.last_view.board, &self.public_info.get_player_info(turn_player) + &self.last_view.board, + &self.public_info.get_player_info(turn_player), ); if self.last_view.board.hints_remaining > 0 { @@ -891,8 +989,12 @@ impl InformationPlayerStrategy { if known_useless_indices.len() > 1 { // unwrap is safe because *if* a discard happened, and there were known // dead cards, it must be a dead card - let value = known_useless_indices.iter().position(|&i| i == *index).unwrap(); - let info = ModulusInformation::new(known_useless_indices.len() as u32, value as u32); + let value = known_useless_indices + .iter() + .position(|&i| i == *index) + .unwrap(); + let info = + ModulusInformation::new(known_useless_indices.len() as u32, value as u32); self.public_info.update_from_hat_sum(info, &self.last_view); } } @@ -914,37 +1016,57 @@ impl PlayerStrategy for InformationPlayerStrategy { fn update(&mut self, turn_record: &TurnRecord, view: &BorrowedGameView) { let hint_matches = if let &TurnResult::Hint(ref matches) = &turn_record.result { Some(matches) - } else { None }; + } else { + None + }; self.update_wrapped(&turn_record.player, &turn_record.choice, hint_matches); if let Some(new_public_info) = self.new_public_info.take() { if !self.public_info.agrees_with(new_public_info) { - panic!("The change made to public_info in self.decide_wrapped differs from \ - the corresponding change in self.update_wrapped!"); + panic!( + "The change made to public_info in self.decide_wrapped differs from \ + the corresponding change in self.update_wrapped!" + ); } } match turn_record.choice { - TurnChoice::Hint(ref hint) => { + TurnChoice::Hint(ref hint) => { if let &TurnResult::Hint(ref matches) = &turn_record.result { self.public_info.update_from_hint_matches(hint, matches); } else { - panic!("Got turn choice {:?}, but turn result {:?}", - turn_record.choice, turn_record.result); + panic!( + "Got turn choice {:?}, but turn result {:?}", + turn_record.choice, turn_record.result + ); } } TurnChoice::Discard(index) => { if let &TurnResult::Discard(ref card) = &turn_record.result { - self.public_info.update_from_discard_or_play_result(view, &turn_record.player, index, card); + self.public_info.update_from_discard_or_play_result( + view, + &turn_record.player, + index, + card, + ); } else { - panic!("Got turn choice {:?}, but turn result {:?}", - turn_record.choice, turn_record.result); + panic!( + "Got turn choice {:?}, but turn result {:?}", + turn_record.choice, turn_record.result + ); } } - TurnChoice::Play(index) => { + TurnChoice::Play(index) => { if let &TurnResult::Play(ref card, _) = &turn_record.result { - self.public_info.update_from_discard_or_play_result(view, &turn_record.player, index, card); + self.public_info.update_from_discard_or_play_result( + view, + &turn_record.player, + index, + card, + ); } else { - panic!("Got turn choice {:?}, but turn result {:?}", - turn_record.choice, turn_record.result); + panic!( + "Got turn choice {:?}, but turn result {:?}", + turn_record.choice, turn_record.result + ); } } } diff --git a/src/strategy.rs b/src/strategy.rs index c3823ee..eade9b8 100644 --- a/src/strategy.rs +++ b/src/strategy.rs @@ -23,4 +23,3 @@ pub trait GameStrategy { pub trait GameStrategyConfig { fn initialize(&self, opts: &GameOptions) -> Box; } - From 2b866ac227e4e43e6741bb6a1cdcfa8f7852fd5a Mon Sep 17 00:00:00 2001 From: timotree3 Date: Thu, 19 Jan 2023 20:34:17 -0500 Subject: [PATCH 05/10] Add specific versions for dependencies --- Cargo.lock | 109 ++++++++++++++++++++++++++++++++++++++++++++++------- Cargo.toml | 10 ++--- 2 files changed, 100 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7f6b6db..b972c22 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,10 +3,16 @@ version = 3 [[package]] -name = "crossbeam" -version = "0.2.8" +name = "cfg-if" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "348228ce9f93d20ffc30c18e575f82fa41b9c8bf064806c65d41eba4771595a0" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "crossbeam" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd66663db5a988098a89599d4857919b3acf7f61402e61365acfd3919857b9be" [[package]] name = "float-ord" @@ -16,9 +22,15 @@ checksum = "7bad48618fdb549078c333a7a8528acb57af271d0433bdecd523eb620628364e" [[package]] name = "fnv" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" [[package]] name = "getopts" @@ -31,26 +43,73 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.7" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4870ef6725dde13394134e587e4ab4eca13cb92e916209a31c851b49131d3c75" +checksum = "5916d2ae698f6de9bfb891ad7a8d65c09d232dc58cc4ac433c7da3b2fd84bc2b" [[package]] name = "log" -version = "0.3.5" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "038b5d13189a14e5b6ac384fdb7c691a45ef0885f6d2dddbf422e6c3506b8234" +checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" dependencies = [ - "libc", + "log 0.4.17", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", ] [[package]] name = "rand" -version = "0.3.14" +version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2791d88c6defac799c3f20d74f094ca33b9332612d9aef9078519c82e4fe04a5" +checksum = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c" dependencies = [ "libc", + "rand 0.4.6", +] + +[[package]] +name = "rand" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" +dependencies = [ + "fuchsia-cprng", + "libc", + "rand_core 0.3.1", + "rdrand", + "winapi", +] + +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", ] [[package]] @@ -61,8 +120,8 @@ dependencies = [ "float-ord", "fnv", "getopts", - "log", - "rand", + "log 0.3.9", + "rand 0.3.23", ] [[package]] @@ -70,3 +129,25 @@ name = "unicode-width" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml index 79c0936..1154c73 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,9 +4,9 @@ version = "0.1.0" authors = ["Jeff Wu "] [dependencies] -rand = "*" -log = "*" -getopts = "*" -fnv = "*" -float-ord = "*" +rand = "0.3.0" +log = "0.3.0" +getopts = "0.2.14" +fnv = "1.0.0" +float-ord = "0.2.0" crossbeam = "0.2.5" From b8a82294d1b00830eb134e52746497c6b29552fc Mon Sep 17 00:00:00 2001 From: timotree3 Date: Thu, 19 Jan 2023 20:55:47 -0500 Subject: [PATCH 06/10] Update to 2021 edition --- Cargo.toml | 1 + src/helpers.rs | 2 +- src/main.rs | 2 +- src/simulator.rs | 5 ++--- src/strategies/cheating.rs | 4 ++-- src/strategies/examples.rs | 4 ++-- src/strategies/hat_helpers.rs | 4 ++-- src/strategies/information.rs | 8 ++++---- src/strategy.rs | 2 +- 9 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1154c73..3cf3cfb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ name = "rust_hanabi" version = "0.1.0" authors = ["Jeff Wu "] +edition = "2021" [dependencies] rand = "0.3.0" diff --git a/src/helpers.rs b/src/helpers.rs index 800756e..9f9e946 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -6,7 +6,7 @@ use std::hash::Hash; use std::ops::{Index, IndexMut}; use std::slice; -use game::*; +use crate::game::*; // trait representing information about a card pub trait CardInfo { diff --git a/src/main.rs b/src/main.rs index 69e03ed..0caef30 100644 --- a/src/main.rs +++ b/src/main.rs @@ -236,7 +236,7 @@ fn get_results_table() -> String { } fn concat_twolines(body: Vec) -> String { body.into_iter().fold(String::default(), |output, (a, b)| { - (output + &a + "\n" + &b + "\n") + output + &a + "\n" + &b + "\n" }) } let header = make_twolines(&player_nums, (space.clone(), dashes), &|n_players| { diff --git a/src/simulator.rs b/src/simulator.rs index fdcd105..884103b 100644 --- a/src/simulator.rs +++ b/src/simulator.rs @@ -1,10 +1,9 @@ -use crossbeam; use fnv::FnvHashMap; use rand::{self, Rng, SeedableRng}; use std::fmt; -use game::*; -use strategy::*; +use crate::game::*; +use crate::strategy::*; fn new_deck(seed: u32) -> Cards { let mut deck: Cards = Cards::new(); diff --git a/src/strategies/cheating.rs b/src/strategies/cheating.rs index 00a4da2..fe24cd2 100644 --- a/src/strategies/cheating.rs +++ b/src/strategies/cheating.rs @@ -2,8 +2,8 @@ use fnv::{FnvHashMap, FnvHashSet}; use std::cell::RefCell; use std::rc::Rc; -use game::*; -use strategy::*; +use crate::game::*; +use crate::strategy::*; // strategy that explicitly cheats by using Rc/RefCell // serves as a reference point for other strategies diff --git a/src/strategies/examples.rs b/src/strategies/examples.rs index 5b5f15f..a944a50 100644 --- a/src/strategies/examples.rs +++ b/src/strategies/examples.rs @@ -1,6 +1,6 @@ -use game::*; +use crate::game::*; +use crate::strategy::*; use rand::{self, Rng}; -use strategy::*; // dummy, terrible strategy, as an example #[derive(Clone)] diff --git a/src/strategies/hat_helpers.rs b/src/strategies/hat_helpers.rs index f4d94e0..bc01152 100644 --- a/src/strategies/hat_helpers.rs +++ b/src/strategies/hat_helpers.rs @@ -1,5 +1,5 @@ -use game::*; -use helpers::*; +use crate::game::*; +use crate::helpers::*; #[derive(Debug, Clone)] pub struct ModulusInformation { diff --git a/src/strategies/information.rs b/src/strategies/information.rs index baea99a..f2da8f5 100644 --- a/src/strategies/information.rs +++ b/src/strategies/information.rs @@ -2,10 +2,10 @@ use float_ord::*; use fnv::{FnvHashMap, FnvHashSet}; use std::cmp::Ordering; -use game::*; -use helpers::*; -use strategies::hat_helpers::*; -use strategy::*; +use crate::game::*; +use crate::helpers::*; +use crate::strategies::hat_helpers::*; +use crate::strategy::*; // TODO: use random extra information - i.e. when casting up and down, // we sometimes have 2 choices of value to choose diff --git a/src/strategy.rs b/src/strategy.rs index eade9b8..f688379 100644 --- a/src/strategy.rs +++ b/src/strategy.rs @@ -1,4 +1,4 @@ -use game::*; +use crate::game::*; // Traits to implement for any valid Hanabi strategy From d65b7616f1d947ef16658174ca3b2d8324a8f758 Mon Sep 17 00:00:00 2001 From: timotree3 Date: Thu, 19 Jan 2023 20:59:20 -0500 Subject: [PATCH 07/10] Fix clippy warnings --- src/game.rs | 32 ++++-------- src/helpers.rs | 22 ++++---- src/main.rs | 12 +++-- src/simulator.rs | 6 +-- src/strategies/hat_helpers.rs | 2 +- src/strategies/information.rs | 96 ++++++++++++++++------------------- src/strategy.rs | 2 +- 7 files changed, 78 insertions(+), 94 deletions(-) diff --git a/src/game.rs b/src/game.rs index e2b8915..8d08dce 100644 --- a/src/game.rs +++ b/src/game.rs @@ -188,7 +188,7 @@ pub enum Hinted { } impl fmt::Display for Hinted { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { + match *self { Hinted::Color(color) => { write!(f, "{}", color) } @@ -355,16 +355,10 @@ impl BoardState { // 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(); - if firework.complete() { - true - } else { - let needed = firework.needed_value().unwrap(); - if card.value < needed || card.value > self.highest_attainable(card.color) { - true - } else { - self.discard.remaining(card) != 1 - } - } + firework.complete() + || card.value < firework.needed_value().unwrap() + || card.value > self.highest_attainable(card.color) + || self.discard.remaining(card) != 1 } pub fn get_players(&self) -> Range { @@ -372,10 +366,7 @@ impl BoardState { } pub fn score(&self) -> Score { - self.fireworks - .iter() - .map(|(_, firework)| firework.score()) - .sum() + self.fireworks.values().map(Firework::score).sum() } pub fn discard_size(&self) -> u32 { @@ -449,8 +440,7 @@ pub trait GameView { fn has_card(&self, player: &Player, card: &Card) -> bool { self.get_hand(player) .iter() - .position(|other_card| card == other_card) - .is_some() + .any(|other_card| card == other_card) } fn get_other_players(&self) -> Vec { @@ -624,12 +614,12 @@ impl GameState { // takes a card from the player's hand, and replaces it if possible fn take_from_hand(&mut self, index: usize) -> Card { - let ref mut hand = self.hands.get_mut(&self.board.player).unwrap(); + let hand = &mut self.hands.get_mut(&self.board.player).unwrap(); hand.remove(index) } fn replenish_hand(&mut self) { - let ref mut hand = self.hands.get_mut(&self.board.player).unwrap(); + let hand = &mut self.hands.get_mut(&self.board.player).unwrap(); if (hand.len() as u32) < self.board.hand_size { if let Some(new_card) = self.deck.pop() { self.board.deck_size -= 1; @@ -650,8 +640,8 @@ impl GameState { self.board.hints_remaining -= 1; debug!("Hint to player {}, about {}", hint.player, hint.hinted); - assert!( - self.board.player != hint.player, + assert_ne!( + self.board.player, hint.player, "Player {} gave a hint to himself", hint.player ); diff --git a/src/helpers.rs b/src/helpers.rs index 9f9e946..ce0358c 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -33,7 +33,7 @@ pub trait CardInfo { // get probability weight for the card #[allow(unused_variables)] fn get_weight(&self, card: &Card) -> f32 { - 1. + 1.0 } fn get_weighted_possibilities(&self) -> Vec<(Card, f32)> { @@ -135,7 +135,7 @@ pub trait CardInfo { // Represents hinted information about possible values of type T pub trait Info where - T: Hash + Eq + Clone, + T: Hash + Eq + Clone + Copy, { // get all a-priori possibilities fn get_all_possibilities() -> Vec; @@ -149,7 +149,7 @@ where fn get_possibilities(&self) -> Vec { self.get_possibility_set() .iter() - .cloned() + .copied() .collect::>() } @@ -160,7 +160,7 @@ where fn initialize() -> HashSet { Self::get_all_possibilities() .iter() - .cloned() + .copied() .collect::>() } @@ -301,10 +301,10 @@ impl CardPossibilityTable { pub fn decrement_weight(&mut self, card: &Card) { let remove = { - let weight = self.possible.get_mut(card).expect(&format!( - "Decrementing weight for impossible card: {}", - card - )); + let weight = self + .possible + .get_mut(card) + .unwrap_or_else(|| panic!("Decrementing weight for impossible card: {}", card)); *weight -= 1; *weight == 0 }; @@ -425,15 +425,15 @@ where // update for hint to me pub fn update_for_hint(&mut self, hinted: &Hinted, matches: &[bool]) { - match hinted { + match *hinted { Hinted::Color(color) => { for (card_info, &matched) in self.hand_info.iter_mut().zip(matches.iter()) { - card_info.mark_color(*color, matched); + card_info.mark_color(color, matched); } } Hinted::Value(value) => { for (card_info, &matched) in self.hand_info.iter_mut().zip(matches.iter()) { - card_info.mark_value(*value, matched); + card_info.mark_value(value, matched); } } } diff --git a/src/main.rs b/src/main.rs index 0caef30..372c315 100644 --- a/src/main.rs +++ b/src/main.rs @@ -105,7 +105,8 @@ fn main() { return print!("{}", get_results_table()); } - let log_level_str: &str = &matches.opt_str("l").unwrap_or("info".to_string()); + let l_opt = matches.opt_str("l"); + let log_level_str = l_opt.as_deref().unwrap_or("info"); let log_level = match log_level_str { "trace" => log::LogLevelFilter::Trace, "debug" => log::LogLevelFilter::Debug, @@ -124,16 +125,17 @@ fn main() { }) .unwrap(); - let n_trials = u32::from_str(&matches.opt_str("n").unwrap_or("1".to_string())).unwrap(); + let n_trials = u32::from_str(matches.opt_str("n").as_deref().unwrap_or("1")).unwrap(); let seed = matches .opt_str("s") .map(|seed_str| u32::from_str(&seed_str).unwrap()); let progress_info = matches .opt_str("o") .map(|freq_str| u32::from_str(&freq_str).unwrap()); - let n_threads = u32::from_str(&matches.opt_str("t").unwrap_or("1".to_string())).unwrap(); - let n_players = u32::from_str(&matches.opt_str("p").unwrap_or("4".to_string())).unwrap(); - let strategy_str: &str = &matches.opt_str("g").unwrap_or("cheat".to_string()); + let n_threads = u32::from_str(matches.opt_str("t").as_deref().unwrap_or("1")).unwrap(); + let n_players = u32::from_str(matches.opt_str("p").as_deref().unwrap_or("4")).unwrap(); + let g_opt = matches.opt_str("g"); + let strategy_str: &str = g_opt.as_deref().unwrap_or("cheat"); sim_games( n_players, diff --git a/src/simulator.rs b/src/simulator.rs index 884103b..db0a1b2 100644 --- a/src/simulator.rs +++ b/src/simulator.rs @@ -85,7 +85,7 @@ impl Histogram { fn insert_many(&mut self, val: Score, count: u32) { let new_count = self.get_count(&val) + count; self.hist.insert(val, new_count); - self.sum += val * (count as u32); + self.sum += val * count; self.total_count += count; } pub fn insert(&mut self, val: Score) { @@ -195,11 +195,11 @@ where lives_histogram.merge(thread_lives_histogram); } - non_perfect_seeds.sort(); + non_perfect_seeds.sort_unstable(); SimResult { scores: score_histogram, lives: lives_histogram, - non_perfect_seed: non_perfect_seeds.get(0).cloned(), + non_perfect_seed: non_perfect_seeds.first().cloned(), } }) } diff --git a/src/strategies/hat_helpers.rs b/src/strategies/hat_helpers.rs index bc01152..cf82d49 100644 --- a/src/strategies/hat_helpers.rs +++ b/src/strategies/hat_helpers.rs @@ -253,7 +253,7 @@ pub trait PublicInformation: Clone { fn get_private_info(&self, view: &OwnedGameView) -> HandInfo { let mut info = self.get_player_info(&view.player); for card_table in info.iter_mut() { - for (_, hand) in &view.other_hands { + for hand in view.other_hands.values() { for card in hand { card_table.decrement_weight_if_possible(card); } diff --git a/src/strategies/information.rs b/src/strategies/information.rs index f2da8f5..d91b17d 100644 --- a/src/strategies/information.rs +++ b/src/strategies/information.rs @@ -23,7 +23,7 @@ impl Question for CardHasProperty { 2 } fn answer(&self, hand: &Cards, board: &BoardState) -> u32 { - let ref card = hand[self.index]; + let card = &hand[self.index]; if (self.property)(board, card) { 1 } else { @@ -36,17 +36,15 @@ impl Question for CardHasProperty { hand_info: &mut HandInfo, board: &BoardState, ) { - let ref mut card_table = hand_info[self.index]; + let card_table = &mut hand_info[self.index]; let possible = card_table.get_possibilities(); for card in &possible { if (self.property)(board, card) { if answer == 0 { card_table.mark_false(card); } - } else { - if answer == 1 { - card_table.mark_false(card); - } + } else if answer == 1 { + card_table.mark_false(card); } } } @@ -184,7 +182,7 @@ impl Question for CardPossibilityPartition { self.n_partitions } fn answer(&self, hand: &Cards, _: &BoardState) -> u32 { - let ref card = hand[self.index]; + let card = &hand[self.index]; *self.partition.get(card).unwrap() } fn acknowledge_answer( @@ -193,7 +191,7 @@ impl Question for CardPossibilityPartition { hand_info: &mut HandInfo, _: &BoardState, ) { - let ref mut card_table = hand_info[self.index]; + let card_table = &mut hand_info[self.index]; let possible = card_table.get_possibilities(); for card in &possible { if *self.partition.get(card).unwrap() != answer { @@ -220,10 +218,7 @@ impl MyPublicInformation { fn get_other_players_starting_after(&self, player: Player) -> Vec { let n = self.board.num_players; - (0..n - 1) - .into_iter() - .map(|i| (player + 1 + i) % n) - .collect() + (0..n - 1).map(|i| (player + 1 + i) % n).collect() } // Returns the number of ways to hint the player. @@ -232,7 +227,7 @@ impl MyPublicInformation { // - it is public that there are at least two colors // - it is public that there are at least two numbers - let ref info = self.hand_info[&player]; + let info = &self.hand_info[&player]; let may_be_all_one_color = COLORS .iter() @@ -276,7 +271,7 @@ impl MyPublicInformation { (-score, i) }) .collect::>(); - scores.sort(); + scores.sort_unstable(); scores[0].1 } @@ -411,10 +406,7 @@ impl MyPublicInformation { let player_amt = (n + hint.player - hinter - 1) % n; - let amt_from_prev_players = info_per_player - .iter() - .take(player_amt as usize) - .sum::(); + let amt_from_prev_players: u32 = info_per_player.iter().take(player_amt as usize).sum(); let hint_info_we_can_give_to_this_player = info_per_player[player_amt as usize]; let card_index = self.get_index_for_hint(&hint.player); @@ -427,17 +419,15 @@ impl MyPublicInformation { } else { 2 } + } else if result[card_index] { + match hint.hinted { + Hinted::Value(_) => 0, + Hinted::Color(_) => 1, + } } else { - if result[card_index] { - match hint.hinted { - Hinted::Value(_) => 0, - Hinted::Color(_) => 1, - } - } else { - match hint.hinted { - Hinted::Value(_) => 2, - Hinted::Color(_) => 3, - } + match hint.hinted { + Hinted::Value(_) => 2, + Hinted::Color(_) => 3, } }; @@ -571,10 +561,10 @@ impl PublicInformation for MyPublicInformation { .iter() .cloned() .enumerate() - .filter_map(|(i, card_table)| { + .map(|(i, card_table)| { let p_play = card_table.probability_is_playable(&self.board); let p_dead = card_table.probability_is_dead(&self.board); - Some((i, p_play, p_dead)) + (i, p_play, p_dead) }) .collect::>(); let know_playable_card = augmented_hand_info_raw @@ -588,10 +578,10 @@ impl PublicInformation for MyPublicInformation { let augmented_hand_info = augmented_hand_info_raw .into_iter() .filter(|&(i, _, p_dead)| { - if p_dead == 1.0 || hand_info[i].is_determined() { + if p_dead == 1.0 { false } else { - true + !hand_info[i].is_determined() } }) .collect::>(); @@ -655,14 +645,14 @@ impl PublicInformation for MyPublicInformation { .cloned() .collect::>(); ask_play.sort_by_key(|&(i, p_play, _)| (ask_play_score(p_play), i)); - if let Some(&(i, _, _)) = ask_play.get(0) { + if let Some(&(i, _, _)) = ask_play.first() { return Some(Box::new(q_is_playable(i))); } let mut ask_partition = augmented_hand_info; // sort by probability of death (lowest first), then by index ask_partition.sort_by_key(|&(i, _, p_death)| (FloatOrd(p_death), i)); - if let Some(&(i, _, _)) = ask_partition.get(0) { + if let Some(&(i, _, _)) = ask_partition.first() { let question = CardPossibilityPartition::new(i, total_info, &hand_info[i], &self.board); Some(Box::new(question)) } else { @@ -739,6 +729,8 @@ impl InformationPlayerStrategy { board: &BoardState, hand: &HandInfo, ) -> Vec { + use std::collections::hash_map::Entry::{Occupied, Vacant}; + let mut useless: FnvHashSet = FnvHashSet::default(); let mut seen: FnvHashMap = FnvHashMap::default(); @@ -746,17 +738,20 @@ impl InformationPlayerStrategy { if card_table.probability_is_dead(board) == 1.0 { useless.insert(i); } else if let Some(card) = card_table.get_card() { - if seen.contains_key(&card) { - // found a duplicate card - useless.insert(i); - useless.insert(*seen.get(&card).unwrap()); - } else { - seen.insert(card, i); + match seen.entry(card) { + Occupied(e) => { + // found a duplicate card + useless.insert(i); + useless.insert(*e.get()); + } + Vacant(e) => { + e.insert(i); + } } } } let mut useless_vec: Vec = useless.into_iter().collect(); - useless_vec.sort(); + useless_vec.sort_unstable(); useless_vec } @@ -859,7 +854,7 @@ impl InformationPlayerStrategy { }) .collect::>(); playable_cards.sort_by_key(|&(i, play_score)| (FloatOrd(-play_score), i)); - if let Some(&(play_index, _)) = playable_cards.get(0) { + if let Some(&(play_index, _)) = playable_cards.first() { return TurnChoice::Play(play_index); } @@ -912,12 +907,9 @@ impl InformationPlayerStrategy { // (probably because it stalls the deck-drawing). else if view.board.hints_remaining > 0 && view.someone_else_can_play() { true - } else if view.board.hints_remaining > 4 { - true - } - // this is the only case in which we discard a potentially useful card. - else { - false + } else { + // this being false is the only case in which we discard a potentially useful card. + view.board.hints_remaining > 4 }; if will_hint { @@ -1014,7 +1006,7 @@ impl PlayerStrategy for InformationPlayerStrategy { } fn update(&mut self, turn_record: &TurnRecord, view: &BorrowedGameView) { - let hint_matches = if let &TurnResult::Hint(ref matches) = &turn_record.result { + let hint_matches = if let TurnResult::Hint(matches) = &turn_record.result { Some(matches) } else { None @@ -1030,7 +1022,7 @@ impl PlayerStrategy for InformationPlayerStrategy { } match turn_record.choice { TurnChoice::Hint(ref hint) => { - if let &TurnResult::Hint(ref matches) = &turn_record.result { + if let TurnResult::Hint(matches) = &turn_record.result { self.public_info.update_from_hint_matches(hint, matches); } else { panic!( @@ -1040,7 +1032,7 @@ impl PlayerStrategy for InformationPlayerStrategy { } } TurnChoice::Discard(index) => { - if let &TurnResult::Discard(ref card) = &turn_record.result { + if let TurnResult::Discard(card) = &turn_record.result { self.public_info.update_from_discard_or_play_result( view, &turn_record.player, @@ -1055,7 +1047,7 @@ impl PlayerStrategy for InformationPlayerStrategy { } } TurnChoice::Play(index) => { - if let &TurnResult::Play(ref card, _) = &turn_record.result { + if let TurnResult::Play(card, _) = &turn_record.result { self.public_info.update_from_discard_or_play_result( view, &turn_record.player, diff --git a/src/strategy.rs b/src/strategy.rs index f688379..5b83f58 100644 --- a/src/strategy.rs +++ b/src/strategy.rs @@ -15,7 +15,7 @@ pub trait PlayerStrategy { // Shouldn't do much, except store configuration parameters and // possibility initialize some shared randomness between players pub trait GameStrategy { - fn initialize(&self, player: Player, view: &BorrowedGameView) -> Box; + fn initialize(&self, me: Player, view: &BorrowedGameView) -> Box; } // Represents configuration for a strategy. From 91fc4c279d5d04ed29a3ad057ee0a8fe1789fc0f Mon Sep 17 00:00:00 2001 From: timotree3 Date: Wed, 11 May 2022 15:21:10 -0400 Subject: [PATCH 08/10] Clean up formatting code --- src/game.rs | 48 +++++++++++++++++------------------ src/helpers.rs | 6 ++--- src/simulator.rs | 2 +- src/strategies/information.rs | 4 +-- 4 files changed, 29 insertions(+), 31 deletions(-) diff --git a/src/game.rs b/src/game.rs index 8d08dce..49dca76 100644 --- a/src/game.rs +++ b/src/game.rs @@ -78,11 +78,11 @@ impl CardCounts { impl fmt::Display for CardCounts { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { for &color in COLORS.iter() { - f.write_str(&format!("{}: ", color,))?; + write!(f, "{}: ", color)?; for &value in VALUES.iter() { let count = self.get_count(&Card::new(color, value)); let total = get_count_for_value(value); - f.write_str(&format!("{}/{} {}s", count, total, value))?; + write!(f, "{}/{} {}s", count, total, value)?; if value != FINAL_VALUE { f.write_str(", ")?; } @@ -123,9 +123,7 @@ impl Discard { } impl fmt::Display for Discard { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - // f.write_str(&format!( - // "{}", self.cards, - // ))?; + // write!(f, "{}", self.cards)?; write!(f, "{}", self.counts) } } @@ -387,35 +385,35 @@ impl BoardState { impl fmt::Display for BoardState { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if self.is_over() { - f.write_str(&format!("Turn {} (GAME ENDED):\n", self.turn))?; + writeln!(f, "Turn {} (GAME ENDED):", self.turn)?; } else { - f.write_str(&format!( - "Turn {} (Player {}'s turn):\n", - self.turn, self.player - ))?; + writeln!(f, "Turn {} (Player {}'s turn):", self.turn, self.player)?; } - f.write_str(&format!("{} cards remaining in deck\n", self.deck_size))?; + writeln!(f, "{} cards remaining in deck", self.deck_size)?; if self.deck_size == 0 { - f.write_str(&format!( - "Deck is empty. {} turns remaining in game\n", + writeln!( + f, + "Deck is empty. {} turns remaining in game", self.deckless_turns_remaining - ))?; + )?; } - f.write_str(&format!( - "{}/{} hints remaining\n", + writeln!( + f, + "{}/{} hints remaining", self.hints_remaining, self.hints_total - ))?; - f.write_str(&format!( - "{}/{} lives remaining\n", + )?; + writeln!( + f, + "{}/{} lives remaining", self.lives_remaining, self.lives_total - ))?; + )?; f.write_str("Fireworks:\n")?; for &color in COLORS.iter() { - f.write_str(&format!(" {}\n", self.get_firework(color)))?; + writeln!(f, " {}", self.get_firework(color))?; } f.write_str("Discard:\n")?; - f.write_str(&format!("{}\n", self.discard))?; + writeln!(f, "{}\n", self.discard)?; Ok(()) } @@ -550,16 +548,16 @@ impl fmt::Display for GameState { f.write_str("======\n")?; for player in self.board.get_players() { let hand = &self.hands.get(&player).unwrap(); - f.write_str(&format!("player {}:", player))?; + write!(f, "player {}:", player)?; for card in hand.iter() { - f.write_str(&format!(" {}", card))?; + write!(f, " {}", card)?; } f.write_str("\n")?; } f.write_str("======\n")?; f.write_str("Board:\n")?; f.write_str("======\n")?; - f.write_str(&format!("{}", self.board))?; + write!(f, "{}", self.board)?; Ok(()) } } diff --git a/src/helpers.rs b/src/helpers.rs index ce0358c..9895edd 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -1,7 +1,7 @@ use std::cmp::Eq; use std::collections::{HashMap, HashSet}; use std::convert::From; -use std::fmt; +use std::fmt::{self, Write}; use std::hash::Hash; use std::ops::{Index, IndexMut}; use std::slice; @@ -268,7 +268,7 @@ impl fmt::Display for SimpleCardInfo { //} for &value in &VALUES { if self.value_info.is_possible(value) { - string.push_str(&format!("{}", value)); + write!(string, "{}", value).unwrap(); } } f.pad(&string) @@ -401,7 +401,7 @@ impl CardInfo for CardPossibilityTable { impl fmt::Display for CardPossibilityTable { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { for (card, weight) in &self.possible { - f.write_str(&format!("{} {}, ", weight, card))?; + write!(f, "{} {}, ", weight, card)?; } Ok(()) } diff --git a/src/simulator.rs b/src/simulator.rs index db0a1b2..71ffc11 100644 --- a/src/simulator.rs +++ b/src/simulator.rs @@ -122,7 +122,7 @@ impl fmt::Display for Histogram { let mut keys = self.hist.keys().collect::>(); keys.sort(); for val in keys { - f.write_str(&format!("\n{}: {}", val, self.get_count(val),))?; + write!(f, "\n{}: {}", val, self.get_count(val))?; } Ok(()) } diff --git a/src/strategies/information.rs b/src/strategies/information.rs index d91b17d..8ab6c24 100644 --- a/src/strategies/information.rs +++ b/src/strategies/information.rs @@ -159,14 +159,14 @@ impl CardPossibilityPartition { n_partitions += 1; } - // let mut s : String = "Partition: |".to_string(); + // let mut s: String = "Partition: |".to_string(); // for i in 0..n_partitions { // for (card, block) in partition.iter() { // if *block == i { // s = s + &format!(" {}", card); // } // } - // s = s + &format!(" |"); + // s.push_str(" |"); // } // debug!("{}", s); From ae4f67174016cf825ade55d80a8d92d541c73bbe Mon Sep 17 00:00:00 2001 From: timotree3 Date: Wed, 11 May 2022 15:27:43 -0400 Subject: [PATCH 09/10] Clean up boolean logic --- src/game.rs | 19 ++++--------------- src/strategies/information.rs | 11 ++--------- 2 files changed, 6 insertions(+), 24 deletions(-) diff --git a/src/game.rs b/src/game.rs index 49dca76..ed80aa1 100644 --- a/src/game.rs +++ b/src/game.rs @@ -338,25 +338,14 @@ 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(); - if firework.complete() { - true - } else { - let needed = firework.needed_value().unwrap(); - if card.value < needed { - true - } else { - card.value > self.highest_attainable(card.color) - } - } + firework.complete() + || card.value < firework.needed_value().unwrap() + || 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(); - firework.complete() - || card.value < firework.needed_value().unwrap() - || card.value > self.highest_attainable(card.color) - || self.discard.remaining(card) != 1 + self.is_dead(card) || self.discard.remaining(card) != 1 } pub fn get_players(&self) -> Range { diff --git a/src/strategies/information.rs b/src/strategies/information.rs index 8ab6c24..c56652f 100644 --- a/src/strategies/information.rs +++ b/src/strategies/information.rs @@ -577,13 +577,7 @@ impl PublicInformation for MyPublicInformation { // We don't need to find out anything about cards that are determined or dead. let augmented_hand_info = augmented_hand_info_raw .into_iter() - .filter(|&(i, _, p_dead)| { - if p_dead == 1.0 { - false - } else { - !hand_info[i].is_determined() - } - }) + .filter(|&(i, _, p_dead)| p_dead != 1.0 && !hand_info[i].is_determined()) .collect::>(); if !know_playable_card { @@ -902,10 +896,9 @@ impl InformationPlayerStrategy { true } else if view.board.discard_size() <= discard_threshold && !useless_indices.is_empty() { false - } // hinting is better than discarding dead cards // (probably because it stalls the deck-drawing). - else if view.board.hints_remaining > 0 && view.someone_else_can_play() { + } else if view.board.hints_remaining > 0 && view.someone_else_can_play() { true } else { // this being false is the only case in which we discard a potentially useful card. From bed45135d52837f9648ee80ba94ac60bc4b3938d Mon Sep 17 00:00:00 2001 From: timotree3 Date: Thu, 19 Jan 2023 21:05:32 -0500 Subject: [PATCH 10/10] Use variables directly in format expressions (Clippy lint) --- src/game.rs | 14 +++++++------- src/helpers.rs | 6 +++--- src/main.rs | 21 ++++++++++----------- 3 files changed, 20 insertions(+), 21 deletions(-) diff --git a/src/game.rs b/src/game.rs index ed80aa1..a037f0b 100644 --- a/src/game.rs +++ b/src/game.rs @@ -20,7 +20,7 @@ pub fn get_count_for_value(value: Value) -> u32 { 2 | 3 | 4 => 2, 5 => 1, _ => { - panic!("Unexpected value: {}", value); + panic!("Unexpected value: {value}"); } } } @@ -78,11 +78,11 @@ impl CardCounts { impl fmt::Display for CardCounts { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { for &color in COLORS.iter() { - write!(f, "{}: ", color)?; + write!(f, "{color}: ")?; for &value in VALUES.iter() { let count = self.get_count(&Card::new(color, value)); let total = get_count_for_value(value); - write!(f, "{}/{} {}s", count, total, value)?; + write!(f, "{count}/{total} {value}s")?; if value != FINAL_VALUE { f.write_str(", ")?; } @@ -188,10 +188,10 @@ impl fmt::Display for Hinted { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { Hinted::Color(color) => { - write!(f, "{}", color) + write!(f, "{color}") } Hinted::Value(value) => { - write!(f, "{}", value) + write!(f, "{value}") } } } @@ -537,9 +537,9 @@ impl fmt::Display for GameState { f.write_str("======\n")?; for player in self.board.get_players() { let hand = &self.hands.get(&player).unwrap(); - write!(f, "player {}:", player)?; + write!(f, "player {player}:")?; for card in hand.iter() { - write!(f, " {}", card)?; + write!(f, " {card}")?; } f.write_str("\n")?; } diff --git a/src/helpers.rs b/src/helpers.rs index 9895edd..ac9fb99 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -268,7 +268,7 @@ impl fmt::Display for SimpleCardInfo { //} for &value in &VALUES { if self.value_info.is_possible(value) { - write!(string, "{}", value).unwrap(); + write!(string, "{value}").unwrap(); } } f.pad(&string) @@ -304,7 +304,7 @@ impl CardPossibilityTable { let weight = self .possible .get_mut(card) - .unwrap_or_else(|| panic!("Decrementing weight for impossible card: {}", card)); + .unwrap_or_else(|| panic!("Decrementing weight for impossible card: {card}")); *weight -= 1; *weight == 0 }; @@ -401,7 +401,7 @@ impl CardInfo for CardPossibilityTable { impl fmt::Display for CardPossibilityTable { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { for (card, weight) in &self.possible { - write!(f, "{} {}, ", weight, card)?; + write!(f, "{weight} {card}, ")?; } Ok(()) } diff --git a/src/main.rs b/src/main.rs index 372c315..3e8d538 100644 --- a/src/main.rs +++ b/src/main.rs @@ -34,7 +34,7 @@ impl log::Log for SimpleLogger { } fn print_usage(program: &str, opts: Options) { - print!("{}", opts.usage(&format!("Usage: {} [options]", program))); + print!("{}", opts.usage(&format!("Usage: {program} [options]"))); } fn main() { @@ -115,7 +115,7 @@ fn main() { "error" => log::LogLevelFilter::Error, _ => { print_usage(&program, opts); - panic!("Unexpected log level argument {}", log_level_str); + panic!("Unexpected log level argument {log_level_str}"); } }; @@ -162,7 +162,7 @@ fn sim_games( 4 => 4, 5 => 4, _ => { - panic!("There should be 2 to 5 players, not {}", n_players); + panic!("There should be 2 to 5 players, not {n_players}"); } }; @@ -185,7 +185,7 @@ fn sim_games( "info" => Box::new(strategies::information::InformationStrategyConfig::new()) as Box, _ => { - panic!("Unexpected strategy argument {}", strategy_str); + panic!("Unexpected strategy argument {strategy_str}"); } }; simulator::simulate( @@ -206,13 +206,12 @@ fn get_results_table() -> String { let n_threads = 8; let intro = format!( - "On the first {} seeds, we have these scores and win rates (average ± standard error):\n\n", - n_trials + "On the first {n_trials} seeds, we have these scores and win rates (average ± standard error):\n\n" ); - let format_name = |x| format!(" {:7} ", x); - let format_players = |x| format!(" {}p ", x); - let format_percent = |x, stderr| format!(" {:05.2} ± {:.2} % ", x, stderr); - let format_score = |x, stderr| format!(" {:07.4} ± {:.4} ", x, stderr); + let format_name = |x| format!(" {x:7} "); + let format_players = |x| format!(" {x}p "); + let format_percent = |x, stderr| format!(" {x:05.2} ± {stderr:.2} % "); + let format_score = |x, stderr| format!(" {x:07.4} ± {stderr:.4} "); let space = String::from(" "); let dashes = String::from("---------"); let dashes_long = String::from("------------------"); @@ -288,7 +287,7 @@ time cargo run --release -- --write-results-table let readme_init = { let parts = readme_contents.splitn(2, separator).collect::>(); if parts.len() != 2 { - panic!("{} has been modified in the Results section!", readme); + panic!("{readme} has been modified in the Results section!"); } parts[0] };