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

View file

@ -5,7 +5,7 @@ use std::hash::Hash;
use game::*;
// 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
fn get_possibilities() -> Vec<T>;
@ -42,6 +42,14 @@ trait Info<T> where T: Hash + Eq + Clone {
fn mark_false(&mut self, value: &T) {
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)]

View file

@ -10,9 +10,7 @@ mod info;
struct SimpleLogger;
impl log::Log for SimpleLogger {
fn enabled(&self, metadata: &log::LogMetadata) -> bool {
// metadata.level() <= log::LogLevel::Warn
metadata.level() <= log::LogLevel::Info
// metadata.level() <= log::LogLevel::Debug
true
}
fn log(&self, record: &log::LogRecord) {
@ -24,6 +22,7 @@ impl log::Log for SimpleLogger {
fn main() {
log::set_logger(|max_log_level| {
// Trace, Debug, Info, Warn, ...
max_log_level.set(log::LogLevelFilter::Info);
Box::new(SimpleLogger)
});
@ -34,5 +33,8 @@ fn main() {
num_hints: 8,
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 std::collections::HashMap;
use rand::{self, Rng};
// Trait to implement for any valid Hanabi strategy
// State management is done by the simulator, to avoid cheating
@ -10,7 +11,7 @@ pub trait Strategy {
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 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() {
debug!("Turn {}", game.board.turn);
let player = game.board.player;
let choice = {
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);
info!("Player {:?} decided to {:?}", player, choice);
let turn = Turn {
player: &player,
choice: &choice,
@ -43,7 +44,7 @@ pub fn simulate_once<S: Strategy>(opts: &GameOptions, strategy: &S) -> Score {
}
// TODO: do some stuff
info!("State: {:?}", game);
debug!("State: {:?}", game);
}
game.score()
}
@ -61,15 +62,68 @@ pub fn simulate<S: Strategy>(opts: &GameOptions, strategy: &S, n_trials: u32) ->
}
// dummy, terrible strategy
#[allow(dead_code)]
pub struct AlwaysPlay;
impl Strategy for AlwaysPlay {
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)
}
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) {
}
}