choose index dynamically, use OwnedGameView where possible

This commit is contained in:
Jeff Wu 2016-03-30 10:24:21 -07:00
parent 21e2d05e93
commit 0aad4dfa1c
4 changed files with 86 additions and 44 deletions

View File

@ -9,7 +9,7 @@ It also explores some implementations, based on ideas from
[this paper](https://d0474d97-a-62cb3a1a-s-sites.googlegroups.com/site/rmgpgrwc/research-papers/Hanabi_final.pdf). [this paper](https://d0474d97-a-62cb3a1a-s-sites.googlegroups.com/site/rmgpgrwc/research-papers/Hanabi_final.pdf).
In particular, it contains a variant of their "information strategy", with some improvements. In particular, it contains a variant of their "information strategy", with some improvements.
This strategy achieves the best results I am aware of (see below), for n > 3. 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!
@ -53,5 +53,5 @@ Currently, on seeds 0-9999, we have:
| 2p | 3p | 4p | 5p | | 2p | 3p | 4p | 5p |
----------|---------|---------|---------|---------| ----------|---------|---------|---------|---------|
cheating | 24.8600 | 24.9781 | 24.9715 | 24.9583 | cheating | 24.8600 | 24.9781 | 24.9715 | 24.9583 |
info | 17.147 | 23.357 | 24.76 | 24.824 | info | 17.249 | 23.394 | 24.762 | 24.835 |

View File

@ -305,6 +305,20 @@ impl CardPossibilityTable {
pub fn is_determined(&self) -> bool { pub fn is_determined(&self) -> bool {
self.get_possibilities().len() == 1 self.get_possibilities().len() == 1
} }
pub fn color_determined(&self) -> bool {
self.get_possibilities()
.iter().map(|card| card.color)
.collect::<HashSet<_>>()
.len() == 1
}
pub fn value_determined(&self) -> bool {
self.get_possibilities()
.iter().map(|card| card.value)
.collect::<HashSet<_>>()
.len() == 1
}
} }
impl <'a> From<&'a CardCounts> for CardPossibilityTable { impl <'a> From<&'a CardCounts> for CardPossibilityTable {
fn from(counts: &'a CardCounts) -> CardPossibilityTable { fn from(counts: &'a CardCounts) -> CardPossibilityTable {

View File

@ -33,6 +33,7 @@ impl log::Log for SimpleLogger {
fn print_usage(program: &str, opts: Options) { fn print_usage(program: &str, opts: Options) {
print!("{}", opts.usage(&format!("Usage: {} [options]", program))); print!("{}", opts.usage(&format!("Usage: {} [options]", program)));
// for p in $(seq 5 2); do time cargo run -- -n 1000 -s 0 -t 2 -p $p -g info; done
} }

View File

@ -82,8 +82,8 @@ trait Question {
// how much info does this question ask for? // how much info does this question ask for?
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, Box<&GameView>) -> u32; fn answer(&self, &Cards, &OwnedGameView) -> u32;
fn answer_info(&self, hand: &Cards, view: Box<&GameView>) -> 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)
@ -91,14 +91,14 @@ trait Question {
} }
// process the answer to this question, updating card info // process the answer to this question, updating card info
fn acknowledge_answer( fn acknowledge_answer(
&self, value: u32, &mut Vec<CardPossibilityTable>, Box<&GameView> &self, value: u32, &mut Vec<CardPossibilityTable>, &OwnedGameView
); );
fn acknowledge_answer_info( fn acknowledge_answer_info(
&self, &self,
answer: ModulusInformation, answer: ModulusInformation,
hand_info: &mut Vec<CardPossibilityTable>, hand_info: &mut Vec<CardPossibilityTable>,
view: Box<&GameView> view: &OwnedGameView
) { ) {
assert!(self.info_amount() == answer.modulus); assert!(self.info_amount() == answer.modulus);
self.acknowledge_answer(answer.value, hand_info, view); self.acknowledge_answer(answer.value, hand_info, view);
@ -109,7 +109,7 @@ struct IsPlayable {
} }
impl Question for IsPlayable { impl Question for IsPlayable {
fn info_amount(&self) -> u32 { 2 } fn info_amount(&self) -> u32 { 2 }
fn answer(&self, hand: &Cards, view: Box<&GameView>) -> u32 { fn answer(&self, hand: &Cards, view: &OwnedGameView) -> u32 {
let ref card = hand[self.index]; let ref card = hand[self.index];
if view.get_board().is_playable(card) { 1 } else { 0 } if view.get_board().is_playable(card) { 1 } else { 0 }
} }
@ -117,7 +117,7 @@ impl Question for IsPlayable {
&self, &self,
answer: u32, answer: u32,
hand_info: &mut Vec<CardPossibilityTable>, hand_info: &mut Vec<CardPossibilityTable>,
view: Box<&GameView>, view: &OwnedGameView,
) { ) {
let ref mut card_table = hand_info[self.index]; let ref mut card_table = hand_info[self.index];
let possible = card_table.get_possibilities(); let possible = card_table.get_possibilities();
@ -136,7 +136,7 @@ struct IsDead {
} }
impl Question for IsDead { impl Question for IsDead {
fn info_amount(&self) -> u32 { 2 } fn info_amount(&self) -> u32 { 2 }
fn answer(&self, hand: &Cards, view: Box<&GameView>) -> u32 { fn answer(&self, hand: &Cards, view: &OwnedGameView) -> u32 {
let ref card = hand[self.index]; let ref card = hand[self.index];
if view.get_board().is_dead(card) { 1 } else { 0 } if view.get_board().is_dead(card) { 1 } else { 0 }
} }
@ -144,7 +144,7 @@ impl Question for IsDead {
&self, &self,
answer: u32, answer: u32,
hand_info: &mut Vec<CardPossibilityTable>, hand_info: &mut Vec<CardPossibilityTable>,
view: Box<&GameView>, view: &OwnedGameView,
) { ) {
let ref mut card_table = hand_info[self.index]; let ref mut card_table = hand_info[self.index];
let possible = card_table.get_possibilities(); let possible = card_table.get_possibilities();
@ -207,7 +207,7 @@ impl CardPossibilityPartition {
} }
impl Question for CardPossibilityPartition { impl Question for CardPossibilityPartition {
fn info_amount(&self) -> u32 { self.n_partitions } fn info_amount(&self) -> u32 { self.n_partitions }
fn answer(&self, hand: &Cards, _: Box<&GameView>) -> u32 { fn answer(&self, hand: &Cards, _: &OwnedGameView) -> u32 {
let ref card = hand[self.index]; let ref card = hand[self.index];
*self.partition.get(&card).unwrap() *self.partition.get(&card).unwrap()
} }
@ -215,7 +215,7 @@ impl Question for CardPossibilityPartition {
&self, &self,
answer: u32, answer: u32,
hand_info: &mut Vec<CardPossibilityTable>, hand_info: &mut Vec<CardPossibilityTable>,
_: Box<&GameView>, _: &OwnedGameView,
) { ) {
let ref mut card_table = hand_info[self.index]; let ref mut card_table = hand_info[self.index];
let possible = card_table.get_possibilities(); let possible = card_table.get_possibilities();
@ -335,23 +335,20 @@ impl InformationPlayerStrategy {
return questions return questions
} }
fn answer_questions<T>( fn answer_questions(
questions: &Vec<Box<Question>>, hand: &Cards, view: &T questions: &Vec<Box<Question>>, hand: &Cards, view: &OwnedGameView
) -> ModulusInformation ) -> ModulusInformation {
where T: GameView
{
let mut info = ModulusInformation::none(); let mut info = ModulusInformation::none();
for question in questions { for question in questions {
let answer_info = question.answer_info(hand, Box::new(view as &GameView)); let answer_info = question.answer_info(hand, view);
info.combine(answer_info); info.combine(answer_info);
} }
info info
} }
fn get_hint_info_for_player<T>( fn get_hint_info_for_player(
&self, player: &Player, total_info: u32, view: &T &self, player: &Player, total_info: u32, view: &OwnedGameView
) -> ModulusInformation where T: GameView ) -> ModulusInformation {
{
assert!(player != &self.me); assert!(player != &self.me);
let hand_info = self.get_player_public_info(player); let hand_info = self.get_player_public_info(player);
let questions = Self::get_questions(total_info, view, hand_info); let questions = Self::get_questions(total_info, view, hand_info);
@ -362,9 +359,7 @@ impl InformationPlayerStrategy {
answer answer
} }
fn get_hint_sum_info<T>(&self, total_info: u32, view: &T) -> ModulusInformation fn get_hint_sum_info(&self, total_info: u32, view: &OwnedGameView) -> ModulusInformation {
where T: GameView
{
let mut sum = ModulusInformation::new(total_info, 0); let mut sum = ModulusInformation::new(total_info, 0);
for player in view.get_board().get_players() { for player in view.get_board().get_players() {
if player != self.me { if player != self.me {
@ -396,7 +391,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.emit(question.info_amount());
question.acknowledge_answer_info(answer_info, &mut hand_info, Box::new(view as &GameView)); question.acknowledge_answer_info(answer_info, &mut hand_info, view);
} }
} }
debug!("Current state of hand_info for {}:", me); debug!("Current state of hand_info for {}:", me);
@ -431,8 +426,8 @@ impl InformationPlayerStrategy {
let hand = view.get_hand(&player); let hand = view.get_hand(&player);
let questions = Self::get_questions(hint.modulus, view, &mut hand_info); let questions = Self::get_questions(hint.modulus, view, &mut hand_info);
for question in questions { for question in questions {
let answer = question.answer(hand, Box::new(view as &GameView)); let answer = question.answer(hand, view);
question.acknowledge_answer(answer, &mut hand_info, Box::new(view as &GameView)); question.acknowledge_answer(answer, &mut hand_info, view);
} }
} }
self.return_public_info(&player, hand_info); self.return_public_info(&player, hand_info);
@ -446,14 +441,14 @@ impl InformationPlayerStrategy {
} }
// how badly do we need to play a particular card // how badly do we need to play a particular card
fn get_average_play_score(&self, view: &BorrowedGameView, card_table: &CardPossibilityTable) -> f32 { fn get_average_play_score(&self, view: &OwnedGameView, card_table: &CardPossibilityTable) -> f32 {
let f = |card: &Card| { let f = |card: &Card| {
self.get_play_score(view, card) as f32 self.get_play_score(view, card) as f32
}; };
card_table.weighted_score(&f) card_table.weighted_score(&f)
} }
fn get_play_score(&self, view: &BorrowedGameView, card: &Card) -> i32 { fn get_play_score(&self, view: &OwnedGameView, card: &Card) -> i32 {
if view.board.deck_size() > 0 { if view.board.deck_size() > 0 {
for player in view.board.get_players() { for player in view.board.get_players() {
if player != self.me { if player != self.me {
@ -492,7 +487,7 @@ impl InformationPlayerStrategy {
return useless_vec; return useless_vec;
} }
fn someone_else_can_play(&self, view: &BorrowedGameView) -> bool { fn someone_else_can_play(&self, view: &OwnedGameView) -> bool {
for player in view.board.get_players() { for player in view.board.get_players() {
if player != self.me { if player != self.me {
for card in view.get_hand(&player) { for card in view.get_hand(&player) {
@ -574,7 +569,7 @@ impl InformationPlayerStrategy {
self.public_counts.increment(card); self.public_counts.increment(card);
} }
fn get_private_info(&self, view: &BorrowedGameView) -> Vec<CardPossibilityTable> { fn get_private_info(&self, view: &OwnedGameView) -> Vec<CardPossibilityTable> {
let mut info = self.get_my_public_info().clone(); let mut info = self.get_my_public_info().clone();
for card_table in info.iter_mut() { for card_table in info.iter_mut() {
for (_, state) in &view.other_player_states { for (_, state) in &view.other_player_states {
@ -586,7 +581,35 @@ impl InformationPlayerStrategy {
info info
} }
fn get_hint(&self, view: &BorrowedGameView) -> TurnChoice { fn get_hint_index_score<T>(&self, card_table: &CardPossibilityTable, view: &T) -> i32
where T: GameView
{
if card_table.probability_is_dead(view.get_board()) == 1.0 {
return 0;
}
let mut score = -1;
if !card_table.color_determined() {
score -= 1;
}
if !card_table.value_determined() {
score -= 1;
}
return score;
}
fn get_index_for_hint<T>(&self, info: &Vec<CardPossibilityTable>, view: &T) -> usize
where T: GameView
{
let mut scores = info.iter().enumerate().map(|(i, card_table)| {
let score = self.get_hint_index_score(card_table, view);
(score, i)
}).collect::<Vec<_>>();
scores.sort();
scores[0].1
}
fn get_hint(&self) -> TurnChoice {
let view = &self.last_view;
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);
@ -597,7 +620,7 @@ impl InformationPlayerStrategy {
let hint_player = (self.me + 1 + player_amt) % view.board.num_players; let hint_player = (self.me + 1 + player_amt) % view.board.num_players;
let hand = view.get_hand(&hint_player); let hand = view.get_hand(&hint_player);
let card_index = hand.len() -1; 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];
let hinted = match hint_type { let hinted = match hint_type {
@ -637,13 +660,14 @@ impl InformationPlayerStrategy {
}) })
} }
fn infer_from_hint(&mut self, view: &BorrowedGameView, hint: &Hint, result: &Vec<bool>) { fn infer_from_hint(&mut self, hint: &Hint, result: &Vec<bool>) {
let total_info = 3 * (view.board.num_players - 1); let n = self.last_view.board.num_players;
let total_info = 3 * (n - 1);
let hinter = self.last_view.board.player; let hinter = self.last_view.board.player;
let player_amt = (view.board.num_players + hint.player - hinter - 1) % view.board.num_players; let player_amt = (n + hint.player - hinter - 1) % n;
let card_index = result.len() - 1; let card_index = self.get_index_for_hint(self.get_player_public_info(&hint.player), &self.last_view);
let hint_type = if result[card_index] { let hint_type = if result[card_index] {
match hint.hinted { match hint.hinted {
Hinted::Value(_) => 0, Hinted::Value(_) => 0,
@ -662,7 +686,10 @@ impl InformationPlayerStrategy {
} }
impl PlayerStrategy for InformationPlayerStrategy { impl PlayerStrategy for InformationPlayerStrategy {
fn decide(&mut self, view: &BorrowedGameView) -> TurnChoice { fn decide(&mut self, _: &BorrowedGameView) -> TurnChoice {
// we already stored the view
let view = &self.last_view;
let private_info = self.get_private_info(view); let private_info = self.get_private_info(view);
// debug!("My info:"); // debug!("My info:");
// for (i, card_table) in private_info.iter().enumerate() { // for (i, card_table) in private_info.iter().enumerate() {
@ -670,7 +697,7 @@ impl PlayerStrategy for InformationPlayerStrategy {
// } // }
let playable_cards = private_info.iter().enumerate().filter(|&(_, card_table)| { let playable_cards = private_info.iter().enumerate().filter(|&(_, card_table)| {
card_table.probability_is_playable(view.board) == 1.0 card_table.probability_is_playable(&view.board) == 1.0
}).collect::<Vec<_>>(); }).collect::<Vec<_>>();
if playable_cards.len() > 0 { if playable_cards.len() > 0 {
@ -699,7 +726,7 @@ impl PlayerStrategy for InformationPlayerStrategy {
view.board.is_playable(card) || view.board.is_dead(card) view.board.is_playable(card) || view.board.is_dead(card)
}) == 1.0 }) == 1.0
}).map(|(i, card_table)| { }).map(|(i, card_table)| {
let p = card_table.probability_is_playable(view.board); let p = card_table.probability_is_playable(&view.board);
(i, card_table, p) (i, card_table, p)
}).collect::<Vec<_>>(); }).collect::<Vec<_>>();
@ -737,7 +764,7 @@ impl PlayerStrategy for InformationPlayerStrategy {
// (probably because it stalls the deck-drawing). // (probably because it stalls the deck-drawing).
if view.board.hints_remaining > 0 { if view.board.hints_remaining > 0 {
if self.someone_else_can_play(view) { if self.someone_else_can_play(view) {
return self.get_hint(view); return self.get_hint();
} else { } else {
// print!("This actually happens"); // print!("This actually happens");
} }
@ -763,7 +790,7 @@ impl PlayerStrategy for InformationPlayerStrategy {
}); });
let my_compval = let my_compval =
20.0 * probability_is_seen 20.0 * probability_is_seen
+ 10.0 * card_table.probability_is_dispensable(view.board) + 10.0 * card_table.probability_is_dispensable(&view.board)
+ card_table.average_value(); + card_table.average_value();
if my_compval > compval { if my_compval > compval {
@ -778,7 +805,7 @@ impl PlayerStrategy for InformationPlayerStrategy {
match turn.choice { match turn.choice {
TurnChoice::Hint(ref hint) => { TurnChoice::Hint(ref hint) => {
if let &TurnResult::Hint(ref matches) = &turn.result { if let &TurnResult::Hint(ref matches) = &turn.result {
self.infer_from_hint(view, hint, matches); self.infer_from_hint(hint, matches);
self.update_public_info_for_hint(hint, matches); self.update_public_info_for_hint(hint, matches);
} else { } else {
panic!("Got turn choice {:?}, but turn result {:?}", panic!("Got turn choice {:?}, but turn result {:?}",