diff --git a/README.md b/README.md index e47e9fc..05def07 100644 --- a/README.md +++ b/README.md @@ -73,5 +73,5 @@ On the first 20000 seeds, we have these average scores and win rates: |---------|---------|---------|---------|---------| | cheat | 24.8594 | 24.9785 | 24.9720 | 24.9557 | | | 90.59 % | 98.17 % | 97.76 % | 96.42 % | -| info | 22.3316 | 24.7246 | 24.8892 | 24.8969 | -| | 09.81 % | 80.38 % | 91.45 % | 91.94 % | +| info | 22.2908 | 24.7171 | 24.8875 | 24.8957 | +| | 09.40 % | 79.94 % | 91.32 % | 91.83 % | diff --git a/src/game.rs b/src/game.rs index ce6d388..e973a3c 100644 --- a/src/game.rs +++ b/src/game.rs @@ -44,7 +44,7 @@ impl fmt::Debug for Card { } } -#[derive(Debug,Clone)] +#[derive(Debug,Clone,Eq,PartialEq)] pub struct CardCounts { counts: FnvHashMap, } @@ -99,7 +99,7 @@ impl fmt::Display for CardCounts { pub type Cards = Vec; -#[derive(Debug,Clone)] +#[derive(Debug,Clone,Eq,PartialEq)] pub struct Discard { pub cards: Cards, counts: CardCounts, @@ -137,7 +137,7 @@ impl fmt::Display for Discard { pub type Score = u32; pub const PERFECT_SCORE: Score = (NUM_COLORS * NUM_VALUES) as u32; -#[derive(Debug,Clone)] +#[derive(Debug,Clone,Eq,PartialEq)] pub struct Firework { pub color: Color, pub top: Value, @@ -198,14 +198,14 @@ impl fmt::Display for Hinted { } } -#[derive(Debug,Clone)] +#[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)] +#[derive(Debug,Clone,Eq,PartialEq)] pub enum TurnChoice { Hint(Hint), Discard(usize), // index of card to discard @@ -213,7 +213,7 @@ pub enum TurnChoice { } // represents what happened in a turn -#[derive(Debug,Clone)] +#[derive(Debug,Clone,Eq,PartialEq)] pub enum TurnResult { Hint(Vec), // vector of whether each was in the hint Discard(Card), // card discarded @@ -221,7 +221,7 @@ pub enum TurnResult { } // represents a turn taken in the game -#[derive(Debug,Clone)] +#[derive(Debug,Clone,Eq,PartialEq)] pub struct TurnRecord { pub player: Player, pub choice: TurnChoice, @@ -243,7 +243,7 @@ pub struct GameOptions { // State of everything except the player's hands // Is all completely common knowledge -#[derive(Debug,Clone)] +#[derive(Debug,Clone,Eq,PartialEq)] pub struct BoardState { pub deck_size: u32, pub total_cards: u32, diff --git a/src/helpers.rs b/src/helpers.rs index 504319a..43aa3b7 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -246,7 +246,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)] +#[derive(Clone,Debug,Eq,PartialEq)] pub struct CardPossibilityTable { possible: HashMap, } @@ -369,7 +369,7 @@ impl fmt::Display for CardPossibilityTable { } } -#[derive(Clone)] +#[derive(Clone,Eq,PartialEq)] pub struct HandInfo where T: CardInfo { pub hand_info: Vec } diff --git a/src/main.rs b/src/main.rs index c322ba9..12a05f7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,6 +12,7 @@ mod strategy; mod strategies { pub mod examples; pub mod cheating; + mod hat_helpers; pub mod information; } diff --git a/src/strategies/hat_helpers.rs b/src/strategies/hat_helpers.rs new file mode 100644 index 0000000..ad50a04 --- /dev/null +++ b/src/strategies/hat_helpers.rs @@ -0,0 +1,218 @@ +use game::*; +use helpers::*; + +#[derive(Debug,Clone)] +pub struct ModulusInformation { + pub modulus: u32, + pub value: u32, +} +impl ModulusInformation { + pub fn new(modulus: u32, value: u32) -> Self { + assert!(value < modulus); + ModulusInformation { + modulus: modulus, + value: value, + } + } + + pub fn none() -> Self { + Self::new(1, 0) + } + + pub fn combine(&mut self, other: Self) { + self.value = self.value + self.modulus * other.value; + self.modulus = self.modulus * other.modulus; + } + + pub fn split(&mut self, modulus: u32) -> Self { + assert!(self.modulus >= modulus); + assert!(self.modulus % modulus == 0); + let original_modulus = self.modulus; + let original_value = self.value; + self.modulus = self.modulus / modulus; + let value = self.value % modulus; + self.value = self.value / modulus; + assert!(original_modulus == modulus * self.modulus); + assert!(original_value == value + modulus * self.value); + Self::new(modulus, value) + } + + pub fn cast_up(&mut self, modulus: u32) { + assert!(self.modulus <= modulus); + self.modulus = modulus; + } + + pub fn cast_down(&mut self, modulus: u32) { + assert!(self.modulus >= modulus); + assert!(self.value < modulus); + self.modulus = modulus; + } + + pub fn add(&mut self, other: &Self) { + assert!(self.modulus == other.modulus); + self.value = (self.value + other.value) % self.modulus; + } + + pub fn subtract(&mut self, other: &Self) { + assert!(self.modulus == other.modulus); + self.value = (self.modulus + self.value - other.value) % self.modulus; + } +} + +pub trait Question { + // how much info does this question ask for? + fn info_amount(&self) -> u32; + // get the answer to this question, given cards + fn answer(&self, &Cards, &BoardState) -> u32; + // process the answer to this question, updating card info + 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) + ) + } + + fn acknowledge_answer_info( + &self, + answer: ModulusInformation, + hand_info: &mut HandInfo, + board: &BoardState + ) { + assert!(self.info_amount() == answer.modulus); + self.acknowledge_answer(answer.value, hand_info, board); + } +} + +pub trait PublicInformation: Clone { + fn get_player_info(&self, &Player) -> HandInfo; + fn set_player_info(&mut self, &Player, HandInfo); + + fn new(&BoardState) -> Self; + 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 agrees_with(&self, other: Self) -> bool; + + /// By defining `ask_questions`, we decides which `Question`s a player learns the answers to. + /// + /// A player "asks" a question by calling the callback. Questions can depend on the answers to + /// earlier questions: We are given a `&mut HandInfo` that we'll have to pass + /// to that callback; there, it will be modified to reflect the answer to the question. Note that `self` + /// is not modified and thus reflects the state before any player "asked" any question. + /// + /// The product of the `info_amount()`s of all questions we have may not exceed `total_info`. + /// For convenience, we pass a `&mut u32` to the callback, and it will be updated to the + /// "remaining" information amount. + fn ask_questions(&self, &Player, &mut HandInfo, Callback, total_info: u32) + where Callback: FnMut(&mut HandInfo, &mut u32, Box); + + fn set_player_infos(&mut self, infos: Vec<(Player, HandInfo)>) { + for (player, new_hand_info) in infos { + self.set_player_info(&player, new_hand_info); + } + self.update_other_info(); + } + + fn get_hat_info_for_player( + &self, player: &Player, hand_info: &mut HandInfo, total_info: u32, view: &OwnedGameView + ) -> ModulusInformation { + assert!(player != &view.player); + let mut answer_info = ModulusInformation::none(); + { + let callback = |hand_info: &mut HandInfo, info_remaining: &mut u32, question: Box| { + *info_remaining = *info_remaining / question.info_amount(); + 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); + }; + self.ask_questions(player, hand_info, callback, total_info); + } + answer_info.cast_up(total_info); + answer_info + } + + fn update_from_hat_info_for_player( + &self, + player: &Player, + hand_info: &mut HandInfo, + board: &BoardState, + mut info: ModulusInformation, + ) { + let total_info = info.modulus; + let callback = |hand_info: &mut HandInfo, info_remaining: &mut u32, question: Box| { + let q_info_amount = question.info_amount(); + *info_remaining = *info_remaining / q_info_amount; + let info_modulus = info.modulus; + // Instead of casting down the ModulusInformation to the product of all the questions before + // answering any, we now have to cast down the ModulusInformation question-by-question. + info.cast_down((info_modulus / q_info_amount) * q_info_amount); + let answer_info = info.split(question.info_amount()); + question.acknowledge_answer_info(answer_info, hand_info, board); + }; + self.ask_questions(player, hand_info, callback, total_info); + } + + /// When deciding on a move, if we can choose between `total_info` choices, + /// `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(); + 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 + } + ) + } + + /// When updating on a move, if we infer that the player making the move called `get_hat_sum()` + /// and got the result `info`, we can call `self.update_from_hat_sum(info, view)` to update + /// 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(); + for other_info in other_infos { + info.subtract(&other_info); + } + let me = view.player; + if me == info_source { + assert!(info.value == 0); + } else { + let mut my_hand = self.get_player_info(&me); + self.update_from_hat_info_for_player(&me, &mut my_hand, &view.board, info); + new_player_hands.push((me, my_hand)); + } + self.set_player_infos(new_player_hands); + } + + 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 card in hand { + card_table.decrement_weight_if_possible(card); + } + } + } + info + } +} diff --git a/src/strategies/information.rs b/src/strategies/information.rs index 49cf466..ea739e2 100644 --- a/src/strategies/information.rs +++ b/src/strategies/information.rs @@ -4,99 +4,15 @@ use std::cmp::Ordering; use strategy::*; use game::*; use helpers::*; +use strategies::hat_helpers::*; // TODO: use random extra information - i.e. when casting up and down, // we sometimes have 2 choices of value to choose // TODO: guess very aggressively at very end of game (first, see whether // situation ever occurs) -#[derive(Debug,Clone)] -struct ModulusInformation { - modulus: u32, - value: u32, -} -impl ModulusInformation { - pub fn new(modulus: u32, value: u32) -> Self { - assert!(value < modulus); - ModulusInformation { - modulus: modulus, - value: value, - } - } - - pub fn none() -> Self { - Self::new(1, 0) - } - - pub fn combine(&mut self, other: Self) { - self.value = self.value + self.modulus * other.value; - self.modulus = self.modulus * other.modulus; - } - - pub fn split(&mut self, modulus: u32) -> Self { - assert!(self.modulus >= modulus); - assert!(self.modulus % modulus == 0); - let original_modulus = self.modulus; - let original_value = self.value; - self.modulus = self.modulus / modulus; - let value = self.value % modulus; - self.value = self.value / modulus; - assert!(original_modulus == modulus * self.modulus); - assert!(original_value == value + modulus * self.value); - Self::new(modulus, value) - } - - pub fn cast_up(&mut self, modulus: u32) { - assert!(self.modulus <= modulus); - self.modulus = modulus; - } - - pub fn cast_down(&mut self, modulus: u32) { - assert!(self.modulus >= modulus); - assert!(self.value < modulus); - self.modulus = modulus; - } - - pub fn add(&mut self, other: &Self) { - assert!(self.modulus == other.modulus); - self.value = (self.value + other.value) % self.modulus; - } - - pub fn subtract(&mut self, other: &Self) { - assert!(self.modulus == other.modulus); - self.value = (self.modulus + self.value - other.value) % self.modulus; - } -} - -trait Question { - // how much info does this question ask for? - fn info_amount(&self) -> u32; - // get the answer to this question, given cards - fn answer(&self, &Cards, &OwnedGameView) -> u32; - // process the answer to this question, updating card info - fn acknowledge_answer( - &self, value: u32, &mut HandInfo, &OwnedGameView - ); - - fn answer_info(&self, hand: &Cards, view: &OwnedGameView) -> ModulusInformation { - ModulusInformation::new( - self.info_amount(), - self.answer(hand, view) - ) - } - - fn acknowledge_answer_info( - &self, - answer: ModulusInformation, - hand_info: &mut HandInfo, - view: &OwnedGameView - ) { - assert!(self.info_amount() == answer.modulus); - self.acknowledge_answer(answer.value, hand_info, view); - } -} - type PropertyPredicate = fn(&BoardState, &Card) -> bool; + struct CardHasProperty { index: usize, @@ -105,20 +21,20 @@ struct CardHasProperty impl Question for CardHasProperty { fn info_amount(&self) -> u32 { 2 } - fn answer(&self, hand: &Cards, view: &OwnedGameView) -> u32 { + fn answer(&self, hand: &Cards, board: &BoardState) -> u32 { let ref card = hand[self.index]; - if (self.property)(view.get_board(), card) { 1 } else { 0 } + if (self.property)(board, card) { 1 } else { 0 } } fn acknowledge_answer( &self, answer: u32, hand_info: &mut HandInfo, - view: &OwnedGameView, + board: &BoardState, ) { let ref mut card_table = hand_info[self.index]; let possible = card_table.get_possibilities(); for card in &possible { - if (self.property)(view.get_board(), card) { + if (self.property)(board, card) { if answer == 0 { card_table.mark_false(card); } } else { if answer == 1 { card_table.mark_false(card); } @@ -133,24 +49,24 @@ fn q_is_dead(index: usize) -> CardHasProperty { CardHasProperty {index, property: |board, card| board.is_dead(card)} } +/// For some list of questions l, the question `AdditiveComboQuestion { questions : l }` asks: +/// "What is the first question in the list `l` that has a nonzero answer, and what is its +/// answer?" +/// If all questions in `l` have the answer `0`, this question has the answer `0` as well. +/// +/// It's named that way because the `info_amount` grows additively with the `info_amount`s of +/// the questions in `l`. struct AdditiveComboQuestion { - /// For some list of questions l, the question `AdditiveComboQuestion { questions : l }` asks: - /// "What is the first question in the list `l` that has a nonzero answer, and what is its - /// answer?" - /// If all questions in `l` have the answer `0`, this question has the answer `0` as well. - /// - /// It's named that way because the `info_amount` grows additively with the `info_amount`s of - /// the questions in `l`. questions: Vec>, } impl Question for AdditiveComboQuestion { fn info_amount(&self) -> u32 { self.questions.iter().map(|q| { q.info_amount() - 1 }).sum::() + 1 } - fn answer(&self, hand: &Cards, view: &OwnedGameView) -> u32 { + fn answer(&self, hand: &Cards, board: &BoardState) -> u32 { let mut toadd = 1; for q in &self.questions { - let q_answer = q.answer(hand, view); + let q_answer = q.answer(hand, board); if q_answer != 0 { return toadd + q_answer - 1; } @@ -163,7 +79,7 @@ impl Question for AdditiveComboQuestion { &self, mut answer: u32, hand_info: &mut HandInfo, - view: &OwnedGameView, + board: &BoardState, ) { if answer == 0 { answer = self.info_amount(); @@ -171,10 +87,10 @@ impl Question for AdditiveComboQuestion { answer -= 1; for q in &self.questions { if answer < q.info_amount() - 1 { - q.acknowledge_answer(answer+1, hand_info, view); + q.acknowledge_answer(answer+1, hand_info, board); return; } else { - q.acknowledge_answer(0, hand_info, view); + q.acknowledge_answer(0, hand_info, board); answer -= q.info_amount() - 1; } } @@ -182,6 +98,7 @@ impl Question for AdditiveComboQuestion { } } +#[derive(Debug)] struct CardPossibilityPartition { index: usize, n_partitions: u32, @@ -189,13 +106,13 @@ struct CardPossibilityPartition { } impl CardPossibilityPartition { fn new( - index: usize, max_n_partitions: u32, card_table: &CardPossibilityTable, view: &OwnedGameView + index: usize, max_n_partitions: u32, card_table: &CardPossibilityTable, board: &BoardState ) -> CardPossibilityPartition { let mut cur_block = 0; let mut partition = FnvHashMap::default(); let mut n_partitions = 0; - let has_dead = card_table.probability_is_dead(&view.board) != 0.0; + let has_dead = card_table.probability_is_dead(&board) != 0.0; // TODO: group things of different colors and values? let mut effective_max = max_n_partitions; @@ -204,7 +121,7 @@ impl CardPossibilityPartition { }; for card in card_table.get_possibilities() { - if !view.board.is_dead(&card) { + if !board.is_dead(&card) { partition.insert(card.clone(), cur_block); cur_block = (cur_block + 1) % effective_max; if n_partitions < effective_max { @@ -215,7 +132,7 @@ impl CardPossibilityPartition { if has_dead { for card in card_table.get_possibilities() { - if view.board.is_dead(&card) { + if board.is_dead(&card) { partition.insert(card.clone(), n_partitions); } } @@ -242,7 +159,7 @@ impl CardPossibilityPartition { } impl Question for CardPossibilityPartition { fn info_amount(&self) -> u32 { self.n_partitions } - fn answer(&self, hand: &Cards, _: &OwnedGameView) -> u32 { + fn answer(&self, hand: &Cards, _: &BoardState) -> u32 { let ref card = hand[self.index]; *self.partition.get(&card).unwrap() } @@ -250,7 +167,7 @@ impl Question for CardPossibilityPartition { &self, answer: u32, hand_info: &mut HandInfo, - _: &OwnedGameView, + _: &BoardState, ) { let ref mut card_table = hand_info[self.index]; let possible = card_table.get_possibilities(); @@ -262,74 +179,353 @@ impl Question for CardPossibilityPartition { } } -pub struct InformationStrategyConfig; - -impl InformationStrategyConfig { - pub fn new() -> InformationStrategyConfig { - InformationStrategyConfig - } -} -impl GameStrategyConfig for InformationStrategyConfig { - fn initialize(&self, _: &GameOptions) -> Box { - Box::new(InformationStrategy::new()) - } +#[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? } -pub struct InformationStrategy; - -impl InformationStrategy { - pub fn new() -> InformationStrategy { - InformationStrategy +impl MyPublicInformation { + fn get_player_info_mut(&mut self, player: &Player) -> &mut HandInfo { + self.hand_info.get_mut(player).unwrap() + } + fn take_player_info(&mut self, player: &Player) -> HandInfo { + self.hand_info.remove(player).unwrap() } -} -impl GameStrategy for InformationStrategy { - fn initialize(&self, player: Player, view: &BorrowedGameView) -> Box { - let public_info = - view.board.get_players().map(|player| { - let hand_info = HandInfo::new(view.board.hand_size); - (player, hand_info) - }).collect::>(); - Box::new(InformationPlayerStrategy { - me: player, - public_info: public_info, - public_counts: CardCounts::new(), - last_view: OwnedGameView::clone_from(view), + 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() + } + + // Returns the number of ways to hint the player. + fn get_info_per_player(&self, player: Player) -> u32 { + // Determine if both: + // - 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 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) + }) + }); + + 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 { + if card_table.probability_is_dead(&self.board) == 1.0 { + return 0; + } + if card_table.is_determined() { + return 0; + } + // Do something more intelligent? + let mut score = 1; + if !card_table.color_determined() { + score += 1; + } + if !card_table.value_determined() { + score += 1; + } + return score; + } + + 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(); + scores[0].1 + } + + // TODO: refactor out the common parts of get_hint and update_from_hint_choice + fn get_hint(&mut self, view: &OwnedGameView) -> Vec { + // Can give up to 3(n-1) hints + // For any given player with at least 4 cards, and index i, there are at least 3 hints that can be given. + // 0. a value hint on card i + // 1. a color hint on card i + // 2. any hint not involving card i + // However, if it is public info that the player has at least two colors + // and at least two numbers, then instead we do + // 2. any color hint not involving i + // 3. any color hint not involving i + + // TODO: make it so space of hints is larger when there is + // 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 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 hint_info = self.get_hat_sum(total_info, view); + + //let hint_type = hint_info.value % 3; + //let player_amt = (hint_info.value - hint_type) / 3; + let mut hint_type = hint_info.value; + let mut player_amt = 0; + while hint_type >= info_per_player[player_amt] { + hint_type -= info_per_player[player_amt]; + player_amt += 1; + } + let hint_info_we_can_give_to_this_player = info_per_player[player_amt]; + + let hint_player = (hinter + 1 + (player_amt as u32)) % view.board.num_players; + + let hand = view.get_hand(&hint_player); + let card_index = card_indices[player_amt]; + let hint_card = &hand[card_index]; + + let hint_option_set = if hint_info_we_can_give_to_this_player == 3 { + match hint_type { + 0 => { + vec![Hinted::Value(hint_card.value)] + } + 1 => { + vec![Hinted::Color(hint_card.color)] + } + 2 => { + // NOTE: this doesn't do that much better than just hinting + // the first thing that doesn't match the hint_card + let mut hint_option_set = Vec::new(); + for card in hand { + if card.color != hint_card.color { + hint_option_set.push(Hinted::Color(card.color)); + } + if card.value != hint_card.value { + hint_option_set.push(Hinted::Value(card.value)); + } + } + hint_option_set + } + _ => { + panic!("Invalid hint type") + } + } + } else { + match hint_type { + 0 => { + vec![Hinted::Value(hint_card.value)] + } + 1 => { + vec![Hinted::Color(hint_card.color)] + } + 2 => { + // Any value hint for a card other than the first + let mut hint_option_set = Vec::new(); + for card in hand { + if card.value != hint_card.value { + hint_option_set.push(Hinted::Value(card.value)); + } + } + hint_option_set + } + 3 => { + // Any color hint for a card other than the first + let mut hint_option_set = Vec::new(); + for card in hand { + if card.color != hint_card.color { + hint_option_set.push(Hinted::Color(card.color)); + } + } + hint_option_set + } + _ => { + panic!("Invalid hint type") + } + } + }; + hint_option_set.into_iter().collect::>().into_iter().map(|hinted| { + Hint { + player: hint_player, + hinted: hinted, + } + }).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 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 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 + } + } 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; + + ModulusInformation::new(total_info, hint_value) + } + + fn update_from_hint_choice(&mut self, hint: &Hint, matches: &Vec, view: &OwnedGameView) { + let info = self.decode_hint_choice(hint, matches); + self.update_from_hat_sum(info, view); + } + + fn update_from_hint_matches(&mut self, hint: &Hint, matches: &Vec) { + let info = self.get_player_info_mut(&hint.player); + info.update_for_hint(&hint.hinted, matches); + } + + fn knows_playable_card(&self, player: &Player) -> bool { + 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) + }); + has_playable_card && !self.knows_playable_card(&player) }) } -} -pub struct InformationPlayerStrategy { - me: Player, - public_info: FnvHashMap>, - public_counts: CardCounts, // what any newly drawn card should be - last_view: OwnedGameView, // the view on the previous turn -} + fn update_noone_else_needs_hint(&mut self) { + // If it becomes public knowledge that someone_else_needs_hint() returns false, + // update accordingly. + for player in self.board.get_players() { + if player != self.board.player && !self.knows_playable_card(&player) { + // If player doesn't know any playable cards, player doesn't have any playable + // cards. + let mut hand_info = self.take_player_info(&player); + for ref mut card_table in hand_info.iter_mut() { + let possible = card_table.get_possibilities(); + for card in &possible { + if self.board.is_playable(card) { + card_table.mark_false(card); + } + } + } + self.set_player_info(&player, hand_info); + } + } + } -impl InformationPlayerStrategy { + fn update_from_discard_or_play_result( + &mut self, + new_view: &BorrowedGameView, + player: &Player, + index: usize, + card: &Card + ) { + let new_card_table = CardPossibilityTable::from(&self.card_counts); + { + let info = self.get_player_info_mut(player); + assert!(info[index].is_possible(card)); + info.remove(index); - fn get_questions( - total_info: u32, - view: &OwnedGameView, - hand_info: &HandInfo, - ) -> Vec> { - let mut questions = Vec::new(); - let mut info_remaining = total_info; - - fn add_question( - questions: &mut Vec>, info_remaining: &mut u32, question: T - ) -> bool where T: Question { - *info_remaining = *info_remaining / question.info_amount(); - questions.push(Box::new(question) as Box); - - // if there's no more info to give, return that we should stop - *info_remaining <= 1 + // push *before* incrementing public counts + if info.len() < new_view.hand_size(&player) { + info.push(new_card_table); + } } - let augmented_hand_info = hand_info.iter().enumerate() + // TODO: decrement weight counts for fully determined cards, ahead of time + + for player in self.board.get_players() { + let info = self.get_player_info_mut(&player); + for card_table in info.iter_mut() { + card_table.decrement_weight_if_possible(card); + } + } + + self.card_counts.increment(card); + } +} + +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::>(); + MyPublicInformation { + hand_info: hand_info, + card_counts: CardCounts::new(), + board: board.clone(), + } + } + + fn set_board(&mut self, board: &BoardState) { + self.board = board.clone(); + } + + fn get_player_info(&self, player: &Player) -> HandInfo { + self.hand_info[player].clone() + } + + fn set_player_info(&mut self, player: &Player, hand_info: HandInfo) { + self.hand_info.insert(*player, hand_info); + } + + fn agrees_with(&self, other: Self) -> bool { + *self == other + } + + fn ask_questions( + &self, + _player: &Player, + hand_info: &mut HandInfo, + mut ask_question: Callback, + mut info_remaining: u32, + ) where Callback: FnMut(&mut HandInfo, &mut u32, Box) { + // Changing anything inside this function will not break the information transfer + // mechanisms! + + let augmented_hand_info = hand_info.iter().cloned().enumerate() .map(|(i, card_table)| { - let p_play = card_table.probability_is_playable(&view.board); - let p_dead = card_table.probability_is_dead(&view.board); + let p_play = card_table.probability_is_playable(&self.board); + let p_dead = card_table.probability_is_dead(&self.board); let is_determined = card_table.is_determined(); (card_table, i, p_play, p_dead, is_determined) }) @@ -363,13 +559,12 @@ impl InformationPlayerStrategy { } else { result.unwrap() } - }); + }); - if view.board.num_players == 5 { + if self.board.num_players == 5 { for &(_, i, _, _, _) in ask_play { - if add_question(&mut questions, &mut info_remaining, q_is_playable(i)) { - return questions; - } + ask_question(hand_info, &mut info_remaining, Box::new(q_is_playable(i))); + if info_remaining <= 1 { return; } } } else { let mut rest_combo = AdditiveComboQuestion {questions: Vec::new()}; @@ -396,16 +591,15 @@ impl InformationPlayerStrategy { } else { result.unwrap() } - }); + }); for &(_, i, _, _, _) in ask_dead { if rest_combo.info_amount() < info_remaining { rest_combo.questions.push(Box::new(q_is_dead(i))); } } } - if add_question(&mut questions, &mut info_remaining, rest_combo) { - return questions; - } + ask_question(hand_info, &mut info_remaining, Box::new(rest_combo)); + if info_remaining <= 1 { return; } } } @@ -425,110 +619,59 @@ impl InformationPlayerStrategy { } else { result.unwrap() } - }); + }); - for &(card_table, i, _, _, _) in ask_partition { - let question = CardPossibilityPartition::new(i, info_remaining, card_table, view); - if add_question(&mut questions, &mut info_remaining, question) { - return questions; - } + for &(ref card_table, i, _, _, _) in ask_partition { + let question = CardPossibilityPartition::new(i, info_remaining, &card_table, &self.board); + ask_question(hand_info, &mut info_remaining, Box::new(question)); + if info_remaining <= 1 { return; } } - - return questions } +} - fn answer_questions( - questions: &Vec>, hand: &Cards, view: &OwnedGameView - ) -> ModulusInformation { - questions.iter() - .fold( - ModulusInformation::none(), - |mut answer_info, question| { - let new_answer_info = question.answer_info(hand, view); - answer_info.combine(new_answer_info); - answer_info - }) + + +pub struct InformationStrategyConfig; + +impl InformationStrategyConfig { + pub fn new() -> InformationStrategyConfig { + InformationStrategyConfig } - - fn get_hint_info_for_player( - &self, player: &Player, total_info: u32, view: &OwnedGameView - ) -> ModulusInformation { - assert!(player != &self.me); - let hand_info = self.get_player_public_info(player); - let questions = Self::get_questions(total_info, view, hand_info); - let mut answer = Self::answer_questions(&questions, view.get_hand(player), view); - answer.cast_up(total_info); - answer +} +impl GameStrategyConfig for InformationStrategyConfig { + fn initialize(&self, _: &GameOptions) -> Box { + Box::new(InformationStrategy::new()) } +} - fn get_hint_sum_info(&self, total_info: u32, view: &OwnedGameView) -> ModulusInformation { - view.get_other_players().iter().fold( - ModulusInformation::new(total_info, 0), - |mut sum_info, player| { - let answer = self.get_hint_info_for_player(&player, total_info, view); - sum_info.add(&answer); - sum_info +pub struct InformationStrategy; + +impl InformationStrategy { + pub fn new() -> InformationStrategy { + InformationStrategy + } +} +impl GameStrategy for InformationStrategy { + fn initialize(&self, player: Player, view: &BorrowedGameView) -> Box { + Box::new(InformationPlayerStrategy { + me: player, + public_info: MyPublicInformation::new(view.board), + new_public_info: None, + last_view: OwnedGameView::clone_from(view), }) } +} - fn infer_own_from_hint_sum(&mut self, mut hint: ModulusInformation) { - let questions = { - let view = &self.last_view; - let hand_info = self.get_my_public_info(); - Self::get_questions(hint.modulus, view, hand_info) - }; - let product = questions.iter().fold(1, |a, ref b| a * b.info_amount()); - hint.cast_down(product); - - let me = self.me.clone(); - let mut hand_info = self.take_public_info(&me); - - { - let view = &self.last_view; - for question in questions { - let answer_info = hint.split(question.info_amount()); - question.acknowledge_answer_info(answer_info, &mut hand_info, view); - } - } - self.return_public_info(&me, hand_info); - } - - fn update_from_hint_sum(&mut self, mut hint: ModulusInformation) { - let hinter = self.last_view.board.player; - let players = { - let view = &self.last_view; - view.board.get_players() - }; - for player in players { - if (player != hinter) && (player != self.me) { - { - let view = &self.last_view; - let hint_info = self.get_hint_info_for_player(&player, hint.modulus, view); - hint.subtract(&hint_info); - } - - // *take* instead of borrowing mutably, because of borrow rules... - let mut hand_info = self.take_public_info(&player); - - { - let view = &self.last_view; - let hand = view.get_hand(&player); - let questions = Self::get_questions(hint.modulus, view, &mut hand_info); - for question in questions { - let answer = question.answer(hand, view); - question.acknowledge_answer(answer, &mut hand_info, view); - } - } - self.return_public_info(&player, hand_info); - } - } - if self.me == hinter { - assert!(hint.value == 0); - } else { - self.infer_own_from_hint_sum(hint); - } - } +pub struct InformationPlayerStrategy { + me: Player, + public_info: MyPublicInformation, + // Inside decide(), modify a copy of public_info and put it here. After that, when + // calling update, check that the updated public_info matches new_public_info. + new_public_info: Option, + last_view: OwnedGameView, // the view on the previous turn +} +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) }; @@ -549,12 +692,12 @@ impl InformationPlayerStrategy { (10.0 - card.value as f32) / (num_with as f32) } - fn find_useless_cards(&self, view: &OwnedGameView, 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(); for (i, card_table) in hand.iter().enumerate() { - if card_table.probability_is_dead(view.get_board()) == 1.0 { + if card_table.probability_is_dead(board) == 1.0 { useless.insert(i); } else { if let Some(card) = card_table.get_card() { @@ -573,161 +716,16 @@ impl InformationPlayerStrategy { return useless_vec; } - fn take_public_info(&mut self, player: &Player) -> HandInfo { - self.public_info.remove(player).unwrap() - } - - fn return_public_info(&mut self, player: &Player, card_info: HandInfo) { - self.public_info.insert(*player, card_info); - } - - fn get_my_public_info(&self) -> &HandInfo { - self.get_player_public_info(&self.me) - } - - // fn get_my_public_info_mut(&mut self) -> &mut HandInfo { - // let me = self.me.clone(); - // self.get_player_public_info_mut(&me) - // } - - fn get_player_public_info(&self, player: &Player) -> &HandInfo { - self.public_info.get(player).unwrap() - } - - fn get_player_public_info_mut(&mut self, player: &Player) -> &mut HandInfo { - self.public_info.get_mut(player).unwrap() - } - - fn update_public_info_for_hint(&mut self, hint: &Hint, matches: &Vec) { - let info = self.get_player_public_info_mut(&hint.player); - info.update_for_hint(&hint.hinted, matches); - } - - fn knows_playable_card(&self, player: &Player) -> bool { - self.get_player_public_info(&player).iter().any(|table| { - table.probability_is_playable(self.last_view.get_board()) == 1.0 - }) - } - - fn someone_else_needs_hint(&self) -> bool { - // Does another player have a playable card, but doesn't know it? - let view = &self.last_view; - view.get_other_players().iter().any(|player| { - 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) - }) - } - - fn update_noone_else_needs_hint(&mut self) { - // If it becomes public knowledge that someone_else_needs_hint() returns false, - // update accordingly. - for player in self.last_view.board.get_players() { - if player != self.last_view.board.player { - if !self.knows_playable_card(&player) { - // If player doesn't know any playable cards, player doesn't have any playable - // cards. - let mut hand_info = self.take_public_info(&player); - for ref mut card_table in hand_info.iter_mut() { - let view = &self.last_view; - let possible = card_table.get_possibilities(); - for card in &possible { - if view.get_board().is_playable(card) { - card_table.mark_false(card); - } - } - } - self.return_public_info(&player, hand_info); - } - } - } - } - - fn update_public_info_for_discard_or_play( - &mut self, - view: &BorrowedGameView, - player: &Player, - index: usize, - card: &Card - ) { - let new_card_table = CardPossibilityTable::from(&self.public_counts); - { - let info = self.get_player_public_info_mut(&player); - assert!(info[index].is_possible(card)); - info.remove(index); - - // push *before* incrementing public counts - if info.len() < view.hand_size(&player) { - info.push(new_card_table); - } - } - - // TODO: decrement weight counts for fully determined cards, ahead of time - - // note: other_player could be player, as well - // in particular, we will decrement the newly drawn card - for other_player in view.board.get_players() { - let info = self.get_player_public_info_mut(&other_player); - for card_table in info.iter_mut() { - card_table.decrement_weight_if_possible(card); - } - } - - self.public_counts.increment(card); - } - - fn get_private_info(&self, view: &OwnedGameView) -> HandInfo { - let mut info = self.get_my_public_info().clone(); - for card_table in info.iter_mut() { - for (_, hand) in &view.other_hands { - for card in hand { - card_table.decrement_weight_if_possible(card); - } - } - } - info - } - - fn get_hint_index_score(&self, card_table: &CardPossibilityTable, view: &OwnedGameView) -> i32 { - if card_table.probability_is_dead(view.get_board()) == 1.0 { - return 0; - } - if card_table.is_determined() { - return 0; - } - // Do something more intelligent? - let mut score = 1; - if !card_table.color_determined() { - score += 1; - } - if !card_table.value_determined() { - score += 1; - } - return score; - } - - fn get_index_for_hint(&self, info: &HandInfo, view: &OwnedGameView) -> usize { - let mut scores = info.iter().enumerate().map(|(i, card_table)| { - let score = self.get_hint_index_score(card_table, view); - (-score, i) - }).collect::>(); - scores.sort(); - scores[0].1 - } - // how good is it to give this hint to this player? - fn hint_goodness(&self, hinted: &Hinted, hint_player: &Player, view: &OwnedGameView) -> f32 { - let hand = view.get_hand(&hint_player); + fn hint_goodness(&self, hint: &Hint, view: &OwnedGameView) -> f32 { + // This gets called after self.public_info.get_hint(), which modifies the public + // info to include information gained through question answering. Therefore, we only + // simulate information gained through the hint result here. - // get post-hint hand_info - let mut hand_info = self.get_player_public_info(hint_player).clone(); - let total_info = view.get_other_players().into_iter().map(|player| self.get_info_per_player(player)).sum(); - let questions = Self::get_questions(total_info, view, &hand_info); - for question in questions { - let answer = question.answer(hand, view); - question.acknowledge_answer(answer, &mut hand_info, view); - } + let hint_player = &hint.player; + let hinted = &hint.hinted; + let hand = view.get_hand(&hint_player); + let mut hand_info = self.public_info.get_player_info(&hint_player); let mut goodness = 1.0; for (i, card_table) in hand_info.iter_mut().enumerate() { @@ -763,41 +761,15 @@ impl InformationPlayerStrategy { goodness } - // Returns the number of ways to hint the player. - fn get_info_per_player(&self, player: Player) -> u32 { - let info = self.get_player_public_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_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 } - - // Determine if both: - // - it is public that there are at least two colors - // - it is public that there are at least two numbers - } - - fn get_other_players_starting_after(&self, player: Player) -> Vec { - let view = &self.last_view; - let n = view.board.num_players; - (0 .. n - 1).into_iter().map(|i| { (player + 1 + i) % n }).collect() - } - - fn get_best_hint_of_options(&self, hint_player: Player, hint_option_set: FnvHashSet) -> Hinted { + fn get_best_hint_of_options(&self, mut hints: Vec) -> Hint { + if hints.len() == 1 { + return hints.remove(0); + } let view = &self.last_view; // using hint goodness barely helps - let mut hint_options = hint_option_set.into_iter().map(|hinted| { - (self.hint_goodness(&hinted, &hint_player, view), hinted) + let mut hint_options = hints.into_iter().map(|hint| { + (self.hint_goodness(&hint, view), hint) }).collect::>(); hint_options.sort_by(|h1, h2| { @@ -814,173 +786,25 @@ impl InformationPlayerStrategy { hint_options.remove(0).1 } - fn get_hint(&self) -> TurnChoice { - let view = &self.last_view; - - // Can give up to 3(n-1) hints - // For any given player with at least 4 cards, and index i, there are at least 3 hints that can be given. - // 0. a value hint on card i - // 1. a color hint on card i - // 2. any hint not involving card i - // However, if it is public info that the player has at least two colors - // and at least two numbers, then instead we do - // 2. any color hint not involving i - // 3. any color hint not involving i - - // TODO: make it so space of hints is larger when there is - // knowledge about the cards? - - let info_per_player: Vec = self.get_other_players_starting_after(self.me).iter().map( - |player| { self.get_info_per_player(*player) } - ).collect(); - let total_info = info_per_player.iter().fold(0, |a, b| a + b); - - let hint_info = self.get_hint_sum_info(total_info, view); - - //let hint_type = hint_info.value % 3; - //let player_amt = (hint_info.value - hint_type) / 3; - let mut hint_type = hint_info.value; - let mut player_amt = 0; - while hint_type >= info_per_player[player_amt] { - hint_type -= info_per_player[player_amt]; - player_amt += 1; - } - let hint_info_we_can_give_to_this_player = info_per_player[player_amt]; - - let hint_player = (self.me + 1 + (player_amt as u32)) % view.board.num_players; - - let hand = view.get_hand(&hint_player); - let card_index = self.get_index_for_hint(self.get_player_public_info(&hint_player), view); - let hint_card = &hand[card_index]; - - let hinted = if hint_info_we_can_give_to_this_player == 3 { - match hint_type { - 0 => { - Hinted::Value(hint_card.value) - } - 1 => { - Hinted::Color(hint_card.color) - } - 2 => { - // NOTE: this doesn't do that much better than just hinting - // the first thing that doesn't match the hint_card - let mut hint_option_set = FnvHashSet::default(); - for card in hand { - if card.color != hint_card.color { - hint_option_set.insert(Hinted::Color(card.color)); - } - if card.value != hint_card.value { - hint_option_set.insert(Hinted::Value(card.value)); - } - } - self.get_best_hint_of_options(hint_player, hint_option_set) - } - _ => { - panic!("Invalid hint type") - } - } - } else { - match hint_type { - 0 => { - Hinted::Value(hint_card.value) - } - 1 => { - Hinted::Color(hint_card.color) - } - 2 => { - // Any value hint for a card other than the first - let mut hint_option_set = FnvHashSet::default(); - for card in hand { - if card.value != hint_card.value { - hint_option_set.insert(Hinted::Value(card.value)); - } - } - self.get_best_hint_of_options(hint_player, hint_option_set) - } - 3 => { - // Any color hint for a card other than the first - let mut hint_option_set = FnvHashSet::default(); - for card in hand { - if card.color != hint_card.color { - hint_option_set.insert(Hinted::Color(card.color)); - } - } - self.get_best_hint_of_options(hint_player, hint_option_set) - } - _ => { - panic!("Invalid hint type") - } - } - }; - - TurnChoice::Hint(Hint { - player: hint_player, - hinted: hinted, - }) - } - - fn infer_from_hint(&mut self, hint: &Hint, result: &Vec) { - let hinter = self.last_view.board.player; - - let info_per_player: Vec = self.get_other_players_starting_after(hinter).iter().map( - |player| { self.get_info_per_player(*player) } - ).collect(); - let total_info = info_per_player.iter().fold(0, |a, b| a + b); - - let n = self.last_view.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 hint_info_we_can_give_to_this_player = info_per_player[player_amt as usize]; - - let card_index = self.get_index_for_hint(self.get_player_public_info(&hint.player), &self.last_view); - 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 - } - } 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; - - let mod_info = ModulusInformation::new(total_info, hint_value); - - self.update_from_hint_sum(mod_info); - } -} - -impl PlayerStrategy for InformationPlayerStrategy { - fn decide(&mut self, _: &BorrowedGameView) -> TurnChoice { + /// Decide on a move. At the same time, simulate the impact of that move on the public + /// information state by modifying `public_info`. Since `self` is immutable and since our + /// public information state change will be compared against the change in the corresponding + /// call to `update_wrapped`, nothing we do here will let our public information state silently + /// get out of sync with other players' public information state! + fn decide_wrapped(&mut self, public_info: &mut MyPublicInformation) -> TurnChoice { // we already stored the view let view = &self.last_view; + let me = &view.player; for player in view.board.get_players() { - let hand_info = self.get_player_public_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); } } - let private_info = self.get_private_info(view); + let private_info = public_info.get_private_info(view); // debug!("My info:"); // for (i, card_table) in private_info.iter().enumerate() { // debug!("{}: {}", i, card_table); @@ -1039,12 +863,12 @@ impl PlayerStrategy for InformationPlayerStrategy { } } - let public_useless_indices = self.find_useless_cards(view, &self.get_my_public_info()); - let useless_indices = self.find_useless_cards(view, &private_info); + 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 && self.someone_else_needs_hint() { true } + 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). @@ -1054,13 +878,24 @@ impl PlayerStrategy for InformationPlayerStrategy { else { false }; if will_hint { - return self.get_hint(); + let hint_set = public_info.get_hint(view); + let hint = self.get_best_hint_of_options(hint_set); + return TurnChoice::Hint(hint); + } + + // We update on the discard choice before updating on the fact that we're discarding to + // match pre-refactor behavior. + // TODO: change this in the next commit! + let discard_info = if public_useless_indices.len() > 1 { + Some(public_info.get_hat_sum(public_useless_indices.len() as u32, view)) + } else { None }; + if self.last_view.board.hints_remaining > 0 { + public_info.update_noone_else_needs_hint(); } // if anything is totally useless, discard it if public_useless_indices.len() > 1 { - let info = self.get_hint_sum_info(public_useless_indices.len() as u32, view); - return TurnChoice::Discard(public_useless_indices[info.value as usize]); + return TurnChoice::Discard(public_useless_indices[discard_info.unwrap().value as usize]); } else if useless_indices.len() > 0 { // TODO: have opponents infer that i knew a card was useless // TODO: after that, potentially prefer useless indices that arent public @@ -1087,39 +922,79 @@ impl PlayerStrategy for InformationPlayerStrategy { TurnChoice::Discard(index) } + /// Update the public information. The "update" operations on the public information state have to + /// exactly match the corresponding "choice" operations in `decide_wrapped()`. + /// + /// We don't have to update on the "turn result" here. If the turn was a hint, we get the + /// matches in order to understand the "intention" behind the hint, but we do not need to + /// update on what the hint says about the hinted player's cards directly. (This is done in the + /// call to `update_hint_matches()` inside `update()`. + fn update_wrapped( + &mut self, + turn_player: &Player, + turn_choice: &TurnChoice, + hint_matches: Option<&Vec>, + ) { + match turn_choice { + TurnChoice::Hint(ref hint) => { + let matches = hint_matches.unwrap(); + 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) + ); + + // TODO: reorder these blocks in the next commit! + 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); + self.public_info.update_from_hat_sum(info, &self.last_view); + } + if self.last_view.board.hints_remaining > 0 { + self.public_info.update_noone_else_needs_hint(); + } + } + TurnChoice::Play(_index) => { + // TODO: Maybe we can transfer information through plays as well? + } + } + } +} + +impl PlayerStrategy for InformationPlayerStrategy { + fn decide(&mut self, _: &BorrowedGameView) -> TurnChoice { + let mut public_info = self.public_info.clone(); + let turn_choice = self.decide_wrapped(&mut public_info); + self.new_public_info = Some(public_info); + turn_choice + } + 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 }; + 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!"); + } + } match turn_record.choice { TurnChoice::Hint(ref hint) => { if let &TurnResult::Hint(ref matches) = &turn_record.result { - self.infer_from_hint(hint, matches); - self.update_public_info_for_hint(hint, matches); + self.public_info.update_from_hint_matches(hint, matches); } else { panic!("Got turn choice {:?}, but turn result {:?}", turn_record.choice, turn_record.result); } } TurnChoice::Discard(index) => { - let known_useless_indices = self.find_useless_cards( - &self.last_view, &self.get_player_public_info(&turn_record.player) - ); - - 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(); - self.update_from_hint_sum(ModulusInformation::new( - known_useless_indices.len() as u32, value as u32 - )); - } - if self.last_view.board.hints_remaining > 0 { - // TODO It would be more information-efficient to do this before the call to - // update_from_hint_sum(). To do that, we would have to restructure decide() - // as well. - self.update_noone_else_needs_hint(); - } - if let &TurnResult::Discard(ref card) = &turn_record.result { - self.update_public_info_for_discard_or_play(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); @@ -1127,7 +1002,7 @@ impl PlayerStrategy for InformationPlayerStrategy { } TurnChoice::Play(index) => { if let &TurnResult::Play(ref card, _) = &turn_record.result { - self.update_public_info_for_discard_or_play(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); @@ -1135,5 +1010,6 @@ impl PlayerStrategy for InformationPlayerStrategy { } } self.last_view = OwnedGameView::clone_from(view); + self.public_info.set_board(view.board); } }