From adf98e0e4a9357334db78f4537cd189e7f11e92f Mon Sep 17 00:00:00 2001 From: Jeff Wu Date: Mon, 28 Mar 2016 22:16:58 -0700 Subject: [PATCH] information strategy, working! --- src/cards.rs | 2 +- src/game.rs | 131 +++++++-- src/info.rs | 29 +- src/simulator.rs | 8 +- src/strategies/cheating.rs | 115 ++++---- src/strategies/examples.rs | 6 +- src/strategies/information.rs | 494 ++++++++++++++++++++++++++++------ 7 files changed, 604 insertions(+), 181 deletions(-) diff --git a/src/cards.rs b/src/cards.rs index 7ddcd9e..ca64c9b 100644 --- a/src/cards.rs +++ b/src/cards.rs @@ -92,7 +92,7 @@ impl fmt::Display for CardCounts { } } -#[derive(Debug)] +#[derive(Debug,Clone)] pub struct Discard { pub cards: Cards, counts: CardCounts, diff --git a/src/game.rs b/src/game.rs index 8ab79aa..c0288da 100644 --- a/src/game.rs +++ b/src/game.rs @@ -66,7 +66,7 @@ pub struct GameOptions { } // The state of a given player: all other players may see this -#[derive(Debug)] +#[derive(Debug,Clone)] pub struct PlayerState { // the player's actual hand pub hand: Cards, @@ -154,7 +154,7 @@ fn new_deck(seed: u32) -> Cards { // State of everything except the player's hands // Is all completely common knowledge -#[derive(Debug)] +#[derive(Debug,Clone)] pub struct BoardState { deck: Cards, pub total_cards: u32, @@ -377,9 +377,50 @@ impl fmt::Display for BoardState { } // complete game view of a given player -// state will be borrowed GameState +pub trait GameView { + fn me(&self) -> Player; + fn my_info(&self) -> &Vec; + fn get_state(&self, player: &Player) -> &PlayerState; + fn get_board(&self) -> &BoardState; + + fn get_hand(&self, player: &Player) -> &Cards { + assert!(self.me() != *player, "Cannot query about your own cards!"); + &self.get_state(player).hand + } + + fn hand_size(&self, player: &Player) -> usize { + if self.me() == *player { + self.my_info().len() + } else { + self.get_hand(player).len() + } + } + + fn has_card(&self, player: &Player, card: &Card) -> bool { + for other_card in self.get_hand(player) { + if *card == *other_card { + return true; + } + } + false + } + + fn can_see(&self, card: &Card) -> bool { + for other_player in self.get_board().get_players() { + if self.me() == other_player { + continue + } + if self.has_card(&other_player, card) { + return true; + } + } + false + } +} + +// version of game view that is borrowed. used in simulator for efficiency, #[derive(Debug)] -pub struct GameStateView<'a> { +pub struct BorrowedGameView<'a> { // the player whose view it is pub player: Player, // what is known about their own hand (and thus common knowledge) @@ -389,30 +430,70 @@ pub struct GameStateView<'a> { // board state pub board: &'a BoardState, } -impl <'a> GameStateView<'a> { - pub fn get_hand(&self, player: &Player) -> &Cards { - assert!(self.player != *player, "Cannot query about your own cards!"); - &self.other_player_states.get(player).unwrap().hand +impl <'a> GameView for BorrowedGameView<'a> { + fn me(&self) -> Player { + self.player } - - pub fn has_card(&self, player: &Player, card: &Card) -> bool { - for other_card in self.get_hand(player) { - if *card == *other_card { - return true; - } - } - false + fn my_info(&self) -> &Vec { + self.info } - pub fn can_see(&self, card: &Card) -> bool { - for other_player in self.other_player_states.keys() { - if self.has_card(other_player, card) { - return true; - } - } - false + fn get_state(&self, player: &Player) -> &PlayerState { + assert!(self.me() != *player, "Cannot query about your own state!"); + self.other_player_states.get(player).unwrap() + } + fn get_board(&self) -> &BoardState { + self.board } } +// version of game view, may be useful to strategies +#[derive(Debug)] +pub struct OwnedGameView { + // the player whose view it is + pub player: Player, + // what is known about their own hand (and thus common knowledge) + pub info: Vec, + // the cards of the other players, as well as the information they have + pub other_player_states: HashMap, + // board state + pub board: BoardState, +} +impl OwnedGameView { + pub fn clone_from(borrowed_view: &BorrowedGameView) -> OwnedGameView { + let mut info : Vec = Vec::new(); + for card_info in borrowed_view.info.iter() { + info.push((*card_info).clone()); + } + let mut other_player_states : HashMap = HashMap::new(); + for (other_player, player_state) in &borrowed_view.other_player_states { + other_player_states.insert(*other_player, (*player_state).clone()); + } + + OwnedGameView { + player: borrowed_view.player.clone(), + info: info, + other_player_states: other_player_states, + board: (*borrowed_view.board).clone(), + } + } +} +impl GameView for OwnedGameView { + fn me(&self) -> Player { + self.player + } + fn my_info(&self) -> &Vec { + &self.info + } + fn get_state(&self, player: &Player) -> &PlayerState { + assert!(self.me() != *player, "Cannot query about your own state!"); + self.other_player_states.get(player).unwrap() + } + fn get_board(&self) -> &BoardState { + &self.board + } +} + + // complete game state (known to nobody!) #[derive(Debug)] pub struct GameState { @@ -471,14 +552,14 @@ impl GameState { } // get the game state view of a particular player - pub fn get_view(&self, player: Player) -> GameStateView { + pub fn get_view(&self, player: Player) -> BorrowedGameView { let mut other_player_states = HashMap::new(); for (other_player, state) in &self.player_states { if player != *other_player { other_player_states.insert(*other_player, state); } } - GameStateView { + BorrowedGameView { player: player, info: &self.player_states.get(&player).unwrap().info, other_player_states: other_player_states, diff --git a/src/info.rs b/src/info.rs index 0293287..74cc19f 100644 --- a/src/info.rs +++ b/src/info.rs @@ -151,7 +151,7 @@ pub trait Info where T: Hash + Eq + Clone { } } -#[derive(Debug)] +#[derive(Debug,Clone)] pub struct ColorInfo(HashSet); impl ColorInfo { pub fn new() -> ColorInfo { ColorInfo(ColorInfo::initialize()) } @@ -162,7 +162,7 @@ impl Info for ColorInfo { fn get_mut_possibility_set(&mut self) -> &mut HashSet { &mut self.0 } } -#[derive(Debug)] +#[derive(Debug,Clone)] pub struct ValueInfo(HashSet); impl ValueInfo { pub fn new() -> ValueInfo { ValueInfo(ValueInfo::initialize()) } @@ -175,7 +175,7 @@ impl Info for ValueInfo { // represents information only of the form: // this color is/isn't possible, this value is/isn't possible -#[derive(Debug)] +#[derive(Debug,Clone)] pub struct SimpleCardInfo { pub color_info: ColorInfo, pub value_info: ValueInfo, @@ -260,10 +260,25 @@ impl CardPossibilityTable { } pub fn decrement_weight(&mut self, card: &Card) { - let weight = - self.possible.get_mut(card) - .expect(&format!("Decrementing weight for impossible card: {}", card)); - *weight -= 1; + let remove = { + let weight = + self.possible.get_mut(card) + .expect(&format!("Decrementing weight for impossible card: {}", card)); + *weight -= 1; + *weight == 0 + }; + if remove { + self.possible.remove(card); + } + } + + pub fn get_card(&self) -> Option { + let possibilities = self.get_possibilities(); + if possibilities.len() == 1 { + Some(possibilities[0].clone()) + } else { + None + } } } impl <'a> From<&'a CardCounts> for CardPossibilityTable { diff --git a/src/simulator.rs b/src/simulator.rs index ca770da..b520f55 100644 --- a/src/simulator.rs +++ b/src/simulator.rs @@ -10,17 +10,17 @@ use game::*; // Represents the strategy of a given player pub trait PlayerStrategy { // A function to decide what to do on the player's turn. - // Given a GameStateView, outputs their choice. - fn decide(&mut self, &GameStateView) -> TurnChoice; + // Given a BorrowedGameView, outputs their choice. + fn decide(&mut self, &BorrowedGameView) -> TurnChoice; // A function to update internal state after other players' turns. // Given what happened last turn, and the new state. - fn update(&mut self, &Turn, &GameStateView); + fn update(&mut self, &Turn, &BorrowedGameView); } // Represents the overall strategy for a game // Shouldn't do much, except store configuration parameters and // possibility initialize some shared randomness between players pub trait GameStrategy { - fn initialize(&self, Player, &GameStateView) -> Box; + fn initialize(&self, Player, &BorrowedGameView) -> Box; } // Represents configuration for a strategy. diff --git a/src/strategies/cheating.rs b/src/strategies/cheating.rs index 06c0527..4e702b4 100644 --- a/src/strategies/cheating.rs +++ b/src/strategies/cheating.rs @@ -1,6 +1,6 @@ +use std::rc::Rc; use std::cell::{RefCell}; use std::collections::{HashMap, HashSet}; -use std::rc::Rc; use simulator::*; use game::*; @@ -43,7 +43,7 @@ impl CheatingStrategy { } } impl GameStrategy for CheatingStrategy { - fn initialize(&self, player: Player, view: &GameStateView) -> Box { + fn initialize(&self, player: Player, view: &BorrowedGameView) -> Box { for (player, state) in &view.other_player_states { self.player_states_cheat.borrow_mut().insert( *player, state.hand.clone() @@ -62,7 +62,7 @@ pub struct CheatingPlayerStrategy { } impl CheatingPlayerStrategy { // last player might've drawn a new card, let him know! - fn inform_last_player_cards(&self, view: &GameStateView) { + fn inform_last_player_cards(&self, view: &BorrowedGameView) { let next = view.board.player_to_right(&self.me); self.player_states_cheat.borrow_mut().insert( next, view.other_player_states.get(&next).unwrap().hand.clone() @@ -70,7 +70,7 @@ impl CheatingPlayerStrategy { } // give a throwaway hint - we only do this when we have nothing to do - fn throwaway_hint(&self, view: &GameStateView) -> TurnChoice { + fn throwaway_hint(&self, view: &BorrowedGameView) -> TurnChoice { let hint_player = view.board.player_to_left(&self.me); let hint_card = &view.get_hand(&hint_player).first().unwrap(); TurnChoice::Hint(Hint { @@ -80,7 +80,7 @@ impl CheatingPlayerStrategy { } // given a hand of cards, represents how badly it will need to play things - fn hand_play_value(&self, view: &GameStateView, hand: &Cards/*, all_viewable: HashMap> */) -> u32 { + fn hand_play_value(&self, view: &BorrowedGameView, hand: &Cards/*, all_viewable: HashMap> */) -> u32 { // dead = 0 points // indispensible = 5 + (5 - value) points // playable, not in another hand = 2 point @@ -102,7 +102,7 @@ impl CheatingPlayerStrategy { } // how badly do we need to play a particular card - fn get_play_score(&self, view: &GameStateView, card: &Card) -> i32 { + fn get_play_score(&self, view: &BorrowedGameView, card: &Card) -> i32 { let states = self.player_states_cheat.borrow(); let my_hand = states.get(&self.me).unwrap(); @@ -124,7 +124,7 @@ impl CheatingPlayerStrategy { 20 - (card.value as i32) } - fn find_useless_card(&self, view: &GameStateView, hand: &Cards) -> Option { + fn find_useless_card(&self, view: &BorrowedGameView, hand: &Cards) -> Option { let mut set: HashSet = HashSet::new(); for (i, card) in hand.iter().enumerate() { @@ -140,7 +140,7 @@ impl CheatingPlayerStrategy { return None } - fn someone_else_can_play(&self, view: &GameStateView) -> bool { + fn someone_else_can_play(&self, view: &BorrowedGameView) -> bool { for player in view.board.get_players() { if player != self.me { for card in view.get_hand(&player) { @@ -154,7 +154,7 @@ impl CheatingPlayerStrategy { } } impl PlayerStrategy for CheatingPlayerStrategy { - fn decide(&mut self, view: &GameStateView) -> TurnChoice { + fn decide(&mut self, view: &BorrowedGameView) -> TurnChoice { self.inform_last_player_cards(view); let states = self.player_states_cheat.borrow(); @@ -180,63 +180,64 @@ impl PlayerStrategy for CheatingPlayerStrategy { let index = my_cards.iter().position(|card| { card == play_card.unwrap() }).unwrap(); - TurnChoice::Play(index) - } else { - // 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 - - (COLORS.len() * VALUES.len()) as u32 - - (view.board.num_players * view.board.hand_size); - if view.board.discard_size() <= discard_threshold { - // if anything is totally useless, discard it - if let Some(i) = self.find_useless_card(view, my_cards) { - return TurnChoice::Discard(i); - } - } - // hinting is better than discarding dead cards - // (probably because it stalls the deck-drawing). - if view.board.hints_remaining > 0 { - if self.someone_else_can_play(view) { - return self.throwaway_hint(view); - } - } + 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 + - (COLORS.len() * VALUES.len()) as u32 + - (view.board.num_players * view.board.hand_size); + if view.board.discard_size() <= discard_threshold { // if anything is totally useless, discard it if let Some(i) = self.find_useless_card(view, my_cards) { return TurnChoice::Discard(i); } + } - // All cards are plausibly useful. - // Play the best discardable card, according to the ordering induced by comparing - // (is in another hand, is dispensable, value) - // The higher, the better to discard - let mut discard_card = None; - let mut compval = (false, false, 0); - for card in my_cards { - let my_compval = ( - view.can_see(card), - view.board.is_dispensable(card), - card.value, - ); - if my_compval > compval { - discard_card = Some(card); - compval = my_compval; - } - } - if let Some(card) = discard_card { - let index = my_cards.iter().position(|iter_card| { - card == iter_card - }).unwrap(); - TurnChoice::Discard(index) - } else { - panic!("This shouldn't happen! No discardable card"); + // hinting is better than discarding dead cards + // (probably because it stalls the deck-drawing). + if view.board.hints_remaining > 0 { + if self.someone_else_can_play(view) { + return self.throwaway_hint(view); } } + + // if anything is totally useless, discard it + if let Some(i) = self.find_useless_card(view, my_cards) { + return TurnChoice::Discard(i); + } + + // All cards are plausibly useful. + // Play the best discardable card, according to the ordering induced by comparing + // (is in another hand, is dispensable, value) + // The higher, the better to discard + let mut discard_card = None; + let mut compval = (false, false, 0); + for card in my_cards { + let my_compval = ( + view.can_see(card), + view.board.is_dispensable(card), + card.value, + ); + if my_compval > compval { + discard_card = Some(card); + compval = my_compval; + } + } + if let Some(card) = discard_card { + let index = my_cards.iter().position(|iter_card| { + card == iter_card + }).unwrap(); + TurnChoice::Discard(index) + } else { + panic!("This shouldn't happen! No discardable card"); + } } - fn update(&mut self, _: &Turn, _: &GameStateView) { + fn update(&mut self, _: &Turn, _: &BorrowedGameView) { } } diff --git a/src/strategies/examples.rs b/src/strategies/examples.rs index 60924c6..ff1c3df 100644 --- a/src/strategies/examples.rs +++ b/src/strategies/examples.rs @@ -24,7 +24,7 @@ pub struct RandomStrategy { play_probability: f64, } impl GameStrategy for RandomStrategy { - fn initialize(&self, player: Player, _: &GameStateView) -> Box { + fn initialize(&self, player: Player, _: &BorrowedGameView) -> Box { Box::new(RandomStrategyPlayer { hint_probability: self.hint_probability, play_probability: self.play_probability, @@ -40,7 +40,7 @@ pub struct RandomStrategyPlayer { } impl PlayerStrategy for RandomStrategyPlayer { - fn decide(&mut self, view: &GameStateView) -> TurnChoice { + fn decide(&mut self, view: &BorrowedGameView) -> TurnChoice { let p = rand::random::(); if p < self.hint_probability { if view.board.hints_remaining > 0 { @@ -67,6 +67,6 @@ impl PlayerStrategy for RandomStrategyPlayer { TurnChoice::Discard(0) } } - fn update(&mut self, _: &Turn, _: &GameStateView) { + fn update(&mut self, _: &Turn, _: &BorrowedGameView) { } } diff --git a/src/strategies/information.rs b/src/strategies/information.rs index 10f1746..784f998 100644 --- a/src/strategies/information.rs +++ b/src/strategies/information.rs @@ -1,5 +1,4 @@ use std::collections::{HashMap, HashSet}; -use rand::{self, Rng}; use simulator::*; use game::*; @@ -13,41 +12,150 @@ use game::*; // - only 9 + 8 hints total. each player goes through 12.5 cards // // For any given player with at least 4 cards, and index i, there are at least 3 hints that can be given. -// 1. a value hint on card i -// 2. a color hint on card i -// 3. any hint not involving card i +// 0. a value hint on card i +// 1. a color hint on card i +// 2. any hint not involving card i // // for 4 players, can give 6 distinct hints +// TODO: currently, you need to be very careful due to +// answers changing from the old view to the new view + +#[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, + } + } -enum Question { - IsPlayable(usize), - IsDead(usize), -} + pub fn none() -> Self { + Self::new(1, 0) + } -fn answer_question(question: Question, hand: &Cards, view: &GameStateView) -> ModulusInformation { - match question { - Question::IsPlayable(index) => { - let ref card = hand[index]; - ModulusInformation { - modulus: 2, - value: if view.board.is_playable(card) { 1 } else { 0 }, - } - }, - Question::IsDead(index) => { - let ref card = hand[index]; - ModulusInformation { - modulus: 2, - value: if view.board.is_dead(card) { 1 } else { 0 }, - } - }, + pub fn combine(&mut self, other: Self) { + self.value = self.value * other.modulus + other.value; + self.modulus = self.modulus * other.modulus; + } + + pub fn emit(&mut self, modulus: u32) -> Self { + assert!(self.modulus >= modulus); + assert!(self.modulus % modulus == 0); + self.modulus = self.modulus / modulus; + let value = self.value / self.modulus; + assert!((self.value - value) % modulus == 0); + self.value = (self.value - value) / modulus; + + 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, Box<&GameView>) -> u32; + fn answer_info(&self, hand: &Cards, view: Box<&GameView>) -> ModulusInformation { + ModulusInformation::new( + self.info_amount(), + self.answer(hand, view) + ) + } + // process the answer to this question, updating card info + fn acknowledge_answer( + &self, value: u32, &mut Vec, Box<&GameView> + ); + + fn acknowledge_answer_info( + &self, + answer: ModulusInformation, + hand_info: &mut Vec, + view: Box<&GameView> + ) { + assert!(self.info_amount() == answer.modulus); + self.acknowledge_answer(answer.value, hand_info, view); + } +} +struct IsPlayable { + index: usize, +} +impl Question for IsPlayable { + fn info_amount(&self) -> u32 { 2 } + fn answer(&self, hand: &Cards, view: Box<&GameView>) -> u32 { + let ref card = hand[self.index]; + if view.get_board().is_playable(card) { 1 } else { 0 } + } + fn acknowledge_answer( + &self, + answer: u32, + hand_info: &mut Vec, + view: Box<&GameView>, + ) { + let ref mut card_table = hand_info[self.index]; + let possible = card_table.get_possibilities(); + for card in &possible { + if view.get_board().is_playable(card) { + if answer == 0 { card_table.mark_false(card); } + } else { + if answer == 1 { card_table.mark_false(card); } + } + } + } +} +// struct IsDead { +// index: usize, +// } +// impl Question for IsDead { +// fn info_amount(&self) -> u32 { 2 } +// fn answer(&self, hand: &Cards, view: &Box) -> u32 { +// let ref card = hand[self.index]; +// if view.get_board().is_dead(card) { 1 } else { 0 } +// } +// fn acknowledge_answer( +// &self, +// answer: u32, +// hand_info: &mut Vec, +// view: &Box, +// ) { +// let ref mut card_table = hand_info[self.index]; +// let possible = card_table.get_possibilities(); +// for card in &possible { +// if view.get_board().is_dead(card) { +// if answer == 0 { card_table.mark_false(card); } +// } else { +// if answer == 1 { card_table.mark_false(card); } +// } +// } +// } +// } + #[allow(dead_code)] pub struct InformationStrategyConfig; @@ -73,7 +181,7 @@ impl InformationStrategy { } } impl GameStrategy for InformationStrategy { - fn initialize(&self, player: Player, view: &GameStateView) -> Box { + fn initialize(&self, player: Player, view: &BorrowedGameView) -> Box { let mut public_info = HashMap::new(); for player in view.board.get_players() { let hand_info = (0..view.board.hand_size).map(|_| { CardPossibilityTable::new() }).collect::>(); @@ -83,6 +191,7 @@ impl GameStrategy for InformationStrategy { me: player, public_info: public_info, public_counts: CardCounts::new(), + last_view: OwnedGameView::clone_from(view), }) } } @@ -91,10 +200,147 @@ pub struct InformationPlayerStrategy { me: Player, public_info: HashMap>, public_counts: CardCounts, // what any newly drawn card should be + last_view: OwnedGameView, // the view on the previous turn } impl InformationPlayerStrategy { + + fn get_questions( + total_info: u32, + view: &T, + hand_info: &Vec, + ) -> Vec> + where T: GameView + { + let mut questions = Vec::new(); + let mut info_remaining = total_info; + + while info_remaining > 1 { + let mut question = None; + for (i, card_table) in hand_info.iter().enumerate() { + let p = view.get_board().probability_is_playable(card_table); + if (p != 0.0) && (p != 1.0) { + question = Some(Box::new(IsPlayable {index: i}) as Box); + break; + } + } + if let Some(q) = question { + info_remaining = info_remaining / q.info_amount(); + questions.push(q); + } else { + break; + } + } + questions + } + + fn answer_questions( + questions: &Vec>, hand: &Cards, view: &T + ) -> ModulusInformation + where T: GameView + { + let mut info = ModulusInformation::none(); + for question in questions { + let answer_info = question.answer_info(hand, Box::new(view as &GameView)); + info.combine(answer_info); + } + info + } + + fn get_hint_info_for_player( + &self, player: &Player, total_info: u32, view: &T + ) -> ModulusInformation where T: GameView + { + assert!(player != &self.me); + let hand_info = self.get_player_public_info(player); + let questions = Self::get_questions(total_info, view, hand_info); + trace!("Getting hint for player {}, questions {:?}", player, questions.len()); + let mut answer = Self::answer_questions(&questions, view.get_hand(player), view); + answer.cast_up(total_info); + trace!("Resulting answer {:?}", answer); + answer + } + + fn get_hint_sum(&self, total_info: u32, view: &T) -> ModulusInformation + where T: GameView + { + let mut sum = ModulusInformation::new(total_info, 0); + for player in view.get_board().get_players() { + if player != self.me { + let answer = self.get_hint_info_for_player(&player, total_info, view); + sum.add(&answer); + } + } + trace!("Summed answer {:?}\n", sum); + sum + } + + fn infer_own_from_hint_sum(&mut self, hint: ModulusInformation) { + let mut hint = hint; + let questions = { + let view = &self.last_view; + let hand_info = self.get_my_public_info(); + Self::get_questions(hint.modulus, view, hand_info) + }; + trace!("{}: Questions {:?}", self.me, questions.len()); + let product = questions.iter().fold(1, |a, ref b| a * b.info_amount()); + trace!("{}: Product {}, hint: {:?}", self.me, product, hint); + hint.cast_down(product); + trace!("{}: Inferred for myself {:?}", self.me, hint); + + 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.emit(question.info_amount()); + question.acknowledge_answer_info(answer_info, &mut hand_info, Box::new(view as &GameView)); + } + } + self.return_public_info(&me, hand_info); + } + + fn update_from_hint_sum(&mut self, hint: ModulusInformation) { + let hinter = self.last_view.board.player; + let players = { + let view = &self.last_view; + view.board.get_players() + }; + trace!("{}: inferring for myself, starting with {:?}", self.me, hint); + let mut hint = hint; + 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); + trace!("{}: subtracted for {}, now {:?}", self.me, player, hint); + } + + // *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, Box::new(view as &GameView)); + question.acknowledge_answer(answer, &mut hand_info, Box::new(view as &GameView)); + } + } + self.return_public_info(&player, hand_info); + } + } + if self.me == hinter { + assert!(hint.value == 0); + } else { + self.infer_own_from_hint_sum(hint); + } + } + // given a hand of cards, represents how badly it will need to play things - fn hand_play_value(&self, view: &GameStateView, hand: &Cards/*, all_viewable: HashMap> */) -> u32 { + fn hand_play_value(&self, view: &BorrowedGameView, hand: &Cards/*, all_viewable: HashMap> */) -> u32 { // dead = 0 points // indispensible = 5 + (5 - value) points // playable = 1 point @@ -112,19 +358,20 @@ impl InformationPlayerStrategy { value } - fn estimate_hand_play_value(&self, view: &GameStateView) -> u32 { + fn estimate_hand_play_value(&self, view: &BorrowedGameView) -> u32 { + // TODO: fix this 0 } // how badly do we need to play a particular card - fn get_average_play_score(&self, view: &GameStateView, card_table: &CardPossibilityTable) -> f32 { + fn get_average_play_score(&self, view: &BorrowedGameView, card_table: &CardPossibilityTable) -> f32 { let f = |card: &Card| { self.get_play_score(view, card) as f32 }; card_table.weighted_score(&f) } - fn get_play_score(&self, view: &GameStateView, card: &Card) -> i32 { + fn get_play_score(&self, view: &BorrowedGameView, card: &Card) -> i32 { let my_hand_value = self.estimate_hand_play_value(view); for player in view.board.get_players() { @@ -143,23 +390,25 @@ impl InformationPlayerStrategy { 5 + (5 - (card.value as i32)) } - fn find_useless_card(&self, view: &GameStateView, hand: &Cards) -> Option { + fn find_useless_card(&self, view: &BorrowedGameView, hand: &Vec) -> Option { let mut set: HashSet = HashSet::new(); - for (i, card) in hand.iter().enumerate() { - if view.board.is_dead(card) { + for (i, card_table) in hand.iter().enumerate() { + if view.board.probability_is_dead(card_table) == 1.0 { return Some(i); } - if set.contains(card) { - // found a duplicate card - return Some(i); + if let Some(card) = card_table.get_card() { + if set.contains(&card) { + // found a duplicate card + return Some(i); + } + set.insert(card); } - set.insert(card.clone()); } return None } - fn someone_else_can_play(&self, view: &GameStateView) -> bool { + fn someone_else_can_play(&self, view: &BorrowedGameView) -> bool { for player in view.board.get_players() { if player != self.me { for card in view.get_hand(&player) { @@ -172,6 +421,18 @@ impl InformationPlayerStrategy { false } + fn take_public_info(&mut self, player: &Player) -> Vec { + self.public_info.remove(player).unwrap() + } + + fn return_public_info(&mut self, player: &Player, card_info: Vec) { + self.public_info.insert(*player, card_info); + } + + fn get_my_public_info(&self) -> &Vec { + self.get_player_public_info(&self.me) + } + fn get_player_public_info(&self, player: &Player) -> &Vec { self.public_info.get(player).unwrap() } @@ -200,7 +461,7 @@ impl InformationPlayerStrategy { fn update_public_info_for_discard_or_play( &mut self, - view: &GameStateView, + view: &BorrowedGameView, player: &Player, index: usize, card: &Card @@ -212,7 +473,7 @@ impl InformationPlayerStrategy { info.remove(index); // push *before* incrementing public counts - if info.len() < view.info.len() { + if info.len() < view.hand_size(&player) { info.push(new_card_table); } } @@ -220,7 +481,7 @@ impl InformationPlayerStrategy { // 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 mut info = self.get_player_public_info_mut(&other_player); + let info = self.get_player_public_info_mut(&other_player); for card_table in info { card_table.decrement_weight_if_possible(card); } @@ -229,10 +490,10 @@ impl InformationPlayerStrategy { self.public_counts.increment(card); } - fn get_private_info(&self, view: &GameStateView) -> Vec { - let mut info = self.get_player_public_info(&self.me).clone(); + fn get_private_info(&self, view: &BorrowedGameView) -> Vec { + let mut info = self.get_my_public_info().clone(); for card_table in info.iter_mut() { - for (other_player, state) in &view.other_player_states { + for (_, state) in &view.other_player_states { for card in &state.hand { card_table.decrement_weight_if_possible(card); } @@ -241,9 +502,81 @@ impl InformationPlayerStrategy { info } + fn get_hint(&self, view: &BorrowedGameView) -> TurnChoice { + let total_info = 3 * (view.board.num_players - 1); + + let hint_info = self.get_hint_sum(total_info, view); + + let hint_type = hint_info.value % 3; + let player_amt = (hint_info.value - hint_type) / 3; + + let hint_player = (self.me + 1 + player_amt) % view.board.num_players; + let card_index = 0; + + let hand = view.get_hand(&hint_player); + let hint_card = &hand[card_index]; + + let hinted = match hint_type { + 0 => { + Hinted::Value(hint_card.value) + } + 1 => { + Hinted::Color(hint_card.color) + } + 2 => { + let mut hinted_opt = None; + for card in hand { + if card.color != hint_card.color { + hinted_opt = Some(Hinted::Color(card.color)); + break; + } + if card.value != hint_card.value { + hinted_opt = Some(Hinted::Value(card.value)); + break; + } + } + if let Some(hinted) = hinted_opt { + hinted + } else { + panic!("Found nothing to hint!") + } + } + _ => { + panic!("Invalid hint type") + } + }; + + TurnChoice::Hint(Hint { + player: hint_player, + hinted: hinted, + }) + } + + fn infer_from_hint(&mut self, view: &BorrowedGameView, hint: &Hint, result: &Vec) { + let total_info = 3 * (view.board.num_players - 1); + + let hinter = self.last_view.board.player; + let player_amt = (view.board.num_players + hint.player - hinter - 1) % view.board.num_players; + + let hint_type = if result[0] { + match hint.hinted { + Hinted::Value(_) => 0, + Hinted::Color(_) => 1, + } + } else { + 2 + }; + + let hint_value = player_amt * 3 + 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, view: &GameStateView) -> TurnChoice { + fn decide(&mut self, view: &BorrowedGameView) -> TurnChoice { let private_info = self.get_private_info(view); // debug!("My info:"); // for (i, card_table) in private_info.iter().enumerate() { @@ -268,48 +601,33 @@ impl PlayerStrategy for InformationPlayerStrategy { } } - TurnChoice::Play(play_index) - } else { - 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 hinted = { - if rand::random() { - // hint a color - Hinted::Color(hint_card.color) - } else { - Hinted::Value(hint_card.value) - } - }; - TurnChoice::Hint(Hint { - player: hint_player, - hinted: hinted, - }) - } else { - TurnChoice::Discard(0) + return TurnChoice::Play(play_index) + } + + 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 { + // if anything is totally useless, discard it + if let Some(i) = self.find_useless_card(view, &private_info) { + return TurnChoice::Discard(i); } } - // // 50 total, 25 to play, 20 in hand - // if view.board.discard.cards.len() < 6 { - // // if anything is totally useless, discard it - // if let Some(i) = self.find_useless_card(view) { - // return TurnChoice::Discard(i); - // } - // } + // hinting is better than discarding dead cards + // (probably because it stalls the deck-drawing). + if view.board.hints_remaining > 1 { + if self.someone_else_can_play(view) { + return self.get_hint(view); + } + } - // // hinting is better than discarding dead cards - // // (probably because it stalls the deck-drawing). - // if view.board.hints_remaining > 1 { - // if self.someone_else_can_play(view) { - // return self.throwaway_hint(view); - // } - // } - - // // if anything is totally useless, discard it - // if let Some(i) = self.find_useless_card(view) { - // return TurnChoice::Discard(i); - // } + // if anything is totally useless, discard it + if let Some(i) = self.find_useless_card(view, &private_info) { + return TurnChoice::Discard(i); + } // // All cards are plausibly useful. // // Play the best discardable card, according to the ordering induced by comparing @@ -343,12 +661,19 @@ impl PlayerStrategy for InformationPlayerStrategy { // panic!("This shouldn't happen! No discardable card"); // } // } + + if view.board.hints_remaining > 0 { + self.get_hint(view) + } else { + TurnChoice::Discard(0) + } } - fn update(&mut self, turn: &Turn, view: &GameStateView) { + fn update(&mut self, turn: &Turn, view: &BorrowedGameView) { match turn.choice { TurnChoice::Hint(ref hint) => { if let &TurnResult::Hint(ref matches) = &turn.result { + self.infer_from_hint(view, hint, matches); self.update_public_info_for_hint(hint, matches); } else { panic!("Got turn choice {:?}, but turn result {:?}", @@ -364,7 +689,7 @@ impl PlayerStrategy for InformationPlayerStrategy { } } TurnChoice::Play(index) => { - if let &TurnResult::Play(ref card, played) = &turn.result { + if let &TurnResult::Play(ref card, _) = &turn.result { self.update_public_info_for_discard_or_play(view, &turn.player, index, card); } else { panic!("Got turn choice {:?}, but turn result {:?}", @@ -372,5 +697,6 @@ impl PlayerStrategy for InformationPlayerStrategy { } } } + self.last_view = OwnedGameView::clone_from(view); } }