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:
- 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:
- https://github.com/rjtobin/HanSim (written for the paper mentioned above)
@ -30,20 +30,33 @@ Usage: target/debug/rust_hanabi [options]
Options:
-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
Number of games to simulate
Number of games to simulate (default 1)
-t, --nthreads NTHREADS
Number of threads to use for simulation
-s, --seed SEED Seed for PRNG
Number of threads to use for simulation (default 1)
-s, --seed SEED Seed for PRNG (default random)
-p, --nplayers NPLAYERS
Number of players
-g, --strategy STRATEGY
Which strategy to use. One of 'random', 'cheat', and
'info'
-h, --help Print this help menu
```
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

View file

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

View file

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

View file

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

View file

@ -27,7 +27,7 @@ impl ModulusInformation {
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 == 0);
let original_modulus = self.modulus;
@ -68,16 +68,17 @@ trait Question {
fn info_amount(&self) -> u32;
// get the answer to this question, given cards
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 {
ModulusInformation::new(
self.info_amount(),
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(
&self,
@ -353,7 +354,7 @@ impl InformationPlayerStrategy {
{
let view = &self.last_view;
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);
}
}
@ -487,12 +488,12 @@ impl InformationPlayerStrategy {
let mut info = self.get_player_public_info_mut(&hint.player);
let zip_iter = info.iter_mut().zip(matches);
match hint.hinted {
Hinted::Color(ref color) => {
Hinted::Color(color) => {
for (card_info, matched) in zip_iter {
card_info.mark_color(color, *matched);
}
}
Hinted::Value(ref value) => {
Hinted::Value(value) => {
for (card_info, matched) in zip_iter {
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
// in particular, we will decrement the newly drawn card
for other_player in view.board.get_players() {
@ -574,6 +578,16 @@ impl InformationPlayerStrategy {
fn get_hint(&self) -> TurnChoice {
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 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 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 {
0 => {