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 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
|
||||||
);
|
);
|
||||||
|
|
10
src/info.rs
10
src/info.rs
|
@ -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)]
|
||||||
|
|
10
src/main.rs
10
src/main.rs
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue