hints and better logging
This commit is contained in:
parent
a44b017eae
commit
49627b91b6
4 changed files with 172 additions and 40 deletions
126
src/game.rs
126
src/game.rs
|
@ -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
|
||||
);
|
||||
|
|
10
src/info.rs
10
src/info.rs
|
@ -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)]
|
||||
|
|
10
src/main.rs
10
src/main.rs
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue