Run cargo fmt

This commit is contained in:
timotree3 2023-01-19 21:22:16 -05:00
parent ade33395c2
commit 1a42a4dd21
10 changed files with 877 additions and 548 deletions

View file

@ -19,7 +19,9 @@ pub fn get_count_for_value(value: Value) -> u32 {
1 => 3, 1 => 3,
2 | 3 | 4 => 2, 2 | 3 | 4 => 2,
5 => 1, 5 => 1,
_ => { panic!(format!("Unexpected value: {}", value)); } _ => {
panic!(format!("Unexpected value: {}", value));
}
} }
} }
@ -30,7 +32,10 @@ pub struct Card {
} }
impl Card { impl Card {
pub fn new(color: Color, value: Value) -> Card { pub fn new(color: Color, value: Value) -> Card {
Card { color: color, value: value } Card {
color: color,
value: value,
}
} }
} }
impl fmt::Display for Card { impl fmt::Display for Card {
@ -56,9 +61,7 @@ impl CardCounts {
counts.insert(Card::new(color, value), 0); counts.insert(Card::new(color, value), 0);
} }
} }
CardCounts { CardCounts { counts: counts }
counts: counts,
}
} }
pub fn get_count(&self, card: &Card) -> u32 { pub fn get_count(&self, card: &Card) -> u32 {
@ -78,15 +81,11 @@ impl CardCounts {
impl fmt::Display for CardCounts { impl fmt::Display for CardCounts {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for &color in COLORS.iter() { for &color in COLORS.iter() {
try!(f.write_str(&format!( try!(f.write_str(&format!("{}: ", color,)));
"{}: ", color,
)));
for &value in VALUES.iter() { for &value in VALUES.iter() {
let count = self.get_count(&Card::new(color, value)); let count = self.get_count(&Card::new(color, value));
let total = get_count_for_value(value); let total = get_count_for_value(value);
try!(f.write_str(&format!( try!(f.write_str(&format!("{}/{} {}s", count, total, value)));
"{}/{} {}s", count, total, value
)));
if value != FINAL_VALUE { if value != FINAL_VALUE {
try!(f.write_str(", ")); try!(f.write_str(", "));
} }
@ -151,7 +150,11 @@ impl Firework {
} }
pub fn needed_value(&self) -> Option<Value> { pub fn needed_value(&self) -> Option<Value> {
if self.complete() { None } else { Some(self.top + 1) } if self.complete() {
None
} else {
Some(self.top + 1)
}
} }
pub fn score(&self) -> Score { pub fn score(&self) -> Score {
@ -192,8 +195,12 @@ pub enum Hinted {
impl fmt::Display for Hinted { impl fmt::Display for Hinted {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self { match self {
&Hinted::Color(color) => { write!(f, "{}", color) } &Hinted::Color(color) => {
&Hinted::Value(value) => { write!(f, "{}", value) } write!(f, "{}", color)
}
&Hinted::Value(value) => {
write!(f, "{}", value)
}
} }
} }
} }
@ -269,9 +276,10 @@ pub struct BoardState {
} }
impl BoardState { impl BoardState {
pub fn new(opts: &GameOptions, deck_size: u32) -> BoardState { pub fn new(opts: &GameOptions, deck_size: u32) -> BoardState {
let fireworks = COLORS.iter().map(|&color| { let fireworks = COLORS
(color, Firework::new(color)) .iter()
}).collect::<FnvHashMap<_, _>>(); .map(|&color| (color, Firework::new(color)))
.collect::<FnvHashMap<_, _>>();
BoardState { BoardState {
deck_size: deck_size, deck_size: deck_size,
@ -324,7 +332,7 @@ impl BoardState {
for &value in VALUES.iter() { for &value in VALUES.iter() {
if value < needed { if value < needed {
// already have these cards // already have these cards
continue continue;
} }
let needed_card = Card::new(color, value); let needed_card = Card::new(color, value);
if self.discard.has_all(&needed_card) { if self.discard.has_all(&needed_card) {
@ -374,7 +382,10 @@ impl BoardState {
} }
pub fn score(&self) -> Score { pub fn score(&self) -> Score {
self.fireworks.iter().map(|(_, firework)| firework.score()).fold(0, |a, b| a + b) self.fireworks
.iter()
.map(|(_, firework)| firework.score())
.fold(0, |a, b| a + b)
} }
pub fn discard_size(&self) -> u32 { pub fn discard_size(&self) -> u32 {
@ -389,34 +400,36 @@ impl BoardState {
} }
pub fn is_over(&self) -> bool { pub fn is_over(&self) -> bool {
(self.lives_remaining == 0) || (self.deckless_turns_remaining == 0) || (self.score() == PERFECT_SCORE) (self.lives_remaining == 0)
|| (self.deckless_turns_remaining == 0)
|| (self.score() == PERFECT_SCORE)
} }
} }
impl fmt::Display for BoardState { impl fmt::Display for BoardState {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.is_over() { if self.is_over() {
try!(f.write_str(&format!( try!(f.write_str(&format!("Turn {} (GAME ENDED):\n", self.turn)));
"Turn {} (GAME ENDED):\n", self.turn
)));
} else { } else {
try!(f.write_str(&format!( try!(f.write_str(&format!(
"Turn {} (Player {}'s turn):\n", self.turn, self.player "Turn {} (Player {}'s turn):\n",
self.turn, self.player
))); )));
} }
try!(f.write_str(&format!( try!(f.write_str(&format!("{} cards remaining in deck\n", self.deck_size)));
"{} cards remaining in deck\n", self.deck_size
)));
if self.deck_size == 0 { if self.deck_size == 0 {
try!(f.write_str(&format!( try!(f.write_str(&format!(
"Deck is empty. {} turns remaining in game\n", self.deckless_turns_remaining "Deck is empty. {} turns remaining in game\n",
self.deckless_turns_remaining
))); )));
} }
try!(f.write_str(&format!( try!(f.write_str(&format!(
"{}/{} hints remaining\n", self.hints_remaining, self.hints_total "{}/{} hints remaining\n",
self.hints_remaining, self.hints_total
))); )));
try!(f.write_str(&format!( try!(f.write_str(&format!(
"{}/{} lives remaining\n", self.lives_remaining, self.lives_total "{}/{} lives remaining\n",
self.lives_remaining, self.lives_total
))); )));
try!(f.write_str("Fireworks:\n")); try!(f.write_str("Fireworks:\n"));
for &color in COLORS.iter() { for &color in COLORS.iter() {
@ -446,28 +459,30 @@ pub trait GameView {
} }
fn has_card(&self, player: &Player, card: &Card) -> bool { fn has_card(&self, player: &Player, card: &Card) -> bool {
self.get_hand(player).iter().position(|other_card| { self.get_hand(player)
card == other_card .iter()
}).is_some() .position(|other_card| card == other_card)
.is_some()
} }
fn get_other_players(&self) -> Vec<Player> { fn get_other_players(&self) -> Vec<Player> {
self.get_board().get_players().filter(|&player| { self.get_board()
player != self.me() .get_players()
}).collect() .filter(|&player| player != self.me())
.collect()
} }
fn can_see(&self, card: &Card) -> bool { fn can_see(&self, card: &Card) -> bool {
self.get_other_players().iter().any(|player| { self.get_other_players()
self.has_card(&player, card) .iter()
}) .any(|player| self.has_card(&player, card))
} }
fn someone_else_can_play(&self) -> bool { fn someone_else_can_play(&self) -> bool {
self.get_other_players().iter().any(|player| { self.get_other_players().iter().any(|player| {
self.get_hand(&player).iter().any(|card| { self.get_hand(&player)
self.get_board().is_playable(card) .iter()
}) .any(|card| self.get_board().is_playable(card))
}) })
} }
} }
@ -512,10 +527,11 @@ pub struct OwnedGameView {
} }
impl OwnedGameView { impl OwnedGameView {
pub fn clone_from(borrowed_view: &BorrowedGameView) -> OwnedGameView { pub fn clone_from(borrowed_view: &BorrowedGameView) -> OwnedGameView {
let other_hands = borrowed_view.other_hands.iter() let other_hands = borrowed_view
.map(|(&other_player, &player_state)| { .other_hands
(other_player, player_state.clone()) .iter()
}).collect::<FnvHashMap<_, _>>(); .map(|(&other_player, &player_state)| (other_player, player_state.clone()))
.collect::<FnvHashMap<_, _>>();
OwnedGameView { OwnedGameView {
player: borrowed_view.player.clone(), player: borrowed_view.player.clone(),
@ -552,7 +568,7 @@ pub type AnnotatedCard = (usize, Card);
pub type AnnotatedCards = Vec<AnnotatedCard>; pub type AnnotatedCards = Vec<AnnotatedCard>;
fn strip_annotations(cards: &AnnotatedCards) -> Cards { fn strip_annotations(cards: &AnnotatedCards) -> Cards {
cards.iter().map(|(_i, card)| { card.clone() }).collect() cards.iter().map(|(_i, card)| card.clone()).collect()
} }
// complete game state (known to nobody!) // complete game state (known to nobody!)
@ -592,18 +608,22 @@ impl GameState {
let mut deck: AnnotatedCards = deck.into_iter().rev().enumerate().rev().collect(); let mut deck: AnnotatedCards = deck.into_iter().rev().enumerate().rev().collect();
let mut board = BoardState::new(opts, deck.len() as u32); let mut board = BoardState::new(opts, deck.len() as u32);
let hands = let hands = (0..opts.num_players)
(0..opts.num_players).map(|player| { .map(|player| {
let hand = (0..opts.hand_size).map(|_| { let hand = (0..opts.hand_size)
.map(|_| {
// we can assume the deck is big enough to draw initial hands // we can assume the deck is big enough to draw initial hands
board.deck_size -= 1; board.deck_size -= 1;
deck.pop().unwrap() deck.pop().unwrap()
}).collect::<Vec<_>>(); })
.collect::<Vec<_>>();
(player, hand) (player, hand)
}).collect::<FnvHashMap<_, _>>(); })
let unannotated_hands = hands.iter().map(|(player, hand)| { .collect::<FnvHashMap<_, _>>();
(player.clone(), strip_annotations(hand)) let unannotated_hands = hands
}).collect::<FnvHashMap<_, _>>(); .iter()
.map(|(player, hand)| (player.clone(), strip_annotations(hand)))
.collect::<FnvHashMap<_, _>>();
GameState { GameState {
hands, hands,
@ -643,15 +663,15 @@ impl GameState {
fn update_player_hand(&mut self) { fn update_player_hand(&mut self) {
let player = self.board.player.clone(); let player = self.board.player.clone();
self.unannotated_hands.insert(player, strip_annotations(self.hands.get(&player).unwrap())); self.unannotated_hands
.insert(player, strip_annotations(self.hands.get(&player).unwrap()));
} }
// takes a card from the player's hand, and replaces it if possible // takes a card from the player's hand, and replaces it if possible
fn take_from_hand(&mut self, index: usize) -> Card { fn take_from_hand(&mut self, index: usize) -> Card {
// FIXME this code looks like it's awfully contorted in order to please the borrow checker. // FIXME this code looks like it's awfully contorted in order to please the borrow checker.
// Can we have this look nicer? // Can we have this look nicer?
let result = let result = {
{
let ref mut hand = self.hands.get_mut(&self.board.player).unwrap(); let ref mut hand = self.hands.get_mut(&self.board.player).unwrap();
hand.remove(index).1 hand.remove(index).1
}; };
@ -679,26 +699,34 @@ impl GameState {
let turn_result = { let turn_result = {
match choice { match choice {
TurnChoice::Hint(ref hint) => { TurnChoice::Hint(ref hint) => {
assert!(self.board.hints_remaining > 0, assert!(
"Tried to hint with no hints remaining"); self.board.hints_remaining > 0,
"Tried to hint with no hints remaining"
);
self.board.hints_remaining -= 1; self.board.hints_remaining -= 1;
debug!("Hint to player {}, about {}", hint.player, hint.hinted); debug!("Hint to player {}, about {}", hint.player, hint.hinted);
assert!(self.board.player != hint.player, assert!(
format!("Player {} gave a hint to himself", hint.player)); self.board.player != hint.player,
format!("Player {} gave a hint to himself", hint.player)
);
let hand = self.hands.get(&hint.player).unwrap(); let hand = self.hands.get(&hint.player).unwrap();
let results = match hint.hinted { let results = match hint.hinted {
Hinted::Color(color) => { Hinted::Color(color) => hand
hand.iter().map(|(_i, card)| { card.color == color }).collect::<Vec<_>>() .iter()
} .map(|(_i, card)| card.color == color)
Hinted::Value(value) => { .collect::<Vec<_>>(),
hand.iter().map(|(_i, card)| { card.value == value }).collect::<Vec<_>>() Hinted::Value(value) => hand
} .iter()
.map(|(_i, card)| card.value == value)
.collect::<Vec<_>>(),
}; };
if !self.board.allow_empty_hints { if !self.board.allow_empty_hints {
assert!(results.iter().any(|matched| *matched), assert!(
"Tried hinting an empty hint"); results.iter().any(|matched| *matched),
"Tried hinting an empty hint"
);
} }
TurnResult::Hint(results) TurnResult::Hint(results)
@ -714,10 +742,7 @@ impl GameState {
TurnChoice::Play(index) => { TurnChoice::Play(index) => {
let card = self.take_from_hand(index); let card = self.take_from_hand(index);
debug!( debug!("Playing card at position {}, which is {}", index, card);
"Playing card at position {}, which is {}",
index, card
);
let playable = self.board.is_playable(&card); let playable = self.board.is_playable(&card);
if playable { if playable {
{ {
@ -758,7 +783,10 @@ impl GameState {
let cur = self.board.player; let cur = self.board.player;
self.board.player_to_left(&cur) self.board.player_to_left(&cur)
}; };
assert_eq!((self.board.turn - 1) % self.board.num_players, self.board.player); assert_eq!(
(self.board.turn - 1) % self.board.num_players,
self.board.player
);
turn_record turn_record
} }

View file

@ -1,9 +1,9 @@
use std::cmp::Eq; use std::cmp::Eq;
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::fmt;
use std::ops::{Index,IndexMut};
use std::hash::Hash;
use std::convert::From; use std::convert::From;
use std::fmt;
use std::hash::Hash;
use std::ops::{Index, IndexMut};
use std::slice; use std::slice;
use game::*; use game::*;
@ -37,21 +37,25 @@ pub trait CardInfo {
} }
fn get_weighted_possibilities(&self) -> Vec<(Card, f32)> { fn get_weighted_possibilities(&self) -> Vec<(Card, f32)> {
self.get_possibilities().into_iter() self.get_possibilities()
.into_iter()
.map(|card| { .map(|card| {
let weight = self.get_weight(&card); let weight = self.get_weight(&card);
(card, weight) (card, weight)
}).collect::<Vec<_>>() })
.collect::<Vec<_>>()
} }
fn total_weight(&self) -> f32 { fn total_weight(&self) -> f32 {
self.get_possibilities().iter() self.get_possibilities()
.iter()
.map(|card| self.get_weight(&card)) .map(|card| self.get_weight(&card))
.fold(0.0, |a, b| a + b) .fold(0.0, |a, b| a + b)
} }
fn weighted_score<T>(&self, score_fn: &Fn(&Card) -> T) -> f32 fn weighted_score<T>(&self, score_fn: &Fn(&Card) -> T) -> f32
where f32: From<T> where
f32: From<T>,
{ {
let mut total_score = 0.; let mut total_score = 0.;
let mut total_weight = 0.; let mut total_weight = 0.;
@ -70,7 +74,11 @@ pub trait CardInfo {
fn probability_of_predicate(&self, predicate: &Fn(&Card) -> bool) -> f32 { fn probability_of_predicate(&self, predicate: &Fn(&Card) -> bool) -> f32 {
let f = |card: &Card| { let f = |card: &Card| {
if predicate(card) { 1.0 } else { 0.0 } if predicate(card) {
1.0
} else {
0.0
}
}; };
self.weighted_score(&f) self.weighted_score(&f)
} }
@ -124,9 +132,11 @@ pub trait CardInfo {
} }
} }
// Represents hinted information about possible values of type T // Represents hinted information about possible values of type T
pub trait Info<T> where T: Hash + Eq + Clone + Copy { pub trait Info<T>
where
T: Hash + Eq + Clone + Copy,
{
// get all a-priori possibilities // get all a-priori possibilities
fn get_all_possibilities() -> Vec<T>; fn get_all_possibilities() -> Vec<T>;
@ -137,7 +147,10 @@ pub trait Info<T> where T: Hash + Eq + Clone + Copy {
// get what is now possible // get what is now possible
fn get_possibilities(&self) -> Vec<T> { fn get_possibilities(&self) -> Vec<T> {
self.get_possibility_set().iter().map(|t| t.clone()).collect::<Vec<T>>() self.get_possibility_set()
.iter()
.map(|t| t.clone())
.collect::<Vec<T>>()
} }
fn is_possible(&self, value: T) -> bool { fn is_possible(&self, value: T) -> bool {
@ -145,8 +158,10 @@ pub trait Info<T> where T: Hash + Eq + Clone + Copy {
} }
fn initialize() -> HashSet<T> { fn initialize() -> HashSet<T> {
Self::get_all_possibilities().iter() Self::get_all_possibilities()
.map(|val| val.clone()).collect::<HashSet<_>>() .iter()
.map(|val| val.clone())
.collect::<HashSet<_>>()
} }
fn mark_true(&mut self, value: T) { fn mark_true(&mut self, value: T) {
@ -160,30 +175,50 @@ pub trait Info<T> where T: Hash + Eq + Clone + Copy {
} }
fn mark(&mut self, value: T, info: bool) { fn mark(&mut self, value: T, info: bool) {
if info { self.mark_true(value); } else { self.mark_false(value); } if info {
self.mark_true(value);
} else {
self.mark_false(value);
}
} }
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ColorInfo(HashSet<Color>); pub struct ColorInfo(HashSet<Color>);
impl ColorInfo { impl ColorInfo {
pub fn new() -> ColorInfo { ColorInfo(ColorInfo::initialize()) } pub fn new() -> ColorInfo {
ColorInfo(ColorInfo::initialize())
}
} }
impl Info<Color> for ColorInfo { impl Info<Color> for ColorInfo {
fn get_all_possibilities() -> Vec<Color> { COLORS.to_vec() } fn get_all_possibilities() -> Vec<Color> {
fn get_possibility_set(&self) -> &HashSet<Color> { &self.0 } COLORS.to_vec()
fn get_mut_possibility_set(&mut self) -> &mut HashSet<Color> { &mut self.0 } }
fn get_possibility_set(&self) -> &HashSet<Color> {
&self.0
}
fn get_mut_possibility_set(&mut self) -> &mut HashSet<Color> {
&mut self.0
}
} }
#[derive(Debug, Clone)] #[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())
}
} }
impl Info<Value> for ValueInfo { impl Info<Value> for ValueInfo {
fn get_all_possibilities() -> Vec<Value> { VALUES.to_vec() } fn get_all_possibilities() -> Vec<Value> {
fn get_possibility_set(&self) -> &HashSet<Value> { &self.0 } VALUES.to_vec()
fn get_mut_possibility_set(&mut self) -> &mut HashSet<Value> { &mut self.0 } }
fn get_possibility_set(&self) -> &HashSet<Value> {
&self.0
}
fn get_mut_possibility_set(&mut self) -> &mut HashSet<Value> {
&mut self.0
}
} }
// represents information only of the form: // represents information only of the form:
@ -211,13 +246,10 @@ impl CardInfo for SimpleCardInfo {
v v
} }
fn is_possible(&self, card: &Card) -> bool { fn is_possible(&self, card: &Card) -> bool {
self.color_info.is_possible(card.color) && self.color_info.is_possible(card.color) && self.value_info.is_possible(card.value)
self.value_info.is_possible(card.value)
} }
fn mark_color_false(&mut self, color: Color) { fn mark_color_false(&mut self, color: Color) {
self.color_info.mark_false(color); self.color_info.mark_false(color);
} }
fn mark_value_false(&mut self, value: Value) { fn mark_value_false(&mut self, value: Value) {
self.value_info.mark_false(value); self.value_info.mark_false(value);
@ -269,9 +301,10 @@ impl CardPossibilityTable {
pub fn decrement_weight(&mut self, card: &Card) { pub fn decrement_weight(&mut self, card: &Card) {
let remove = { let remove = {
let weight = let weight = self.possible.get_mut(card).expect(&format!(
self.possible.get_mut(card) "Decrementing weight for impossible card: {}",
.expect(&format!("Decrementing weight for impossible card: {}", card)); card
));
*weight -= 1; *weight -= 1;
*weight == 0 *weight == 0
}; };
@ -295,24 +328,32 @@ impl CardPossibilityTable {
pub fn color_determined(&self) -> bool { pub fn color_determined(&self) -> bool {
self.get_possibilities() self.get_possibilities()
.iter().map(|card| card.color) .iter()
.map(|card| card.color)
.collect::<HashSet<_>>() .collect::<HashSet<_>>()
.len() == 1 .len()
== 1
} }
pub fn value_determined(&self) -> bool { pub fn value_determined(&self) -> bool {
self.get_possibilities() self.get_possibilities()
.iter().map(|card| card.value) .iter()
.map(|card| card.value)
.collect::<HashSet<_>>() .collect::<HashSet<_>>()
.len() == 1 .len()
== 1
} }
pub fn can_be_color(&self, color: Color) -> bool { pub fn can_be_color(&self, color: Color) -> bool {
self.get_possibilities().into_iter().any(|card| card.color == color) self.get_possibilities()
.into_iter()
.any(|card| card.color == color)
} }
pub fn can_be_value(&self, value: Value) -> bool { pub fn can_be_value(&self, value: Value) -> bool {
self.get_possibilities().into_iter().any(|card| card.value == value) self.get_possibilities()
.into_iter()
.any(|card| card.value == value)
} }
} }
impl<'a> From<&'a CardCounts> for CardPossibilityTable { impl<'a> From<&'a CardCounts> for CardPossibilityTable {
@ -327,9 +368,7 @@ impl <'a> From<&'a CardCounts> for CardPossibilityTable {
} }
} }
} }
CardPossibilityTable { CardPossibilityTable { possible: possible }
possible: possible,
}
} }
} }
impl CardInfo for CardPossibilityTable { impl CardInfo for CardPossibilityTable {
@ -341,7 +380,11 @@ impl CardInfo for CardPossibilityTable {
self.possible.contains_key(card) self.possible.contains_key(card)
} }
fn get_possibilities(&self) -> Vec<Card> { fn get_possibilities(&self) -> Vec<Card> {
let mut cards = self.possible.keys().map(|card| {card.clone() }).collect::<Vec<_>>(); let mut cards = self
.possible
.keys()
.map(|card| card.clone())
.collect::<Vec<_>>();
cards.sort(); cards.sort();
cards cards
} }
@ -349,7 +392,6 @@ impl CardInfo for CardPossibilityTable {
for &value in VALUES.iter() { for &value in VALUES.iter() {
self.mark_false(&Card::new(color, value)); self.mark_false(&Card::new(color, value));
} }
} }
fn mark_value_false(&mut self, value: Value) { fn mark_value_false(&mut self, value: Value) {
for &color in COLORS.iter() { for &color in COLORS.iter() {
@ -370,10 +412,16 @@ impl fmt::Display for CardPossibilityTable {
} }
#[derive(Clone, Eq, PartialEq)] #[derive(Clone, Eq, PartialEq)]
pub struct HandInfo<T> where T: CardInfo { pub struct HandInfo<T>
pub hand_info: Vec<T> where
T: CardInfo,
{
pub hand_info: Vec<T>,
} }
impl <T> HandInfo<T> where T: CardInfo { impl<T> HandInfo<T>
where
T: CardInfo,
{
pub fn new(hand_size: u32) -> Self { pub fn new(hand_size: u32) -> Self {
let hand_info = (0..hand_size).map(|_| T::new()).collect::<Vec<_>>(); let hand_info = (0..hand_size).map(|_| T::new()).collect::<Vec<_>>();
HandInfo { HandInfo {
@ -397,19 +445,35 @@ impl <T> HandInfo<T> where T: CardInfo {
} }
} }
pub fn remove(&mut self, index: usize) -> T { self.hand_info.remove(index) } pub fn remove(&mut self, index: usize) -> T {
pub fn push(&mut self, card_info: T) { self.hand_info.push(card_info) } self.hand_info.remove(index)
pub fn iter_mut(&mut self) -> slice::IterMut<T> { self.hand_info.iter_mut() }
pub fn iter(&self) -> slice::Iter<T> { self.hand_info.iter() }
pub fn len(&self) -> usize { self.hand_info.len() }
} }
impl <T> Index<usize> for HandInfo<T> where T: CardInfo { pub fn push(&mut self, card_info: T) {
self.hand_info.push(card_info)
}
pub fn iter_mut(&mut self) -> slice::IterMut<T> {
self.hand_info.iter_mut()
}
pub fn iter(&self) -> slice::Iter<T> {
self.hand_info.iter()
}
pub fn len(&self) -> usize {
self.hand_info.len()
}
}
impl<T> Index<usize> for HandInfo<T>
where
T: CardInfo,
{
type Output = T; type Output = T;
fn index(&self, index: usize) -> &T { fn index(&self, index: usize) -> &T {
&self.hand_info[index] &self.hand_info[index]
} }
} }
impl <T> IndexMut<usize> for HandInfo<T> where T: CardInfo { impl<T> IndexMut<usize> for HandInfo<T>
where
T: CardInfo,
{
fn index_mut(&mut self, index: usize) -> &mut T { fn index_mut(&mut self, index: usize) -> &mut T {
&mut self.hand_info[index] &mut self.hand_info[index]
} }

View file

@ -2,7 +2,10 @@ use game::*;
use serde_json::*; use serde_json::*;
fn color_value(color: &Color) -> usize { fn color_value(color: &Color) -> usize {
COLORS.iter().position(|&card_color| &card_color == color).unwrap() COLORS
.iter()
.position(|&card_color| &card_color == color)
.unwrap()
} }
fn card_to_json(card: &Card) -> serde_json::Value { fn card_to_json(card: &Card) -> serde_json::Value {
@ -43,7 +46,11 @@ pub fn action_discard((i, _card): &AnnotatedCard) -> serde_json::Value {
}) })
} }
pub fn json_format(deck: &Cards, actions: &Vec<serde_json::Value>, players: &Vec<String>) -> serde_json::Value { pub fn json_format(
deck: &Cards,
actions: &Vec<serde_json::Value>,
players: &Vec<String>,
) -> serde_json::Value {
json!({ json!({
"variant": "No Variant", "variant": "No Variant",
"players": players, "players": players,

View file

@ -1,20 +1,20 @@
extern crate getopts; extern crate getopts;
#[macro_use] #[macro_use]
extern crate log; extern crate log;
extern crate rand;
extern crate crossbeam; extern crate crossbeam;
extern crate fnv;
extern crate float_ord; extern crate float_ord;
extern crate fnv;
extern crate rand;
extern crate serde_json; extern crate serde_json;
mod helpers;
mod game; mod game;
mod helpers;
mod json_output; mod json_output;
mod simulator; mod simulator;
mod strategy; mod strategy;
mod strategies { mod strategies {
pub mod examples;
pub mod cheating; pub mod cheating;
pub mod examples;
mod hat_helpers; mod hat_helpers;
pub mod information; pub mod information;
} }
@ -35,51 +35,71 @@ impl log::Log for SimpleLogger {
} }
} }
fn print_usage(program: &str, opts: Options) { fn print_usage(program: &str, opts: Options) {
print!("{}", opts.usage(&format!("Usage: {} [options]", program))); print!("{}", opts.usage(&format!("Usage: {} [options]", program)));
} }
fn main() { fn main() {
let args: Vec<String> = std::env::args().collect(); let args: Vec<String> = std::env::args().collect();
let program = args[0].clone(); let program = args[0].clone();
let mut opts = Options::new(); let mut opts = Options::new();
opts.optopt("l", "loglevel", opts.optopt(
"l",
"loglevel",
"Log level, one of 'trace', 'debug', 'info', 'warn', and 'error'", "Log level, one of 'trace', 'debug', 'info', 'warn', and 'error'",
"LOGLEVEL"); "LOGLEVEL",
opts.optopt("n", "ntrials", );
opts.optopt(
"n",
"ntrials",
"Number of games to simulate (default 1)", "Number of games to simulate (default 1)",
"NTRIALS"); "NTRIALS",
opts.optopt("o", "output", );
opts.optopt(
"o",
"output",
"Number of games after which to print an update", "Number of games after which to print an update",
"OUTPUT_FREQ"); "OUTPUT_FREQ",
opts.optopt("j", "json-output", );
opts.optopt(
"j",
"json-output",
"Pattern for the JSON output file. '%s' will be replaced by the seed.", "Pattern for the JSON output file. '%s' will be replaced by the seed.",
"FILE_PATTERN"); "FILE_PATTERN",
opts.optopt("t", "nthreads", );
opts.optopt(
"t",
"nthreads",
"Number of threads to use for simulation (default 1)", "Number of threads to use for simulation (default 1)",
"NTHREADS"); "NTHREADS",
opts.optopt("s", "seed", );
"Seed for PRNG (default random)", opts.optopt("s", "seed", "Seed for PRNG (default random)", "SEED");
"SEED"); opts.optopt("p", "nplayers", "Number of players", "NPLAYERS");
opts.optopt("p", "nplayers", opts.optopt(
"Number of players", "g",
"NPLAYERS"); "strategy",
opts.optopt("g", "strategy",
"Which strategy to use. One of 'random', 'cheat', and 'info'", "Which strategy to use. One of 'random', 'cheat', and 'info'",
"STRATEGY"); "STRATEGY",
opts.optflag("h", "help", );
"Print this help menu"); opts.optflag("h", "help", "Print this help menu");
opts.optflag("", "results-table", opts.optflag(
"Print a table of results for each strategy"); "",
opts.optflag("", "write-results-table", "results-table",
"Update the results table in README.md"); "Print a table of results for each strategy",
opts.optflag("", "losses-only", );
"When saving JSON outputs, save lost games only"); opts.optflag(
"",
"write-results-table",
"Update the results table in README.md",
);
opts.optflag(
"",
"losses-only",
"When saving JSON outputs, save lost games only",
);
let matches = match opts.parse(&args[1..]) { let matches = match opts.parse(&args[1..]) {
Ok(m) => { m } Ok(m) => m,
Err(f) => { Err(f) => {
print_usage(&program, opts); print_usage(&program, opts);
panic!(f.to_string()) panic!(f.to_string())
@ -100,11 +120,11 @@ fn main() {
let log_level_str: &str = &matches.opt_str("l").unwrap_or("info".to_string()); let log_level_str: &str = &matches.opt_str("l").unwrap_or("info".to_string());
let log_level = match log_level_str { let log_level = match log_level_str {
"trace" => { log::LogLevelFilter::Trace } "trace" => log::LogLevelFilter::Trace,
"debug" => { log::LogLevelFilter::Debug } "debug" => log::LogLevelFilter::Debug,
"info" => { log::LogLevelFilter::Info } "info" => log::LogLevelFilter::Info,
"warn" => { log::LogLevelFilter::Warn } "warn" => log::LogLevelFilter::Warn,
"error" => { log::LogLevelFilter::Error } "error" => log::LogLevelFilter::Error,
_ => { _ => {
print_usage(&program, opts); print_usage(&program, opts);
panic!("Unexpected log level argument {}", log_level_str); panic!("Unexpected log level argument {}", log_level_str);
@ -114,11 +134,16 @@ fn main() {
log::set_logger(|max_log_level| { log::set_logger(|max_log_level| {
max_log_level.set(log_level); max_log_level.set(log_level);
Box::new(SimpleLogger) Box::new(SimpleLogger)
}).unwrap(); })
.unwrap();
let n_trials = u32::from_str(&matches.opt_str("n").unwrap_or("1".to_string())).unwrap(); let n_trials = u32::from_str(&matches.opt_str("n").unwrap_or("1".to_string())).unwrap();
let seed = matches.opt_str("s").map(|seed_str| { u32::from_str(&seed_str).unwrap() }); let seed = matches
let progress_info = matches.opt_str("o").map(|freq_str| { u32::from_str(&freq_str).unwrap() }); .opt_str("s")
.map(|seed_str| u32::from_str(&seed_str).unwrap());
let progress_info = matches
.opt_str("o")
.map(|freq_str| u32::from_str(&freq_str).unwrap());
let n_threads = u32::from_str(&matches.opt_str("t").unwrap_or("1".to_string())).unwrap(); let n_threads = u32::from_str(&matches.opt_str("t").unwrap_or("1".to_string())).unwrap();
let json_output_pattern = matches.opt_str("j"); let json_output_pattern = matches.opt_str("j");
@ -127,7 +152,17 @@ fn main() {
let n_players = u32::from_str(&matches.opt_str("p").unwrap_or("4".to_string())).unwrap(); let n_players = u32::from_str(&matches.opt_str("p").unwrap_or("4".to_string())).unwrap();
let strategy_str: &str = &matches.opt_str("g").unwrap_or("cheat".to_string()); let strategy_str: &str = &matches.opt_str("g").unwrap_or("cheat".to_string());
sim_games(n_players, strategy_str, seed, n_trials, n_threads, progress_info, json_output_pattern, json_losses_only).info(); sim_games(
n_players,
strategy_str,
seed,
n_trials,
n_threads,
progress_info,
json_output_pattern,
json_losses_only,
)
.info();
} }
fn sim_games( fn sim_games(
@ -145,7 +180,9 @@ fn sim_games(
3 => 5, 3 => 5,
4 => 4, 4 => 4,
5 => 4, 5 => 4,
_ => { panic!("There should be 2 to 5 players, not {}", n_players); } _ => {
panic!("There should be 2 to 5 players, not {}", n_players);
}
}; };
let game_opts = game::GameOptions { let game_opts = game::GameOptions {
@ -158,25 +195,28 @@ fn sim_games(
}; };
let strategy_config: Box<strategy::GameStrategyConfig + Sync> = match strategy_str { let strategy_config: Box<strategy::GameStrategyConfig + Sync> = match strategy_str {
"random" => { "random" => Box::new(strategies::examples::RandomStrategyConfig {
Box::new(strategies::examples::RandomStrategyConfig {
hint_probability: 0.4, hint_probability: 0.4,
play_probability: 0.2, play_probability: 0.2,
}) as Box<strategy::GameStrategyConfig + Sync> }) as Box<strategy::GameStrategyConfig + Sync>,
}, "cheat" => Box::new(strategies::cheating::CheatingStrategyConfig::new())
"cheat" => { as Box<strategy::GameStrategyConfig + Sync>,
Box::new(strategies::cheating::CheatingStrategyConfig::new()) "info" => Box::new(strategies::information::InformationStrategyConfig::new())
as Box<strategy::GameStrategyConfig + Sync> as Box<strategy::GameStrategyConfig + Sync>,
},
"info" => {
Box::new(strategies::information::InformationStrategyConfig::new())
as Box<strategy::GameStrategyConfig + Sync>
},
_ => { _ => {
panic!("Unexpected strategy argument {}", strategy_str); panic!("Unexpected strategy argument {}", strategy_str);
}, }
}; };
simulator::simulate(&game_opts, strategy_config, seed, n_trials, n_threads, progress_info, json_output_pattern, json_losses_only) simulator::simulate(
&game_opts,
strategy_config,
seed,
n_trials,
n_threads,
progress_info,
json_output_pattern,
json_losses_only,
)
} }
fn get_results_table() -> String { fn get_results_table() -> String {
@ -186,7 +226,10 @@ fn get_results_table() -> String {
let n_trials = 20000; let n_trials = 20000;
let n_threads = 8; let n_threads = 8;
let intro = format!("On the first {} seeds, we have these scores and win rates (average ± standard error):\n\n", n_trials); let intro = format!(
"On the first {} seeds, we have these scores and win rates (average ± standard error):\n\n",
n_trials
);
let format_name = |x| format!(" {:7} ", x); let format_name = |x| format!(" {:7} ", x);
let format_players = |x| format!(" {}p ", x); let format_players = |x| format!(" {}p ", x);
let format_percent = |x, stderr| format!(" {:05.2} ± {:.2} % ", x, stderr); let format_percent = |x, stderr| format!(" {:05.2} ± {:.2} % ", x, stderr);
@ -195,30 +238,63 @@ fn get_results_table() -> String {
let dashes = String::from("---------"); let dashes = String::from("---------");
let dashes_long = String::from("------------------"); let dashes_long = String::from("------------------");
type TwoLines = (String, String); type TwoLines = (String, String);
fn make_twolines(player_nums: &Vec<u32>, head: TwoLines, make_block: &dyn Fn(u32) -> TwoLines) -> TwoLines { fn make_twolines(
let mut blocks = player_nums.iter().cloned().map(make_block).collect::<Vec<_>>(); player_nums: &Vec<u32>,
head: TwoLines,
make_block: &dyn Fn(u32) -> TwoLines,
) -> TwoLines {
let mut blocks = player_nums
.iter()
.cloned()
.map(make_block)
.collect::<Vec<_>>();
blocks.insert(0, head); blocks.insert(0, head);
fn combine(items: Vec<String>) -> String { fn combine(items: Vec<String>) -> String {
items.iter().fold(String::from("|"), |init, next| { init + next + "|" }) items
.iter()
.fold(String::from("|"), |init, next| init + next + "|")
} }
let (a, b): (Vec<_>, Vec<_>) = blocks.into_iter().unzip(); let (a, b): (Vec<_>, Vec<_>) = blocks.into_iter().unzip();
(combine(a), combine(b)) (combine(a), combine(b))
} }
fn concat_twolines(body: Vec<TwoLines>) -> String { fn concat_twolines(body: Vec<TwoLines>) -> String {
body.into_iter().fold(String::default(), |output, (a, b)| (output + &a + "\n" + &b + "\n")) body.into_iter().fold(String::default(), |output, (a, b)| {
(output + &a + "\n" + &b + "\n")
})
} }
let header = make_twolines(&player_nums, let header = make_twolines(
&player_nums,
(space.clone(), dashes.clone()), (space.clone(), dashes.clone()),
&|n_players| (format_players(n_players), dashes_long.clone())); &|n_players| (format_players(n_players), dashes_long.clone()),
let mut body = strategies.iter().map(|strategy| { );
make_twolines(&player_nums, (format_name(strategy), space.clone()), &|n_players| { let mut body = strategies
let simresult = sim_games(n_players, strategy, Some(seed), n_trials, n_threads, None, None, false); .iter()
.map(|strategy| {
make_twolines(
&player_nums,
(format_name(strategy), space.clone()),
&|n_players| {
let simresult = sim_games(
n_players,
strategy,
Some(seed),
n_trials,
n_threads,
None,
None,
false,
);
( (
format_score(simresult.average_score(), simresult.score_stderr()), format_score(simresult.average_score(), simresult.score_stderr()),
format_percent(simresult.percent_perfect(), simresult.percent_perfect_stderr()) format_percent(
simresult.percent_perfect(),
simresult.percent_perfect_stderr(),
),
)
},
) )
}) })
}).collect::<Vec<_>>(); .collect::<Vec<_>>();
body.insert(0, header); body.insert(0, header);
intro + &concat_twolines(body) intro + &concat_twolines(body)
} }

View file

@ -1,11 +1,11 @@
use rand::{self, Rng, SeedableRng};
use fnv::FnvHashMap;
use std::fmt;
use crossbeam; use crossbeam;
use fnv::FnvHashMap;
use rand::{self, Rng, SeedableRng};
use std::fmt;
use game::*; use game::*;
use strategy::*;
use json_output::*; use json_output::*;
use strategy::*;
fn new_deck(seed: u32) -> Cards { fn new_deck(seed: u32) -> Cards {
let mut deck: Cards = Cards::new(); let mut deck: Cards = Cards::new();
@ -16,28 +16,32 @@ fn new_deck(seed: u32) -> Cards {
deck.push(Card::new(color, value)); deck.push(Card::new(color, value));
} }
} }
}; }
rand::ChaChaRng::from_seed(&[seed]).shuffle(&mut deck[..]); rand::ChaChaRng::from_seed(&[seed]).shuffle(&mut deck[..]);
debug!("Deck: {:?}", deck); debug!("Deck: {:?}", deck);
deck deck
} }
pub fn simulate_once( pub fn simulate_once(
opts: &GameOptions, opts: &GameOptions,
game_strategy: Box<GameStrategy>, game_strategy: Box<GameStrategy>,
seed: u32, seed: u32,
output_json: bool, output_json: bool,
) -> (GameState, Option<serde_json::Value>) { ) -> (GameState, Option<serde_json::Value>) {
let deck = new_deck(seed); let deck = new_deck(seed);
let mut game = GameState::new(opts, deck.clone()); let mut game = GameState::new(opts, deck.clone());
let mut strategies = game.get_players().map(|player| { let mut strategies = game
(player, game_strategy.initialize(player, &game.get_view(player))) .get_players()
}).collect::<FnvHashMap<Player, Box<PlayerStrategy>>>(); .map(|player| {
(
player,
game_strategy.initialize(player, &game.get_view(player)),
)
})
.collect::<FnvHashMap<Player, Box<PlayerStrategy>>>();
let mut actions = Vec::new(); let mut actions = Vec::new();
@ -50,16 +54,13 @@ pub fn simulate_once(
debug!("======================================================="); debug!("=======================================================");
debug!("{}", game); debug!("{}", game);
let choice = { let choice = {
let mut strategy = strategies.get_mut(&player).unwrap(); let mut strategy = strategies.get_mut(&player).unwrap();
strategy.decide(&game.get_view(player)) strategy.decide(&game.get_view(player))
}; };
if output_json { if output_json {
actions.push(match choice { actions.push(match choice {
TurnChoice::Hint(ref hint) => { TurnChoice::Hint(ref hint) => action_clue(hint),
action_clue(hint)
}
TurnChoice::Play(index) => { TurnChoice::Play(index) => {
let card = &game.hands[&player][index]; let card = &game.hands[&player][index];
action_play(card) action_play(card)
@ -71,7 +72,6 @@ pub fn simulate_once(
}); });
} }
let turn = game.process_choice(choice); let turn = game.process_choice(choice);
for player in game.get_players() { for player in game.get_players() {
@ -84,9 +84,10 @@ pub fn simulate_once(
debug!("Final state:\n{}", game); debug!("Final state:\n{}", game);
debug!("SCORE: {:?}", game.score()); debug!("SCORE: {:?}", game.score());
let json_output = if output_json { let json_output = if output_json {
let player_names = game.get_players().map(|player| { let player_names = game
strategies[&player].name() .get_players()
}).collect(); .map(|player| strategies[&player].name())
.collect();
Some(json_format(&deck, &actions, &player_names)) Some(json_format(&deck, &actions, &player_names))
} else { } else {
None None
@ -148,9 +149,7 @@ impl fmt::Display for Histogram {
let mut keys = self.hist.keys().collect::<Vec<_>>(); let mut keys = self.hist.keys().collect::<Vec<_>>();
keys.sort(); keys.sort();
for val in keys { for val in keys {
try!(f.write_str(&format!( try!(f.write_str(&format!("\n{}: {}", val, self.get_count(val),)));
"\n{}: {}", val, self.get_count(val),
)));
} }
Ok(()) Ok(())
} }
@ -166,8 +165,9 @@ pub fn simulate<T: ?Sized>(
json_output_pattern: Option<String>, json_output_pattern: Option<String>,
json_losses_only: bool, json_losses_only: bool,
) -> SimResult ) -> SimResult
where T: GameStrategyConfig + Sync { where
T: GameStrategyConfig + Sync,
{
let first_seed = first_seed_opt.unwrap_or_else(|| rand::thread_rng().next_u32()); let first_seed = first_seed_opt.unwrap_or_else(|| rand::thread_rng().next_u32());
let strat_config_ref = &strat_config; let strat_config_ref = &strat_config;
@ -191,22 +191,30 @@ pub fn simulate<T: ?Sized>(
if (seed > start) && ((seed - start) % progress_info_frequency == 0) { if (seed > start) && ((seed - start) % progress_info_frequency == 0) {
info!( info!(
"Thread {}, Trials: {}, Stats so far: {} score, {} lives, {}% win", "Thread {}, Trials: {}, Stats so far: {} score, {} lives, {}% win",
i, seed-start, score_histogram.average(), lives_histogram.average(), i,
seed - start,
score_histogram.average(),
lives_histogram.average(),
score_histogram.percentage_with(&PERFECT_SCORE) * 100.0 score_histogram.percentage_with(&PERFECT_SCORE) * 100.0
); );
} }
} }
let (game, json_output) = simulate_once(&opts, let (game, json_output) = simulate_once(
&opts,
strat_config_ref.initialize(&opts), strat_config_ref.initialize(&opts),
seed, seed,
json_output_pattern_ref.is_some()); json_output_pattern_ref.is_some(),
);
let score = game.score(); let score = game.score();
lives_histogram.insert(game.board.lives_remaining); lives_histogram.insert(game.board.lives_remaining);
score_histogram.insert(score); score_histogram.insert(score);
if score != PERFECT_SCORE { non_perfect_seeds.push(seed); } if score != PERFECT_SCORE {
non_perfect_seeds.push(seed);
}
if let Some(file_pattern) = json_output_pattern_ref { if let Some(file_pattern) = json_output_pattern_ref {
if !(score == PERFECT_SCORE && json_losses_only) { if !(score == PERFECT_SCORE && json_losses_only) {
let file_pattern = file_pattern.clone().replace("%s", &seed.to_string()); let file_pattern =
file_pattern.clone().replace("%s", &seed.to_string());
let path = std::path::Path::new(&file_pattern); let path = std::path::Path::new(&file_pattern);
let file = std::fs::File::create(path).unwrap(); let file = std::fs::File::create(path).unwrap();
serde_json::to_writer(file, &json_output.unwrap()).unwrap(); serde_json::to_writer(file, &json_output.unwrap()).unwrap();
@ -224,7 +232,8 @@ pub fn simulate<T: ?Sized>(
let mut score_histogram = Histogram::new(); let mut score_histogram = Histogram::new();
let mut lives_histogram = Histogram::new(); let mut lives_histogram = Histogram::new();
for join_handle in join_handles { for join_handle in join_handles {
let (thread_non_perfect_seeds, thread_score_histogram, thread_lives_histogram) = join_handle.join(); let (thread_non_perfect_seeds, thread_score_histogram, thread_lives_histogram) =
join_handle.join();
non_perfect_seeds.extend(thread_non_perfect_seeds.iter()); non_perfect_seeds.extend(thread_non_perfect_seeds.iter());
score_histogram.merge(thread_score_histogram); score_histogram.merge(thread_score_histogram);
lives_histogram.merge(thread_lives_histogram); lives_histogram.merge(thread_lives_histogram);

View file

@ -1,9 +1,9 @@
use std::rc::Rc;
use std::cell::{RefCell};
use fnv::{FnvHashMap, FnvHashSet}; use fnv::{FnvHashMap, FnvHashSet};
use std::cell::RefCell;
use std::rc::Rc;
use strategy::*;
use game::*; use game::*;
use strategy::*;
// strategy that explicitly cheats by using Rc/RefCell // strategy that explicitly cheats by using Rc/RefCell
// serves as a reference point for other strategies // serves as a reference point for other strategies
@ -44,9 +44,9 @@ impl CheatingStrategy {
impl GameStrategy for CheatingStrategy { impl GameStrategy for CheatingStrategy {
fn initialize(&self, player: Player, view: &BorrowedGameView) -> Box<PlayerStrategy> { fn initialize(&self, player: Player, view: &BorrowedGameView) -> Box<PlayerStrategy> {
for (&player, &hand) in &view.other_hands { for (&player, &hand) in &view.other_hands {
self.player_hands_cheat.borrow_mut().insert( self.player_hands_cheat
player, hand.clone() .borrow_mut()
); .insert(player, hand.clone());
} }
Box::new(CheatingPlayerStrategy { Box::new(CheatingPlayerStrategy {
player_hands_cheat: self.player_hands_cheat.clone(), player_hands_cheat: self.player_hands_cheat.clone(),
@ -64,9 +64,9 @@ impl CheatingPlayerStrategy {
fn inform_last_player_cards(&self, view: &BorrowedGameView) { 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);
let their_hand = *view.other_hands.get(&next).unwrap(); let their_hand = *view.other_hands.get(&next).unwrap();
self.player_hands_cheat.borrow_mut().insert( self.player_hands_cheat
next, their_hand.clone() .borrow_mut()
); .insert(next, their_hand.clone());
} }
// 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
@ -75,7 +75,7 @@ impl CheatingPlayerStrategy {
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 {
player: hint_player, player: hint_player,
hinted: Hinted::Value(hint_card.value) hinted: Hinted::Value(hint_card.value),
}) })
} }
@ -93,7 +93,9 @@ 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: &BorrowedGameView, hand: &Cards) -> u32 { fn hand_play_value(&self, view: &BorrowedGameView, hand: &Cards) -> u32 {
hand.iter().map(|card| self.card_play_value(view, card)).fold(0, |a,b| a+b) hand.iter()
.map(|card| self.card_play_value(view, card))
.fold(0, |a, b| a + b)
} }
// how badly do we need to play a particular card // how badly do we need to play a particular card
@ -109,7 +111,7 @@ impl CheatingPlayerStrategy {
let their_hand_value = self.hand_play_value(view, hands.get(&player).unwrap()); let their_hand_value = self.hand_play_value(view, hands.get(&player).unwrap());
// they can play this card, and have less urgent plays than i do // they can play this card, and have less urgent plays than i do
if their_hand_value < my_hand_value { if their_hand_value < my_hand_value {
return 10 - (card.value as i32) return 10 - (card.value as i32);
} }
} }
} }
@ -132,7 +134,7 @@ impl CheatingPlayerStrategy {
} }
set.insert(card.clone()); set.insert(card.clone());
} }
return None return None;
} }
} }
impl PlayerStrategy for CheatingPlayerStrategy { impl PlayerStrategy for CheatingPlayerStrategy {
@ -144,9 +146,11 @@ impl PlayerStrategy for CheatingPlayerStrategy {
let hands = self.player_hands_cheat.borrow(); let hands = self.player_hands_cheat.borrow();
let my_hand = hands.get(&self.me).unwrap(); let my_hand = hands.get(&self.me).unwrap();
let playable_cards = my_hand.iter().enumerate().filter(|&(_, card)| { let playable_cards = my_hand
view.board.is_playable(card) .iter()
}).collect::<Vec<_>>(); .enumerate()
.filter(|&(_, card)| view.board.is_playable(card))
.collect::<Vec<_>>();
if playable_cards.len() > 0 { if playable_cards.len() > 0 {
// play the best playable card // play the best playable card
@ -161,15 +165,14 @@ impl PlayerStrategy for CheatingPlayerStrategy {
play_score = score; play_score = score;
} }
} }
return TurnChoice::Play(index) return TurnChoice::Play(index);
} }
// discard threshold is how many cards we're willing to discard // discard threshold is how many cards we're willing to discard
// such that if we only played, // such that if we only played,
// we would not reach the final countdown round // we would not reach the final countdown round
// e.g. 50 total, 25 to play, 20 in hand // e.g. 50 total, 25 to play, 20 in hand
let discard_threshold = let discard_threshold = view.board.total_cards
view.board.total_cards
- (COLORS.len() * VALUES.len()) as u32 - (COLORS.len() * VALUES.len()) as u32
- (view.board.num_players * view.board.hand_size); - (view.board.num_players * view.board.hand_size);
if view.board.discard_size() <= discard_threshold { if view.board.discard_size() <= discard_threshold {
@ -211,6 +214,5 @@ impl PlayerStrategy for CheatingPlayerStrategy {
} }
TurnChoice::Discard(index) TurnChoice::Discard(index)
} }
fn update(&mut self, _: &TurnRecord, _: &BorrowedGameView) { fn update(&mut self, _: &TurnRecord, _: &BorrowedGameView) {}
}
} }

View file

@ -1,6 +1,6 @@
use strategy::*;
use game::*; use game::*;
use rand::{self, Rng}; use rand::{self, Rng};
use strategy::*;
// dummy, terrible strategy, as an example // dummy, terrible strategy, as an example
#[derive(Clone)] #[derive(Clone)]
@ -40,14 +40,19 @@ pub struct RandomStrategyPlayer {
impl PlayerStrategy for RandomStrategyPlayer { impl PlayerStrategy for RandomStrategyPlayer {
fn name(&self) -> String { fn name(&self) -> String {
format!("random(hint={}, play={})", self.hint_probability, self.play_probability) format!(
"random(hint={}, play={})",
self.hint_probability, self.play_probability
)
} }
fn decide(&mut self, view: &BorrowedGameView) -> TurnChoice { 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 {
let hint_player = view.board.player_to_left(&self.me); let hint_player = view.board.player_to_left(&self.me);
let hint_card = rand::thread_rng().choose(&view.get_hand(&hint_player)).unwrap(); let hint_card = rand::thread_rng()
.choose(&view.get_hand(&hint_player))
.unwrap();
let hinted = { let hinted = {
if rand::random() { if rand::random() {
// hint a color // hint a color
@ -69,6 +74,5 @@ impl PlayerStrategy for RandomStrategyPlayer {
TurnChoice::Discard(0) TurnChoice::Discard(0)
} }
} }
fn update(&mut self, _: &TurnRecord, _: &BorrowedGameView) { fn update(&mut self, _: &TurnRecord, _: &BorrowedGameView) {}
}
} }

View file

@ -82,22 +82,17 @@ pub trait Question {
// get the answer to this question, given cards // get the answer to this question, given cards
fn answer(&self, &Cards, &BoardState) -> u32; fn answer(&self, &Cards, &BoardState) -> u32;
// process the answer to this question, updating card info // process the answer to this question, updating card info
fn acknowledge_answer( fn acknowledge_answer(&self, value: u32, &mut HandInfo<CardPossibilityTable>, &BoardState);
&self, value: u32, &mut HandInfo<CardPossibilityTable>, &BoardState
);
fn answer_info(&self, hand: &Cards, board: &BoardState) -> ModulusInformation { fn answer_info(&self, hand: &Cards, board: &BoardState) -> ModulusInformation {
ModulusInformation::new( ModulusInformation::new(self.info_amount(), self.answer(hand, board))
self.info_amount(),
self.answer(hand, board)
)
} }
fn acknowledge_answer_info( fn acknowledge_answer_info(
&self, &self,
answer: ModulusInformation, answer: ModulusInformation,
hand_info: &mut HandInfo<CardPossibilityTable>, hand_info: &mut HandInfo<CardPossibilityTable>,
board: &BoardState board: &BoardState,
) { ) {
assert!(self.info_amount() == answer.modulus); assert!(self.info_amount() == answer.modulus);
self.acknowledge_answer(answer.value, hand_info, board); self.acknowledge_answer(answer.value, hand_info, board);
@ -112,8 +107,7 @@ pub trait PublicInformation: Clone {
fn set_board(&mut self, &BoardState); fn set_board(&mut self, &BoardState);
/// If we store more state than just `HandInfo<CardPossibilityTable>`s, update it after `set_player_info` has been called. /// If we store more state than just `HandInfo<CardPossibilityTable>`s, update it after `set_player_info` has been called.
fn update_other_info(&mut self) { fn update_other_info(&mut self) {}
}
fn agrees_with(&self, other: Self) -> bool; fn agrees_with(&self, other: Self) -> bool;
@ -126,11 +120,19 @@ pub trait PublicInformation: Clone {
/// ///
/// Note that `self` does not reflect the answers to previous questions; it reflects the state /// Note that `self` does not reflect the answers to previous questions; it reflects the state
/// before the entire "hat value" calculation. /// before the entire "hat value" calculation.
fn ask_question(&self, &Player, &HandInfo<CardPossibilityTable>, total_info: u32) -> Option<Box<Question>>; fn ask_question(
&self,
&Player,
&HandInfo<CardPossibilityTable>,
total_info: u32,
) -> Option<Box<Question>>;
fn ask_question_wrapper(&self, player: &Player, hand_info: &HandInfo<CardPossibilityTable>, total_info: u32) fn ask_question_wrapper(
-> Option<Box<Question>> &self,
{ player: &Player,
hand_info: &HandInfo<CardPossibilityTable>,
total_info: u32,
) -> Option<Box<Question>> {
assert!(total_info > 0); assert!(total_info > 0);
if total_info == 1 { if total_info == 1 {
None None
@ -138,8 +140,11 @@ pub trait PublicInformation: Clone {
let result = self.ask_question(player, hand_info, total_info); let result = self.ask_question(player, hand_info, total_info);
if let Some(ref question) = result { if let Some(ref question) = result {
if question.info_amount() > total_info { if question.info_amount() > total_info {
panic!("ask_question returned question with info_amount = {} > total_info = {}!", panic!(
question.info_amount(), total_info); "ask_question returned question with info_amount = {} > total_info = {}!",
question.info_amount(),
total_info
);
} }
if question.info_amount() == 1 { if question.info_amount() == 1 {
panic!("ask_question returned a trivial question!"); panic!("ask_question returned a trivial question!");
@ -157,11 +162,17 @@ pub trait PublicInformation: Clone {
} }
fn get_hat_info_for_player( fn get_hat_info_for_player(
&self, player: &Player, hand_info: &mut HandInfo<CardPossibilityTable>, total_info: u32, view: &OwnedGameView &self,
player: &Player,
hand_info: &mut HandInfo<CardPossibilityTable>,
total_info: u32,
view: &OwnedGameView,
) -> ModulusInformation { ) -> ModulusInformation {
assert!(player != &view.player); assert!(player != &view.player);
let mut answer_info = ModulusInformation::none(); let mut answer_info = ModulusInformation::none();
while let Some(question) = self.ask_question_wrapper(player, hand_info, answer_info.info_remaining(total_info)) { while let Some(question) =
self.ask_question_wrapper(player, hand_info, answer_info.info_remaining(total_info))
{
let new_answer_info = question.answer_info(view.get_hand(player), view.get_board()); 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()); question.acknowledge_answer_info(new_answer_info.clone(), hand_info, view.get_board());
answer_info.combine(new_answer_info, total_info); answer_info.combine(new_answer_info, total_info);
@ -188,18 +199,22 @@ pub trait PublicInformation: Clone {
/// `self.get_hat_sum(total_info, view)` tells us which choice to take, and at the same time /// `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. /// mutates `self` to simulate the choice becoming common knowledge.
fn get_hat_sum(&mut self, total_info: u32, view: &OwnedGameView) -> ModulusInformation { 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 (infos, new_player_hands): (Vec<_>, Vec<_>) = view
.get_other_players()
.iter()
.map(|player| {
let mut hand_info = self.get_player_info(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); let info = self.get_hat_info_for_player(player, &mut hand_info, total_info, view);
(info, (player.clone(), hand_info)) (info, (player.clone(), hand_info))
}).unzip(); })
.unzip();
self.set_player_infos(new_player_hands); self.set_player_infos(new_player_hands);
infos.into_iter().fold( infos.into_iter().fold(
ModulusInformation::new(total_info, 0), ModulusInformation::new(total_info, 0),
|mut sum_info, info| { |mut sum_info, info| {
sum_info.add(&info); sum_info.add(&info);
sum_info sum_info
} },
) )
} }
@ -208,13 +223,17 @@ pub trait PublicInformation: Clone {
/// from that fact. /// from that fact.
fn update_from_hat_sum(&mut self, mut info: ModulusInformation, view: &OwnedGameView) { fn update_from_hat_sum(&mut self, mut info: ModulusInformation, view: &OwnedGameView) {
let info_source = view.board.player; let info_source = view.board.player;
let (other_infos, mut new_player_hands): (Vec<_>, Vec<_>) = view.get_other_players().into_iter().filter(|player| { let (other_infos, mut new_player_hands): (Vec<_>, Vec<_>) = view
*player != info_source .get_other_players()
}).map(|player| { .into_iter()
.filter(|player| *player != info_source)
.map(|player| {
let mut hand_info = self.get_player_info(&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); let player_info =
self.get_hat_info_for_player(&player, &mut hand_info, info.modulus, view);
(player_info, (player.clone(), hand_info)) (player_info, (player.clone(), hand_info))
}).unzip(); })
.unzip();
for other_info in other_infos { for other_info in other_infos {
info.subtract(&other_info); info.subtract(&other_info);
} }

View file

@ -1,11 +1,11 @@
use float_ord::*;
use fnv::{FnvHashMap, FnvHashSet}; use fnv::{FnvHashMap, FnvHashSet};
use std::cmp::Ordering; use std::cmp::Ordering;
use float_ord::*;
use strategy::*;
use game::*; use game::*;
use helpers::*; use helpers::*;
use strategies::hat_helpers::*; use strategies::hat_helpers::*;
use strategy::*;
// TODO: use random extra information - i.e. when casting up and down, // TODO: use random extra information - i.e. when casting up and down,
// we sometimes have 2 choices of value to choose // we sometimes have 2 choices of value to choose
@ -14,17 +14,21 @@ use strategies::hat_helpers::*;
type PropertyPredicate = fn(&BoardState, &Card) -> bool; type PropertyPredicate = fn(&BoardState, &Card) -> bool;
struct CardHasProperty struct CardHasProperty {
{
index: usize, index: usize,
property: PropertyPredicate, property: PropertyPredicate,
} }
impl Question for CardHasProperty impl Question for CardHasProperty {
{ fn info_amount(&self) -> u32 {
fn info_amount(&self) -> u32 { 2 } 2
}
fn answer(&self, hand: &Cards, board: &BoardState) -> u32 { fn answer(&self, hand: &Cards, board: &BoardState) -> u32 {
let ref card = hand[self.index]; let ref card = hand[self.index];
if (self.property)(board, card) { 1 } else { 0 } if (self.property)(board, card) {
1
} else {
0
}
} }
fn acknowledge_answer( fn acknowledge_answer(
&self, &self,
@ -36,18 +40,28 @@ impl Question for CardHasProperty
let possible = card_table.get_possibilities(); let possible = card_table.get_possibilities();
for card in &possible { for card in &possible {
if (self.property)(board, card) { if (self.property)(board, card) {
if answer == 0 { card_table.mark_false(card); } if answer == 0 {
card_table.mark_false(card);
}
} else { } else {
if answer == 1 { card_table.mark_false(card); } if answer == 1 {
card_table.mark_false(card);
}
} }
} }
} }
} }
fn q_is_playable(index: usize) -> CardHasProperty { fn q_is_playable(index: usize) -> CardHasProperty {
CardHasProperty {index, property: |board, card| board.is_playable(card)} CardHasProperty {
index,
property: |board, card| board.is_playable(card),
}
} }
fn q_is_dead(index: usize) -> CardHasProperty { fn q_is_dead(index: usize) -> CardHasProperty {
CardHasProperty {index, property: |board, card| board.is_dead(card)} CardHasProperty {
index,
property: |board, card| board.is_dead(card),
}
} }
/// For some list of questions l, the question `AdditiveComboQuestion { questions : l }` asks: /// For some list of questions l, the question `AdditiveComboQuestion { questions : l }` asks:
@ -62,7 +76,11 @@ struct AdditiveComboQuestion {
} }
impl Question for AdditiveComboQuestion { impl Question for AdditiveComboQuestion {
fn info_amount(&self) -> u32 { fn info_amount(&self) -> u32 {
self.questions.iter().map(|q| { q.info_amount() - 1 }).sum::<u32>() + 1 self.questions
.iter()
.map(|q| q.info_amount() - 1)
.sum::<u32>()
+ 1
} }
fn answer(&self, hand: &Cards, board: &BoardState) -> u32 { fn answer(&self, hand: &Cards, board: &BoardState) -> u32 {
let mut toadd = 1; let mut toadd = 1;
@ -107,7 +125,10 @@ struct CardPossibilityPartition {
} }
impl CardPossibilityPartition { impl CardPossibilityPartition {
fn new( fn new(
index: usize, max_n_partitions: u32, card_table: &CardPossibilityTable, board: &BoardState index: usize,
max_n_partitions: u32,
card_table: &CardPossibilityTable,
board: &BoardState,
) -> CardPossibilityPartition { ) -> CardPossibilityPartition {
let mut cur_block = 0; let mut cur_block = 0;
let mut partition = FnvHashMap::default(); let mut partition = FnvHashMap::default();
@ -159,7 +180,9 @@ impl CardPossibilityPartition {
} }
} }
impl Question for CardPossibilityPartition { impl Question for CardPossibilityPartition {
fn info_amount(&self) -> u32 { self.n_partitions } fn info_amount(&self) -> u32 {
self.n_partitions
}
fn answer(&self, hand: &Cards, _: &BoardState) -> u32 { fn answer(&self, hand: &Cards, _: &BoardState) -> u32 {
let ref card = hand[self.index]; let ref card = hand[self.index];
*self.partition.get(&card).unwrap() *self.partition.get(&card).unwrap()
@ -197,7 +220,10 @@ impl MyPublicInformation {
fn get_other_players_starting_after(&self, player: Player) -> Vec<Player> { fn get_other_players_starting_after(&self, player: Player) -> Vec<Player> {
let n = self.board.num_players; let n = self.board.num_players;
(0 .. n - 1).into_iter().map(|i| { (player + 1 + i) % n }).collect() (0..n - 1)
.into_iter()
.map(|i| (player + 1 + i) % n)
.collect()
} }
// Returns the number of ways to hint the player. // Returns the number of ways to hint the player.
@ -208,19 +234,19 @@ impl MyPublicInformation {
let ref info = self.hand_info[&player]; let ref info = self.hand_info[&player];
let may_be_all_one_color = COLORS.iter().any(|color| { let may_be_all_one_color = COLORS
info.iter().all(|card| { .iter()
card.can_be_color(*color) .any(|color| info.iter().all(|card| card.can_be_color(*color)));
})
});
let may_be_all_one_number = VALUES.iter().any(|value| { let may_be_all_one_number = VALUES
info.iter().all(|card| { .iter()
card.can_be_value(*value) .any(|value| info.iter().all(|card| card.can_be_value(*value)));
})
});
return if !may_be_all_one_color && !may_be_all_one_number { 4 } else { 3 } return if !may_be_all_one_color && !may_be_all_one_number {
4
} else {
3
};
} }
fn get_hint_index_score(&self, card_table: &CardPossibilityTable) -> i32 { fn get_hint_index_score(&self, card_table: &CardPossibilityTable) -> i32 {
@ -242,10 +268,14 @@ impl MyPublicInformation {
} }
fn get_index_for_hint(&self, player: &Player) -> usize { fn get_index_for_hint(&self, player: &Player) -> usize {
let mut scores = self.hand_info[player].iter().enumerate().map(|(i, card_table)| { let mut scores = self.hand_info[player]
.iter()
.enumerate()
.map(|(i, card_table)| {
let score = self.get_hint_index_score(card_table); let score = self.get_hint_index_score(card_table);
(-score, i) (-score, i)
}).collect::<Vec<_>>(); })
.collect::<Vec<_>>();
scores.sort(); scores.sort();
scores[0].1 scores[0].1
} }
@ -266,14 +296,18 @@ impl MyPublicInformation {
// knowledge about the cards? // knowledge about the cards?
let hinter = view.player; let hinter = view.player;
let info_per_player: Vec<_> = self.get_other_players_starting_after(hinter).into_iter().map( let info_per_player: Vec<_> = self
|player| { self.get_info_per_player(player) } .get_other_players_starting_after(hinter)
).collect(); .into_iter()
.map(|player| self.get_info_per_player(player))
.collect();
let total_info = info_per_player.iter().sum(); let total_info = info_per_player.iter().sum();
// FIXME explain and clean up // FIXME explain and clean up
let card_indices: Vec<_> = self.get_other_players_starting_after(hinter).into_iter().map( let card_indices: Vec<_> = self
|player| { self.get_index_for_hint(&player) } .get_other_players_starting_after(hinter)
).collect(); .into_iter()
.map(|player| self.get_index_for_hint(&player))
.collect();
let hint_info = self.get_hat_sum(total_info, view); let hint_info = self.get_hat_sum(total_info, view);
@ -352,32 +386,39 @@ impl MyPublicInformation {
} }
} }
}; };
hint_option_set.into_iter().collect::<FnvHashSet<_>>().into_iter().map(|hinted| { hint_option_set
Hint { .into_iter()
.collect::<FnvHashSet<_>>()
.into_iter()
.map(|hinted| Hint {
player: hint_player, player: hint_player,
hinted: hinted, hinted: hinted,
} })
}).collect() .collect()
} }
fn decode_hint_choice(&self, hint: &Hint, result: &Vec<bool>) -> ModulusInformation { fn decode_hint_choice(&self, hint: &Hint, result: &Vec<bool>) -> ModulusInformation {
let hinter = self.board.player; let hinter = self.board.player;
let info_per_player: Vec<_> = self.get_other_players_starting_after(hinter).into_iter().map( let info_per_player: Vec<_> = self
|player| { self.get_info_per_player(player) } .get_other_players_starting_after(hinter)
).collect(); .into_iter()
.map(|player| self.get_info_per_player(player))
.collect();
let total_info = info_per_player.iter().sum(); let total_info = info_per_player.iter().sum();
let n = self.board.num_players; let n = self.board.num_players;
let player_amt = (n + hint.player - hinter - 1) % n; let player_amt = (n + hint.player - hinter - 1) % n;
let amt_from_prev_players = info_per_player.iter().take(player_amt as usize).fold(0, |a, b| a + b); let amt_from_prev_players = info_per_player
.iter()
.take(player_amt as usize)
.fold(0, |a, b| a + b);
let hint_info_we_can_give_to_this_player = info_per_player[player_amt as usize]; let 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 card_index = self.get_index_for_hint(&hint.player);
let hint_type = let hint_type = if hint_info_we_can_give_to_this_player == 3 {
if hint_info_we_can_give_to_this_player == 3 {
if result[card_index] { if result[card_index] {
match hint.hinted { match hint.hinted {
Hinted::Value(_) => 0, Hinted::Value(_) => 0,
@ -416,17 +457,18 @@ impl MyPublicInformation {
} }
fn knows_playable_card(&self, player: &Player) -> bool { fn knows_playable_card(&self, player: &Player) -> bool {
self.hand_info[player].iter().any(|table| { self.hand_info[player]
table.probability_is_playable(&self.board) == 1.0 .iter()
}) .any(|table| table.probability_is_playable(&self.board) == 1.0)
} }
fn someone_else_needs_hint(&self, view: &OwnedGameView) -> bool { fn someone_else_needs_hint(&self, view: &OwnedGameView) -> bool {
// Does another player have a playable card, but doesn't know it? // Does another player have a playable card, but doesn't know it?
view.get_other_players().iter().any(|player| { view.get_other_players().iter().any(|player| {
let has_playable_card = view.get_hand(&player).iter().any(|card| { let has_playable_card = view
view.get_board().is_playable(card) .get_hand(&player)
}); .iter()
.any(|card| view.get_board().is_playable(card));
has_playable_card && !self.knows_playable_card(&player) has_playable_card && !self.knows_playable_card(&player)
}) })
} }
@ -457,7 +499,7 @@ impl MyPublicInformation {
new_view: &BorrowedGameView, new_view: &BorrowedGameView,
player: &Player, player: &Player,
index: usize, index: usize,
card: &Card card: &Card,
) { ) {
let new_card_table = CardPossibilityTable::from(&self.card_counts); let new_card_table = CardPossibilityTable::from(&self.card_counts);
{ {
@ -486,10 +528,13 @@ impl MyPublicInformation {
impl PublicInformation for MyPublicInformation { impl PublicInformation for MyPublicInformation {
fn new(board: &BoardState) -> Self { fn new(board: &BoardState) -> Self {
let hand_info = board.get_players().map(|player| { let hand_info = board
.get_players()
.map(|player| {
let hand_info = HandInfo::new(board.hand_size); let hand_info = HandInfo::new(board.hand_size);
(player, hand_info) (player, hand_info)
}).collect::<FnvHashMap<_,_>>(); })
.collect::<FnvHashMap<_, _>>();
MyPublicInformation { MyPublicInformation {
hand_info: hand_info, hand_info: hand_info,
card_counts: CardCounts::new(), card_counts: CardCounts::new(),
@ -522,33 +567,58 @@ impl PublicInformation for MyPublicInformation {
// Changing anything inside this function will not break the information transfer // Changing anything inside this function will not break the information transfer
// mechanisms! // mechanisms!
let augmented_hand_info_raw = hand_info.iter().cloned().enumerate().filter_map(|(i, card_table)| { let augmented_hand_info_raw = hand_info
.iter()
.cloned()
.enumerate()
.filter_map(|(i, card_table)| {
let p_play = card_table.probability_is_playable(&self.board); let p_play = card_table.probability_is_playable(&self.board);
let p_dead = card_table.probability_is_dead(&self.board); let p_dead = card_table.probability_is_dead(&self.board);
Some((i, p_play, p_dead)) Some((i, p_play, p_dead))
}).collect::<Vec<_>>(); })
let know_playable_card = augmented_hand_info_raw.iter().any(|&(_, p_play, _)| p_play == 1.0); .collect::<Vec<_>>();
let know_dead_card = augmented_hand_info_raw.iter().any(|&(_, _, p_dead)| p_dead == 1.0); let know_playable_card = augmented_hand_info_raw
.iter()
.any(|&(_, p_play, _)| p_play == 1.0);
let know_dead_card = augmented_hand_info_raw
.iter()
.any(|&(_, _, p_dead)| p_dead == 1.0);
// We don't need to find out anything about cards that are determined or dead. // We don't need to find out anything about cards that are determined or dead.
let augmented_hand_info = augmented_hand_info_raw.into_iter().filter(|&(i, _, p_dead)| { let augmented_hand_info = augmented_hand_info_raw
if p_dead == 1.0 { false } .into_iter()
else if hand_info[i].is_determined() { false } .filter(|&(i, _, p_dead)| {
else { true } if p_dead == 1.0 {
}).collect::<Vec<_>>(); false
} else if hand_info[i].is_determined() {
false
} else {
true
}
})
.collect::<Vec<_>>();
if !know_playable_card { if !know_playable_card {
// Vector of tuples (ask_dead, i, p_yes), where ask_dead=false means we'll // Vector of tuples (ask_dead, i, p_yes), where ask_dead=false means we'll
// ask if the card at i is playable, and ask_dead=true means we ask if the card at i is // ask if the card at i is playable, and ask_dead=true means we ask if the card at i is
// dead. p_yes is the probability the answer is nonzero. // dead. p_yes is the probability the answer is nonzero.
let mut to_ask: Vec<(bool, usize, f32)> = augmented_hand_info.iter().filter_map(|&(i, p_play, _)| { let mut to_ask: Vec<(bool, usize, f32)> = augmented_hand_info
if p_play == 0.0 { None } .iter()
else { Some((false, i, p_play)) } .filter_map(|&(i, p_play, _)| {
}).collect(); if p_play == 0.0 {
None
} else {
Some((false, i, p_play))
}
})
.collect();
if !know_dead_card { if !know_dead_card {
to_ask.extend(augmented_hand_info.iter().filter_map(|&(i, _, p_dead)| { to_ask.extend(augmented_hand_info.iter().filter_map(|&(i, _, p_dead)| {
if p_dead == 0.0 { None } if p_dead == 0.0 {
else { Some((true, i, p_dead)) } None
} else {
Some((true, i, p_dead))
}
})); }));
} }
@ -556,7 +626,7 @@ impl PublicInformation for MyPublicInformation {
if to_ask.len() > combo_question_capacity { if to_ask.len() > combo_question_capacity {
// The questions don't fit into an AdditiveComboQuestion. // The questions don't fit into an AdditiveComboQuestion.
// Sort by type (ask_dead=false first), then by p_yes (bigger first) // Sort by type (ask_dead=false first), then by p_yes (bigger first)
to_ask.sort_by_key(|&(ask_dead, _, p_yes)| {(ask_dead, FloatOrd(-p_yes))}); to_ask.sort_by_key(|&(ask_dead, _, p_yes)| (ask_dead, FloatOrd(-p_yes)));
to_ask.truncate(combo_question_capacity); to_ask.truncate(combo_question_capacity);
} }
@ -564,20 +634,28 @@ impl PublicInformation for MyPublicInformation {
// better to put lower-probability-of-playability/death cards first: The difference // better to put lower-probability-of-playability/death cards first: The difference
// only matters if we find a playable/dead card, and conditional on that, it's better // only matters if we find a playable/dead card, and conditional on that, it's better
// to find out about as many non-playable/non-dead cards as possible. // to find out about as many non-playable/non-dead cards as possible.
to_ask.sort_by_key(|&(ask_dead, _, p_yes)| {(ask_dead, FloatOrd(p_yes))}); to_ask.sort_by_key(|&(ask_dead, _, p_yes)| (ask_dead, FloatOrd(p_yes)));
let questions = to_ask.into_iter().map(|(ask_dead, i, _)| -> Box<Question> { let questions = to_ask
if ask_dead { Box::new(q_is_dead(i)) } .into_iter()
else { Box::new(q_is_playable(i)) } .map(|(ask_dead, i, _)| -> Box<Question> {
}).collect::<Vec<_>>(); if ask_dead {
Box::new(q_is_dead(i))
} else {
Box::new(q_is_playable(i))
}
})
.collect::<Vec<_>>();
if questions.len() > 0 { if questions.len() > 0 {
return Some(Box::new(AdditiveComboQuestion { questions })) return Some(Box::new(AdditiveComboQuestion { questions }));
} }
} }
let ask_play_score = |p_play: f32| FloatOrd((p_play - 0.7).abs()); let ask_play_score = |p_play: f32| FloatOrd((p_play - 0.7).abs());
let mut ask_play = augmented_hand_info.iter().filter(|&&(_, p_play, _)| { let mut ask_play = augmented_hand_info
ask_play_score(p_play) < FloatOrd(0.2) .iter()
}).cloned().collect::<Vec<_>>(); .filter(|&&(_, p_play, _)| ask_play_score(p_play) < FloatOrd(0.2))
.cloned()
.collect::<Vec<_>>();
ask_play.sort_by_key(|&(i, p_play, _)| (ask_play_score(p_play), i)); ask_play.sort_by_key(|&(i, p_play, _)| (ask_play_score(p_play), i));
if let Some(&(i, _, _)) = ask_play.get(0) { if let Some(&(i, _, _)) = ask_play.get(0) {
return Some(Box::new(q_is_playable(i))); return Some(Box::new(q_is_playable(i)));
@ -585,9 +663,7 @@ impl PublicInformation for MyPublicInformation {
let mut ask_partition = augmented_hand_info; let mut ask_partition = augmented_hand_info;
// sort by probability of death (lowest first), then by index // sort by probability of death (lowest first), then by index
ask_partition.sort_by_key(|&(i, _, p_death)| { ask_partition.sort_by_key(|&(i, _, p_death)| (FloatOrd(p_death), i));
(FloatOrd(p_death), i)
});
if let Some(&(i, _, _)) = ask_partition.get(0) { if let Some(&(i, _, _)) = ask_partition.get(0) {
let question = CardPossibilityPartition::new(i, total_info, &hand_info[i], &self.board); let question = CardPossibilityPartition::new(i, total_info, &hand_info[i], &self.board);
Some(Box::new(question)) Some(Box::new(question))
@ -597,8 +673,6 @@ impl PublicInformation for MyPublicInformation {
} }
} }
pub struct InformationStrategyConfig; pub struct InformationStrategyConfig;
impl InformationStrategyConfig { impl InformationStrategyConfig {
@ -641,8 +715,12 @@ pub struct InformationPlayerStrategy {
impl InformationPlayerStrategy { impl InformationPlayerStrategy {
// 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: &OwnedGameView, card_table: &CardPossibilityTable) -> f32 { fn get_average_play_score(
let f = |card: &Card| { self.get_play_score(view, card) }; &self,
view: &OwnedGameView,
card_table: &CardPossibilityTable,
) -> f32 {
let f = |card: &Card| self.get_play_score(view, card);
card_table.weighted_score(&f) card_table.weighted_score(&f)
} }
@ -660,7 +738,11 @@ impl InformationPlayerStrategy {
(10.0 - card.value as f32) / (num_with as f32) (10.0 - card.value as f32) / (num_with as f32)
} }
fn find_useless_cards(&self, board: &BoardState, hand: &HandInfo<CardPossibilityTable>) -> Vec<usize> { fn find_useless_cards(
&self,
board: &BoardState,
hand: &HandInfo<CardPossibilityTable>,
) -> Vec<usize> {
let mut useless: FnvHashSet<usize> = FnvHashSet::default(); let mut useless: FnvHashSet<usize> = FnvHashSet::default();
let mut seen: FnvHashMap<Card, usize> = FnvHashMap::default(); let mut seen: FnvHashMap<Card, usize> = FnvHashMap::default();
@ -706,12 +788,8 @@ impl InformationPlayerStrategy {
} }
let old_weight = card_table.total_weight(); let old_weight = card_table.total_weight();
match *hinted { match *hinted {
Hinted::Color(color) => { Hinted::Color(color) => card_table.mark_color(color, color == card.color),
card_table.mark_color(color, color == card.color) Hinted::Value(value) => card_table.mark_value(value, value == card.value),
}
Hinted::Value(value) => {
card_table.mark_value(value, value == card.value)
}
}; };
let new_weight = card_table.total_weight(); let new_weight = card_table.total_weight();
assert!(new_weight <= old_weight); assert!(new_weight <= old_weight);
@ -736,13 +814,12 @@ impl InformationPlayerStrategy {
let view = &self.last_view; let view = &self.last_view;
// using hint goodness barely helps // using hint goodness barely helps
let mut hint_options = hints.into_iter().map(|hint| { let mut hint_options = hints
(self.hint_goodness(&hint, view), hint) .into_iter()
}).collect::<Vec<_>>(); .map(|hint| (self.hint_goodness(&hint, view), hint))
.collect::<Vec<_>>();
hint_options.sort_by(|h1, h2| { hint_options.sort_by(|h1, h2| h2.0.partial_cmp(&h1.0).unwrap_or(Ordering::Equal));
h2.0.partial_cmp(&h1.0).unwrap_or(Ordering::Equal)
});
if hint_options.len() == 0 { if hint_options.len() == 0 {
// NOTE: Technically possible, but never happens // NOTE: Technically possible, but never happens
@ -780,39 +857,46 @@ impl InformationPlayerStrategy {
// If possible, play the best playable card // If possible, play the best playable card
// the higher the play_score, the better to play // the higher the play_score, the better to play
let mut playable_cards = private_info.iter().enumerate().filter_map(|(i, card_table)| { let mut playable_cards = private_info
if card_table.probability_is_playable(&view.board) != 1.0 { return None; } .iter()
.enumerate()
.filter_map(|(i, card_table)| {
if card_table.probability_is_playable(&view.board) != 1.0 {
return None;
}
Some((i, self.get_average_play_score(view, card_table))) Some((i, self.get_average_play_score(view, card_table)))
}).collect::<Vec<_>>(); })
.collect::<Vec<_>>();
playable_cards.sort_by_key(|&(i, play_score)| (FloatOrd(-play_score), i)); playable_cards.sort_by_key(|&(i, play_score)| (FloatOrd(-play_score), i));
if let Some(&(play_index, _)) = playable_cards.get(0) { if let Some(&(play_index, _)) = playable_cards.get(0) {
return TurnChoice::Play(play_index) return TurnChoice::Play(play_index);
} }
let discard_threshold = let discard_threshold = view.board.total_cards
view.board.total_cards
- (COLORS.len() * VALUES.len()) as u32 - (COLORS.len() * VALUES.len()) as u32
- (view.board.num_players * view.board.hand_size); - (view.board.num_players * view.board.hand_size);
// make a possibly risky play // make a possibly risky play
// TODO: consider removing this, if we improve information transfer // TODO: consider removing this, if we improve information transfer
if view.board.lives_remaining > 1 && if view.board.lives_remaining > 1 && view.board.discard_size() <= discard_threshold {
view.board.discard_size() <= discard_threshold let mut risky_playable_cards = private_info
{ .iter()
let mut risky_playable_cards = private_info.iter().enumerate().filter(|&(_, card_table)| { .enumerate()
.filter(|&(_, card_table)| {
// card is either playable or dead // card is either playable or dead
card_table.probability_of_predicate(&|card| { card_table.probability_of_predicate(&|card| {
view.board.is_playable(card) || view.board.is_dead(card) view.board.is_playable(card) || view.board.is_dead(card)
}) == 1.0 }) == 1.0
}).map(|(i, card_table)| { })
.map(|(i, card_table)| {
let p = card_table.probability_is_playable(&view.board); let p = card_table.probability_is_playable(&view.board);
(i, card_table, p) (i, card_table, p)
}).collect::<Vec<_>>(); })
.collect::<Vec<_>>();
if risky_playable_cards.len() > 0 { if risky_playable_cards.len() > 0 {
risky_playable_cards.sort_by(|c1, c2| { risky_playable_cards
c2.2.partial_cmp(&c1.2).unwrap_or(Ordering::Equal) .sort_by(|c1, c2| c2.2.partial_cmp(&c1.2).unwrap_or(Ordering::Equal));
});
let maybe_play = risky_playable_cards[0]; let maybe_play = risky_playable_cards[0];
if maybe_play.2 > 0.75 { if maybe_play.2 > 0.75 {
@ -821,19 +905,28 @@ impl InformationPlayerStrategy {
} }
} }
let public_useless_indices = self.find_useless_cards(&view.board, &public_info.get_player_info(me)); let public_useless_indices =
self.find_useless_cards(&view.board, &public_info.get_player_info(me));
let useless_indices = self.find_useless_cards(&view.board, &private_info); 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! // NOTE When changing this, make sure to keep the "discard" branch of update() up to date!
let will_hint = let will_hint =
if view.board.hints_remaining > 0 && public_info.someone_else_needs_hint(view) { true } if view.board.hints_remaining > 0 && public_info.someone_else_needs_hint(view) {
else if view.board.discard_size() <= discard_threshold && useless_indices.len() > 0 { false } true
} else if view.board.discard_size() <= discard_threshold && useless_indices.len() > 0 {
false
}
// hinting is better than discarding dead cards // hinting is better than discarding dead cards
// (probably because it stalls the deck-drawing). // (probably because it stalls the deck-drawing).
else if view.board.hints_remaining > 0 && view.someone_else_can_play() { true } else if view.board.hints_remaining > 0 && view.someone_else_can_play() {
else if view.board.hints_remaining > 4 { true } true
} else if view.board.hints_remaining > 4 {
true
}
// this is the only case in which we discard a potentially useful card. // this is the only case in which we discard a potentially useful card.
else { false }; else {
false
};
if will_hint { if will_hint {
let hint_set = public_info.get_hint(view); let hint_set = public_info.get_hint(view);
@ -856,16 +949,18 @@ impl InformationPlayerStrategy {
} }
// Make the least risky discard. // Make the least risky discard.
let mut cards_by_discard_value = private_info.iter().enumerate().map(|(i, card_table)| { let mut cards_by_discard_value = private_info
let probability_is_seen = card_table.probability_of_predicate(&|card| { .iter()
view.can_see(card) .enumerate()
}); .map(|(i, card_table)| {
let compval = let probability_is_seen =
20.0 * probability_is_seen card_table.probability_of_predicate(&|card| view.can_see(card));
let compval = 20.0 * probability_is_seen
+ 10.0 * card_table.probability_is_dispensable(&view.board) + 10.0 * card_table.probability_is_dispensable(&view.board)
+ card_table.average_value(); + card_table.average_value();
(i, compval) (i, compval)
}).collect::<Vec<_>>(); })
.collect::<Vec<_>>();
cards_by_discard_value.sort_by_key(|&(i, compval)| (FloatOrd(-compval), i)); cards_by_discard_value.sort_by_key(|&(i, compval)| (FloatOrd(-compval), i));
let (index, _) = cards_by_discard_value[0]; let (index, _) = cards_by_discard_value[0];
TurnChoice::Discard(index) TurnChoice::Discard(index)
@ -887,11 +982,13 @@ impl InformationPlayerStrategy {
match turn_choice { match turn_choice {
TurnChoice::Hint(ref hint) => { TurnChoice::Hint(ref hint) => {
let matches = hint_matches.unwrap(); let matches = hint_matches.unwrap();
self.public_info.update_from_hint_choice(hint, matches, &self.last_view); self.public_info
.update_from_hint_choice(hint, matches, &self.last_view);
} }
TurnChoice::Discard(index) => { TurnChoice::Discard(index) => {
let known_useless_indices = self.find_useless_cards( let known_useless_indices = self.find_useless_cards(
&self.last_view.board, &self.public_info.get_player_info(turn_player) &self.last_view.board,
&self.public_info.get_player_info(turn_player),
); );
if self.last_view.board.hints_remaining > 0 { if self.last_view.board.hints_remaining > 0 {
@ -900,8 +997,12 @@ impl InformationPlayerStrategy {
if known_useless_indices.len() > 1 { if known_useless_indices.len() > 1 {
// unwrap is safe because *if* a discard happened, and there were known // unwrap is safe because *if* a discard happened, and there were known
// dead cards, it must be a dead card // dead cards, it must be a dead card
let value = known_useless_indices.iter().position(|&i| i == *index).unwrap(); let value = known_useless_indices
let info = ModulusInformation::new(known_useless_indices.len() as u32, value as u32); .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); self.public_info.update_from_hat_sum(info, &self.last_view);
} }
} }
@ -927,12 +1028,16 @@ impl PlayerStrategy for InformationPlayerStrategy {
fn update(&mut self, turn_record: &TurnRecord, view: &BorrowedGameView) { fn update(&mut self, turn_record: &TurnRecord, view: &BorrowedGameView) {
let hint_matches = if let &TurnResult::Hint(ref matches) = &turn_record.result { let hint_matches = if let &TurnResult::Hint(ref matches) = &turn_record.result {
Some(matches) Some(matches)
} else { None }; } else {
None
};
self.update_wrapped(&turn_record.player, &turn_record.choice, hint_matches); self.update_wrapped(&turn_record.player, &turn_record.choice, hint_matches);
if let Some(new_public_info) = self.new_public_info.take() { if let Some(new_public_info) = self.new_public_info.take() {
if !self.public_info.agrees_with(new_public_info) { if !self.public_info.agrees_with(new_public_info) {
panic!("The change made to public_info in self.decide_wrapped differs from \ panic!(
the corresponding change in self.update_wrapped!"); "The change made to public_info in self.decide_wrapped differs from \
the corresponding change in self.update_wrapped!"
);
} }
} }
match turn_record.choice { match turn_record.choice {
@ -940,24 +1045,40 @@ impl PlayerStrategy for InformationPlayerStrategy {
if let &TurnResult::Hint(ref matches) = &turn_record.result { if let &TurnResult::Hint(ref matches) = &turn_record.result {
self.public_info.update_from_hint_matches(hint, matches); self.public_info.update_from_hint_matches(hint, matches);
} else { } else {
panic!("Got turn choice {:?}, but turn result {:?}", panic!(
turn_record.choice, turn_record.result); "Got turn choice {:?}, but turn result {:?}",
turn_record.choice, turn_record.result
);
} }
} }
TurnChoice::Discard(index) => { TurnChoice::Discard(index) => {
if let &TurnResult::Discard(ref card) = &turn_record.result { if let &TurnResult::Discard(ref card) = &turn_record.result {
self.public_info.update_from_discard_or_play_result(view, &turn_record.player, index, card); self.public_info.update_from_discard_or_play_result(
view,
&turn_record.player,
index,
card,
);
} else { } else {
panic!("Got turn choice {:?}, but turn result {:?}", panic!(
turn_record.choice, turn_record.result); "Got turn choice {:?}, but turn result {:?}",
turn_record.choice, turn_record.result
);
} }
} }
TurnChoice::Play(index) => { TurnChoice::Play(index) => {
if let &TurnResult::Play(ref card, _) = &turn_record.result { if let &TurnResult::Play(ref card, _) = &turn_record.result {
self.public_info.update_from_discard_or_play_result(view, &turn_record.player, index, card); self.public_info.update_from_discard_or_play_result(
view,
&turn_record.player,
index,
card,
);
} else { } else {
panic!("Got turn choice {:?}, but turn result {:?}", panic!(
turn_record.choice, turn_record.result); "Got turn choice {:?}, but turn result {:?}",
turn_record.choice, turn_record.result
);
} }
} }
} }

View file

@ -28,4 +28,3 @@ pub trait GameStrategy {
pub trait GameStrategyConfig { pub trait GameStrategyConfig {
fn initialize(&self, &GameOptions) -> Box<GameStrategy>; fn initialize(&self, &GameOptions) -> Box<GameStrategy>;
} }