minor cleanups
This commit is contained in:
parent
f2de390e0e
commit
cab576f883
8 changed files with 71 additions and 70 deletions
|
@ -38,7 +38,7 @@ cargo run -- -s 222 -p 5 -g info -l debug | less
|
|||
|
||||
## 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
|
||||
so that you *can't cheat*, without using stuff like `Cell` or `Arc` or `Mutex`.
|
||||
|
|
25
src/game.rs
25
src/game.rs
|
@ -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 color: Color,
|
||||
pub value: Value,
|
||||
|
@ -38,6 +38,11 @@ impl fmt::Display for Card {
|
|||
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)]
|
||||
pub struct CardCounts {
|
||||
|
@ -217,11 +222,12 @@ pub enum TurnResult {
|
|||
|
||||
// represents a turn taken in the game
|
||||
#[derive(Debug,Clone)]
|
||||
pub struct Turn {
|
||||
pub struct TurnRecord {
|
||||
pub player: Player,
|
||||
pub choice: TurnChoice,
|
||||
pub result: TurnResult,
|
||||
}
|
||||
pub type TurnHistory = Vec<TurnRecord>;
|
||||
|
||||
// represents possible settings for the game
|
||||
pub struct GameOptions {
|
||||
|
@ -248,6 +254,7 @@ pub struct BoardState {
|
|||
|
||||
// which turn is it?
|
||||
pub turn: u32,
|
||||
pub turn_history: TurnHistory,
|
||||
// // whose turn is it?
|
||||
pub player: Player,
|
||||
pub hand_size: u32,
|
||||
|
@ -257,7 +264,6 @@ pub struct BoardState {
|
|||
pub allow_empty_hints: bool,
|
||||
pub lives_total: u32,
|
||||
pub lives_remaining: u32,
|
||||
pub turn_history: Vec<Turn>,
|
||||
// only relevant when deck runs out
|
||||
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 = {
|
||||
match choice {
|
||||
TurnChoice::Hint(ref hint) => {
|
||||
|
@ -649,8 +655,9 @@ impl GameState {
|
|||
hand.iter().map(|card| { card.value == value }).collect::<Vec<_>>()
|
||||
}
|
||||
};
|
||||
if (!self.board.allow_empty_hints) && (results.iter().all(|matched| !matched)) {
|
||||
panic!("Tried hinting an empty hint");
|
||||
if !self.board.allow_empty_hints {
|
||||
assert!(results.iter().any(|matched| *matched),
|
||||
"Tried hinting an empty hint");
|
||||
}
|
||||
|
||||
TurnResult::Hint(results)
|
||||
|
@ -693,12 +700,12 @@ impl GameState {
|
|||
}
|
||||
}
|
||||
};
|
||||
let turn = Turn {
|
||||
let turn_record = TurnRecord {
|
||||
player: self.board.player.clone(),
|
||||
result: turn_result,
|
||||
choice: choice,
|
||||
};
|
||||
self.board.turn_history.push(turn.clone());
|
||||
self.board.turn_history.push(turn_record.clone());
|
||||
|
||||
self.replenish_hand();
|
||||
|
||||
|
@ -712,6 +719,6 @@ impl GameState {
|
|||
};
|
||||
assert_eq!((self.board.turn - 1) % self.board.num_players, self.board.player);
|
||||
|
||||
turn
|
||||
turn_record
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ extern crate crossbeam;
|
|||
mod helpers;
|
||||
mod game;
|
||||
mod simulator;
|
||||
mod strategy;
|
||||
mod strategies {
|
||||
pub mod examples;
|
||||
pub mod cheating;
|
||||
|
@ -122,20 +123,20 @@ fn main() {
|
|||
};
|
||||
|
||||
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" => {
|
||||
Box::new(strategies::examples::RandomStrategyConfig {
|
||||
hint_probability: 0.4,
|
||||
play_probability: 0.2,
|
||||
}) as Box<simulator::GameStrategyConfig + Sync>
|
||||
}) as Box<strategy::GameStrategyConfig + Sync>
|
||||
},
|
||||
"cheat" => {
|
||||
Box::new(strategies::cheating::CheatingStrategyConfig::new())
|
||||
as Box<simulator::GameStrategyConfig + Sync>
|
||||
as Box<strategy::GameStrategyConfig + Sync>
|
||||
},
|
||||
"info" => {
|
||||
Box::new(strategies::information::InformationStrategyConfig::new())
|
||||
as Box<simulator::GameStrategyConfig + Sync>
|
||||
as Box<strategy::GameStrategyConfig + Sync>
|
||||
},
|
||||
_ => {
|
||||
print_usage(&program, opts);
|
||||
|
|
|
@ -4,46 +4,21 @@ use std::fmt;
|
|||
use crossbeam;
|
||||
|
||||
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, &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>;
|
||||
}
|
||||
use strategy::*;
|
||||
|
||||
fn new_deck(seed: u32) -> Cards {
|
||||
let mut deck: Cards = Cards::new();
|
||||
|
||||
for &color in COLORS.iter() {
|
||||
for &value in VALUES.iter() {
|
||||
let count = get_count_for_value(value);
|
||||
for _ in 0..count {
|
||||
for _ in 0..get_count_for_value(value) {
|
||||
deck.push(Card::new(color, value));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
rand::ChaChaRng::from_seed(&[seed]).shuffle(&mut deck[..]);
|
||||
|
||||
trace!("Created deck: {:?}", deck);
|
||||
debug!("Deck: {:?}", deck);
|
||||
deck
|
||||
}
|
||||
|
||||
|
@ -59,7 +34,7 @@ pub fn simulate_once(
|
|||
let mut game = GameState::new(opts, deck);
|
||||
|
||||
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>>>();
|
||||
|
||||
while !game.is_over() {
|
||||
|
|
|
@ -2,7 +2,7 @@ use std::rc::Rc;
|
|||
use std::cell::{RefCell};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use simulator::*;
|
||||
use strategy::*;
|
||||
use game::*;
|
||||
|
||||
// strategy that explicitly cheats by using Rc/RefCell
|
||||
|
@ -208,6 +208,6 @@ impl PlayerStrategy for CheatingPlayerStrategy {
|
|||
}
|
||||
TurnChoice::Discard(index)
|
||||
}
|
||||
fn update(&mut self, _: &Turn, _: &BorrowedGameView) {
|
||||
fn update(&mut self, _: &TurnRecord, _: &BorrowedGameView) {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use simulator::*;
|
||||
use strategy::*;
|
||||
use game::*;
|
||||
use rand::{self, Rng};
|
||||
|
||||
|
@ -66,6 +66,6 @@ impl PlayerStrategy for RandomStrategyPlayer {
|
|||
TurnChoice::Discard(0)
|
||||
}
|
||||
}
|
||||
fn update(&mut self, _: &Turn, _: &BorrowedGameView) {
|
||||
fn update(&mut self, _: &TurnRecord, _: &BorrowedGameView) {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
use std::collections::{HashMap, HashSet};
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use simulator::*;
|
||||
use strategy::*;
|
||||
use game::*;
|
||||
use helpers::*;
|
||||
|
||||
// TODO: use random extra information - i.e. when casting up and down,
|
||||
// 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)]
|
||||
struct ModulusInformation {
|
||||
|
@ -39,8 +41,7 @@ impl ModulusInformation {
|
|||
self.modulus = self.modulus / modulus;
|
||||
let value = self.value / self.modulus;
|
||||
self.value = self.value - value * self.modulus;
|
||||
trace!("orig value {}, orig modulus {}, self.value {}, self.modulus {}, value {}, modulus {}",
|
||||
original_value, original_modulus, self.value, self.modulus, value, modulus);
|
||||
assert!(original_modulus == modulus * self.modulus);
|
||||
assert!(original_value == value * self.modulus + self.value);
|
||||
Self::new(modulus, value)
|
||||
}
|
||||
|
@ -303,8 +304,6 @@ impl InformationPlayerStrategy {
|
|||
return questions;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
debug!("Something known playable");
|
||||
}
|
||||
|
||||
let mut ask_partition = augmented_hand_info.iter()
|
||||
|
@ -354,10 +353,8 @@ impl InformationPlayerStrategy {
|
|||
assert!(player != &self.me);
|
||||
let hand_info = self.get_player_public_info(player);
|
||||
let questions = Self::get_questions(total_info, view, hand_info);
|
||||
trace!("Getting hint for player {}, questions {:?}", player, questions.len());
|
||||
let mut answer = Self::answer_questions(&questions, view.get_hand(player), view);
|
||||
answer.cast_up(total_info);
|
||||
trace!("Resulting answer {:?}", answer);
|
||||
answer
|
||||
}
|
||||
|
||||
|
@ -379,11 +376,8 @@ impl InformationPlayerStrategy {
|
|||
let hand_info = self.get_my_public_info();
|
||||
Self::get_questions(hint.modulus, view, hand_info)
|
||||
};
|
||||
trace!("{}: Questions {:?}", self.me, questions.len());
|
||||
let product = questions.iter().fold(1, |a, ref b| a * b.info_amount());
|
||||
trace!("{}: Product {}, hint: {:?}", self.me, product, hint);
|
||||
hint.cast_down(product);
|
||||
trace!("{}: Inferred for myself {:?}", self.me, hint);
|
||||
|
||||
let me = self.me.clone();
|
||||
let mut hand_info = self.take_public_info(&me);
|
||||
|
@ -404,14 +398,12 @@ impl InformationPlayerStrategy {
|
|||
let view = &self.last_view;
|
||||
view.board.get_players()
|
||||
};
|
||||
trace!("{}: inferring for myself, starting with {:?}", self.me, hint);
|
||||
for player in players {
|
||||
if (player != hinter) && (player != self.me) {
|
||||
{
|
||||
let view = &self.last_view;
|
||||
let hint_info = self.get_hint_info_for_player(&player, hint.modulus, view);
|
||||
hint.subtract(&hint_info);
|
||||
trace!("{}: subtracted for {}, now {:?}", self.me, player, hint);
|
||||
}
|
||||
|
||||
// *take* instead of borrowing mutably, because of borrow rules...
|
||||
|
@ -856,20 +848,20 @@ impl PlayerStrategy for InformationPlayerStrategy {
|
|||
TurnChoice::Discard(index)
|
||||
}
|
||||
|
||||
fn update(&mut self, turn: &Turn, view: &BorrowedGameView) {
|
||||
match turn.choice {
|
||||
fn update(&mut self, turn_record: &TurnRecord, view: &BorrowedGameView) {
|
||||
match turn_record.choice {
|
||||
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.update_public_info_for_hint(hint, matches);
|
||||
} else {
|
||||
panic!("Got turn choice {:?}, but turn result {:?}",
|
||||
turn.choice, turn.result);
|
||||
turn_record.choice, turn_record.result);
|
||||
}
|
||||
}
|
||||
TurnChoice::Discard(index) => {
|
||||
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 {
|
||||
|
@ -881,19 +873,19 @@ impl PlayerStrategy for InformationPlayerStrategy {
|
|||
));
|
||||
}
|
||||
|
||||
if let &TurnResult::Discard(ref card) = &turn.result {
|
||||
self.update_public_info_for_discard_or_play(view, &turn.player, index, card);
|
||||
if let &TurnResult::Discard(ref card) = &turn_record.result {
|
||||
self.update_public_info_for_discard_or_play(view, &turn_record.player, index, card);
|
||||
} else {
|
||||
panic!("Got turn choice {:?}, but turn result {:?}",
|
||||
turn.choice, turn.result);
|
||||
turn_record.choice, turn_record.result);
|
||||
}
|
||||
}
|
||||
TurnChoice::Play(index) => {
|
||||
if let &TurnResult::Play(ref card, _) = &turn.result {
|
||||
self.update_public_info_for_discard_or_play(view, &turn.player, index, card);
|
||||
if let &TurnResult::Play(ref card, _) = &turn_record.result {
|
||||
self.update_public_info_for_discard_or_play(view, &turn_record.player, index, card);
|
||||
} else {
|
||||
panic!("Got turn choice {:?}, but turn result {:?}",
|
||||
turn.choice, turn.result);
|
||||
turn_record.choice, turn_record.result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
26
src/strategy.rs
Normal file
26
src/strategy.rs
Normal 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>;
|
||||
}
|
||||
|
Loading…
Reference in a new issue