From e36700d93f5e46266e21a3fe3d332a2cbceb09ff Mon Sep 17 00:00:00 2001 From: Jeff Wu Date: Fri, 18 Mar 2016 23:34:07 -0700 Subject: [PATCH] no empty hints, by default --- src/game.rs | 79 ++++++++++++++++++++------------------ src/info.rs | 2 +- src/main.rs | 21 ++++++++-- src/simulator.rs | 31 +++++++-------- src/strategies/cheating.rs | 19 ++++++--- src/strategies/examples.rs | 8 ++-- 6 files changed, 95 insertions(+), 65 deletions(-) diff --git a/src/game.rs b/src/game.rs index fc09fb3..b2ee641 100644 --- a/src/game.rs +++ b/src/game.rs @@ -19,7 +19,7 @@ pub type Value = u32; pub const VALUES : [Value; 5] = [1, 2, 3, 4, 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 { 1 => 3, 2 | 3 | 4 => 2, @@ -49,10 +49,10 @@ impl fmt::Display for Card { pub type Cards = Vec; pub type CardsInfo = Vec; -#[derive(Debug)] +#[derive(Debug,Clone)] pub struct Firework { pub color: Color, - top: Value, + pub top: Value, } impl Firework { fn new(color: Color) -> Firework { @@ -66,8 +66,8 @@ impl Firework { if self.complete() { None } else { Some(self.top + 1) } } - fn score(&self) -> usize { - (self.top as usize) + fn score(&self) -> Score { + self.top } fn complete(&self) -> bool { @@ -83,7 +83,6 @@ impl Firework { Some(card.value) == self.desired_value(), "Attempted to place card of wrong value on firework!" ); - self.top = card.value; } } @@ -100,7 +99,7 @@ impl fmt::Display for Firework { #[derive(Debug)] pub struct Discard { pub cards: Cards, - counts: HashMap>, + counts: HashMap>, } impl 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(); color_count.get(&card.value).unwrap().clone() } @@ -127,7 +126,7 @@ impl Discard { self.remaining(card) == 0 } - fn remaining(&self, card: &Card) -> usize { + fn remaining(&self, card: &Card) -> u32 { let count = self.get_count(&card); get_count_for_value(&card.value) - count } @@ -195,13 +194,13 @@ pub enum TurnChoice { // represents what happened in a turn #[derive(Debug,Clone)] pub enum TurnResult { - Hint(Vec), - Discard(Card), - Play(Card, bool), + Hint(Vec), // indices revealed + Discard(Card), // card discarded + Play(Card, bool), // card played, whether it succeeded } // represents a turn taken in the game -#[derive(Debug)] +#[derive(Debug,Clone)] pub struct Turn { pub player: Player, pub choice: TurnChoice, @@ -216,8 +215,8 @@ pub struct GameOptions { pub num_hints: u32, // when hits 0, you lose pub num_lives: u32, - // TODO: - // pub allow_empty_hints: bool, + // whether to allow hints that reveal no cards + pub allow_empty_hints: bool, } // The state of a given player: all other players may see this @@ -269,22 +268,18 @@ impl PlayerState { &Hinted::Color(ref color) => { let mut i = 0; for card in &self.hand { - self.info[i].color_info.mark( - color, - card.color == *color - ); - indices.push(i); + let matches = card.color == *color; + self.info[i].color_info.mark(color, matches); + if matches { indices.push(i); } i += 1; } } &Hinted::Value(ref value) => { let mut i = 0; for card in &self.hand { - self.info[i].value_info.mark( - value, - card.value == *value - ); - indices.push(i); + let matches = card.value == *value; + self.info[i].value_info.mark(value, matches); + if matches { indices.push(i); } i += 1; } } @@ -317,6 +312,7 @@ fn new_deck(seed: u32) -> Cards { #[derive(Debug)] pub struct BoardState { deck: Cards, + pub total_cards: u32, pub discard: Discard, pub fireworks: HashMap, @@ -330,6 +326,7 @@ pub struct BoardState { pub hints_total: u32, pub hints_remaining: u32, + pub allow_empty_hints: bool, pub lives_total: u32, pub lives_remaining: u32, pub turn_history: Vec, @@ -342,15 +339,19 @@ impl BoardState { for color in COLORS.iter() { fireworks.insert(color, Firework::new(color)); } + let deck = new_deck(seed); + let total_cards = deck.len() as u32; BoardState { - deck: new_deck(seed), + deck: deck, + total_cards: total_cards, fireworks: fireworks, discard: Discard::new(), num_players: opts.num_players, hand_size: opts.hand_size, player: 0, turn: 1, + allow_empty_hints: opts.allow_empty_hints, hints_total: opts.num_hints, hints_remaining: opts.num_hints, lives_total: opts.num_lives, @@ -458,8 +459,8 @@ impl BoardState { score as u32 } - pub fn deck_size(&self) -> usize { - self.deck.len() + pub fn deck_size(&self) -> u32 { + self.deck.len() as u32 } pub fn player_to_left(&self, player: &Player) -> Player { @@ -556,16 +557,17 @@ pub struct GameState { } impl fmt::Display for GameState { 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("==========================\n")); + try!(f.write_str("======\n")); for player in self.board.get_players() { let state = &self.player_states.get(&player).unwrap(); 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("==========================\n")); + try!(f.write_str("======\n")); try!(f.write_str(&format!("{}", self.board))); Ok(()) } @@ -599,7 +601,6 @@ impl GameState { } pub fn is_over(&self) -> bool { - // TODO: add condition that fireworks cannot be further completed? self.board.is_over() } @@ -640,8 +641,7 @@ impl GameState { } } - pub fn process_choice(&mut self, choice: TurnChoice) -> TurnResult { - debug!("Player {}'s move", self.board.player); + pub fn process_choice(&mut self, choice: TurnChoice) -> Turn { let turn_result = { match choice { TurnChoice::Hint(ref hint) => { @@ -655,6 +655,9 @@ impl GameState { let ref mut state = self.player_states.get_mut(&hint.player).unwrap(); let indices = state.reveal(&hint.hinted); + if (!self.board.allow_empty_hints) && (indices.len() == 0) { + panic!("Tried hinting an empty hint"); + } TurnResult::Hint(indices) } TurnChoice::Discard(index) => { @@ -697,10 +700,10 @@ impl GameState { }; let turn = Turn { player: self.board.player.clone(), - result: turn_result.clone(), + result: turn_result, choice: choice, }; - self.board.turn_history.push(turn); + self.board.turn_history.push(turn.clone()); self.replenish_hand(); @@ -714,6 +717,6 @@ impl GameState { }; assert_eq!((self.board.turn - 1) % self.board.num_players, self.board.player); - turn_result + turn } } diff --git a/src/info.rs b/src/info.rs index ee9e4c8..b7c3e69 100644 --- a/src/info.rs +++ b/src/info.rs @@ -5,7 +5,7 @@ use std::hash::Hash; use game::*; -// Represents information about possible values of type T +// Represents hinted information about possible values of type T pub trait Info where T: Hash + Eq + Clone { // get all a-priori possibilities fn get_all_possibilities() -> Vec; diff --git a/src/main.rs b/src/main.rs index 678f32c..62c77bf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -43,6 +43,7 @@ fn main() { opts.optopt("n", "ntrials", "Number of games to simulate", "NTRIALS"); 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("p", "nplayers", "Number of players", "NPLAYERS"); opts.optflag("h", "help", "Print this help menu"); let matches = match opts.parse(&args[1..]) { Ok(m) => { m } @@ -65,7 +66,10 @@ fn main() { "info" => { log::LogLevelFilter::Info } "warn" => { log::LogLevelFilter::Warn } "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| { @@ -79,11 +83,22 @@ fn main() { 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 { - num_players: 5, - hand_size: 4, + num_players: n_players, + hand_size: hand_size, num_hints: 8, 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 diff --git a/src/simulator.rs b/src/simulator.rs index a36756d..e4c3a07 100644 --- a/src/simulator.rs +++ b/src/simulator.rs @@ -40,40 +40,41 @@ pub fn simulate_once( ); } - debug!("Initial state:\n{}", game); - while !game.is_over() { - debug!("Turn {}", game.board.turn); let player = game.board.player; + + debug!(""); + debug!("======================================================="); + debug!("Turn {}, Player {} to go", game.board.turn, player); + debug!("======================================================="); + debug!("{}", game); + + let choice = { let mut strategy = strategies.get_mut(&player).unwrap(); strategy.decide(&game.get_view(player)) }; - let turn_result = game.process_choice(choice.clone()); - - let turn = Turn { - player: player, - choice: choice, - result: turn_result, - }; + let turn = game.process_choice(choice); for player in game.get_players() { let mut strategy = strategies.get_mut(&player).unwrap(); strategy.update(&turn, &game.get_view(player)); } - debug!("State:\n{}", game); } + debug!(""); + debug!("======================================================="); + debug!("Final state:\n{}", game); let score = game.score(); debug!("SCORED: {:?}", score); score } struct Histogram { - pub hist: HashMap, + pub hist: HashMap, pub sum: Score, - pub total_count: usize, + pub total_count: u32, } impl Histogram { pub fn new() -> Histogram { @@ -83,7 +84,7 @@ impl Histogram { 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; self.hist.insert(val, new_count); self.sum += val * (count as u32); @@ -92,7 +93,7 @@ impl Histogram { pub fn insert(&mut self, val: Score) { 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) } pub fn average(&self) -> f32 { diff --git a/src/strategies/cheating.rs b/src/strategies/cheating.rs index 56b33d3..f025144 100644 --- a/src/strategies/cheating.rs +++ b/src/strategies/cheating.rs @@ -71,14 +71,16 @@ impl CheatingPlayerStrategy { // give a throwaway hint - we only do this when we have nothing to do 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 { - player: view.board.player_to_left(&self.me), - hinted: Hinted::Value(1) + player: hint_player, + hinted: Hinted::Value(hint_card.value) }) } // 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> */) -> u32 { + fn hand_play_value(&self, view: &GameStateView, hand: &Cards/*, all_viewable: HashMap> */) -> u32 { // dead = 0 points // indispensible = 5 + (5 - value) points // playable, not in another hand = 2 point @@ -188,8 +190,15 @@ impl PlayerStrategy for CheatingPlayerStrategy { }).unwrap(); TurnChoice::Play(index) } else { - // 50 total, 25 to play, 20 in hand - if view.board.discard.cards.len() < 6 { + // discard threshold is how many cards we're willing to discard + // 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 let Some(i) = self.find_useless_card(view, my_cards) { return TurnChoice::Discard(i); diff --git a/src/strategies/examples.rs b/src/strategies/examples.rs index d141c49..60924c6 100644 --- a/src/strategies/examples.rs +++ b/src/strategies/examples.rs @@ -44,16 +44,18 @@ impl PlayerStrategy for RandomStrategyPlayer { let p = rand::random::(); if p < self.hint_probability { 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 = { if rand::random() { // hint a color - Hinted::Color(rand::thread_rng().choose(&COLORS).unwrap()) + Hinted::Color(hint_card.color) } else { - Hinted::Value(*rand::thread_rng().choose(&VALUES).unwrap()) + Hinted::Value(hint_card.value) } }; TurnChoice::Hint(Hint { - player: view.board.player_to_left(&self.me), + player: hint_player, hinted: hinted, }) } else {