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
|
## 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`.
|
||||||
|
|
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 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
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