no empty hints, by default

This commit is contained in:
Jeff Wu 2016-03-18 23:34:07 -07:00
parent 964602d03d
commit e36700d93f
6 changed files with 95 additions and 65 deletions

View file

@ -19,7 +19,7 @@ pub type Value = u32;
pub const VALUES : [Value; 5] = [1, 2, 3, 4, 5]; pub const VALUES : [Value; 5] = [1, 2, 3, 4, 5];
pub const FINAL_VALUE : Value = 5; pub const FINAL_VALUE : Value = 5;
pub fn get_count_for_value(value: &Value) -> usize { pub fn get_count_for_value(value: &Value) -> u32 {
match *value { match *value {
1 => 3, 1 => 3,
2 | 3 | 4 => 2, 2 | 3 | 4 => 2,
@ -49,10 +49,10 @@ impl fmt::Display for Card {
pub type Cards = Vec<Card>; pub type Cards = Vec<Card>;
pub type CardsInfo = Vec<CardInfo>; pub type CardsInfo = Vec<CardInfo>;
#[derive(Debug)] #[derive(Debug,Clone)]
pub struct Firework { pub struct Firework {
pub color: Color, pub color: Color,
top: Value, pub top: Value,
} }
impl Firework { impl Firework {
fn new(color: Color) -> Firework { fn new(color: Color) -> Firework {
@ -66,8 +66,8 @@ impl Firework {
if self.complete() { None } else { Some(self.top + 1) } if self.complete() { None } else { Some(self.top + 1) }
} }
fn score(&self) -> usize { fn score(&self) -> Score {
(self.top as usize) self.top
} }
fn complete(&self) -> bool { fn complete(&self) -> bool {
@ -83,7 +83,6 @@ impl Firework {
Some(card.value) == self.desired_value(), Some(card.value) == self.desired_value(),
"Attempted to place card of wrong value on firework!" "Attempted to place card of wrong value on firework!"
); );
self.top = card.value; self.top = card.value;
} }
} }
@ -100,7 +99,7 @@ impl fmt::Display for Firework {
#[derive(Debug)] #[derive(Debug)]
pub struct Discard { pub struct Discard {
pub cards: Cards, pub cards: Cards,
counts: HashMap<Color, HashMap<Value, usize>>, counts: HashMap<Color, HashMap<Value, u32>>,
} }
impl Discard { impl Discard {
fn new() -> Discard { fn new() -> Discard {
@ -118,7 +117,7 @@ impl Discard {
} }
} }
fn get_count(&self, card: &Card) -> usize { fn get_count(&self, card: &Card) -> u32 {
let color_count = self.counts.get(card.color).unwrap(); let color_count = self.counts.get(card.color).unwrap();
color_count.get(&card.value).unwrap().clone() color_count.get(&card.value).unwrap().clone()
} }
@ -127,7 +126,7 @@ impl Discard {
self.remaining(card) == 0 self.remaining(card) == 0
} }
fn remaining(&self, card: &Card) -> usize { fn remaining(&self, card: &Card) -> u32 {
let count = self.get_count(&card); let count = self.get_count(&card);
get_count_for_value(&card.value) - count get_count_for_value(&card.value) - count
} }
@ -195,13 +194,13 @@ pub enum TurnChoice {
// represents what happened in a turn // represents what happened in a turn
#[derive(Debug,Clone)] #[derive(Debug,Clone)]
pub enum TurnResult { pub enum TurnResult {
Hint(Vec<usize>), Hint(Vec<usize>), // indices revealed
Discard(Card), Discard(Card), // card discarded
Play(Card, bool), Play(Card, bool), // card played, whether it succeeded
} }
// represents a turn taken in the game // represents a turn taken in the game
#[derive(Debug)] #[derive(Debug,Clone)]
pub struct Turn { pub struct Turn {
pub player: Player, pub player: Player,
pub choice: TurnChoice, pub choice: TurnChoice,
@ -216,8 +215,8 @@ pub struct GameOptions {
pub num_hints: u32, pub num_hints: u32,
// when hits 0, you lose // when hits 0, you lose
pub num_lives: u32, pub num_lives: u32,
// TODO: // whether to allow hints that reveal no cards
// pub allow_empty_hints: bool, pub allow_empty_hints: bool,
} }
// The state of a given player: all other players may see this // The state of a given player: all other players may see this
@ -269,22 +268,18 @@ impl PlayerState {
&Hinted::Color(ref color) => { &Hinted::Color(ref color) => {
let mut i = 0; let mut i = 0;
for card in &self.hand { for card in &self.hand {
self.info[i].color_info.mark( let matches = card.color == *color;
color, self.info[i].color_info.mark(color, matches);
card.color == *color if matches { indices.push(i); }
);
indices.push(i);
i += 1; i += 1;
} }
} }
&Hinted::Value(ref value) => { &Hinted::Value(ref value) => {
let mut i = 0; let mut i = 0;
for card in &self.hand { for card in &self.hand {
self.info[i].value_info.mark( let matches = card.value == *value;
value, self.info[i].value_info.mark(value, matches);
card.value == *value if matches { indices.push(i); }
);
indices.push(i);
i += 1; i += 1;
} }
} }
@ -317,6 +312,7 @@ fn new_deck(seed: u32) -> Cards {
#[derive(Debug)] #[derive(Debug)]
pub struct BoardState { pub struct BoardState {
deck: Cards, deck: Cards,
pub total_cards: u32,
pub discard: Discard, pub discard: Discard,
pub fireworks: HashMap<Color, Firework>, pub fireworks: HashMap<Color, Firework>,
@ -330,6 +326,7 @@ pub struct BoardState {
pub hints_total: u32, pub hints_total: u32,
pub hints_remaining: u32, pub hints_remaining: u32,
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>, pub turn_history: Vec<Turn>,
@ -342,15 +339,19 @@ impl BoardState {
for color in COLORS.iter() { for color in COLORS.iter() {
fireworks.insert(color, Firework::new(color)); fireworks.insert(color, Firework::new(color));
} }
let deck = new_deck(seed);
let total_cards = deck.len() as u32;
BoardState { BoardState {
deck: new_deck(seed), deck: deck,
total_cards: total_cards,
fireworks: fireworks, fireworks: fireworks,
discard: Discard::new(), discard: Discard::new(),
num_players: opts.num_players, num_players: opts.num_players,
hand_size: opts.hand_size, hand_size: opts.hand_size,
player: 0, player: 0,
turn: 1, turn: 1,
allow_empty_hints: opts.allow_empty_hints,
hints_total: opts.num_hints, hints_total: opts.num_hints,
hints_remaining: opts.num_hints, hints_remaining: opts.num_hints,
lives_total: opts.num_lives, lives_total: opts.num_lives,
@ -458,8 +459,8 @@ impl BoardState {
score as u32 score as u32
} }
pub fn deck_size(&self) -> usize { pub fn deck_size(&self) -> u32 {
self.deck.len() self.deck.len() as u32
} }
pub fn player_to_left(&self, player: &Player) -> Player { pub fn player_to_left(&self, player: &Player) -> Player {
@ -556,16 +557,17 @@ pub struct GameState {
} }
impl fmt::Display for GameState { impl fmt::Display for GameState {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
try!(f.write_str("==========================\n")); try!(f.write_str("\n"));
try!(f.write_str("======\n"));
try!(f.write_str("Hands:\n")); try!(f.write_str("Hands:\n"));
try!(f.write_str("==========================\n")); try!(f.write_str("======\n"));
for player in self.board.get_players() { for player in self.board.get_players() {
let state = &self.player_states.get(&player).unwrap(); let state = &self.player_states.get(&player).unwrap();
try!(f.write_str(&format!("player {} {}\n", player, state))); try!(f.write_str(&format!("player {} {}\n", player, state)));
} }
try!(f.write_str("==========================\n")); try!(f.write_str("======\n"));
try!(f.write_str("Board:\n")); try!(f.write_str("Board:\n"));
try!(f.write_str("==========================\n")); try!(f.write_str("======\n"));
try!(f.write_str(&format!("{}", self.board))); try!(f.write_str(&format!("{}", self.board)));
Ok(()) Ok(())
} }
@ -599,7 +601,6 @@ impl GameState {
} }
pub fn is_over(&self) -> bool { pub fn is_over(&self) -> bool {
// TODO: add condition that fireworks cannot be further completed?
self.board.is_over() self.board.is_over()
} }
@ -640,8 +641,7 @@ impl GameState {
} }
} }
pub fn process_choice(&mut self, choice: TurnChoice) -> TurnResult { pub fn process_choice(&mut self, choice: TurnChoice) -> Turn {
debug!("Player {}'s move", self.board.player);
let turn_result = { let turn_result = {
match choice { match choice {
TurnChoice::Hint(ref hint) => { TurnChoice::Hint(ref hint) => {
@ -655,6 +655,9 @@ impl GameState {
let ref mut state = self.player_states.get_mut(&hint.player).unwrap(); let ref mut state = self.player_states.get_mut(&hint.player).unwrap();
let indices = state.reveal(&hint.hinted); let indices = state.reveal(&hint.hinted);
if (!self.board.allow_empty_hints) && (indices.len() == 0) {
panic!("Tried hinting an empty hint");
}
TurnResult::Hint(indices) TurnResult::Hint(indices)
} }
TurnChoice::Discard(index) => { TurnChoice::Discard(index) => {
@ -697,10 +700,10 @@ impl GameState {
}; };
let turn = Turn { let turn = Turn {
player: self.board.player.clone(), player: self.board.player.clone(),
result: turn_result.clone(), result: turn_result,
choice: choice, choice: choice,
}; };
self.board.turn_history.push(turn); self.board.turn_history.push(turn.clone());
self.replenish_hand(); self.replenish_hand();
@ -714,6 +717,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_result turn
} }
} }

View file

@ -5,7 +5,7 @@ use std::hash::Hash;
use game::*; use game::*;
// Represents 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>;

View file

@ -43,6 +43,7 @@ fn main() {
opts.optopt("n", "ntrials", "Number of games to simulate", "NTRIALS"); opts.optopt("n", "ntrials", "Number of games to simulate", "NTRIALS");
opts.optopt("t", "nthreads", "Number of threads to use for simulation", "NTHREADS"); opts.optopt("t", "nthreads", "Number of threads to use for simulation", "NTHREADS");
opts.optopt("s", "seed", "Seed for PRNG (can only be used with n=1)", "SEED"); opts.optopt("s", "seed", "Seed for PRNG (can only be used with n=1)", "SEED");
opts.optopt("p", "nplayers", "Number of players", "NPLAYERS");
opts.optflag("h", "help", "Print this help menu"); opts.optflag("h", "help", "Print this help menu");
let matches = match opts.parse(&args[1..]) { let matches = match opts.parse(&args[1..]) {
Ok(m) => { m } Ok(m) => { m }
@ -65,7 +66,10 @@ fn main() {
"info" => { log::LogLevelFilter::Info } "info" => { log::LogLevelFilter::Info }
"warn" => { log::LogLevelFilter::Warn } "warn" => { log::LogLevelFilter::Warn }
"error" => { log::LogLevelFilter::Error } "error" => { log::LogLevelFilter::Error }
_ => { panic!("Unexpected log level argument {}", log_level_str); } _ => {
print_usage(&program, opts);
panic!("Unexpected log level argument {}", log_level_str);
}
}; };
log::set_logger(|max_log_level| { log::set_logger(|max_log_level| {
@ -79,11 +83,22 @@ fn main() {
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 hand_size = match n_players {
2 => 5,
3 => 5,
4 => 4,
5 => 4,
_ => { panic!("There should be 2 to 5 players, not {}", n_players); }
};
let opts = game::GameOptions { let opts = game::GameOptions {
num_players: 5, num_players: n_players,
hand_size: 4, hand_size: hand_size,
num_hints: 8, num_hints: 8,
num_lives: 3, num_lives: 3,
// hanabi rules are a bit ambiguous about whether you can give hints that match 0 cards
allow_empty_hints: false,
}; };
// TODO: make this configurable // TODO: make this configurable

View file

@ -40,40 +40,41 @@ pub fn simulate_once(
); );
} }
debug!("Initial state:\n{}", game);
while !game.is_over() { while !game.is_over() {
debug!("Turn {}", game.board.turn);
let player = game.board.player; let player = game.board.player;
debug!("");
debug!("=======================================================");
debug!("Turn {}, Player {} to go", game.board.turn, player);
debug!("=======================================================");
debug!("{}", game);
let choice = { let choice = {
let mut strategy = strategies.get_mut(&player).unwrap(); let mut strategy = strategies.get_mut(&player).unwrap();
strategy.decide(&game.get_view(player)) strategy.decide(&game.get_view(player))
}; };
let turn_result = game.process_choice(choice.clone()); let turn = game.process_choice(choice);
let turn = Turn {
player: player,
choice: choice,
result: turn_result,
};
for player in game.get_players() { for player in game.get_players() {
let mut strategy = strategies.get_mut(&player).unwrap(); let mut strategy = strategies.get_mut(&player).unwrap();
strategy.update(&turn, &game.get_view(player)); strategy.update(&turn, &game.get_view(player));
} }
debug!("State:\n{}", game);
} }
debug!("");
debug!("=======================================================");
debug!("Final state:\n{}", game);
let score = game.score(); let score = game.score();
debug!("SCORED: {:?}", score); debug!("SCORED: {:?}", score);
score score
} }
struct Histogram { struct Histogram {
pub hist: HashMap<Score, usize>, pub hist: HashMap<Score, u32>,
pub sum: Score, pub sum: Score,
pub total_count: usize, pub total_count: u32,
} }
impl Histogram { impl Histogram {
pub fn new() -> Histogram { pub fn new() -> Histogram {
@ -83,7 +84,7 @@ impl Histogram {
total_count: 0, total_count: 0,
} }
} }
fn insert_many(&mut self, val: Score, count: usize) { fn insert_many(&mut self, val: Score, count: u32) {
let new_count = self.get_count(&val) + count; let new_count = self.get_count(&val) + count;
self.hist.insert(val, new_count); self.hist.insert(val, new_count);
self.sum += val * (count as u32); self.sum += val * (count as u32);
@ -92,7 +93,7 @@ impl Histogram {
pub fn insert(&mut self, val: Score) { pub fn insert(&mut self, val: Score) {
self.insert_many(val, 1); self.insert_many(val, 1);
} }
pub fn get_count(&self, val: &Score) -> usize { pub fn get_count(&self, val: &Score) -> u32 {
*self.hist.get(&val).unwrap_or(&0) *self.hist.get(&val).unwrap_or(&0)
} }
pub fn average(&self) -> f32 { pub fn average(&self) -> f32 {

View file

@ -71,14 +71,16 @@ impl CheatingPlayerStrategy {
// 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
fn throwaway_hint(&self, view: &GameStateView) -> TurnChoice { fn throwaway_hint(&self, view: &GameStateView) -> TurnChoice {
let hint_player = view.board.player_to_left(&self.me);
let hint_card = &view.get_hand(&hint_player).first().unwrap();
TurnChoice::Hint(Hint { TurnChoice::Hint(Hint {
player: view.board.player_to_left(&self.me), player: hint_player,
hinted: Hinted::Value(1) hinted: Hinted::Value(hint_card.value)
}) })
} }
// 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: &GameStateView, hand: &Cards/*, all_viewable: HashMap<Color, <Value, usize>> */) -> u32 { fn hand_play_value(&self, view: &GameStateView, hand: &Cards/*, all_viewable: HashMap<Color, <Value, u32>> */) -> u32 {
// dead = 0 points // dead = 0 points
// indispensible = 5 + (5 - value) points // indispensible = 5 + (5 - value) points
// playable, not in another hand = 2 point // playable, not in another hand = 2 point
@ -188,8 +190,15 @@ impl PlayerStrategy for CheatingPlayerStrategy {
}).unwrap(); }).unwrap();
TurnChoice::Play(index) TurnChoice::Play(index)
} else { } else {
// 50 total, 25 to play, 20 in hand // discard threshold is how many cards we're willing to discard
if view.board.discard.cards.len() < 6 { // such that if we only played,
// we would not reach the final countdown round
// e.g. 50 total, 25 to play, 20 in hand
let discard_threshold =
view.board.total_cards as usize
- (COLORS.len() * VALUES.len())
- (view.board.num_players * view.board.hand_size) as usize;
if view.board.discard.cards.len() <= discard_threshold {
// if anything is totally useless, discard it // if anything is totally useless, discard it
if let Some(i) = self.find_useless_card(view, my_cards) { if let Some(i) = self.find_useless_card(view, my_cards) {
return TurnChoice::Discard(i); return TurnChoice::Discard(i);

View file

@ -44,16 +44,18 @@ impl PlayerStrategy for RandomStrategyPlayer {
let p = rand::random::<f64>(); let p = rand::random::<f64>();
if p < self.hint_probability { if p < self.hint_probability {
if view.board.hints_remaining > 0 { if view.board.hints_remaining > 0 {
let hint_player = view.board.player_to_left(&self.me);
let hint_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
Hinted::Color(rand::thread_rng().choose(&COLORS).unwrap()) Hinted::Color(hint_card.color)
} else { } else {
Hinted::Value(*rand::thread_rng().choose(&VALUES).unwrap()) Hinted::Value(hint_card.value)
} }
}; };
TurnChoice::Hint(Hint { TurnChoice::Hint(Hint {
player: view.board.player_to_left(&self.me), player: hint_player,
hinted: hinted, hinted: hinted,
}) })
} else { } else {