information strategy, working!
This commit is contained in:
parent
efba24d6e8
commit
adf98e0e4a
7 changed files with 604 additions and 181 deletions
|
@ -92,7 +92,7 @@ impl fmt::Display for CardCounts {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug,Clone)]
|
||||||
pub struct Discard {
|
pub struct Discard {
|
||||||
pub cards: Cards,
|
pub cards: Cards,
|
||||||
counts: CardCounts,
|
counts: CardCounts,
|
||||||
|
|
131
src/game.rs
131
src/game.rs
|
@ -66,7 +66,7 @@ pub struct GameOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
// The state of a given player: all other players may see this
|
// The state of a given player: all other players may see this
|
||||||
#[derive(Debug)]
|
#[derive(Debug,Clone)]
|
||||||
pub struct PlayerState {
|
pub struct PlayerState {
|
||||||
// the player's actual hand
|
// the player's actual hand
|
||||||
pub hand: Cards,
|
pub hand: Cards,
|
||||||
|
@ -154,7 +154,7 @@ fn new_deck(seed: u32) -> Cards {
|
||||||
|
|
||||||
// State of everything except the player's hands
|
// State of everything except the player's hands
|
||||||
// Is all completely common knowledge
|
// Is all completely common knowledge
|
||||||
#[derive(Debug)]
|
#[derive(Debug,Clone)]
|
||||||
pub struct BoardState {
|
pub struct BoardState {
|
||||||
deck: Cards,
|
deck: Cards,
|
||||||
pub total_cards: u32,
|
pub total_cards: u32,
|
||||||
|
@ -377,9 +377,50 @@ impl fmt::Display for BoardState {
|
||||||
}
|
}
|
||||||
|
|
||||||
// complete game view of a given player
|
// complete game view of a given player
|
||||||
// state will be borrowed GameState
|
pub trait GameView {
|
||||||
|
fn me(&self) -> Player;
|
||||||
|
fn my_info(&self) -> &Vec<SimpleCardInfo>;
|
||||||
|
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)]
|
#[derive(Debug)]
|
||||||
pub struct GameStateView<'a> {
|
pub struct BorrowedGameView<'a> {
|
||||||
// the player whose view it is
|
// the player whose view it is
|
||||||
pub player: Player,
|
pub player: Player,
|
||||||
// what is known about their own hand (and thus common knowledge)
|
// what is known about their own hand (and thus common knowledge)
|
||||||
|
@ -389,30 +430,70 @@ pub struct GameStateView<'a> {
|
||||||
// board state
|
// board state
|
||||||
pub board: &'a BoardState,
|
pub board: &'a BoardState,
|
||||||
}
|
}
|
||||||
impl <'a> GameStateView<'a> {
|
impl <'a> GameView for BorrowedGameView<'a> {
|
||||||
pub fn get_hand(&self, player: &Player) -> &Cards {
|
fn me(&self) -> Player {
|
||||||
assert!(self.player != *player, "Cannot query about your own cards!");
|
self.player
|
||||||
&self.other_player_states.get(player).unwrap().hand
|
|
||||||
}
|
}
|
||||||
|
fn my_info(&self) -> &Vec<SimpleCardInfo> {
|
||||||
pub fn has_card(&self, player: &Player, card: &Card) -> bool {
|
self.info
|
||||||
for other_card in self.get_hand(player) {
|
|
||||||
if *card == *other_card {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
}
|
||||||
pub fn can_see(&self, card: &Card) -> bool {
|
fn get_state(&self, player: &Player) -> &PlayerState {
|
||||||
for other_player in self.other_player_states.keys() {
|
assert!(self.me() != *player, "Cannot query about your own state!");
|
||||||
if self.has_card(other_player, card) {
|
self.other_player_states.get(player).unwrap()
|
||||||
return true;
|
}
|
||||||
}
|
fn get_board(&self) -> &BoardState {
|
||||||
}
|
self.board
|
||||||
false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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<SimpleCardInfo>,
|
||||||
|
// the cards of the other players, as well as the information they have
|
||||||
|
pub other_player_states: HashMap<Player, PlayerState>,
|
||||||
|
// board state
|
||||||
|
pub board: BoardState,
|
||||||
|
}
|
||||||
|
impl OwnedGameView {
|
||||||
|
pub fn clone_from(borrowed_view: &BorrowedGameView) -> OwnedGameView {
|
||||||
|
let mut info : Vec<SimpleCardInfo> = Vec::new();
|
||||||
|
for card_info in borrowed_view.info.iter() {
|
||||||
|
info.push((*card_info).clone());
|
||||||
|
}
|
||||||
|
let mut other_player_states : HashMap<Player, PlayerState> = 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<SimpleCardInfo> {
|
||||||
|
&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!)
|
// complete game state (known to nobody!)
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct GameState {
|
pub struct GameState {
|
||||||
|
@ -471,14 +552,14 @@ impl GameState {
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the game state view of a particular player
|
// 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();
|
let mut other_player_states = HashMap::new();
|
||||||
for (other_player, state) in &self.player_states {
|
for (other_player, state) in &self.player_states {
|
||||||
if player != *other_player {
|
if player != *other_player {
|
||||||
other_player_states.insert(*other_player, state);
|
other_player_states.insert(*other_player, state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
GameStateView {
|
BorrowedGameView {
|
||||||
player: player,
|
player: player,
|
||||||
info: &self.player_states.get(&player).unwrap().info,
|
info: &self.player_states.get(&player).unwrap().info,
|
||||||
other_player_states: other_player_states,
|
other_player_states: other_player_states,
|
||||||
|
|
29
src/info.rs
29
src/info.rs
|
@ -151,7 +151,7 @@ pub trait Info<T> where T: Hash + Eq + Clone {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug,Clone)]
|
||||||
pub struct ColorInfo(HashSet<Color>);
|
pub struct ColorInfo(HashSet<Color>);
|
||||||
impl ColorInfo {
|
impl ColorInfo {
|
||||||
pub fn new() -> ColorInfo { ColorInfo(ColorInfo::initialize()) }
|
pub fn new() -> ColorInfo { ColorInfo(ColorInfo::initialize()) }
|
||||||
|
@ -162,7 +162,7 @@ impl Info<Color> for ColorInfo {
|
||||||
fn get_mut_possibility_set(&mut self) -> &mut HashSet<Color> { &mut self.0 }
|
fn get_mut_possibility_set(&mut self) -> &mut HashSet<Color> { &mut self.0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug,Clone)]
|
||||||
pub struct ValueInfo(HashSet<Value>);
|
pub struct ValueInfo(HashSet<Value>);
|
||||||
impl ValueInfo {
|
impl ValueInfo {
|
||||||
pub fn new() -> ValueInfo { ValueInfo(ValueInfo::initialize()) }
|
pub fn new() -> ValueInfo { ValueInfo(ValueInfo::initialize()) }
|
||||||
|
@ -175,7 +175,7 @@ impl Info<Value> for ValueInfo {
|
||||||
|
|
||||||
// represents information only of the form:
|
// represents information only of the form:
|
||||||
// this color is/isn't possible, this value is/isn't possible
|
// this color is/isn't possible, this value is/isn't possible
|
||||||
#[derive(Debug)]
|
#[derive(Debug,Clone)]
|
||||||
pub struct SimpleCardInfo {
|
pub struct SimpleCardInfo {
|
||||||
pub color_info: ColorInfo,
|
pub color_info: ColorInfo,
|
||||||
pub value_info: ValueInfo,
|
pub value_info: ValueInfo,
|
||||||
|
@ -260,10 +260,25 @@ impl CardPossibilityTable {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn decrement_weight(&mut self, card: &Card) {
|
pub fn decrement_weight(&mut self, card: &Card) {
|
||||||
let weight =
|
let remove = {
|
||||||
self.possible.get_mut(card)
|
let weight =
|
||||||
.expect(&format!("Decrementing weight for impossible card: {}", card));
|
self.possible.get_mut(card)
|
||||||
*weight -= 1;
|
.expect(&format!("Decrementing weight for impossible card: {}", card));
|
||||||
|
*weight -= 1;
|
||||||
|
*weight == 0
|
||||||
|
};
|
||||||
|
if remove {
|
||||||
|
self.possible.remove(card);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_card(&self) -> Option<Card> {
|
||||||
|
let possibilities = self.get_possibilities();
|
||||||
|
if possibilities.len() == 1 {
|
||||||
|
Some(possibilities[0].clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl <'a> From<&'a CardCounts> for CardPossibilityTable {
|
impl <'a> From<&'a CardCounts> for CardPossibilityTable {
|
||||||
|
|
|
@ -10,17 +10,17 @@ use game::*;
|
||||||
// Represents the strategy of a given player
|
// Represents the strategy of a given player
|
||||||
pub trait PlayerStrategy {
|
pub trait PlayerStrategy {
|
||||||
// A function to decide what to do on the player's turn.
|
// A function to decide what to do on the player's turn.
|
||||||
// Given a GameStateView, outputs their choice.
|
// Given a BorrowedGameView, outputs their choice.
|
||||||
fn decide(&mut self, &GameStateView) -> TurnChoice;
|
fn decide(&mut self, &BorrowedGameView) -> TurnChoice;
|
||||||
// A function to update internal state after other players' turns.
|
// A function to update internal state after other players' turns.
|
||||||
// Given what happened last turn, and the new state.
|
// 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
|
// Represents the overall strategy for a game
|
||||||
// Shouldn't do much, except store configuration parameters and
|
// Shouldn't do much, except store configuration parameters and
|
||||||
// possibility initialize some shared randomness between players
|
// possibility initialize some shared randomness between players
|
||||||
pub trait GameStrategy {
|
pub trait GameStrategy {
|
||||||
fn initialize(&self, Player, &GameStateView) -> Box<PlayerStrategy>;
|
fn initialize(&self, Player, &BorrowedGameView) -> Box<PlayerStrategy>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Represents configuration for a strategy.
|
// Represents configuration for a strategy.
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
|
use std::rc::Rc;
|
||||||
use std::cell::{RefCell};
|
use std::cell::{RefCell};
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use simulator::*;
|
use simulator::*;
|
||||||
use game::*;
|
use game::*;
|
||||||
|
@ -43,7 +43,7 @@ impl CheatingStrategy {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl GameStrategy for CheatingStrategy {
|
impl GameStrategy for CheatingStrategy {
|
||||||
fn initialize(&self, player: Player, view: &GameStateView) -> Box<PlayerStrategy> {
|
fn initialize(&self, player: Player, view: &BorrowedGameView) -> Box<PlayerStrategy> {
|
||||||
for (player, state) in &view.other_player_states {
|
for (player, state) in &view.other_player_states {
|
||||||
self.player_states_cheat.borrow_mut().insert(
|
self.player_states_cheat.borrow_mut().insert(
|
||||||
*player, state.hand.clone()
|
*player, state.hand.clone()
|
||||||
|
@ -62,7 +62,7 @@ pub struct CheatingPlayerStrategy {
|
||||||
}
|
}
|
||||||
impl CheatingPlayerStrategy {
|
impl CheatingPlayerStrategy {
|
||||||
// last player might've drawn a new card, let him know!
|
// 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);
|
let next = view.board.player_to_right(&self.me);
|
||||||
self.player_states_cheat.borrow_mut().insert(
|
self.player_states_cheat.borrow_mut().insert(
|
||||||
next, view.other_player_states.get(&next).unwrap().hand.clone()
|
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
|
// 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_player = view.board.player_to_left(&self.me);
|
||||||
let hint_card = &view.get_hand(&hint_player).first().unwrap();
|
let hint_card = &view.get_hand(&hint_player).first().unwrap();
|
||||||
TurnChoice::Hint(Hint {
|
TurnChoice::Hint(Hint {
|
||||||
|
@ -80,7 +80,7 @@ impl CheatingPlayerStrategy {
|
||||||
}
|
}
|
||||||
|
|
||||||
// given a hand of cards, represents how badly it will need to play things
|
// 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<Color, <Value, u32>> */) -> u32 {
|
fn hand_play_value(&self, view: &BorrowedGameView, hand: &Cards/*, all_viewable: HashMap<Color, <Value, u32>> */) -> u32 {
|
||||||
// dead = 0 points
|
// dead = 0 points
|
||||||
// indispensible = 5 + (5 - value) points
|
// indispensible = 5 + (5 - value) points
|
||||||
// playable, not in another hand = 2 point
|
// playable, not in another hand = 2 point
|
||||||
|
@ -102,7 +102,7 @@ impl CheatingPlayerStrategy {
|
||||||
}
|
}
|
||||||
|
|
||||||
// how badly do we need to play a particular card
|
// 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 states = self.player_states_cheat.borrow();
|
||||||
let my_hand = states.get(&self.me).unwrap();
|
let my_hand = states.get(&self.me).unwrap();
|
||||||
|
|
||||||
|
@ -124,7 +124,7 @@ impl CheatingPlayerStrategy {
|
||||||
20 - (card.value as i32)
|
20 - (card.value as i32)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_useless_card(&self, view: &GameStateView, hand: &Cards) -> Option<usize> {
|
fn find_useless_card(&self, view: &BorrowedGameView, hand: &Cards) -> Option<usize> {
|
||||||
let mut set: HashSet<Card> = HashSet::new();
|
let mut set: HashSet<Card> = HashSet::new();
|
||||||
|
|
||||||
for (i, card) in hand.iter().enumerate() {
|
for (i, card) in hand.iter().enumerate() {
|
||||||
|
@ -140,7 +140,7 @@ impl CheatingPlayerStrategy {
|
||||||
return None
|
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() {
|
for player in view.board.get_players() {
|
||||||
if player != self.me {
|
if player != self.me {
|
||||||
for card in view.get_hand(&player) {
|
for card in view.get_hand(&player) {
|
||||||
|
@ -154,7 +154,7 @@ impl CheatingPlayerStrategy {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl PlayerStrategy for 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);
|
self.inform_last_player_cards(view);
|
||||||
|
|
||||||
let states = self.player_states_cheat.borrow();
|
let states = self.player_states_cheat.borrow();
|
||||||
|
@ -180,63 +180,64 @@ impl PlayerStrategy for CheatingPlayerStrategy {
|
||||||
let index = my_cards.iter().position(|card| {
|
let index = my_cards.iter().position(|card| {
|
||||||
card == play_card.unwrap()
|
card == play_card.unwrap()
|
||||||
}).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
|
return TurnChoice::Play(index)
|
||||||
// (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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// 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 anything is totally useless, discard it
|
||||||
if let Some(i) = self.find_useless_card(view, my_cards) {
|
if let Some(i) = self.find_useless_card(view, my_cards) {
|
||||||
return TurnChoice::Discard(i);
|
return TurnChoice::Discard(i);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// All cards are plausibly useful.
|
// hinting is better than discarding dead cards
|
||||||
// Play the best discardable card, according to the ordering induced by comparing
|
// (probably because it stalls the deck-drawing).
|
||||||
// (is in another hand, is dispensable, value)
|
if view.board.hints_remaining > 0 {
|
||||||
// The higher, the better to discard
|
if self.someone_else_can_play(view) {
|
||||||
let mut discard_card = None;
|
return self.throwaway_hint(view);
|
||||||
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");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ pub struct RandomStrategy {
|
||||||
play_probability: f64,
|
play_probability: f64,
|
||||||
}
|
}
|
||||||
impl GameStrategy for RandomStrategy {
|
impl GameStrategy for RandomStrategy {
|
||||||
fn initialize(&self, player: Player, _: &GameStateView) -> Box<PlayerStrategy> {
|
fn initialize(&self, player: Player, _: &BorrowedGameView) -> Box<PlayerStrategy> {
|
||||||
Box::new(RandomStrategyPlayer {
|
Box::new(RandomStrategyPlayer {
|
||||||
hint_probability: self.hint_probability,
|
hint_probability: self.hint_probability,
|
||||||
play_probability: self.play_probability,
|
play_probability: self.play_probability,
|
||||||
|
@ -40,7 +40,7 @@ pub struct RandomStrategyPlayer {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlayerStrategy for RandomStrategyPlayer {
|
impl PlayerStrategy for RandomStrategyPlayer {
|
||||||
fn decide(&mut self, view: &GameStateView) -> TurnChoice {
|
fn decide(&mut self, view: &BorrowedGameView) -> TurnChoice {
|
||||||
let p = rand::random::<f64>();
|
let p = rand::random::<f64>();
|
||||||
if p < self.hint_probability {
|
if p < self.hint_probability {
|
||||||
if view.board.hints_remaining > 0 {
|
if view.board.hints_remaining > 0 {
|
||||||
|
@ -67,6 +67,6 @@ impl PlayerStrategy for RandomStrategyPlayer {
|
||||||
TurnChoice::Discard(0)
|
TurnChoice::Discard(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn update(&mut self, _: &Turn, _: &GameStateView) {
|
fn update(&mut self, _: &Turn, _: &BorrowedGameView) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use rand::{self, Rng};
|
|
||||||
|
|
||||||
use simulator::*;
|
use simulator::*;
|
||||||
use game::*;
|
use game::*;
|
||||||
|
@ -13,41 +12,150 @@ use game::*;
|
||||||
// - only 9 + 8 hints total. each player goes through 12.5 cards
|
// - 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.
|
// 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
|
// 0. a value hint on card i
|
||||||
// 2. a color hint on card i
|
// 1. a color hint on card i
|
||||||
// 3. any hint not involving card i
|
// 2. any hint not involving card i
|
||||||
//
|
//
|
||||||
// for 4 players, can give 6 distinct hints
|
// 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 {
|
struct ModulusInformation {
|
||||||
modulus: u32,
|
modulus: u32,
|
||||||
value: u32,
|
value: u32,
|
||||||
}
|
}
|
||||||
|
impl ModulusInformation {
|
||||||
|
pub fn new(modulus: u32, value: u32) -> Self {
|
||||||
|
assert!(value < modulus);
|
||||||
|
ModulusInformation {
|
||||||
|
modulus: modulus,
|
||||||
|
value: value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
enum Question {
|
pub fn none() -> Self {
|
||||||
IsPlayable(usize),
|
Self::new(1, 0)
|
||||||
IsDead(usize),
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fn answer_question(question: Question, hand: &Cards, view: &GameStateView) -> ModulusInformation {
|
pub fn combine(&mut self, other: Self) {
|
||||||
match question {
|
self.value = self.value * other.modulus + other.value;
|
||||||
Question::IsPlayable(index) => {
|
self.modulus = self.modulus * other.modulus;
|
||||||
let ref card = hand[index];
|
}
|
||||||
ModulusInformation {
|
|
||||||
modulus: 2,
|
pub fn emit(&mut self, modulus: u32) -> Self {
|
||||||
value: if view.board.is_playable(card) { 1 } else { 0 },
|
assert!(self.modulus >= modulus);
|
||||||
}
|
assert!(self.modulus % modulus == 0);
|
||||||
},
|
self.modulus = self.modulus / modulus;
|
||||||
Question::IsDead(index) => {
|
let value = self.value / self.modulus;
|
||||||
let ref card = hand[index];
|
assert!((self.value - value) % modulus == 0);
|
||||||
ModulusInformation {
|
self.value = (self.value - value) / modulus;
|
||||||
modulus: 2,
|
|
||||||
value: if view.board.is_dead(card) { 1 } else { 0 },
|
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<CardPossibilityTable>, Box<&GameView>
|
||||||
|
);
|
||||||
|
|
||||||
|
fn acknowledge_answer_info(
|
||||||
|
&self,
|
||||||
|
answer: ModulusInformation,
|
||||||
|
hand_info: &mut Vec<CardPossibilityTable>,
|
||||||
|
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<CardPossibilityTable>,
|
||||||
|
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<GameView>) -> 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<CardPossibilityTable>,
|
||||||
|
// 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_dead(card) {
|
||||||
|
// if answer == 0 { card_table.mark_false(card); }
|
||||||
|
// } else {
|
||||||
|
// if answer == 1 { card_table.mark_false(card); }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub struct InformationStrategyConfig;
|
pub struct InformationStrategyConfig;
|
||||||
|
|
||||||
|
@ -73,7 +181,7 @@ impl InformationStrategy {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl GameStrategy for InformationStrategy {
|
impl GameStrategy for InformationStrategy {
|
||||||
fn initialize(&self, player: Player, view: &GameStateView) -> Box<PlayerStrategy> {
|
fn initialize(&self, player: Player, view: &BorrowedGameView) -> Box<PlayerStrategy> {
|
||||||
let mut public_info = HashMap::new();
|
let mut public_info = HashMap::new();
|
||||||
for player in view.board.get_players() {
|
for player in view.board.get_players() {
|
||||||
let hand_info = (0..view.board.hand_size).map(|_| { CardPossibilityTable::new() }).collect::<Vec<_>>();
|
let hand_info = (0..view.board.hand_size).map(|_| { CardPossibilityTable::new() }).collect::<Vec<_>>();
|
||||||
|
@ -83,6 +191,7 @@ impl GameStrategy for InformationStrategy {
|
||||||
me: player,
|
me: player,
|
||||||
public_info: public_info,
|
public_info: public_info,
|
||||||
public_counts: CardCounts::new(),
|
public_counts: CardCounts::new(),
|
||||||
|
last_view: OwnedGameView::clone_from(view),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -91,10 +200,147 @@ pub struct InformationPlayerStrategy {
|
||||||
me: Player,
|
me: Player,
|
||||||
public_info: HashMap<Player, Vec<CardPossibilityTable>>,
|
public_info: HashMap<Player, Vec<CardPossibilityTable>>,
|
||||||
public_counts: CardCounts, // what any newly drawn card should be
|
public_counts: CardCounts, // what any newly drawn card should be
|
||||||
|
last_view: OwnedGameView, // the view on the previous turn
|
||||||
}
|
}
|
||||||
impl InformationPlayerStrategy {
|
impl InformationPlayerStrategy {
|
||||||
|
|
||||||
|
fn get_questions<T>(
|
||||||
|
total_info: u32,
|
||||||
|
view: &T,
|
||||||
|
hand_info: &Vec<CardPossibilityTable>,
|
||||||
|
) -> Vec<Box<Question>>
|
||||||
|
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<Question>);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(q) = question {
|
||||||
|
info_remaining = info_remaining / q.info_amount();
|
||||||
|
questions.push(q);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
questions
|
||||||
|
}
|
||||||
|
|
||||||
|
fn answer_questions<T>(
|
||||||
|
questions: &Vec<Box<Question>>, 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<T>(
|
||||||
|
&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<T>(&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
|
// 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<Color, <Value, usize>> */) -> u32 {
|
fn hand_play_value(&self, view: &BorrowedGameView, hand: &Cards/*, all_viewable: HashMap<Color, <Value, usize>> */) -> u32 {
|
||||||
// dead = 0 points
|
// dead = 0 points
|
||||||
// indispensible = 5 + (5 - value) points
|
// indispensible = 5 + (5 - value) points
|
||||||
// playable = 1 point
|
// playable = 1 point
|
||||||
|
@ -112,19 +358,20 @@ impl InformationPlayerStrategy {
|
||||||
value
|
value
|
||||||
}
|
}
|
||||||
|
|
||||||
fn estimate_hand_play_value(&self, view: &GameStateView) -> u32 {
|
fn estimate_hand_play_value(&self, view: &BorrowedGameView) -> u32 {
|
||||||
|
// TODO: fix this
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
||||||
// how badly do we need to play a particular card
|
// 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| {
|
let f = |card: &Card| {
|
||||||
self.get_play_score(view, card) as f32
|
self.get_play_score(view, card) as f32
|
||||||
};
|
};
|
||||||
card_table.weighted_score(&f)
|
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);
|
let my_hand_value = self.estimate_hand_play_value(view);
|
||||||
|
|
||||||
for player in view.board.get_players() {
|
for player in view.board.get_players() {
|
||||||
|
@ -143,23 +390,25 @@ impl InformationPlayerStrategy {
|
||||||
5 + (5 - (card.value as i32))
|
5 + (5 - (card.value as i32))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_useless_card(&self, view: &GameStateView, hand: &Cards) -> Option<usize> {
|
fn find_useless_card(&self, view: &BorrowedGameView, hand: &Vec<CardPossibilityTable>) -> Option<usize> {
|
||||||
let mut set: HashSet<Card> = HashSet::new();
|
let mut set: HashSet<Card> = HashSet::new();
|
||||||
|
|
||||||
for (i, card) in hand.iter().enumerate() {
|
for (i, card_table) in hand.iter().enumerate() {
|
||||||
if view.board.is_dead(card) {
|
if view.board.probability_is_dead(card_table) == 1.0 {
|
||||||
return Some(i);
|
return Some(i);
|
||||||
}
|
}
|
||||||
if set.contains(card) {
|
if let Some(card) = card_table.get_card() {
|
||||||
// found a duplicate card
|
if set.contains(&card) {
|
||||||
return Some(i);
|
// found a duplicate card
|
||||||
|
return Some(i);
|
||||||
|
}
|
||||||
|
set.insert(card);
|
||||||
}
|
}
|
||||||
set.insert(card.clone());
|
|
||||||
}
|
}
|
||||||
return None
|
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() {
|
for player in view.board.get_players() {
|
||||||
if player != self.me {
|
if player != self.me {
|
||||||
for card in view.get_hand(&player) {
|
for card in view.get_hand(&player) {
|
||||||
|
@ -172,6 +421,18 @@ impl InformationPlayerStrategy {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn take_public_info(&mut self, player: &Player) -> Vec<CardPossibilityTable> {
|
||||||
|
self.public_info.remove(player).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn return_public_info(&mut self, player: &Player, card_info: Vec<CardPossibilityTable>) {
|
||||||
|
self.public_info.insert(*player, card_info);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_my_public_info(&self) -> &Vec<CardPossibilityTable> {
|
||||||
|
self.get_player_public_info(&self.me)
|
||||||
|
}
|
||||||
|
|
||||||
fn get_player_public_info(&self, player: &Player) -> &Vec<CardPossibilityTable> {
|
fn get_player_public_info(&self, player: &Player) -> &Vec<CardPossibilityTable> {
|
||||||
self.public_info.get(player).unwrap()
|
self.public_info.get(player).unwrap()
|
||||||
}
|
}
|
||||||
|
@ -200,7 +461,7 @@ impl InformationPlayerStrategy {
|
||||||
|
|
||||||
fn update_public_info_for_discard_or_play(
|
fn update_public_info_for_discard_or_play(
|
||||||
&mut self,
|
&mut self,
|
||||||
view: &GameStateView,
|
view: &BorrowedGameView,
|
||||||
player: &Player,
|
player: &Player,
|
||||||
index: usize,
|
index: usize,
|
||||||
card: &Card
|
card: &Card
|
||||||
|
@ -212,7 +473,7 @@ impl InformationPlayerStrategy {
|
||||||
info.remove(index);
|
info.remove(index);
|
||||||
|
|
||||||
// push *before* incrementing public counts
|
// push *before* incrementing public counts
|
||||||
if info.len() < view.info.len() {
|
if info.len() < view.hand_size(&player) {
|
||||||
info.push(new_card_table);
|
info.push(new_card_table);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -220,7 +481,7 @@ impl InformationPlayerStrategy {
|
||||||
// note: other_player could be player, as well
|
// note: other_player could be player, as well
|
||||||
// in particular, we will decrement the newly drawn card
|
// in particular, we will decrement the newly drawn card
|
||||||
for other_player in view.board.get_players() {
|
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 {
|
for card_table in info {
|
||||||
card_table.decrement_weight_if_possible(card);
|
card_table.decrement_weight_if_possible(card);
|
||||||
}
|
}
|
||||||
|
@ -229,10 +490,10 @@ impl InformationPlayerStrategy {
|
||||||
self.public_counts.increment(card);
|
self.public_counts.increment(card);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_private_info(&self, view: &GameStateView) -> Vec<CardPossibilityTable> {
|
fn get_private_info(&self, view: &BorrowedGameView) -> Vec<CardPossibilityTable> {
|
||||||
let mut info = self.get_player_public_info(&self.me).clone();
|
let mut info = self.get_my_public_info().clone();
|
||||||
for card_table in info.iter_mut() {
|
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 {
|
for card in &state.hand {
|
||||||
card_table.decrement_weight_if_possible(card);
|
card_table.decrement_weight_if_possible(card);
|
||||||
}
|
}
|
||||||
|
@ -241,9 +502,81 @@ impl InformationPlayerStrategy {
|
||||||
info
|
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<bool>) {
|
||||||
|
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 {
|
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);
|
let private_info = self.get_private_info(view);
|
||||||
// debug!("My info:");
|
// debug!("My info:");
|
||||||
// for (i, card_table) in private_info.iter().enumerate() {
|
// for (i, card_table) in private_info.iter().enumerate() {
|
||||||
|
@ -268,48 +601,33 @@ impl PlayerStrategy for InformationPlayerStrategy {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TurnChoice::Play(play_index)
|
return TurnChoice::Play(play_index)
|
||||||
} else {
|
}
|
||||||
if view.board.hints_remaining > 0 {
|
|
||||||
let hint_player = view.board.player_to_left(&self.me);
|
let discard_threshold =
|
||||||
let hint_card = rand::thread_rng().choose(&view.get_hand(&hint_player)).unwrap();
|
view.board.total_cards
|
||||||
let hinted = {
|
- (COLORS.len() * VALUES.len()) as u32
|
||||||
if rand::random() {
|
- (view.board.num_players * view.board.hand_size);
|
||||||
// hint a color
|
|
||||||
Hinted::Color(hint_card.color)
|
if view.board.discard_size() <= discard_threshold {
|
||||||
} else {
|
// if anything is totally useless, discard it
|
||||||
Hinted::Value(hint_card.value)
|
if let Some(i) = self.find_useless_card(view, &private_info) {
|
||||||
}
|
return TurnChoice::Discard(i);
|
||||||
};
|
|
||||||
TurnChoice::Hint(Hint {
|
|
||||||
player: hint_player,
|
|
||||||
hinted: hinted,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
TurnChoice::Discard(0)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// // 50 total, 25 to play, 20 in hand
|
// hinting is better than discarding dead cards
|
||||||
// if view.board.discard.cards.len() < 6 {
|
// (probably because it stalls the deck-drawing).
|
||||||
// // if anything is totally useless, discard it
|
if view.board.hints_remaining > 1 {
|
||||||
// if let Some(i) = self.find_useless_card(view) {
|
if self.someone_else_can_play(view) {
|
||||||
// return TurnChoice::Discard(i);
|
return self.get_hint(view);
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
// // hinting is better than discarding dead cards
|
// if anything is totally useless, discard it
|
||||||
// // (probably because it stalls the deck-drawing).
|
if let Some(i) = self.find_useless_card(view, &private_info) {
|
||||||
// if view.board.hints_remaining > 1 {
|
return TurnChoice::Discard(i);
|
||||||
// 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);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // All cards are plausibly useful.
|
// // All cards are plausibly useful.
|
||||||
// // Play the best discardable card, according to the ordering induced by comparing
|
// // 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");
|
// 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 {
|
match turn.choice {
|
||||||
TurnChoice::Hint(ref hint) => {
|
TurnChoice::Hint(ref hint) => {
|
||||||
if let &TurnResult::Hint(ref matches) = &turn.result {
|
if let &TurnResult::Hint(ref matches) = &turn.result {
|
||||||
|
self.infer_from_hint(view, hint, matches);
|
||||||
self.update_public_info_for_hint(hint, matches);
|
self.update_public_info_for_hint(hint, matches);
|
||||||
} else {
|
} else {
|
||||||
panic!("Got turn choice {:?}, but turn result {:?}",
|
panic!("Got turn choice {:?}, but turn result {:?}",
|
||||||
|
@ -364,7 +689,7 @@ impl PlayerStrategy for InformationPlayerStrategy {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TurnChoice::Play(index) => {
|
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);
|
self.update_public_info_for_discard_or_play(view, &turn.player, index, card);
|
||||||
} else {
|
} else {
|
||||||
panic!("Got turn choice {:?}, but turn result {:?}",
|
panic!("Got turn choice {:?}, but turn result {:?}",
|
||||||
|
@ -372,5 +697,6 @@ impl PlayerStrategy for InformationPlayerStrategy {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.last_view = OwnedGameView::clone_from(view);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue