make color = char

This commit is contained in:
Jeff Wu 2016-03-31 10:17:22 -07:00
parent 58c881130a
commit ec1fd2eb07
5 changed files with 105 additions and 97 deletions

View file

@ -13,7 +13,7 @@ This strategy achieves the best results I am aware of for n > 2 (see below).
Please contact me if: Please contact me if:
- You know of other interesting/good strategy ideas! - You know of other interesting/good strategy ideas!
- Have questions about the framework - Have questions about the framework or existing strategies
Some similar projects I am aware of: Some similar projects I am aware of:
- https://github.com/rjtobin/HanSim (written for the paper mentioned above) - https://github.com/rjtobin/HanSim (written for the paper mentioned above)
@ -30,20 +30,33 @@ Usage: target/debug/rust_hanabi [options]
Options: Options:
-l, --loglevel LOGLEVEL -l, --loglevel LOGLEVEL
Log level, one of 'trace', 'debug', 'info', 'warn', and 'error' Log level, one of 'trace', 'debug', 'info', 'warn',
and 'error'
-n, --ntrials NTRIALS -n, --ntrials NTRIALS
Number of games to simulate Number of games to simulate (default 1)
-t, --nthreads NTHREADS -t, --nthreads NTHREADS
Number of threads to use for simulation Number of threads to use for simulation (default 1)
-s, --seed SEED Seed for PRNG -s, --seed SEED Seed for PRNG (default random)
-p, --nplayers NPLAYERS -p, --nplayers NPLAYERS
Number of players Number of players
-g, --strategy STRATEGY
Which strategy to use. One of 'random', 'cheat', and
'info'
-h, --help Print this help menu -h, --help Print this help menu
``` ```
For example, For example,
`cargo run -- -n 10000 -s 0 -t 2 -p 3` ```
cargo run -- -n 10000 -s 0 -t 2 -p 5 -g cheat
```
Or, if the simulation is slow (as the info strategy is),
```
cargo build --release
time ./target/release/rust_hanabi -n 10000 -s 0 -t 2 -p 5 -g info
```
## Results ## Results

View file

@ -1,19 +1,16 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt; use std::fmt;
pub type Color = &'static str; pub type Color = char;
pub const COLORS: [Color; 5] = ["red", "yellow", "green", "blue", "white"]; pub const COLORS: [Color; 5] = ['r', 'y', 'g', 'b', 'w'];
pub fn display_color(color: Color) -> char {
color.chars().next().unwrap()
}
pub type Value = u32; pub type Value = u32;
// list of values, assumed to be small to large // list of values, assumed to be small to large
pub const VALUES : [Value; 5] = [1, 2, 3, 4, 5]; pub const VALUES : [Value; 5] = [1, 2, 3, 4, 5];
pub const FINAL_VALUE : Value = 5; pub const FINAL_VALUE : Value = 5;
pub fn get_count_for_value(value: &Value) -> u32 { pub fn get_count_for_value(value: Value) -> u32 {
match *value { match value {
1 => 3, 1 => 3,
2 | 3 | 4 => 2, 2 | 3 | 4 => 2,
5 => 1, 5 => 1,
@ -33,7 +30,7 @@ impl Card {
} }
impl fmt::Display 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, "{}{}", display_color(self.color), self.value) write!(f, "{}{}", self.color, self.value)
} }
} }
@ -46,9 +43,9 @@ pub struct CardCounts {
impl CardCounts { impl CardCounts {
pub fn new() -> CardCounts { pub fn new() -> CardCounts {
let mut counts = HashMap::new(); let mut counts = HashMap::new();
for color in COLORS.iter() { for &color in COLORS.iter() {
for value in VALUES.iter() { for &value in VALUES.iter() {
counts.insert(Card::new(*color, *value), 0); counts.insert(Card::new(color, value), 0);
} }
} }
CardCounts { CardCounts {
@ -62,7 +59,7 @@ impl CardCounts {
pub fn remaining(&self, card: &Card) -> u32 { pub fn remaining(&self, card: &Card) -> u32 {
let count = self.get_count(card); let count = self.get_count(card);
get_count_for_value(&card.value) - count get_count_for_value(card.value) - count
} }
pub fn increment(&mut self, card: &Card) { pub fn increment(&mut self, card: &Card) {
@ -72,17 +69,17 @@ impl CardCounts {
} }
impl fmt::Display for CardCounts { impl fmt::Display for CardCounts {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for color in COLORS.iter() { for &color in COLORS.iter() {
try!(f.write_str(&format!( try!(f.write_str(&format!(
"{}: ", display_color(color), "{}: ", color,
))); )));
for value in VALUES.iter() { for &value in VALUES.iter() {
let count = self.get_count(&Card::new(color, *value)); let count = self.get_count(&Card::new(color, value));
let total = get_count_for_value(value); let total = get_count_for_value(value);
try!(f.write_str(&format!( try!(f.write_str(&format!(
"{}/{} {}s", count, total, value "{}/{} {}s", count, total, value
))); )));
if *value != FINAL_VALUE { if value != FINAL_VALUE {
try!(f.write_str(", ")); try!(f.write_str(", "));
} }
} }

View file

@ -116,16 +116,16 @@ impl PlayerState {
pub fn reveal(&mut self, hinted: &Hinted) -> Vec<bool> { pub fn reveal(&mut self, hinted: &Hinted) -> Vec<bool> {
match hinted { match hinted {
&Hinted::Color(ref color) => { &Hinted::Color(color) => {
self.hand_info_iter_mut().map(|(card, info)| { self.hand_info_iter_mut().map(|(card, info)| {
let matches = card.color == *color; let matches = card.color == color;
info.mark_color(color, matches); info.mark_color(color, matches);
matches matches
}).collect::<Vec<_>>() }).collect::<Vec<_>>()
} }
&Hinted::Value(ref value) => { &Hinted::Value(value) => {
self.hand_info_iter_mut().map(|(card, info)| { self.hand_info_iter_mut().map(|(card, info)| {
let matches = card.value == *value; let matches = card.value == value;
info.mark_value(value, matches); info.mark_value(value, matches);
matches matches
}).collect::<Vec<_>>() }).collect::<Vec<_>>()
@ -137,11 +137,11 @@ impl PlayerState {
fn new_deck(seed: u32) -> Cards { fn new_deck(seed: u32) -> Cards {
let mut deck: Cards = Cards::new(); let mut deck: Cards = Cards::new();
for color in COLORS.iter() { for &color in COLORS.iter() {
for value in VALUES.iter() { for &value in VALUES.iter() {
let count = get_count_for_value(value); let count = get_count_for_value(value);
for _ in 0..count { for _ in 0..count {
deck.push(Card::new(color, value.clone())); deck.push(Card::new(color, value));
} }
} }
}; };
@ -181,7 +181,7 @@ pub struct BoardState {
impl BoardState { impl BoardState {
pub fn new(opts: &GameOptions, seed: u32) -> BoardState { pub fn new(opts: &GameOptions, seed: u32) -> BoardState {
let mut fireworks : HashMap<Color, Firework> = HashMap::new(); let mut fireworks : HashMap<Color, Firework> = HashMap::new();
for color in COLORS.iter() { for &color in COLORS.iter() {
fireworks.insert(color, Firework::new(color)); fireworks.insert(color, Firework::new(color));
} }
let deck = new_deck(seed); let deck = new_deck(seed);
@ -213,34 +213,34 @@ impl BoardState {
} }
} }
pub fn get_firework(&self, color: &Color) -> &Firework { pub fn get_firework(&self, color: Color) -> &Firework {
self.fireworks.get(color).unwrap() self.fireworks.get(&color).unwrap()
} }
fn get_firework_mut(&mut self, color: &Color) -> &mut Firework { fn get_firework_mut(&mut self, color: Color) -> &mut Firework {
self.fireworks.get_mut(color).unwrap() self.fireworks.get_mut(&color).unwrap()
} }
// returns whether a card would place on a firework // returns whether a card would place on a firework
pub fn is_playable(&self, card: &Card) -> bool { pub fn is_playable(&self, card: &Card) -> bool {
Some(card.value) == self.get_firework(&card.color).desired_value() Some(card.value) == self.get_firework(card.color).desired_value()
} }
// best possible value we can get for firework of that color, // best possible value we can get for firework of that color,
// based on looking at discard + fireworks // based on looking at discard + fireworks
fn highest_attainable(&self, color: &Color) -> Value { fn highest_attainable(&self, color: Color) -> Value {
let firework = self.fireworks.get(color).unwrap(); let firework = self.fireworks.get(&color).unwrap();
if firework.complete() { if firework.complete() {
return FINAL_VALUE; return FINAL_VALUE;
} }
let desired = firework.desired_value().unwrap(); let desired = firework.desired_value().unwrap();
for value in VALUES.iter() { for &value in VALUES.iter() {
if *value < desired { if value < desired {
// already have these cards // already have these cards
continue continue
} }
let needed_card = Card::new(color, value.clone()); let needed_card = Card::new(color, value);
if self.discard.has_all(&needed_card) { if self.discard.has_all(&needed_card) {
// already discarded all of these // already discarded all of these
return value - 1; return value - 1;
@ -251,7 +251,7 @@ impl BoardState {
// is never going to play, based on discard + fireworks // is never going to play, based on discard + fireworks
pub fn is_dead(&self, card: &Card) -> bool { pub fn is_dead(&self, card: &Card) -> bool {
let firework = self.fireworks.get(card.color).unwrap(); let firework = self.fireworks.get(&card.color).unwrap();
if firework.complete() { if firework.complete() {
true true
} else { } else {
@ -259,14 +259,14 @@ impl BoardState {
if card.value < desired { if card.value < desired {
true true
} else { } else {
card.value > self.highest_attainable(&card.color) card.value > self.highest_attainable(card.color)
} }
} }
} }
// can be discarded without necessarily sacrificing score, based on discard + fireworks // can be discarded without necessarily sacrificing score, based on discard + fireworks
pub fn is_dispensable(&self, card: &Card) -> bool { pub fn is_dispensable(&self, card: &Card) -> bool {
let firework = self.fireworks.get(card.color).unwrap(); let firework = self.fireworks.get(&card.color).unwrap();
if firework.complete() { if firework.complete() {
true true
} else { } else {
@ -274,7 +274,7 @@ impl BoardState {
if card.value < desired { if card.value < desired {
true true
} else { } else {
if card.value > self.highest_attainable(&card.color) { if card.value > self.highest_attainable(card.color) {
true true
} else { } else {
self.discard.remaining(&card) != 1 self.discard.remaining(&card) != 1
@ -342,7 +342,7 @@ impl fmt::Display for BoardState {
"{}/{} lives remaining\n", self.lives_remaining, self.lives_total "{}/{} lives remaining\n", self.lives_remaining, self.lives_total
))); )));
try!(f.write_str("Fireworks:\n")); try!(f.write_str("Fireworks:\n"));
for color in COLORS.iter() { for &color in COLORS.iter() {
try!(f.write_str(&format!(" {}\n", self.get_firework(color)))); try!(f.write_str(&format!(" {}\n", self.get_firework(color))));
} }
try!(f.write_str("Discard:\n")); try!(f.write_str("Discard:\n"));
@ -597,7 +597,7 @@ impl GameState {
let playable = self.board.is_playable(&card); let playable = self.board.is_playable(&card);
if playable { if playable {
{ {
let firework = self.board.get_firework_mut(&card.color); let firework = self.board.get_firework_mut(card.color);
debug!("Successfully played {}!", card); debug!("Successfully played {}!", card);
firework.place(&card); firework.place(&card);
} }

View file

@ -9,20 +9,11 @@ use game::BoardState;
// trait representing information about a card // trait representing information about a card
pub trait CardInfo { pub trait CardInfo {
// get all a-priori possibilities
fn get_all_possibilities(&self) -> Vec<Card> {
let mut v = Vec::new();
for &color in COLORS.iter() {
for &value in VALUES.iter() {
v.push(Card::new(color, value));
}
}
v
}
// whether the card is possible // whether the card is possible
fn is_possible(&self, card: &Card) -> bool; fn is_possible(&self, card: &Card) -> bool;
// mark all current possibilities for the card // mark all current possibilities for the card
// this should generally be overridden, for efficiency
fn get_possibilities(&self) -> Vec<Card> { fn get_possibilities(&self) -> Vec<Card> {
let mut v = Vec::new(); let mut v = Vec::new();
for &color in COLORS.iter() { for &color in COLORS.iter() {
@ -89,16 +80,16 @@ pub trait CardInfo {
} }
// mark a whole color as false // mark a whole color as false
fn mark_color_false(&mut self, color: &Color); fn mark_color_false(&mut self, color: Color);
// mark a color as correct // mark a color as correct
fn mark_color_true(&mut self, color: &Color) { fn mark_color_true(&mut self, color: Color) {
for other_color in COLORS.iter() { for &other_color in COLORS.iter() {
if other_color != color { if other_color != color {
self.mark_color_false(other_color); self.mark_color_false(other_color);
} }
} }
} }
fn mark_color(&mut self, color: &Color, is_color: bool) { fn mark_color(&mut self, color: Color, is_color: bool) {
if is_color { if is_color {
self.mark_color_true(color); self.mark_color_true(color);
} else { } else {
@ -107,16 +98,16 @@ pub trait CardInfo {
} }
// mark a whole value as false // mark a whole value as false
fn mark_value_false(&mut self, value: &Value); fn mark_value_false(&mut self, value: Value);
// mark a value as correct // mark a value as correct
fn mark_value_true(&mut self, value: &Value) { fn mark_value_true(&mut self, value: Value) {
for other_value in VALUES.iter() { for &other_value in VALUES.iter() {
if other_value != value { if other_value != value {
self.mark_value_false(other_value); self.mark_value_false(other_value);
} }
} }
} }
fn mark_value(&mut self, value: &Value, is_value: bool) { fn mark_value(&mut self, value: Value, is_value: bool) {
if is_value { if is_value {
self.mark_value_true(value); self.mark_value_true(value);
} else { } else {
@ -127,7 +118,7 @@ pub trait CardInfo {
// Represents hinted 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 { pub trait Info<T> where T: Hash + Eq + Clone + Copy {
// get all a-priori possibilities // get all a-priori possibilities
fn get_all_possibilities() -> Vec<T>; fn get_all_possibilities() -> Vec<T>;
@ -141,8 +132,8 @@ pub trait Info<T> where T: Hash + Eq + Clone {
self.get_possibility_set().iter().map(|t| t.clone()).collect::<Vec<T>>() self.get_possibility_set().iter().map(|t| t.clone()).collect::<Vec<T>>()
} }
fn is_possible(&self, value: &T) -> bool { fn is_possible(&self, value: T) -> bool {
self.get_possibility_set().contains(value) self.get_possibility_set().contains(&value)
} }
fn initialize() -> HashSet<T> { fn initialize() -> HashSet<T> {
@ -153,17 +144,17 @@ pub trait Info<T> where T: Hash + Eq + Clone {
possible_map possible_map
} }
fn mark_true(&mut self, value: &T) { fn mark_true(&mut self, value: T) {
let possible = self.get_mut_possibility_set(); let possible = self.get_mut_possibility_set();
possible.clear(); possible.clear();
possible.insert(value.clone()); possible.insert(value.clone());
} }
fn mark_false(&mut self, value: &T) { fn mark_false(&mut self, value: T) {
self.get_mut_possibility_set().remove(value); self.get_mut_possibility_set().remove(&value);
} }
fn mark(&mut self, value: &T, info: bool) { fn mark(&mut self, value: T, info: bool) {
if info { if info {
self.mark_true(value); self.mark_true(value);
} else { } else {
@ -220,30 +211,30 @@ impl CardInfo for SimpleCardInfo {
v v
} }
fn is_possible(&self, card: &Card) -> bool { fn is_possible(&self, card: &Card) -> bool {
self.color_info.is_possible(&card.color) && self.color_info.is_possible(card.color) &&
self.value_info.is_possible(&card.value) self.value_info.is_possible(card.value)
} }
fn mark_color_false(&mut self, color: &Color) { fn mark_color_false(&mut self, color: Color) {
self.color_info.mark_false(color); self.color_info.mark_false(color);
} }
fn mark_value_false(&mut self, value: &Value) { fn mark_value_false(&mut self, value: Value) {
self.value_info.mark_false(value); self.value_info.mark_false(value);
} }
} }
impl fmt::Display for SimpleCardInfo { impl fmt::Display for SimpleCardInfo {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut string = String::new(); let mut string = String::new();
for color in &COLORS { for &color in &COLORS {
if self.color_info.is_possible(color) { if self.color_info.is_possible(color) {
string.push(display_color(color)); string.push(color);
} }
} }
// while string.len() < COLORS.len() + 1 { // while string.len() < COLORS.len() + 1 {
string.push(' '); string.push(' ');
//} //}
for value in &VALUES { for &value in &VALUES {
if self.value_info.is_possible(value) { if self.value_info.is_possible(value) {
string.push_str(&format!("{}", value)); string.push_str(&format!("{}", value));
} }
@ -346,15 +337,15 @@ impl CardInfo for CardPossibilityTable {
cards.sort(); cards.sort();
cards cards
} }
fn mark_color_false(&mut self, color: &Color) { fn mark_color_false(&mut self, color: Color) {
for &value in VALUES.iter() { for &value in VALUES.iter() {
self.mark_false(&Card::new(color, value)); self.mark_false(&Card::new(color, value));
} }
} }
fn mark_value_false(&mut self, value: &Value) { fn mark_value_false(&mut self, value: Value) {
for &color in COLORS.iter() { for &color in COLORS.iter() {
self.mark_false(&Card::new(color, value.clone())); self.mark_false(&Card::new(color, value));
} }
} }
fn get_weight(&self, card: &Card) -> f32 { fn get_weight(&self, card: &Card) -> f32 {

View file

@ -27,7 +27,7 @@ impl ModulusInformation {
self.modulus = self.modulus * other.modulus; self.modulus = self.modulus * other.modulus;
} }
pub fn emit(&mut self, modulus: u32) -> Self { pub fn split(&mut self, modulus: u32) -> Self {
assert!(self.modulus >= modulus); assert!(self.modulus >= modulus);
assert!(self.modulus % modulus == 0); assert!(self.modulus % modulus == 0);
let original_modulus = self.modulus; let original_modulus = self.modulus;
@ -68,16 +68,17 @@ trait Question {
fn info_amount(&self) -> u32; fn info_amount(&self) -> u32;
// get the answer to this question, given cards // get the answer to this question, given cards
fn answer(&self, &Cards, &OwnedGameView) -> u32; fn answer(&self, &Cards, &OwnedGameView) -> u32;
// process the answer to this question, updating card info
fn acknowledge_answer(
&self, value: u32, &mut Vec<CardPossibilityTable>, &OwnedGameView
);
fn answer_info(&self, hand: &Cards, view: &OwnedGameView) -> ModulusInformation { fn answer_info(&self, hand: &Cards, view: &OwnedGameView) -> ModulusInformation {
ModulusInformation::new( ModulusInformation::new(
self.info_amount(), self.info_amount(),
self.answer(hand, view) self.answer(hand, view)
) )
} }
// process the answer to this question, updating card info
fn acknowledge_answer(
&self, value: u32, &mut Vec<CardPossibilityTable>, &OwnedGameView
);
fn acknowledge_answer_info( fn acknowledge_answer_info(
&self, &self,
@ -353,7 +354,7 @@ impl InformationPlayerStrategy {
{ {
let view = &self.last_view; let view = &self.last_view;
for question in questions { for question in questions {
let answer_info = hint.emit(question.info_amount()); let answer_info = hint.split(question.info_amount());
question.acknowledge_answer_info(answer_info, &mut hand_info, view); question.acknowledge_answer_info(answer_info, &mut hand_info, view);
} }
} }
@ -487,12 +488,12 @@ impl InformationPlayerStrategy {
let mut info = self.get_player_public_info_mut(&hint.player); let mut info = self.get_player_public_info_mut(&hint.player);
let zip_iter = info.iter_mut().zip(matches); let zip_iter = info.iter_mut().zip(matches);
match hint.hinted { match hint.hinted {
Hinted::Color(ref color) => { Hinted::Color(color) => {
for (card_info, matched) in zip_iter { for (card_info, matched) in zip_iter {
card_info.mark_color(color, *matched); card_info.mark_color(color, *matched);
} }
} }
Hinted::Value(ref value) => { Hinted::Value(value) => {
for (card_info, matched) in zip_iter { for (card_info, matched) in zip_iter {
card_info.mark_value(value, *matched); card_info.mark_value(value, *matched);
} }
@ -520,6 +521,9 @@ impl InformationPlayerStrategy {
} }
} }
// TODO: decrement weight counts for fully determined cards,
// ahead of time
// note: other_player could be player, as well // note: other_player could be player, as well
// in particular, we will decrement the newly drawn card // in particular, we will decrement the newly drawn card
for other_player in view.board.get_players() { for other_player in view.board.get_players() {
@ -574,6 +578,16 @@ impl InformationPlayerStrategy {
fn get_hint(&self) -> TurnChoice { fn get_hint(&self) -> TurnChoice {
let view = &self.last_view; let view = &self.last_view;
// Can give up to 3(n-1) hints
// For any given player with at least 4 cards, and index i, there are at least 3 hints that can be given.
// 0. a value hint on card i
// 1. a color hint on card i
// 2. any hint not involving card i
// TODO: make it so space of hints is larger when there is
// knowledge about the cards?
let total_info = 3 * (view.board.num_players - 1); let total_info = 3 * (view.board.num_players - 1);
let hint_info = self.get_hint_sum_info(total_info, view); let hint_info = self.get_hint_sum_info(total_info, view);
@ -587,13 +601,6 @@ impl InformationPlayerStrategy {
let card_index = self.get_index_for_hint(self.get_player_public_info(&hint_player), view); let card_index = self.get_index_for_hint(self.get_player_public_info(&hint_player), view);
let hint_card = &hand[card_index]; let hint_card = &hand[card_index];
// For any given player with at least 4 cards, and index i, there are at least 3 hints that can be given.
// 0. a value hint on card i
// 1. a color hint on card i
// 2. any hint not involving card i
//
// for 4 players, can give 6 distinct hints
let hinted = match hint_type { let hinted = match hint_type {
0 => { 0 => {