make risky plays

This commit is contained in:
Jeff Wu 2016-03-30 00:00:01 -07:00
parent 3d318340eb
commit 9494d549ae
5 changed files with 91 additions and 80 deletions

View file

@ -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 | 14.676 | 22.19 | 24.516 | 24.68 | info | 14.981 | 22.526 | 24.516 | 24.742 |

View file

@ -292,21 +292,6 @@ impl BoardState {
} }
} }
pub fn probability_is_playable<T>(&self, card_info: &T) -> f32 where T: CardInfo {
let f = |card: &Card| { self.is_playable(card) };
card_info.probability_of_predicate(&f)
}
pub fn probability_is_dead<T>(&self, card_info: &T) -> f32 where T: CardInfo {
let f = |card: &Card| { self.is_dead(card) };
card_info.probability_of_predicate(&f)
}
pub fn probability_is_dispensable<T>(&self, card_info: &T) -> f32 where T: CardInfo {
let f = |card: &Card| { self.is_dispensable(card) };
card_info.probability_of_predicate(&f)
}
pub fn get_players(&self) -> Vec<Player> { pub fn get_players(&self) -> Vec<Player> {
(0..self.num_players).collect::<Vec<_>>() (0..self.num_players).collect::<Vec<_>>()
} }

View file

@ -5,6 +5,7 @@ use std::hash::Hash;
use std::convert::From; use std::convert::From;
use cards::*; use cards::*;
use game::BoardState;
// trait representing information about a card // trait representing information about a card
pub trait CardInfo { pub trait CardInfo {
@ -34,11 +35,13 @@ pub trait CardInfo {
} }
v v
} }
// get probability weight for the card // get probability weight for the card
#[allow(unused_variables)] #[allow(unused_variables)]
fn get_weight(&self, card: &Card) -> f32 { fn get_weight(&self, card: &Card) -> f32 {
1 as f32 1 as f32
} }
fn get_weighted_possibilities(&self) -> Vec<(Card, f32)> { fn get_weighted_possibilities(&self) -> Vec<(Card, f32)> {
let mut v = Vec::new(); let mut v = Vec::new();
for card in self.get_possibilities() { for card in self.get_possibilities() {
@ -47,6 +50,7 @@ pub trait CardInfo {
} }
v v
} }
fn weighted_score<T>(&self, score_fn: &Fn(&Card) -> T) -> f32 fn weighted_score<T>(&self, score_fn: &Fn(&Card) -> T) -> f32
where f32: From<T> where f32: From<T>
{ {
@ -60,9 +64,11 @@ pub trait CardInfo {
} }
total_score / total_weight total_score / total_weight
} }
fn average_value(&self) -> f32 { fn average_value(&self) -> f32 {
self.weighted_score(&|card| card.value as f32 ) self.weighted_score(&|card| card.value as f32 )
} }
fn probability_of_predicate(&self, predicate: &Fn(&Card) -> bool) -> f32 { fn probability_of_predicate(&self, predicate: &Fn(&Card) -> bool) -> f32 {
let f = |card: &Card| { let f = |card: &Card| {
if predicate(card) { 1.0 } else { 0.0 } if predicate(card) { 1.0 } else { 0.0 }
@ -70,6 +76,18 @@ pub trait CardInfo {
self.weighted_score(&f) self.weighted_score(&f)
} }
fn probability_is_playable(&self, board: &BoardState) -> f32 {
self.probability_of_predicate(&|card| board.is_playable(card))
}
fn probability_is_dead(&self, board: &BoardState) -> f32 {
self.probability_of_predicate(&|card| board.is_dead(card))
}
fn probability_is_dispensable(&self, board: &BoardState) -> f32 {
self.probability_of_predicate(&|card| board.is_dispensable(card))
}
// 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
@ -237,7 +255,7 @@ impl fmt::Display for SimpleCardInfo {
// Can represent information of the form: // Can represent information of the form:
// this card is/isn't possible // this card is/isn't possible
// also, maintains integer weights for the cards // also, maintains integer weights for the cards
#[derive(Clone)] #[derive(Clone,Debug)]
pub struct CardPossibilityTable { pub struct CardPossibilityTable {
possible: HashMap<Card, u32>, possible: HashMap<Card, u32>,
} }

View file

@ -79,26 +79,21 @@ impl CheatingPlayerStrategy {
}) })
} }
// given a hand of cards, represents how badly it will need to play things // represents how badly a card needs to be played
fn hand_play_value(&self, view: &BorrowedGameView, hand: &Cards/*, all_viewable: HashMap<Color, <Value, u32>> */) -> u32 { fn card_play_value(&self, view: &BorrowedGameView, card: &Card) -> u32 {
// dead = 0 points if view.board.is_dead(card) {
// indispensible = 5 + (5 - value) points return 0;
// playable, not in another hand = 2 point
// playable = 1 point
let mut value = 0;
for card in hand {
if view.board.is_dead(card) {
continue
}
if !view.board.is_dispensable(card) {
value += 20 - card.value;
} else if view.board.is_playable(card) {
value += 10 - card.value;
} else {
value += 1;
}
} }
value if !view.board.is_dispensable(card) {
10 - card.value
} else {
1
}
}
// given a hand of cards, represents how badly it will need to play things
fn hand_play_value(&self, view: &BorrowedGameView, hand: &Cards) -> u32 {
hand.iter().map(|card| self.card_play_value(view, card)).fold(0, |a,b| a+b)
} }
// how badly do we need to play a particular card // how badly do we need to play a particular card

View file

@ -217,7 +217,7 @@ impl InformationStrategyConfig {
} }
} }
impl GameStrategyConfig for InformationStrategyConfig { impl GameStrategyConfig for InformationStrategyConfig {
fn initialize(&self, opts: &GameOptions) -> Box<GameStrategy> { fn initialize(&self, _: &GameOptions) -> Box<GameStrategy> {
Box::new(InformationStrategy::new()) Box::new(InformationStrategy::new())
} }
} }
@ -274,12 +274,12 @@ impl InformationPlayerStrategy {
} }
let mut augmented_hand_info = hand_info.iter().enumerate().map(|(i, card_table)| { let mut augmented_hand_info = hand_info.iter().enumerate().map(|(i, card_table)| {
let p = view.get_board().probability_is_playable(card_table); let p = card_table.probability_is_playable(view.get_board());
(p, card_table, i) (p, card_table, i)
}).collect::<Vec<_>>(); }).collect::<Vec<_>>();
// sort by probability of play, then by index // sort by probability of play, then by index
augmented_hand_info.sort_by(|&(p1, card_table1, i1), &(p2, card_table2, i2)| { augmented_hand_info.sort_by(|&(p1, _, i1), &(p2, _, i2)| {
let result = p1.partial_cmp(&p2); let result = p1.partial_cmp(&p2);
if result == None || result == Some(Ordering::Equal) { if result == None || result == Some(Ordering::Equal) {
i1.cmp(&i2) i1.cmp(&i2)
@ -291,7 +291,7 @@ impl InformationPlayerStrategy {
// let known_playable = augmented_hand_info[0].0 == 1.0; // let known_playable = augmented_hand_info[0].0 == 1.0;
// // if there is a card that is definitely playable, don't ask about playability // // if there is a card that is definitely playable, don't ask about playability
// if !known_playable { // if !known_playable {
for &(p, card_table, i) in &augmented_hand_info { for &(p, _, i) in &augmented_hand_info {
if (p != 0.0) && (p != 1.0) { if (p != 0.0) && (p != 1.0) {
if add_question(&mut questions, &mut info_remaining, IsPlayable {index: i}) { if add_question(&mut questions, &mut info_remaining, IsPlayable {index: i}) {
return questions; return questions;
@ -300,11 +300,11 @@ impl InformationPlayerStrategy {
} }
// } // }
for &(p, card_table, i) in &augmented_hand_info { for &(_, card_table, i) in &augmented_hand_info {
if card_table.is_determined() { if card_table.is_determined() {
continue; continue;
} }
if view.get_board().probability_is_dead(card_table) == 1.0 { if card_table.probability_is_dead(view.get_board()) == 1.0 {
continue; continue;
} }
let question = CardPossibilityPartition::new(i, info_remaining, card_table, view); let question = CardPossibilityPartition::new(i, info_remaining, card_table, view);
@ -380,6 +380,10 @@ impl InformationPlayerStrategy {
question.acknowledge_answer_info(answer_info, &mut hand_info, Box::new(view as &GameView)); question.acknowledge_answer_info(answer_info, &mut hand_info, Box::new(view as &GameView));
} }
} }
debug!("Current state of hand_info for {}:", me);
for (i, card_table) in hand_info.iter().enumerate() {
debug!(" Card {}: {}", i, card_table);
}
self.return_public_info(&me, hand_info); self.return_public_info(&me, hand_info);
} }
@ -422,30 +426,6 @@ impl InformationPlayerStrategy {
} }
} }
// given a hand of cards, represents how badly it will need to play things
fn hand_play_value(&self, view: &BorrowedGameView, hand: &Cards/*, all_viewable: HashMap<Color, <Value, usize>> */) -> u32 {
// dead = 0 points
// indispensible = 5 + (5 - value) points
// playable = 1 point
let mut value = 0;
for card in hand {
if view.board.is_dead(card) {
continue
}
if !view.board.is_dispensable(card) {
value += 10 - card.value;
} else {
value += 1;
}
}
value
}
fn estimate_hand_play_value(&self, view: &BorrowedGameView) -> u32 {
// TODO: fix this
0
}
// 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: &BorrowedGameView, card_table: &CardPossibilityTable) -> f32 {
let f = |card: &Card| { let f = |card: &Card| {
@ -455,29 +435,25 @@ impl InformationPlayerStrategy {
} }
fn get_play_score(&self, view: &BorrowedGameView, card: &Card) -> i32 { fn get_play_score(&self, view: &BorrowedGameView, card: &Card) -> i32 {
let my_hand_value = self.estimate_hand_play_value(view);
for player in view.board.get_players() { for player in view.board.get_players() {
if player != self.me { if player != self.me {
if view.has_card(&player, card) { if view.has_card(&player, card) {
let their_hand_value = self.hand_play_value(view, view.get_hand(&player)); return 1;
// they can play this card, and have less urgent plays than i do
if their_hand_value <= my_hand_value {
return 1;
}
} }
} }
} }
// there are no hints if view.board.is_playable(card) {
// maybe value 5s more? 5 + (5 - (card.value as i32))
5 + (5 - (card.value as i32)) } else {
0
}
} }
fn find_useless_card(&self, view: &BorrowedGameView, hand: &Vec<CardPossibilityTable>) -> Option<usize> { fn find_useless_card(&self, view: &BorrowedGameView, hand: &Vec<CardPossibilityTable>) -> Option<usize> {
let mut set: HashSet<Card> = HashSet::new(); let mut set: HashSet<Card> = HashSet::new();
for (i, card_table) in hand.iter().enumerate() { for (i, card_table) in hand.iter().enumerate() {
if view.board.probability_is_dead(card_table) == 1.0 { if card_table.probability_is_dead(view.board) == 1.0 {
return Some(i); return Some(i);
} }
if let Some(card) = card_table.get_card() { if let Some(card) = card_table.get_card() {
@ -667,11 +643,11 @@ impl PlayerStrategy for InformationPlayerStrategy {
// } // }
let playable_cards = private_info.iter().enumerate().filter(|&(_, card_table)| { let playable_cards = private_info.iter().enumerate().filter(|&(_, card_table)| {
view.board.probability_is_playable(card_table) == 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 {
// TODO: try playing things that have no chance of being dead // TODO: try playing things that have no chance of being indispensable
// play the best playable card // play the best playable card
// the higher the play_score, the better to play // the higher the play_score, the better to play
let mut play_score = -1.0; let mut play_score = -1.0;
@ -688,11 +664,43 @@ impl PlayerStrategy for InformationPlayerStrategy {
return TurnChoice::Play(play_index) return TurnChoice::Play(play_index)
} }
// make a possibly risky play
if view.board.lives_remaining > 1 {
let mut risky_playable_cards = private_info.iter().enumerate().filter(|&(_, card_table)| {
// card is either playable or dead
card_table.probability_of_predicate(&|card| {
view.board.is_playable(card) || view.board.is_dead(card)
}) == 1.0
}).map(|(i, card_table)| {
let p = card_table.probability_is_playable(view.board);
(i, card_table, p)
}).collect::<Vec<_>>();
if risky_playable_cards.len() > 0 {
risky_playable_cards.sort_by(|c1, c2| {
c1.2.partial_cmp(&c2.2).unwrap_or(Ordering::Equal)
});
let maybe_play = risky_playable_cards[0];
if view.board.lives_remaining > 2 {
if maybe_play.2 > 0.5 {
return TurnChoice::Play(maybe_play.0);
}
} else {
if maybe_play.2 > 0.7 {
return TurnChoice::Play(maybe_play.0);
}
}
}
}
let discard_threshold = let discard_threshold =
view.board.total_cards view.board.total_cards
- (COLORS.len() * VALUES.len()) as u32 - (COLORS.len() * VALUES.len()) as u32
- (view.board.num_players * view.board.hand_size); - (view.board.num_players * view.board.hand_size);
// TODO: use the useless card discard as information!
if view.board.discard_size() <= discard_threshold { if view.board.discard_size() <= discard_threshold {
// if anything is totally useless, discard it // if anything is totally useless, discard it
if let Some(i) = self.find_useless_card(view, &private_info) { if let Some(i) = self.find_useless_card(view, &private_info) {
@ -705,9 +713,14 @@ impl PlayerStrategy for InformationPlayerStrategy {
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(view);
} else {
print!("This actually happened");
} }
} }
// TODO: if they discarded a non-useless card, despite there being hints remaining
// infer that we have no playable cards
// if anything is totally useless, discard it // if anything is totally useless, discard it
if let Some(i) = self.find_useless_card(view, &private_info) { if let Some(i) = self.find_useless_card(view, &private_info) {
return TurnChoice::Discard(i); return TurnChoice::Discard(i);
@ -722,7 +735,7 @@ impl PlayerStrategy for InformationPlayerStrategy {
}); });
let my_compval = let my_compval =
20.0 * probability_is_seen 20.0 * probability_is_seen
+ 10.0 * view.board.probability_is_dispensable(card_table) + 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 {