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 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<Card>;
pub type CardsInfo = Vec<CardInfo>;
#[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<Color, HashMap<Value, usize>>,
counts: HashMap<Color, HashMap<Value, u32>>,
}
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<usize>),
Discard(Card),
Play(Card, bool),
Hint(Vec<usize>), // 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<Color, Firework>,
@ -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<Turn>,
@ -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
}
}

View file

@ -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<T> where T: Hash + Eq + Clone {
// get all a-priori possibilities
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("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

View file

@ -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<Score, usize>,
pub hist: HashMap<Score, u32>,
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 {

View file

@ -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<Color, <Value, usize>> */) -> u32 {
fn hand_play_value(&self, view: &GameStateView, hand: &Cards/*, all_viewable: HashMap<Color, <Value, u32>> */) -> 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);

View file

@ -44,16 +44,18 @@ impl PlayerStrategy for RandomStrategyPlayer {
let p = rand::random::<f64>();
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 {