make color = char
This commit is contained in:
parent
58c881130a
commit
ec1fd2eb07
5 changed files with 105 additions and 97 deletions
25
README.md
25
README.md
|
@ -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
|
||||
|
||||
|
|
31
src/cards.rs
31
src/cards.rs
|
@ -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(", "));
|
||||
}
|
||||
}
|
||||
|
|
48
src/game.rs
48
src/game.rs
|
@ -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);
|
||||
}
|
||||
|
|
61
src/info.rs
61
src/info.rs
|
@ -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 {
|
||||
|
|
|
@ -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 => {
|
||||
|
|
Loading…
Reference in a new issue