Run cargo fmt
This commit is contained in:
parent
c9b21fa047
commit
4bab09ba2b
9 changed files with 812 additions and 522 deletions
166
src/game.rs
166
src/game.rs
|
@ -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!("Unexpected value: {}", value); }
|
_ => {
|
||||||
|
panic!("Unexpected value: {}", value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,9 +58,7 @@ impl CardCounts {
|
||||||
counts.insert(Card::new(color, value), 0);
|
counts.insert(Card::new(color, value), 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CardCounts {
|
CardCounts { counts }
|
||||||
counts,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_count(&self, card: &Card) -> u32 {
|
pub fn get_count(&self, card: &Card) -> u32 {
|
||||||
|
@ -78,15 +78,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() {
|
||||||
f.write_str(&format!(
|
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);
|
||||||
f.write_str(&format!(
|
f.write_str(&format!("{}/{} {}s", count, total, value))?;
|
||||||
"{}/{} {}s", count, total, value
|
|
||||||
))?;
|
|
||||||
if value != FINAL_VALUE {
|
if value != FINAL_VALUE {
|
||||||
f.write_str(", ")?;
|
f.write_str(", ")?;
|
||||||
}
|
}
|
||||||
|
@ -144,14 +140,15 @@ pub struct Firework {
|
||||||
}
|
}
|
||||||
impl Firework {
|
impl Firework {
|
||||||
pub fn new(color: Color) -> Firework {
|
pub fn new(color: Color) -> Firework {
|
||||||
Firework {
|
Firework { color, top: 0 }
|
||||||
color,
|
|
||||||
top: 0,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 +189,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 +270,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,
|
||||||
|
@ -324,7 +326,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) {
|
||||||
|
@ -370,7 +372,10 @@ impl BoardState {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn score(&self) -> Score {
|
pub fn score(&self) -> Score {
|
||||||
self.fireworks.iter().map(|(_, firework)| firework.score()).sum()
|
self.fireworks
|
||||||
|
.iter()
|
||||||
|
.map(|(_, firework)| firework.score())
|
||||||
|
.sum()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn discard_size(&self) -> u32 {
|
pub fn discard_size(&self) -> u32 {
|
||||||
|
@ -391,28 +396,28 @@ impl BoardState {
|
||||||
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() {
|
||||||
f.write_str(&format!(
|
f.write_str(&format!("Turn {} (GAME ENDED):\n", self.turn))?;
|
||||||
"Turn {} (GAME ENDED):\n", self.turn
|
|
||||||
))?;
|
|
||||||
} else {
|
} else {
|
||||||
f.write_str(&format!(
|
f.write_str(&format!(
|
||||||
"Turn {} (Player {}'s turn):\n", self.turn, self.player
|
"Turn {} (Player {}'s turn):\n",
|
||||||
|
self.turn, self.player
|
||||||
))?;
|
))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
f.write_str(&format!(
|
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 {
|
||||||
f.write_str(&format!(
|
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
|
||||||
))?;
|
))?;
|
||||||
}
|
}
|
||||||
f.write_str(&format!(
|
f.write_str(&format!(
|
||||||
"{}/{} hints remaining\n", self.hints_remaining, self.hints_total
|
"{}/{} hints remaining\n",
|
||||||
|
self.hints_remaining, self.hints_total
|
||||||
))?;
|
))?;
|
||||||
f.write_str(&format!(
|
f.write_str(&format!(
|
||||||
"{}/{} lives remaining\n", self.lives_remaining, self.lives_total
|
"{}/{} lives remaining\n",
|
||||||
|
self.lives_remaining, self.lives_total
|
||||||
))?;
|
))?;
|
||||||
f.write_str("Fireworks:\n")?;
|
f.write_str("Fireworks:\n")?;
|
||||||
for &color in COLORS.iter() {
|
for &color in COLORS.iter() {
|
||||||
|
@ -442,28 +447,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))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -508,10 +515,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,
|
player: borrowed_view.player,
|
||||||
|
@ -570,21 +578,20 @@ impl GameState {
|
||||||
pub fn new(opts: &GameOptions, mut deck: Cards) -> GameState {
|
pub fn new(opts: &GameOptions, mut deck: Cards) -> GameState {
|
||||||
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<_, _>>();
|
})
|
||||||
|
.collect::<FnvHashMap<_, _>>();
|
||||||
|
|
||||||
GameState {
|
GameState { hands, board, deck }
|
||||||
hands,
|
|
||||||
board,
|
|
||||||
deck,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_players(&self) -> Range<Player> {
|
pub fn get_players(&self) -> Range<Player> {
|
||||||
|
@ -636,26 +643,35 @@ 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!(
|
||||||
"Player {} gave a hint to himself", hint.player);
|
self.board.player != hint.player,
|
||||||
|
"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(|card| { card.color == color }).collect::<Vec<_>>()
|
.iter()
|
||||||
}
|
.map(|card| card.color == color)
|
||||||
Hinted::Value(value) => {
|
.collect::<Vec<_>>(),
|
||||||
hand.iter().map(|card| { card.value == value }).collect::<Vec<_>>()
|
Hinted::Value(value) => hand
|
||||||
}
|
.iter()
|
||||||
|
.map(|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)
|
||||||
|
@ -671,10 +687,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 {
|
||||||
{
|
{
|
||||||
|
@ -715,7 +728,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
|
||||||
}
|
}
|
||||||
|
|
162
src/helpers.rs
162
src/helpers.rs
|
@ -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: &dyn Fn(&Card) -> T) -> f32
|
fn weighted_score<T>(&self, score_fn: &dyn 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: &dyn Fn(&Card) -> bool) -> f32 {
|
fn probability_of_predicate(&self, predicate: &dyn 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 {
|
pub trait Info<T>
|
||||||
|
where
|
||||||
|
T: Hash + Eq + Clone,
|
||||||
|
{
|
||||||
// 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 {
|
||||||
|
|
||||||
// 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().cloned().collect::<Vec<T>>()
|
self.get_possibility_set()
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.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 {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn initialize() -> HashSet<T> {
|
fn initialize() -> HashSet<T> {
|
||||||
Self::get_all_possibilities().iter()
|
Self::get_all_possibilities()
|
||||||
.cloned().collect::<HashSet<_>>()
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.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 {
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl CardInfo for CardPossibilityTable {
|
impl CardInfo for CardPossibilityTable {
|
||||||
|
@ -349,7 +388,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,15 +408,19 @@ 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 { hand_info }
|
||||||
hand_info,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// update for hint to me
|
// update for hint to me
|
||||||
|
@ -397,19 +439,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]
|
||||||
}
|
}
|
||||||
|
|
198
src/main.rs
198
src/main.rs
|
@ -1,18 +1,18 @@
|
||||||
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;
|
||||||
|
|
||||||
mod helpers;
|
|
||||||
mod game;
|
mod game;
|
||||||
|
mod helpers;
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
@ -33,46 +33,60 @@ 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("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(
|
||||||
|
"",
|
||||||
|
"write-results-table",
|
||||||
|
"Update the results table in README.md",
|
||||||
|
);
|
||||||
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)
|
panic!("{}", f)
|
||||||
|
@ -93,11 +107,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);
|
||||||
|
@ -107,26 +121,47 @@ 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 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).info();
|
sim_games(
|
||||||
|
n_players,
|
||||||
|
strategy_str,
|
||||||
|
seed,
|
||||||
|
n_trials,
|
||||||
|
n_threads,
|
||||||
|
progress_info,
|
||||||
|
)
|
||||||
|
.info();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sim_games(n_players: u32, strategy_str: &str, seed: Option<u32>, n_trials: u32, n_threads: u32, progress_info: Option<u32>)
|
fn sim_games(
|
||||||
-> simulator::SimResult {
|
n_players: u32,
|
||||||
|
strategy_str: &str,
|
||||||
|
seed: Option<u32>,
|
||||||
|
n_trials: u32,
|
||||||
|
n_threads: u32,
|
||||||
|
progress_info: Option<u32>,
|
||||||
|
) -> simulator::SimResult {
|
||||||
let hand_size = match n_players {
|
let hand_size = match n_players {
|
||||||
2 => 5,
|
2 => 5,
|
||||||
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 {
|
||||||
|
@ -139,25 +174,26 @@ fn sim_games(n_players: u32, strategy_str: &str, seed: Option<u32>, n_trials: u3
|
||||||
};
|
};
|
||||||
|
|
||||||
let strategy_config: Box<dyn strategy::GameStrategyConfig + Sync> = match strategy_str {
|
let strategy_config: Box<dyn 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<dyn strategy::GameStrategyConfig + Sync>
|
}) as Box<dyn strategy::GameStrategyConfig + Sync>,
|
||||||
},
|
"cheat" => Box::new(strategies::cheating::CheatingStrategyConfig::new())
|
||||||
"cheat" => {
|
as Box<dyn strategy::GameStrategyConfig + Sync>,
|
||||||
Box::new(strategies::cheating::CheatingStrategyConfig::new())
|
"info" => Box::new(strategies::information::InformationStrategyConfig::new())
|
||||||
as Box<dyn strategy::GameStrategyConfig + Sync>
|
as Box<dyn strategy::GameStrategyConfig + Sync>,
|
||||||
},
|
|
||||||
"info" => {
|
|
||||||
Box::new(strategies::information::InformationStrategyConfig::new())
|
|
||||||
as Box<dyn 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)
|
simulator::simulate(
|
||||||
|
&game_opts,
|
||||||
|
strategy_config,
|
||||||
|
seed,
|
||||||
|
n_trials,
|
||||||
|
n_threads,
|
||||||
|
progress_info,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_results_table() -> String {
|
fn get_results_table() -> String {
|
||||||
|
@ -167,7 +203,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);
|
||||||
|
@ -176,30 +215,53 @@ 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: &[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: &[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), &|n_players| {
|
||||||
(space.clone(), dashes),
|
(format_players(n_players), dashes_long.clone())
|
||||||
&|n_players| (format_players(n_players), dashes_long.clone()));
|
});
|
||||||
let mut body = strategies.iter().map(|strategy| {
|
let mut body = strategies
|
||||||
make_twolines(&player_nums, (format_name(strategy), space.clone()), &|n_players| {
|
.iter()
|
||||||
let simresult = sim_games(n_players, strategy, Some(seed), n_trials, n_threads, None);
|
.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);
|
||||||
(
|
(
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
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 strategy::*;
|
||||||
|
@ -15,7 +15,7 @@ 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);
|
||||||
|
@ -31,9 +31,15 @@ pub fn simulate_once(
|
||||||
|
|
||||||
let mut game = GameState::new(opts, deck);
|
let mut game = GameState::new(opts, deck);
|
||||||
|
|
||||||
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<dyn PlayerStrategy>>>();
|
.map(|player| {
|
||||||
|
(
|
||||||
|
player,
|
||||||
|
game_strategy.initialize(player, &game.get_view(player)),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<FnvHashMap<Player, Box<dyn PlayerStrategy>>>();
|
||||||
|
|
||||||
while !game.is_over() {
|
while !game.is_over() {
|
||||||
let player = game.board.player;
|
let player = game.board.player;
|
||||||
|
@ -44,7 +50,6 @@ pub fn simulate_once(
|
||||||
debug!("=======================================================");
|
debug!("=======================================================");
|
||||||
debug!("{}", game);
|
debug!("{}", game);
|
||||||
|
|
||||||
|
|
||||||
let choice = {
|
let choice = {
|
||||||
let strategy = strategies.get_mut(&player).unwrap();
|
let strategy = strategies.get_mut(&player).unwrap();
|
||||||
strategy.decide(&game.get_view(player))
|
strategy.decide(&game.get_view(player))
|
||||||
|
@ -56,7 +61,6 @@ pub fn simulate_once(
|
||||||
let strategy = strategies.get_mut(&player).unwrap();
|
let strategy = strategies.get_mut(&player).unwrap();
|
||||||
strategy.update(&turn, &game.get_view(player));
|
strategy.update(&turn, &game.get_view(player));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
debug!("");
|
debug!("");
|
||||||
debug!("=======================================================");
|
debug!("=======================================================");
|
||||||
|
@ -119,9 +123,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 {
|
||||||
f.write_str(&format!(
|
f.write_str(&format!("\n{}: {}", val, self.get_count(val),))?;
|
||||||
"\n{}: {}", val, self.get_count(val),
|
|
||||||
))?;
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -135,8 +137,9 @@ pub fn simulate<T: ?Sized>(
|
||||||
n_threads: u32,
|
n_threads: u32,
|
||||||
progress_info: Option<u32>,
|
progress_info: Option<u32>,
|
||||||
) -> 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;
|
||||||
|
@ -159,7 +162,10 @@ 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
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -168,7 +174,9 @@ pub fn simulate<T: ?Sized>(
|
||||||
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 progress_info.is_some() {
|
if progress_info.is_some() {
|
||||||
info!("Thread {} done", i);
|
info!("Thread {} done", i);
|
||||||
|
@ -181,7 +189,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);
|
||||||
|
|
|
@ -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<dyn PlayerStrategy> {
|
fn initialize(&self, player: Player, view: &BorrowedGameView) -> Box<dyn 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)).sum()
|
hand.iter()
|
||||||
|
.map(|card| self.card_play_value(view, card))
|
||||||
|
.sum()
|
||||||
}
|
}
|
||||||
|
|
||||||
// how badly do we need to play a particular card
|
// how badly do we need to play a particular card
|
||||||
|
@ -108,7 +110,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -139,9 +141,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.is_empty() {
|
if !playable_cards.is_empty() {
|
||||||
// play the best playable card
|
// play the best playable card
|
||||||
|
@ -156,15 +160,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 {
|
||||||
|
@ -204,6 +207,5 @@ impl PlayerStrategy for CheatingPlayerStrategy {
|
||||||
}
|
}
|
||||||
TurnChoice::Discard(index)
|
TurnChoice::Discard(index)
|
||||||
}
|
}
|
||||||
fn update(&mut self, _: &TurnRecord, _: &BorrowedGameView) {
|
fn update(&mut self, _: &TurnRecord, _: &BorrowedGameView) {}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)]
|
||||||
|
@ -44,7 +44,9 @@ impl PlayerStrategy for RandomStrategyPlayer {
|
||||||
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
|
||||||
|
@ -66,6 +68,5 @@ impl PlayerStrategy for RandomStrategyPlayer {
|
||||||
TurnChoice::Discard(0)
|
TurnChoice::Discard(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn update(&mut self, _: &TurnRecord, _: &BorrowedGameView) {
|
fn update(&mut self, _: &TurnRecord, _: &BorrowedGameView) {}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,10 +9,7 @@ pub struct ModulusInformation {
|
||||||
impl ModulusInformation {
|
impl ModulusInformation {
|
||||||
pub fn new(modulus: u32, value: u32) -> Self {
|
pub fn new(modulus: u32, value: u32) -> Self {
|
||||||
assert!(value < modulus);
|
assert!(value < modulus);
|
||||||
ModulusInformation {
|
ModulusInformation { modulus, value }
|
||||||
modulus,
|
|
||||||
value,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn none() -> Self {
|
pub fn none() -> Self {
|
||||||
|
@ -83,21 +80,21 @@ pub trait Question {
|
||||||
fn answer(&self, hand: &Cards, board: &BoardState) -> u32;
|
fn answer(&self, hand: &Cards, board: &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, hand_info: &mut HandInfo<CardPossibilityTable>, board: &BoardState
|
&self,
|
||||||
|
value: u32,
|
||||||
|
hand_info: &mut HandInfo<CardPossibilityTable>,
|
||||||
|
board: &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 +109,7 @@ pub trait PublicInformation: Clone {
|
||||||
fn set_board(&mut self, board: &BoardState);
|
fn set_board(&mut self, board: &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 +122,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: &Player, hand_info: &HandInfo<CardPossibilityTable>, total_info: u32) -> Option<Box<dyn Question>>;
|
fn ask_question(
|
||||||
|
&self,
|
||||||
|
player: &Player,
|
||||||
|
hand_info: &HandInfo<CardPossibilityTable>,
|
||||||
|
total_info: u32,
|
||||||
|
) -> Option<Box<dyn Question>>;
|
||||||
|
|
||||||
fn ask_question_wrapper(&self, player: &Player, hand_info: &HandInfo<CardPossibilityTable>, total_info: u32)
|
fn ask_question_wrapper(
|
||||||
-> Option<Box<dyn Question>>
|
&self,
|
||||||
{
|
player: &Player,
|
||||||
|
hand_info: &HandInfo<CardPossibilityTable>,
|
||||||
|
total_info: u32,
|
||||||
|
) -> Option<Box<dyn Question>> {
|
||||||
assert!(total_info > 0);
|
assert!(total_info > 0);
|
||||||
if total_info == 1 {
|
if total_info == 1 {
|
||||||
None
|
None
|
||||||
|
@ -138,8 +142,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 +164,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 +201,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, hand_info))
|
(info, (*player, 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 +225,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, hand_info))
|
(player_info, (player, hand_info))
|
||||||
}).unzip();
|
})
|
||||||
|
.unzip();
|
||||||
for other_info in other_infos {
|
for other_info in other_infos {
|
||||||
info.subtract(&other_info);
|
info.subtract(&other_info);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)));
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
if !may_be_all_one_color && !may_be_all_one_number { 4 } else { 3 }
|
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,
|
||||||
}
|
})
|
||||||
}).collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decode_hint_choice(&self, hint: &Hint, result: &[bool]) -> ModulusInformation {
|
fn decode_hint_choice(&self, hint: &Hint, result: &[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).sum::<u32>();
|
let amt_from_prev_players = info_per_player
|
||||||
|
.iter()
|
||||||
|
.take(player_amt as usize)
|
||||||
|
.sum::<u32>();
|
||||||
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,
|
||||||
card_counts: CardCounts::new(),
|
card_counts: CardCounts::new(),
|
||||||
|
@ -522,32 +567,56 @@ 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 || hand_info[i].is_determined() { false }
|
.into_iter()
|
||||||
else { true }
|
.filter(|&(i, _, p_dead)| {
|
||||||
}).collect::<Vec<_>>();
|
if p_dead == 1.0 || 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))
|
||||||
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -555,7 +624,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -563,20 +632,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<dyn 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<dyn Question> {
|
||||||
}).collect::<Vec<_>>();
|
if ask_dead {
|
||||||
|
Box::new(q_is_dead(i))
|
||||||
|
} else {
|
||||||
|
Box::new(q_is_playable(i))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
if !questions.is_empty() {
|
if !questions.is_empty() {
|
||||||
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)));
|
||||||
|
@ -584,9 +661,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))
|
||||||
|
@ -596,8 +671,6 @@ impl PublicInformation for MyPublicInformation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
pub struct InformationStrategyConfig;
|
pub struct InformationStrategyConfig;
|
||||||
|
|
||||||
impl InformationStrategyConfig {
|
impl InformationStrategyConfig {
|
||||||
|
@ -640,8 +713,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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -657,7 +734,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();
|
||||||
|
|
||||||
|
@ -701,17 +782,14 @@ 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);
|
||||||
let bonus = {
|
let bonus = {
|
||||||
if card_table.is_determined() || card_table.probability_is_dead(&view.board) == 1.0 {
|
if card_table.is_determined() || card_table.probability_is_dead(&view.board) == 1.0
|
||||||
|
{
|
||||||
2
|
2
|
||||||
} else {
|
} else {
|
||||||
1
|
1
|
||||||
|
@ -729,13 +807,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.is_empty() {
|
if hint_options.is_empty() {
|
||||||
// NOTE: Technically possible, but never happens
|
// NOTE: Technically possible, but never happens
|
||||||
|
@ -771,39 +848,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.is_empty() {
|
if !risky_playable_cards.is_empty() {
|
||||||
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 {
|
||||||
|
@ -812,19 +896,29 @@ 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
|
||||||
if view.board.hints_remaining > 0 && public_info.someone_else_needs_hint(view) { true }
|
&& public_info.someone_else_needs_hint(view)
|
||||||
else if view.board.discard_size() <= discard_threshold && !useless_indices.is_empty() { false }
|
{
|
||||||
|
true
|
||||||
|
} else if view.board.discard_size() <= discard_threshold && !useless_indices.is_empty() {
|
||||||
|
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);
|
||||||
|
@ -847,16 +941,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)
|
||||||
|
@ -878,11 +974,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 {
|
||||||
|
@ -891,8 +989,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -914,12 +1016,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 {
|
||||||
|
@ -927,24 +1033,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
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,4 +23,3 @@ pub trait GameStrategy {
|
||||||
pub trait GameStrategyConfig {
|
pub trait GameStrategyConfig {
|
||||||
fn initialize(&self, opts: &GameOptions) -> Box<dyn GameStrategy>;
|
fn initialize(&self, opts: &GameOptions) -> Box<dyn GameStrategy>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue