no empty hints, by default
This commit is contained in:
parent
964602d03d
commit
e36700d93f
6 changed files with 95 additions and 65 deletions
79
src/game.rs
79
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<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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>;
|
||||
|
|
21
src/main.rs
21
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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in a new issue