From 1a42a4dd21ef6458cdffef1aa89aa70ac4176a9c Mon Sep 17 00:00:00 2001 From: timotree3 Date: Thu, 19 Jan 2023 21:22:16 -0500 Subject: [PATCH] Run `cargo fmt` --- src/game.rs | 214 ++++++++------- src/helpers.rs | 180 +++++++++---- src/json_output.rs | 11 +- src/main.rs | 274 ++++++++++++------- src/simulator.rs | 109 ++++---- src/strategies/cheating.rs | 46 ++-- src/strategies/examples.rs | 14 +- src/strategies/hat_helpers.rs | 83 +++--- src/strategies/information.rs | 493 +++++++++++++++++++++------------- src/strategy.rs | 1 - 10 files changed, 877 insertions(+), 548 deletions(-) diff --git a/src/game.rs b/src/game.rs index 3e64fc6..43f1359 100644 --- a/src/game.rs +++ b/src/game.rs @@ -11,26 +11,31 @@ 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!(format!("Unexpected value: {}", value)); } + 5 => 1, + _ => { + panic!(format!("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, } impl Card { pub fn new(color: Color, value: Value) -> Card { - Card { color: color, value: value } + Card { + color: color, + value: value, + } } } impl fmt::Display for Card { @@ -44,7 +49,7 @@ impl fmt::Debug for Card { } } -#[derive(Debug,Clone,Eq,PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq)] pub struct CardCounts { counts: FnvHashMap, } @@ -56,9 +61,7 @@ impl CardCounts { counts.insert(Card::new(color, value), 0); } } - CardCounts { - counts: counts, - } + CardCounts { counts: counts } } pub fn get_count(&self, card: &Card) -> u32 { @@ -78,15 +81,11 @@ 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!( - "{}: ", color, - ))); + try!(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!( - "{}/{} {}s", count, total, value - ))); + try!(f.write_str(&format!("{}/{} {}s", count, total, value))); if value != FINAL_VALUE { try!(f.write_str(", ")); } @@ -99,7 +98,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,7 +136,7 @@ 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, @@ -151,7 +150,11 @@ impl Firework { } 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 +187,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 +195,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 +220,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 +228,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 +250,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 +276,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: deck_size, @@ -324,7 +332,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) { @@ -374,7 +382,10 @@ impl BoardState { } 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()) + .fold(0, |a, b| a + b) } pub fn discard_size(&self) -> u32 { @@ -389,34 +400,36 @@ impl BoardState { } pub fn is_over(&self) -> bool { - (self.lives_remaining == 0) || (self.deckless_turns_remaining == 0) || (self.score() == PERFECT_SCORE) + (self.lives_remaining == 0) + || (self.deckless_turns_remaining == 0) + || (self.score() == PERFECT_SCORE) } } impl fmt::Display for BoardState { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if self.is_over() { - try!(f.write_str(&format!( - "Turn {} (GAME ENDED):\n", self.turn - ))); + try!(f.write_str(&format!("Turn {} (GAME ENDED):\n", self.turn))); } else { try!(f.write_str(&format!( - "Turn {} (Player {}'s turn):\n", self.turn, self.player + "Turn {} (Player {}'s turn):\n", + self.turn, self.player ))); } - try!(f.write_str(&format!( - "{} cards remaining in deck\n", self.deck_size - ))); + try!(f.write_str(&format!("{} cards remaining in deck\n", self.deck_size))); if self.deck_size == 0 { try!(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 ))); } try!(f.write_str(&format!( - "{}/{} hints remaining\n", self.hints_remaining, self.hints_total + "{}/{} hints remaining\n", + self.hints_remaining, self.hints_total ))); try!(f.write_str(&format!( - "{}/{} lives remaining\n", self.lives_remaining, self.lives_total + "{}/{} lives remaining\n", + self.lives_remaining, self.lives_total ))); try!(f.write_str("Fireworks:\n")); for &color in COLORS.iter() { @@ -446,28 +459,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)) }) } } @@ -483,7 +498,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 } @@ -512,10 +527,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.clone(), @@ -552,7 +568,7 @@ pub type AnnotatedCard = (usize, Card); pub type AnnotatedCards = Vec; fn strip_annotations(cards: &AnnotatedCards) -> Cards { - cards.iter().map(|(_i, card)| { card.clone() }).collect() + cards.iter().map(|(_i, card)| card.clone()).collect() } // complete game state (known to nobody!) @@ -592,18 +608,22 @@ impl GameState { let mut deck: AnnotatedCards = deck.into_iter().rev().enumerate().rev().collect(); 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::>(); - let unannotated_hands = hands.iter().map(|(player, hand)| { - (player.clone(), strip_annotations(hand)) - }).collect::>(); + }) + .collect::>(); + let unannotated_hands = hands + .iter() + .map(|(player, hand)| (player.clone(), strip_annotations(hand))) + .collect::>(); GameState { hands, @@ -643,15 +663,15 @@ impl GameState { fn update_player_hand(&mut self) { let player = self.board.player.clone(); - self.unannotated_hands.insert(player, strip_annotations(self.hands.get(&player).unwrap())); + self.unannotated_hands + .insert(player, strip_annotations(self.hands.get(&player).unwrap())); } // takes a card from the player's hand, and replaces it if possible fn take_from_hand(&mut self, index: usize) -> Card { // FIXME this code looks like it's awfully contorted in order to please the borrow checker. // Can we have this look nicer? - let result = - { + let result = { let ref mut hand = self.hands.get_mut(&self.board.player).unwrap(); hand.remove(index).1 }; @@ -679,26 +699,34 @@ 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, - format!("Player {} gave a hint to himself", hint.player)); + assert!( + self.board.player != hint.player, + format!("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(|(_i, card)| { card.color == color }).collect::>() - } - Hinted::Value(value) => { - hand.iter().map(|(_i, card)| { card.value == value }).collect::>() - } + Hinted::Color(color) => hand + .iter() + .map(|(_i, card)| card.color == color) + .collect::>(), + Hinted::Value(value) => hand + .iter() + .map(|(_i, 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) @@ -714,10 +742,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 { { @@ -758,7 +783,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 43aa3b7..71306c6 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: &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: &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 + Copy { +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 + 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() + .map(|t| t.clone()) + .collect::>() } fn is_possible(&self, value: T) -> bool { @@ -145,8 +158,10 @@ pub trait Info where T: Hash + Eq + Clone + Copy { } fn initialize() -> HashSet { - Self::get_all_possibilities().iter() - .map(|val| val.clone()).collect::>() + Self::get_all_possibilities() + .iter() + .map(|val| val.clone()) + .collect::>() } fn mark_true(&mut self, value: T) { @@ -160,35 +175,55 @@ pub trait Info where T: Hash + Eq + Clone + Copy { } 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: possible, - } + CardPossibilityTable { possible: possible } } } impl CardInfo for CardPossibilityTable { @@ -341,7 +380,11 @@ 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() + .map(|card| card.clone()) + .collect::>(); cards.sort(); cards } @@ -349,7 +392,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,11 +411,17 @@ 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 { @@ -397,19 +445,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/json_output.rs b/src/json_output.rs index ab8d216..745c8ee 100644 --- a/src/json_output.rs +++ b/src/json_output.rs @@ -2,7 +2,10 @@ use game::*; use serde_json::*; fn color_value(color: &Color) -> usize { - COLORS.iter().position(|&card_color| &card_color == color).unwrap() + COLORS + .iter() + .position(|&card_color| &card_color == color) + .unwrap() } fn card_to_json(card: &Card) -> serde_json::Value { @@ -43,7 +46,11 @@ pub fn action_discard((i, _card): &AnnotatedCard) -> serde_json::Value { }) } -pub fn json_format(deck: &Cards, actions: &Vec, players: &Vec) -> serde_json::Value { +pub fn json_format( + deck: &Cards, + actions: &Vec, + players: &Vec, +) -> serde_json::Value { json!({ "variant": "No Variant", "players": players, diff --git a/src/main.rs b/src/main.rs index d8f2d0c..a0e2014 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,20 +1,20 @@ 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; extern crate serde_json; -mod helpers; mod game; +mod helpers; mod json_output; mod simulator; mod strategy; mod strategies { - pub mod examples; pub mod cheating; + pub mod examples; mod hat_helpers; pub mod information; } @@ -35,51 +35,71 @@ 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("j", "json-output", - "Pattern for the JSON output file. '%s' will be replaced by the seed.", - "FILE_PATTERN"); - 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.optflag("", "losses-only", - "When saving JSON outputs, save lost games only"); + 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( + "j", + "json-output", + "Pattern for the JSON output file. '%s' will be replaced by the seed.", + "FILE_PATTERN", + ); + 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.optflag( + "", + "losses-only", + "When saving JSON outputs, save lost games only", + ); let matches = match opts.parse(&args[1..]) { - Ok(m) => { m } + Ok(m) => m, Err(f) => { print_usage(&program, opts); panic!(f.to_string()) @@ -98,14 +118,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); } @@ -114,38 +134,55 @@ 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 json_output_pattern = matches.opt_str("j"); let json_losses_only = matches.opt_present("losses-only"); 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, json_output_pattern, json_losses_only).info(); + sim_games( + n_players, + strategy_str, + seed, + n_trials, + n_threads, + progress_info, + json_output_pattern, + json_losses_only, + ) + .info(); } fn sim_games( - n_players: u32, - strategy_str: &str, - seed: Option, - n_trials: u32, - n_threads: u32, - progress_info: Option, - json_output_pattern: Option, - json_losses_only: bool, - ) -> simulator::SimResult { + n_players: u32, + strategy_str: &str, + seed: Option, + n_trials: u32, + n_threads: u32, + progress_info: Option, + json_output_pattern: Option, + json_losses_only: bool, +) -> 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 { @@ -157,26 +194,29 @@ fn sim_games( 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, json_output_pattern, json_losses_only) + simulator::simulate( + &game_opts, + strategy_config, + seed, + n_trials, + n_threads, + progress_info, + json_output_pattern, + json_losses_only, + ) } fn get_results_table() -> String { @@ -186,39 +226,75 @@ 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: &Vec, 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: &Vec, + 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.clone()), - &|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, None, false); - ( - 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.clone()), + &|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, + None, + false, + ); + ( + 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 56b4d2e..d8ad866 100644 --- a/src/simulator.rs +++ b/src/simulator.rs @@ -1,11 +1,11 @@ -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::*; use json_output::*; +use strategy::*; fn new_deck(seed: u32) -> Cards { let mut deck: Cards = Cards::new(); @@ -16,28 +16,32 @@ fn new_deck(seed: u32) -> Cards { deck.push(Card::new(color, value)); } } - }; + } rand::ChaChaRng::from_seed(&[seed]).shuffle(&mut deck[..]); debug!("Deck: {:?}", deck); deck } - pub fn simulate_once( - opts: &GameOptions, - game_strategy: Box, - seed: u32, - output_json: bool, - ) -> (GameState, Option) { - + opts: &GameOptions, + game_strategy: Box, + seed: u32, + output_json: bool, +) -> (GameState, Option) { let deck = new_deck(seed); let mut game = GameState::new(opts, deck.clone()); - 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::>>(); let mut actions = Vec::new(); @@ -50,28 +54,24 @@ pub fn simulate_once( debug!("======================================================="); debug!("{}", game); - let choice = { let mut strategy = strategies.get_mut(&player).unwrap(); strategy.decide(&game.get_view(player)) }; if output_json { actions.push(match choice { - TurnChoice::Hint(ref hint) => { - action_clue(hint) - } + TurnChoice::Hint(ref hint) => action_clue(hint), TurnChoice::Play(index) => { let card = &game.hands[&player][index]; action_play(card) } - TurnChoice::Discard(index) => { + TurnChoice::Discard(index) => { let card = &game.hands[&player][index]; action_discard(card) } }); } - let turn = game.process_choice(choice); for player in game.get_players() { @@ -84,9 +84,10 @@ pub fn simulate_once( debug!("Final state:\n{}", game); debug!("SCORE: {:?}", game.score()); let json_output = if output_json { - let player_names = game.get_players().map(|player| { - strategies[&player].name() - }).collect(); + let player_names = game + .get_players() + .map(|player| strategies[&player].name()) + .collect(); Some(json_format(&deck, &actions, &player_names)) } else { None @@ -148,26 +149,25 @@ impl fmt::Display for Histogram { let mut keys = self.hist.keys().collect::>(); keys.sort(); for val in keys { - try!(f.write_str(&format!( - "\n{}: {}", val, self.get_count(val), - ))); + try!(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, - json_output_pattern: Option, - json_losses_only: bool, - ) -> SimResult - where T: GameStrategyConfig + Sync { - + opts: &GameOptions, + strat_config: Box, + first_seed_opt: Option, + n_trials: u32, + n_threads: u32, + progress_info: Option, + json_output_pattern: Option, + json_losses_only: bool, +) -> 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; @@ -176,7 +176,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); @@ -188,25 +188,33 @@ 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 ); } } - let (game, json_output) = simulate_once(&opts, - strat_config_ref.initialize(&opts), - seed, - json_output_pattern_ref.is_some()); + let (game, json_output) = simulate_once( + &opts, + strat_config_ref.initialize(&opts), + seed, + json_output_pattern_ref.is_some(), + ); 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 let Some(file_pattern) = json_output_pattern_ref { if !(score == PERFECT_SCORE && json_losses_only) { - let file_pattern = file_pattern.clone().replace("%s", &seed.to_string()); + let file_pattern = + file_pattern.clone().replace("%s", &seed.to_string()); let path = std::path::Path::new(&file_pattern); let file = std::fs::File::create(path).unwrap(); serde_json::to_writer(file, &json_output.unwrap()).unwrap(); @@ -220,11 +228,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); @@ -252,7 +261,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 c3dc0ef..0614301 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)).fold(0, |a,b| a+b) + hand.iter() + .map(|card| self.card_play_value(view, card)) + .fold(0, |a, b| a + b) } // 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); @@ -109,7 +111,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); } } } @@ -132,7 +134,7 @@ impl CheatingPlayerStrategy { } set.insert(card.clone()); } - return None + return None; } } impl PlayerStrategy for CheatingPlayerStrategy { @@ -144,9 +146,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.len() > 0 { // play the best playable card @@ -161,15 +165,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 { @@ -211,6 +214,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 5b3462f..b818961 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)] @@ -40,14 +40,19 @@ pub struct RandomStrategyPlayer { impl PlayerStrategy for RandomStrategyPlayer { fn name(&self) -> String { - format!("random(hint={}, play={})", self.hint_probability, self.play_probability) + format!( + "random(hint={}, play={})", + self.hint_probability, self.play_probability + ) } fn decide(&mut self, view: &BorrowedGameView) -> TurnChoice { let p = rand::random::(); 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 @@ -69,6 +74,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 7550a5b..44c5697 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, @@ -82,22 +82,17 @@ pub trait Question { // get the answer to this question, given cards fn answer(&self, &Cards, &BoardState) -> u32; // process the answer to this question, updating card info - fn acknowledge_answer( - &self, value: u32, &mut HandInfo, &BoardState - ); + fn acknowledge_answer(&self, value: u32, &mut HandInfo, &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 +107,7 @@ pub trait PublicInformation: Clone { fn set_board(&mut self, &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 +120,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, &HandInfo, total_info: u32) -> Option>; + fn ask_question( + &self, + &Player, + &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 +140,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 +162,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 +199,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.clone(), 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.clone(), 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 +223,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.clone(), 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.clone(), 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 3ca4bb2..ff5b57a 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))); - return if !may_be_all_one_color && !may_be_all_one_number { 4 } else { 3 } + return 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: hinted, - } - }).collect() + }) + .collect() } fn decode_hint_choice(&self, hint: &Hint, result: &Vec) -> 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).fold(0, |a, b| a + b); + let amt_from_prev_players = info_per_player + .iter() + .take(player_amt as usize) + .fold(0, |a, b| a + b); 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: hand_info, card_counts: CardCounts::new(), @@ -522,33 +567,58 @@ 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 { false } - else if 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 { + false + } else if 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)) + } })); } @@ -556,7 +626,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); } @@ -564,20 +634,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.len() > 0 { - 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))); @@ -585,9 +663,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)) @@ -597,8 +673,6 @@ impl PublicInformation for MyPublicInformation { } } - - pub struct InformationStrategyConfig; impl InformationStrategyConfig { @@ -641,8 +715,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) } @@ -660,7 +738,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(); @@ -679,7 +761,7 @@ impl InformationPlayerStrategy { } } } - let mut useless_vec : Vec = useless.into_iter().collect(); + let mut useless_vec: Vec = useless.into_iter().collect(); useless_vec.sort(); return useless_vec; } @@ -706,12 +788,8 @@ 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); @@ -736,13 +814,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.len() == 0 { // NOTE: Technically possible, but never happens @@ -765,7 +842,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); @@ -780,39 +857,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.len() > 0 { - 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 { @@ -821,19 +905,28 @@ 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.len() > 0 { false } + 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 + } // 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 } + 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 { + false + }; if will_hint { let hint_set = public_info.get_hint(view); @@ -856,16 +949,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) @@ -885,13 +980,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 { @@ -900,8 +997,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); } } @@ -927,37 +1028,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 3ba501b..f9d8620 100644 --- a/src/strategy.rs +++ b/src/strategy.rs @@ -28,4 +28,3 @@ pub trait GameStrategy { pub trait GameStrategyConfig { fn initialize(&self, &GameOptions) -> Box; } -