hints and better logging

This commit is contained in:
Jeff Wu 2016-03-06 16:14:47 -08:00
parent a44b017eae
commit 49627b91b6
4 changed files with 172 additions and 40 deletions

View file

@ -1,7 +1,6 @@
use rand::{self, Rng}; use rand::{self, Rng};
use std::convert::From; use std::convert::From;
use std::collections::HashSet;
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt; use std::fmt;
@ -20,13 +19,15 @@ pub const VALUES : [Value; 5] = [1, 2, 3, 4, 5];
pub const VALUE_COUNTS : [(Value, u32); 5] = [(1, 3), (2, 2), (3, 2), (4, 2), (5, 1)]; pub const VALUE_COUNTS : [(Value, u32); 5] = [(1, 3), (2, 2), (3, 2), (4, 2), (5, 1)];
pub const FINAL_VALUE : Value = 5; pub const FINAL_VALUE : Value = 5;
#[derive(Debug)]
pub struct Card { pub struct Card {
pub color: Color, pub color: Color,
pub value: Value, pub value: Value,
} }
impl fmt::Debug for Card { impl fmt::Display for Card {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} {}", self.color, self.value) let colorchar = self.color.chars().next().unwrap();
write!(f, "{}{}", colorchar, self.value)
} }
} }
@ -61,6 +62,15 @@ impl <T> From<Vec<T>> for Pile<T> {
Pile(items) Pile(items)
} }
} }
impl <T> fmt::Display for Pile<T> where T: fmt::Display {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "[");
for item in &self.0 {
write!(f, "{}, ", item);
}
write!(f, "] ")
}
}
pub type Cards = Pile<Card>; pub type Cards = Pile<Card>;
@ -69,15 +79,29 @@ pub type CardsInfo = Pile<CardInfo>;
pub type Player = u32; pub type Player = u32;
#[derive(Debug)] #[derive(Debug)]
pub enum Hint { pub enum Hinted {
Color, Color(Color),
Value, Value(Value),
}
impl fmt::Display for Hinted {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
&Hinted::Color(color) => { write!(f, "{}", color) }
&Hinted::Value(value) => { write!(f, "{}", value) }
}
}
}
#[derive(Debug)]
pub struct Hint {
pub player: Player,
pub hinted: Hinted,
} }
// represents the choice a player made in a given turn // represents the choice a player made in a given turn
#[derive(Debug)] #[derive(Debug)]
pub enum TurnChoice { pub enum TurnChoice {
Hint, Hint(Hint),
Discard(usize), Discard(usize),
Play(usize), Play(usize),
} }
@ -102,9 +126,57 @@ pub struct GameOptions {
#[derive(Debug)] #[derive(Debug)]
pub struct PlayerState { pub struct PlayerState {
// the player's actual hand // the player's actual hand
pub hand: Cards, hand: Cards,
// represents what is common knowledge about the player's hand // represents what is common knowledge about the player's hand
pub info: CardsInfo, info: CardsInfo,
}
impl PlayerState {
pub fn new(hand: Cards) -> PlayerState {
let infos = (0..hand.size()).map(|_| {
CardInfo::new()
}).collect::<Vec<_>>();
PlayerState {
hand: hand,
info: CardsInfo::from(infos),
}
}
pub fn take(&mut self, index: usize) -> (Card, CardInfo) {
let card = self.hand.take(index);
let info = self.info.take(index);
(card, info)
}
pub fn place(&mut self, card: Card) {
self.hand.place(card);
self.info.place(CardInfo::new());
}
pub fn reveal(&mut self, hinted: &Hinted) {
match hinted {
&Hinted::Color(ref color) => {
let mut i = 0;
for card in &self.hand.0 {
self.info.0[i].color_info.mark(
color,
card.color == *color
);
i += 1;
}
}
&Hinted::Value(ref value) => {
let mut i = 0;
for card in &self.hand.0 {
self.info.0[i].value_info.mark(
value,
card.value == *value
);
i += 1;
}
}
}
}
} }
// State of everything except the player's hands // State of everything except the player's hands
@ -163,14 +235,9 @@ impl GameState {
// we can assume the deck is big enough to draw initial hands // we can assume the deck is big enough to draw initial hands
deck.draw().unwrap() deck.draw().unwrap()
}).collect::<Vec<_>>(); }).collect::<Vec<_>>();
let infos = (0..opts.hand_size).map(|_| { player_states.insert(
CardInfo::new() i, PlayerState::new(Cards::from(raw_hand)),
}).collect::<Vec<_>>(); );
let state = PlayerState {
hand: Cards::from(raw_hand),
info: CardsInfo::from(infos),
};
player_states.insert(i, state);
} }
let mut fireworks : HashMap<Color, Cards> = HashMap::new(); let mut fireworks : HashMap<Color, Cards> = HashMap::new();
@ -211,7 +278,7 @@ impl GameState {
} }
}; };
deck.shuffle(); deck.shuffle();
info!("Created deck: {:?}", deck); info!("Created deck: {}", deck);
deck deck
} }
@ -253,11 +320,9 @@ impl GameState {
// takes a card from the player's hand, and replaces it if possible // takes a card from the player's hand, and replaces it if possible
fn take_from_hand(&mut self, index: usize) -> Card { fn take_from_hand(&mut self, index: usize) -> Card {
let ref mut state = self.player_states.get_mut(&self.board.player).unwrap(); let ref mut state = self.player_states.get_mut(&self.board.player).unwrap();
let card = state.hand.take(index); let (card, _) = state.take(index);
state.info.take(index);
if let Some(new_card) = self.board.deck.draw() { if let Some(new_card) = self.board.deck.draw() {
state.hand.place(new_card); state.place(new_card);
state.info.place(CardInfo::new());
} }
card card
} }
@ -269,16 +334,19 @@ impl GameState {
} }
pub fn process_choice(&mut self, choice: &TurnChoice) { pub fn process_choice(&mut self, choice: &TurnChoice) {
info!("Player {}'s move", self.board.player);
match *choice { match *choice {
TurnChoice::Hint => { TurnChoice::Hint(ref hint) => {
assert!(self.board.hints_remaining > 0); assert!(self.board.hints_remaining > 0);
self.board.hints_remaining -= 1; self.board.hints_remaining -= 1;
// TODO: actually inform player of values.. info!("Hint to player {}, about {}", hint.player, hint.hinted);
// nothing to update, really...
// TODO: manage common knowledge let ref mut state = self.player_states.get_mut(&hint.player).unwrap();
state.reveal(&hint.hinted);
} }
TurnChoice::Discard(index) => { TurnChoice::Discard(index) => {
let card = self.take_from_hand(index); let card = self.take_from_hand(index);
info!("Discard {}, which is {}", index, card);
self.board.discard.place(card); self.board.discard.place(card);
self.try_add_hint(); self.try_add_hint();
@ -286,8 +354,8 @@ impl GameState {
TurnChoice::Play(index) => { TurnChoice::Play(index) => {
let card = self.take_from_hand(index); let card = self.take_from_hand(index);
debug!( info!(
"Here! Playing card at {}, which is {:?}", "Playing card at {}, which is {}",
index, card index, card
); );
@ -307,7 +375,7 @@ impl GameState {
} else { } else {
self.board.discard.place(card); self.board.discard.place(card);
self.board.lives_remaining -= 1; self.board.lives_remaining -= 1;
debug!( info!(
"Removing a life! Lives remaining: {}", "Removing a life! Lives remaining: {}",
self.board.lives_remaining self.board.lives_remaining
); );

View file

@ -5,7 +5,7 @@ use std::hash::Hash;
use game::*; use game::*;
// Represents a bit of information about T // Represents a bit of information about T
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_possibilities() -> Vec<T>; fn get_possibilities() -> Vec<T>;
@ -42,6 +42,14 @@ trait Info<T> where T: Hash + Eq + Clone {
fn mark_false(&mut self, value: &T) { fn mark_false(&mut self, value: &T) {
self.get_mut_possibility_map().insert(value.clone(), false); self.get_mut_possibility_map().insert(value.clone(), false);
} }
fn mark(&mut self, value: &T, info: bool) {
if info {
self.mark_true(value);
} else {
self.mark_false(value);
}
}
} }
#[derive(Debug)] #[derive(Debug)]

View file

@ -10,9 +10,7 @@ mod info;
struct SimpleLogger; struct SimpleLogger;
impl log::Log for SimpleLogger { impl log::Log for SimpleLogger {
fn enabled(&self, metadata: &log::LogMetadata) -> bool { fn enabled(&self, metadata: &log::LogMetadata) -> bool {
// metadata.level() <= log::LogLevel::Warn true
metadata.level() <= log::LogLevel::Info
// metadata.level() <= log::LogLevel::Debug
} }
fn log(&self, record: &log::LogRecord) { fn log(&self, record: &log::LogRecord) {
@ -24,6 +22,7 @@ impl log::Log for SimpleLogger {
fn main() { fn main() {
log::set_logger(|max_log_level| { log::set_logger(|max_log_level| {
// Trace, Debug, Info, Warn, ...
max_log_level.set(log::LogLevelFilter::Info); max_log_level.set(log::LogLevelFilter::Info);
Box::new(SimpleLogger) Box::new(SimpleLogger)
}); });
@ -34,5 +33,8 @@ fn main() {
num_hints: 8, num_hints: 8,
num_lives: 3, num_lives: 3,
}; };
strategies::simulate(&opts, &strategies::AlwaysPlay, 100); let n = 1;
// strategies::simulate(&opts, &strategies::AlwaysDiscard, n);
// strategies::simulate(&opts, &strategies::AlwaysPlay, n);
strategies::simulate(&opts, &strategies::RandomStrategy, n);
} }

View file

@ -1,5 +1,6 @@
use game::*; use game::*;
use std::collections::HashMap; use std::collections::HashMap;
use rand::{self, Rng};
// Trait to implement for any valid Hanabi strategy // Trait to implement for any valid Hanabi strategy
// State management is done by the simulator, to avoid cheating // State management is done by the simulator, to avoid cheating
@ -10,7 +11,7 @@ pub trait Strategy {
fn update(&mut Self::InternalState, &Turn, &GameStateView); fn update(&mut Self::InternalState, &Turn, &GameStateView);
} }
pub fn simulate_once<S: Strategy>(opts: &GameOptions, strategy: &S) -> Score { pub fn simulate_once<S: Strategy>(opts: &GameOptions, _: &S) -> Score {
let mut game = GameState::new(opts); let mut game = GameState::new(opts);
let mut internal_states : HashMap<Player, S::InternalState> = HashMap::new(); let mut internal_states : HashMap<Player, S::InternalState> = HashMap::new();
@ -22,6 +23,7 @@ pub fn simulate_once<S: Strategy>(opts: &GameOptions, strategy: &S) -> Score {
} }
while !game.is_over() { while !game.is_over() {
debug!("Turn {}", game.board.turn);
let player = game.board.player; let player = game.board.player;
let choice = { let choice = {
let ref mut internal_state = internal_states.get_mut(&player).unwrap(); let ref mut internal_state = internal_states.get_mut(&player).unwrap();
@ -30,7 +32,6 @@ pub fn simulate_once<S: Strategy>(opts: &GameOptions, strategy: &S) -> Score {
game.process_choice(&choice); game.process_choice(&choice);
info!("Player {:?} decided to {:?}", player, choice);
let turn = Turn { let turn = Turn {
player: &player, player: &player,
choice: &choice, choice: &choice,
@ -43,7 +44,7 @@ pub fn simulate_once<S: Strategy>(opts: &GameOptions, strategy: &S) -> Score {
} }
// TODO: do some stuff // TODO: do some stuff
info!("State: {:?}", game); debug!("State: {:?}", game);
} }
game.score() game.score()
} }
@ -61,15 +62,68 @@ pub fn simulate<S: Strategy>(opts: &GameOptions, strategy: &S, n_trials: u32) ->
} }
// dummy, terrible strategy // dummy, terrible strategy
#[allow(dead_code)]
pub struct AlwaysPlay; pub struct AlwaysPlay;
impl Strategy for AlwaysPlay { impl Strategy for AlwaysPlay {
type InternalState = (); type InternalState = ();
fn initialize(player: &Player, view: &GameStateView) -> () { fn initialize(_: &Player, _: &GameStateView) -> () {
() ()
} }
fn decide(_: &mut (), player: &Player, view: &GameStateView) -> TurnChoice { fn decide(_: &mut (), _: &Player, _: &GameStateView) -> TurnChoice {
TurnChoice::Play(0) TurnChoice::Play(0)
} }
fn update(_: &mut (), turn: &Turn, view: &GameStateView) { fn update(_: &mut (), _: &Turn, _: &GameStateView) {
}
}
// dummy, terrible strategy
#[allow(dead_code)]
pub struct AlwaysDiscard;
impl Strategy for AlwaysDiscard {
type InternalState = ();
fn initialize(_: &Player, _: &GameStateView) -> () {
()
}
fn decide(_: &mut (), _: &Player, _: &GameStateView) -> TurnChoice {
TurnChoice::Discard(0)
}
fn update(_: &mut (), _: &Turn, _: &GameStateView) {
}
}
// dummy, terrible strategy
#[allow(dead_code)]
pub struct RandomStrategy;
impl Strategy for RandomStrategy {
type InternalState = ();
fn initialize(_: &Player, _: &GameStateView) -> () {
()
}
fn decide(_: &mut (), _: &Player, view: &GameStateView) -> TurnChoice {
let p = rand::random::<f64>();
if p < 0.4 {
if view.board.hints_remaining > 0 {
let hinted = {
if rand::random() {
// hint a color
Hinted::Color(rand::thread_rng().choose(&COLORS).unwrap())
} else {
Hinted::Value(*rand::thread_rng().choose(&VALUES).unwrap())
}
};
TurnChoice::Hint(Hint {
player: 0,
hinted: hinted,
})
} else {
TurnChoice::Discard(0)
}
} else if p < 0.8 {
TurnChoice::Discard(0)
} else {
TurnChoice::Play(0)
}
}
fn update(_: &mut (), _: &Turn, _: &GameStateView) {
} }
} }