minor cleanups

This commit is contained in:
Jeff Wu 2016-04-04 00:26:42 -07:00
parent f2de390e0e
commit cab576f883
8 changed files with 71 additions and 70 deletions

View file

@ -38,7 +38,7 @@ cargo run -- -s 222 -p 5 -g info -l debug | less
## Strategies ## Strategies
To write a strategy, you simply [implement a few traits](src/simulator.rs). To write a strategy, you simply [implement a few traits](src/strategy.rs).
The framework is designed to take advantage of Rust's ownership system The framework is designed to take advantage of Rust's ownership system
so that you *can't cheat*, without using stuff like `Cell` or `Arc` or `Mutex`. so that you *can't cheat*, without using stuff like `Cell` or `Arc` or `Mutex`.

View file

@ -23,7 +23,7 @@ pub fn get_count_for_value(value: Value) -> u32 {
} }
} }
#[derive(Debug,Clone,PartialEq,Eq,Hash,Ord,PartialOrd)] #[derive(Clone,PartialEq,Eq,Hash,Ord,PartialOrd)]
pub struct Card { pub struct Card {
pub color: Color, pub color: Color,
pub value: Value, pub value: Value,
@ -38,6 +38,11 @@ impl fmt::Display for Card {
write!(f, "{}{}", self.color, self.value) write!(f, "{}{}", self.color, self.value)
} }
} }
impl fmt::Debug for Card {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}{}", self.color, self.value)
}
}
#[derive(Debug,Clone)] #[derive(Debug,Clone)]
pub struct CardCounts { pub struct CardCounts {
@ -217,11 +222,12 @@ pub enum TurnResult {
// represents a turn taken in the game // represents a turn taken in the game
#[derive(Debug,Clone)] #[derive(Debug,Clone)]
pub struct Turn { pub struct TurnRecord {
pub player: Player, pub player: Player,
pub choice: TurnChoice, pub choice: TurnChoice,
pub result: TurnResult, pub result: TurnResult,
} }
pub type TurnHistory = Vec<TurnRecord>;
// represents possible settings for the game // represents possible settings for the game
pub struct GameOptions { pub struct GameOptions {
@ -248,6 +254,7 @@ pub struct BoardState {
// which turn is it? // which turn is it?
pub turn: u32, pub turn: u32,
pub turn_history: TurnHistory,
// // whose turn is it? // // whose turn is it?
pub player: Player, pub player: Player,
pub hand_size: u32, pub hand_size: u32,
@ -257,7 +264,6 @@ pub struct BoardState {
pub allow_empty_hints: bool, pub allow_empty_hints: bool,
pub lives_total: u32, pub lives_total: u32,
pub lives_remaining: u32, pub lives_remaining: u32,
pub turn_history: Vec<Turn>,
// only relevant when deck runs out // only relevant when deck runs out
pub deckless_turns_remaining: u32, pub deckless_turns_remaining: u32,
} }
@ -628,7 +634,7 @@ impl GameState {
} }
} }
pub fn process_choice(&mut self, choice: TurnChoice) -> Turn { pub fn process_choice(&mut self, choice: TurnChoice) -> TurnRecord {
let turn_result = { let turn_result = {
match choice { match choice {
TurnChoice::Hint(ref hint) => { TurnChoice::Hint(ref hint) => {
@ -649,8 +655,9 @@ impl GameState {
hand.iter().map(|card| { card.value == value }).collect::<Vec<_>>() hand.iter().map(|card| { card.value == value }).collect::<Vec<_>>()
} }
}; };
if (!self.board.allow_empty_hints) && (results.iter().all(|matched| !matched)) { if !self.board.allow_empty_hints {
panic!("Tried hinting an empty hint"); assert!(results.iter().any(|matched| *matched),
"Tried hinting an empty hint");
} }
TurnResult::Hint(results) TurnResult::Hint(results)
@ -693,12 +700,12 @@ impl GameState {
} }
} }
}; };
let turn = Turn { let turn_record = TurnRecord {
player: self.board.player.clone(), player: self.board.player.clone(),
result: turn_result, result: turn_result,
choice: choice, choice: choice,
}; };
self.board.turn_history.push(turn.clone()); self.board.turn_history.push(turn_record.clone());
self.replenish_hand(); self.replenish_hand();
@ -712,6 +719,6 @@ impl GameState {
}; };
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 turn_record
} }
} }

View file

@ -7,6 +7,7 @@ extern crate crossbeam;
mod helpers; mod helpers;
mod game; mod game;
mod simulator; mod simulator;
mod strategy;
mod strategies { mod strategies {
pub mod examples; pub mod examples;
pub mod cheating; pub mod cheating;
@ -122,20 +123,20 @@ fn main() {
}; };
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());
let strategy_config : Box<simulator::GameStrategyConfig + Sync> = match strategy_str { let strategy_config : Box<strategy::GameStrategyConfig + Sync> = match strategy_str {
"random" => { "random" => {
Box::new(strategies::examples::RandomStrategyConfig { Box::new(strategies::examples::RandomStrategyConfig {
hint_probability: 0.4, hint_probability: 0.4,
play_probability: 0.2, play_probability: 0.2,
}) as Box<simulator::GameStrategyConfig + Sync> }) as Box<strategy::GameStrategyConfig + Sync>
}, },
"cheat" => { "cheat" => {
Box::new(strategies::cheating::CheatingStrategyConfig::new()) Box::new(strategies::cheating::CheatingStrategyConfig::new())
as Box<simulator::GameStrategyConfig + Sync> as Box<strategy::GameStrategyConfig + Sync>
}, },
"info" => { "info" => {
Box::new(strategies::information::InformationStrategyConfig::new()) Box::new(strategies::information::InformationStrategyConfig::new())
as Box<simulator::GameStrategyConfig + Sync> as Box<strategy::GameStrategyConfig + Sync>
}, },
_ => { _ => {
print_usage(&program, opts); print_usage(&program, opts);

View file

@ -4,46 +4,21 @@ use std::fmt;
use crossbeam; use crossbeam;
use game::*; use game::*;
use strategy::*;
// Traits to implement for any valid Hanabi strategy
// Represents the strategy of a given player
pub trait PlayerStrategy {
// A function to decide what to do on the player's turn.
// Given a BorrowedGameView, outputs their choice.
fn decide(&mut self, &BorrowedGameView) -> TurnChoice;
// A function to update internal state after other players' turns.
// Given what happened last turn, and the new state.
fn update(&mut self, &Turn, &BorrowedGameView);
}
// Represents the overall strategy for a game
// Shouldn't do much, except store configuration parameters and
// possibility initialize some shared randomness between players
pub trait GameStrategy {
fn initialize(&self, Player, &BorrowedGameView) -> Box<PlayerStrategy>;
}
// Represents configuration for a strategy.
// Acts as a factory for game strategies, so we can play many rounds
pub trait GameStrategyConfig {
fn initialize(&self, &GameOptions) -> Box<GameStrategy>;
}
fn new_deck(seed: u32) -> Cards { fn new_deck(seed: u32) -> Cards {
let mut deck: Cards = Cards::new(); let mut deck: Cards = Cards::new();
for &color in COLORS.iter() { for &color in COLORS.iter() {
for &value in VALUES.iter() { for &value in VALUES.iter() {
let count = get_count_for_value(value); for _ in 0..get_count_for_value(value) {
for _ in 0..count {
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);
trace!("Created deck: {:?}", deck);
deck deck
} }
@ -59,7 +34,7 @@ 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.get_players().map(|player| {
(player, game_strategy.initialize(player.clone(), &game.get_view(player))) (player, game_strategy.initialize(player, &game.get_view(player)))
}).collect::<HashMap<Player, Box<PlayerStrategy>>>(); }).collect::<HashMap<Player, Box<PlayerStrategy>>>();
while !game.is_over() { while !game.is_over() {

View file

@ -2,7 +2,7 @@ use std::rc::Rc;
use std::cell::{RefCell}; use std::cell::{RefCell};
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use simulator::*; use strategy::*;
use game::*; use game::*;
// strategy that explicitly cheats by using Rc/RefCell // strategy that explicitly cheats by using Rc/RefCell
@ -208,6 +208,6 @@ impl PlayerStrategy for CheatingPlayerStrategy {
} }
TurnChoice::Discard(index) TurnChoice::Discard(index)
} }
fn update(&mut self, _: &Turn, _: &BorrowedGameView) { fn update(&mut self, _: &TurnRecord, _: &BorrowedGameView) {
} }
} }

View file

@ -1,4 +1,4 @@
use simulator::*; use strategy::*;
use game::*; use game::*;
use rand::{self, Rng}; use rand::{self, Rng};
@ -66,6 +66,6 @@ impl PlayerStrategy for RandomStrategyPlayer {
TurnChoice::Discard(0) TurnChoice::Discard(0)
} }
} }
fn update(&mut self, _: &Turn, _: &BorrowedGameView) { fn update(&mut self, _: &TurnRecord, _: &BorrowedGameView) {
} }
} }

View file

@ -1,12 +1,14 @@
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::cmp::Ordering; use std::cmp::Ordering;
use simulator::*; use strategy::*;
use game::*; use game::*;
use helpers::*; use helpers::*;
// 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
// TODO: guess very aggressively at very end of game (first, see whether
// situation ever occurs)
#[derive(Debug,Clone)] #[derive(Debug,Clone)]
struct ModulusInformation { struct ModulusInformation {
@ -39,8 +41,7 @@ impl ModulusInformation {
self.modulus = self.modulus / modulus; self.modulus = self.modulus / modulus;
let value = self.value / self.modulus; let value = self.value / self.modulus;
self.value = self.value - value * self.modulus; self.value = self.value - value * self.modulus;
trace!("orig value {}, orig modulus {}, self.value {}, self.modulus {}, value {}, modulus {}", assert!(original_modulus == modulus * self.modulus);
original_value, original_modulus, self.value, self.modulus, value, modulus);
assert!(original_value == value * self.modulus + self.value); assert!(original_value == value * self.modulus + self.value);
Self::new(modulus, value) Self::new(modulus, value)
} }
@ -303,8 +304,6 @@ impl InformationPlayerStrategy {
return questions; return questions;
} }
} }
} else {
debug!("Something known playable");
} }
let mut ask_partition = augmented_hand_info.iter() let mut ask_partition = augmented_hand_info.iter()
@ -354,10 +353,8 @@ impl InformationPlayerStrategy {
assert!(player != &self.me); assert!(player != &self.me);
let hand_info = self.get_player_public_info(player); let hand_info = self.get_player_public_info(player);
let questions = Self::get_questions(total_info, view, hand_info); let questions = Self::get_questions(total_info, view, hand_info);
trace!("Getting hint for player {}, questions {:?}", player, questions.len());
let mut answer = Self::answer_questions(&questions, view.get_hand(player), view); let mut answer = Self::answer_questions(&questions, view.get_hand(player), view);
answer.cast_up(total_info); answer.cast_up(total_info);
trace!("Resulting answer {:?}", answer);
answer answer
} }
@ -379,11 +376,8 @@ impl InformationPlayerStrategy {
let hand_info = self.get_my_public_info(); let hand_info = self.get_my_public_info();
Self::get_questions(hint.modulus, view, hand_info) Self::get_questions(hint.modulus, view, hand_info)
}; };
trace!("{}: Questions {:?}", self.me, questions.len());
let product = questions.iter().fold(1, |a, ref b| a * b.info_amount()); let product = questions.iter().fold(1, |a, ref b| a * b.info_amount());
trace!("{}: Product {}, hint: {:?}", self.me, product, hint);
hint.cast_down(product); hint.cast_down(product);
trace!("{}: Inferred for myself {:?}", self.me, hint);
let me = self.me.clone(); let me = self.me.clone();
let mut hand_info = self.take_public_info(&me); let mut hand_info = self.take_public_info(&me);
@ -404,14 +398,12 @@ impl InformationPlayerStrategy {
let view = &self.last_view; let view = &self.last_view;
view.board.get_players() view.board.get_players()
}; };
trace!("{}: inferring for myself, starting with {:?}", self.me, hint);
for player in players { for player in players {
if (player != hinter) && (player != self.me) { if (player != hinter) && (player != self.me) {
{ {
let view = &self.last_view; let view = &self.last_view;
let hint_info = self.get_hint_info_for_player(&player, hint.modulus, view); let hint_info = self.get_hint_info_for_player(&player, hint.modulus, view);
hint.subtract(&hint_info); hint.subtract(&hint_info);
trace!("{}: subtracted for {}, now {:?}", self.me, player, hint);
} }
// *take* instead of borrowing mutably, because of borrow rules... // *take* instead of borrowing mutably, because of borrow rules...
@ -856,20 +848,20 @@ impl PlayerStrategy for InformationPlayerStrategy {
TurnChoice::Discard(index) TurnChoice::Discard(index)
} }
fn update(&mut self, turn: &Turn, view: &BorrowedGameView) { fn update(&mut self, turn_record: &TurnRecord, view: &BorrowedGameView) {
match turn.choice { match turn_record.choice {
TurnChoice::Hint(ref hint) => { TurnChoice::Hint(ref hint) => {
if let &TurnResult::Hint(ref matches) = &turn.result { if let &TurnResult::Hint(ref matches) = &turn_record.result {
self.infer_from_hint(hint, matches); self.infer_from_hint(hint, matches);
self.update_public_info_for_hint(hint, matches); self.update_public_info_for_hint(hint, matches);
} else { } else {
panic!("Got turn choice {:?}, but turn result {:?}", panic!("Got turn choice {:?}, but turn result {:?}",
turn.choice, turn.result); turn_record.choice, turn_record.result);
} }
} }
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, &self.get_player_public_info(&turn.player) &self.last_view, &self.get_player_public_info(&turn_record.player)
); );
if known_useless_indices.len() > 1 { if known_useless_indices.len() > 1 {
@ -881,19 +873,19 @@ impl PlayerStrategy for InformationPlayerStrategy {
)); ));
} }
if let &TurnResult::Discard(ref card) = &turn.result { if let &TurnResult::Discard(ref card) = &turn_record.result {
self.update_public_info_for_discard_or_play(view, &turn.player, index, card); self.update_public_info_for_discard_or_play(view, &turn_record.player, index, card);
} else { } else {
panic!("Got turn choice {:?}, but turn result {:?}", panic!("Got turn choice {:?}, but turn result {:?}",
turn.choice, turn.result); turn_record.choice, turn_record.result);
} }
} }
TurnChoice::Play(index) => { TurnChoice::Play(index) => {
if let &TurnResult::Play(ref card, _) = &turn.result { if let &TurnResult::Play(ref card, _) = &turn_record.result {
self.update_public_info_for_discard_or_play(view, &turn.player, index, card); self.update_public_info_for_discard_or_play(view, &turn_record.player, index, card);
} else { } else {
panic!("Got turn choice {:?}, but turn result {:?}", panic!("Got turn choice {:?}, but turn result {:?}",
turn.choice, turn.result); turn_record.choice, turn_record.result);
} }
} }
} }

26
src/strategy.rs Normal file
View file

@ -0,0 +1,26 @@
use game::*;
// Traits to implement for any valid Hanabi strategy
// Represents the strategy of a given player
pub trait PlayerStrategy {
// A function to decide what to do on the player's turn.
// Given a BorrowedGameView, outputs their choice.
fn decide(&mut self, &BorrowedGameView) -> TurnChoice;
// A function to update internal state after other players' turns.
// Given what happened last turn, and the new state.
fn update(&mut self, &TurnRecord, &BorrowedGameView);
}
// Represents the overall strategy for a game
// Shouldn't do much, except store configuration parameters and
// possibility initialize some shared randomness between players
pub trait GameStrategy {
fn initialize(&self, Player, &BorrowedGameView) -> Box<PlayerStrategy>;
}
// Represents configuration for a strategy.
// Acts as a factory for game strategies, so we can play many rounds
pub trait GameStrategyConfig {
fn initialize(&self, &GameOptions) -> Box<GameStrategy>;
}