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..3cf3cfb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,11 +2,12 @@ name = "rust_hanabi" version = "0.1.0" authors = ["Jeff Wu "] +edition = "2021" [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" diff --git a/src/game.rs b/src/game.rs index 1b21f11..a037f0b 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, - ))?; + 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, "{count}/{total} {value}s")?; 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, @@ -127,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) } } @@ -137,21 +131,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,28 +179,32 @@ impl fmt::Display for Firework { } } -#[derive(Debug,Clone,Hash,PartialEq,Eq)] +#[derive(Debug, Clone, Hash, PartialEq, Eq)] pub enum Hinted { Color(Color), Value(Value), } 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) } + match *self { + 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 +212,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 +220,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 +242,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 +268,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 +324,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) { @@ -338,31 +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(); - 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 - } - } + self.is_dead(card) || self.discard.remaining(card) != 1 } pub fn get_players(&self) -> Range { @@ -370,7 +353,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 { @@ -391,35 +374,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", self.deckless_turns_remaining - ))?; + writeln!( + f, + "Deck is empty. {} turns remaining in game", + self.deckless_turns_remaining + )?; } - f.write_str(&format!( - "{}/{} hints remaining\n", self.hints_remaining, self.hints_total - ))?; - f.write_str(&format!( - "{}/{} lives remaining\n", self.lives_remaining, self.lives_total - ))?; + writeln!( + f, + "{}/{} hints remaining", + self.hints_remaining, self.hints_total + )?; + 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(()) } @@ -442,28 +425,29 @@ 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() + .any(|other_card| card == other_card) } 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 +463,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 +492,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, @@ -552,16 +537,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(()) } } @@ -570,21 +555,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 { @@ -617,12 +601,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; @@ -636,26 +620,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_ne!( + 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 +664,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 +705,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..ac9fb99 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -1,12 +1,12 @@ 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::{self, Write}; +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 { @@ -33,25 +33,29 @@ 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)> { - 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 + Copy, +{ // 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() + .copied() + .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() + .copied() + .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); @@ -236,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) @@ -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) + .unwrap_or_else(|| panic!("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() { @@ -363,53 +401,73 @@ 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(()) } } -#[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 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); } } } } - 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..3e8d538 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))); + print!("{}", opts.usage(&format!("Usage: {program} [options]"))); } - 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,42 +105,65 @@ 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 } - "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); + panic!("Unexpected log level argument {log_level_str}"); } }; 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 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_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").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, 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 +175,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); - }, + 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 +205,64 @@ 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 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 intro = format!( + "On the first {n_trials} seeds, we have these scores and win rates (average ± standard error):\n\n" + ); + 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("------------------"); 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) } @@ -224,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] }; diff --git a/src/simulator.rs b/src/simulator.rs index 0c51065..71ffc11 100644 --- a/src/simulator.rs +++ b/src/simulator.rs @@ -1,10 +1,9 @@ -use rand::{self, Rng, SeedableRng}; use fnv::FnvHashMap; +use rand::{self, Rng, SeedableRng}; use std::fmt; -use crossbeam; -use game::*; -use strategy::*; +use crate::game::*; +use crate::strategy::*; fn new_deck(seed: u32) -> Cards { let mut deck: Cards = Cards::new(); @@ -15,7 +14,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 +22,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 +49,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 +60,6 @@ pub fn simulate_once( let strategy = strategies.get_mut(&player).unwrap(); strategy.update(&turn, &game.get_view(player)); } - } debug!(""); debug!("======================================================="); @@ -82,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) { @@ -119,24 +122,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), - ))?; + write!(f, "\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 +146,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 +158,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 +173,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,21 +184,22 @@ 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); } - 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(), } }) } @@ -209,7 +217,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..fe24cd2 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 crate::game::*; +use crate::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..a944a50 100644 --- a/src/strategies/examples.rs +++ b/src/strategies/examples.rs @@ -1,5 +1,5 @@ -use strategy::*; -use game::*; +use crate::game::*; +use crate::strategy::*; use rand::{self, Rng}; // dummy, terrible strategy, as an example @@ -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..cf82d49 100644 --- a/src/strategies/hat_helpers.rs +++ b/src/strategies/hat_helpers.rs @@ -1,7 +1,7 @@ -use game::*; -use helpers::*; +use crate::game::*; +use crate::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); } @@ -232,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 53b47bf..c56652f 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 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 @@ -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 } + let card = &hand[self.index]; + if (self.property)(board, card) { + 1 + } else { + 0 + } } fn acknowledge_answer( &self, @@ -32,22 +36,30 @@ 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); } + if answer == 0 { + card_table.mark_false(card); + } + } else 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 +74,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 +104,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 +123,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(); @@ -140,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); @@ -159,9 +178,11 @@ 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]; + let card = &hand[self.index]; *self.partition.get(card).unwrap() } fn acknowledge_answer( @@ -170,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 { @@ -180,11 +201,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 +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. @@ -206,21 +227,21 @@ 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().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,11 +263,15 @@ 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::>(); - scores.sort(); + 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_unstable(); scores[0].1 } @@ -266,14 +291,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 +381,55 @@ 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: 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); - 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 +447,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 +489,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 +518,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 +557,50 @@ 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() + .map(|(i, card_table)| { + let p_play = card_table.probability_is_playable(&self.board); + let p_dead = card_table.probability_is_dead(&self.board); + (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)| p_dead != 1.0 && !hand_info[i].is_determined()) + .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 +608,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,31 +616,37 @@ 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) { + 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) { + ask_partition.sort_by_key(|&(i, _, p_death)| (FloatOrd(p_death), i)); + if let Some(&(i, _, _)) = ask_partition.first() { let question = CardPossibilityPartition::new(i, total_info, &hand_info[i], &self.board); Some(Box::new(question)) } else { @@ -596,8 +655,6 @@ impl PublicInformation for MyPublicInformation { } } - - pub struct InformationStrategyConfig; impl InformationStrategyConfig { @@ -640,8 +697,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 +718,13 @@ 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 { + use std::collections::hash_map::Entry::{Occupied, Vacant}; + let mut useless: FnvHashSet = FnvHashSet::default(); let mut seen: FnvHashMap = FnvHashMap::default(); @@ -665,17 +732,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(); + let mut useless_vec: Vec = useless.into_iter().collect(); + useless_vec.sort_unstable(); useless_vec } @@ -701,17 +771,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 +796,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 +822,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 +837,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) + if let Some(&(play_index, _)) = playable_cards.first() { + 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 +885,25 @@ 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 { + // this being false is the only case in which we discard a potentially useful card. + view.board.hints_remaining > 4 + }; if will_hint { let hint_set = public_info.get_hint(view); @@ -847,16 +926,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 +957,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 +974,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); } } @@ -912,39 +999,59 @@ 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 }; + } 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) => { - if let &TurnResult::Hint(ref matches) = &turn_record.result { + TurnChoice::Hint(ref hint) => { + if let TurnResult::Hint(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); + if let TurnResult::Discard(card) = &turn_record.result { + 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) => { - if let &TurnResult::Play(ref card, _) = &turn_record.result { - self.public_info.update_from_discard_or_play_result(view, &turn_record.player, index, card); + TurnChoice::Play(index) => { + if let TurnResult::Play(card, _) = &turn_record.result { + 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..5b83f58 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 @@ -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. @@ -23,4 +23,3 @@ pub trait GameStrategy { pub trait GameStrategyConfig { fn initialize(&self, opts: &GameOptions) -> Box; } -