choose index dynamically, use OwnedGameView where possible
This commit is contained in:
parent
21e2d05e93
commit
0aad4dfa1c
@ -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 |
|
||||||
|
|
||||||
|
14
src/info.rs
14
src/info.rs
@ -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 {
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -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 {:?}",
|
||||||
|
Loading…
Reference in New Issue
Block a user