make risky plays
This commit is contained in:
parent
3d318340eb
commit
9494d549ae
5 changed files with 91 additions and 80 deletions
|
@ -53,5 +53,5 @@ Currently, on seeds 0-9999, we have:
|
|||
| 2p | 3p | 4p | 5p |
|
||||
----------|---------|---------|---------|---------|
|
||||
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 |
|
||||
|
||||
|
|
15
src/game.rs
15
src/game.rs
|
@ -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> {
|
||||
(0..self.num_players).collect::<Vec<_>>()
|
||||
}
|
||||
|
|
20
src/info.rs
20
src/info.rs
|
@ -5,6 +5,7 @@ use std::hash::Hash;
|
|||
use std::convert::From;
|
||||
|
||||
use cards::*;
|
||||
use game::BoardState;
|
||||
|
||||
// trait representing information about a card
|
||||
pub trait CardInfo {
|
||||
|
@ -34,11 +35,13 @@ pub trait CardInfo {
|
|||
}
|
||||
v
|
||||
}
|
||||
|
||||
// get probability weight for the card
|
||||
#[allow(unused_variables)]
|
||||
fn get_weight(&self, card: &Card) -> f32 {
|
||||
1 as f32
|
||||
}
|
||||
|
||||
fn get_weighted_possibilities(&self) -> Vec<(Card, f32)> {
|
||||
let mut v = Vec::new();
|
||||
for card in self.get_possibilities() {
|
||||
|
@ -47,6 +50,7 @@ pub trait CardInfo {
|
|||
}
|
||||
v
|
||||
}
|
||||
|
||||
fn weighted_score<T>(&self, score_fn: &Fn(&Card) -> T) -> f32
|
||||
where f32: From<T>
|
||||
{
|
||||
|
@ -60,9 +64,11 @@ pub trait CardInfo {
|
|||
}
|
||||
total_score / total_weight
|
||||
}
|
||||
|
||||
fn average_value(&self) -> f32 {
|
||||
self.weighted_score(&|card| card.value as f32 )
|
||||
}
|
||||
|
||||
fn probability_of_predicate(&self, predicate: &Fn(&Card) -> bool) -> f32 {
|
||||
let f = |card: &Card| {
|
||||
if predicate(card) { 1.0 } else { 0.0 }
|
||||
|
@ -70,6 +76,18 @@ pub trait CardInfo {
|
|||
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
|
||||
fn mark_color_false(&mut self, color: &Color);
|
||||
// mark a color as correct
|
||||
|
@ -237,7 +255,7 @@ impl fmt::Display for SimpleCardInfo {
|
|||
// Can represent information of the form:
|
||||
// this card is/isn't possible
|
||||
// also, maintains integer weights for the cards
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone,Debug)]
|
||||
pub struct CardPossibilityTable {
|
||||
possible: HashMap<Card, u32>,
|
||||
}
|
||||
|
|
|
@ -79,26 +79,21 @@ impl CheatingPlayerStrategy {
|
|||
})
|
||||
}
|
||||
|
||||
// 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, u32>> */) -> u32 {
|
||||
// dead = 0 points
|
||||
// indispensible = 5 + (5 - value) points
|
||||
// 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;
|
||||
}
|
||||
// represents how badly a card needs to be played
|
||||
fn card_play_value(&self, view: &BorrowedGameView, card: &Card) -> u32 {
|
||||
if view.board.is_dead(card) {
|
||||
return 0;
|
||||
}
|
||||
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
|
||||
|
|
|
@ -217,7 +217,7 @@ impl InformationStrategyConfig {
|
|||
}
|
||||
}
|
||||
impl GameStrategyConfig for InformationStrategyConfig {
|
||||
fn initialize(&self, opts: &GameOptions) -> Box<GameStrategy> {
|
||||
fn initialize(&self, _: &GameOptions) -> Box<GameStrategy> {
|
||||
Box::new(InformationStrategy::new())
|
||||
}
|
||||
}
|
||||
|
@ -274,12 +274,12 @@ impl InformationPlayerStrategy {
|
|||
}
|
||||
|
||||
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)
|
||||
}).collect::<Vec<_>>();
|
||||
|
||||
// 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);
|
||||
if result == None || result == Some(Ordering::Equal) {
|
||||
i1.cmp(&i2)
|
||||
|
@ -291,7 +291,7 @@ impl InformationPlayerStrategy {
|
|||
// 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 !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 add_question(&mut questions, &mut info_remaining, IsPlayable {index: i}) {
|
||||
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() {
|
||||
continue;
|
||||
}
|
||||
if view.get_board().probability_is_dead(card_table) == 1.0 {
|
||||
if card_table.probability_is_dead(view.get_board()) == 1.0 {
|
||||
continue;
|
||||
}
|
||||
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));
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
fn get_average_play_score(&self, view: &BorrowedGameView, card_table: &CardPossibilityTable) -> f32 {
|
||||
let f = |card: &Card| {
|
||||
|
@ -455,29 +435,25 @@ impl InformationPlayerStrategy {
|
|||
}
|
||||
|
||||
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() {
|
||||
if player != self.me {
|
||||
if view.has_card(&player, card) {
|
||||
let their_hand_value = self.hand_play_value(view, view.get_hand(&player));
|
||||
// they can play this card, and have less urgent plays than i do
|
||||
if their_hand_value <= my_hand_value {
|
||||
return 1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
// there are no hints
|
||||
// maybe value 5s more?
|
||||
5 + (5 - (card.value as i32))
|
||||
if view.board.is_playable(card) {
|
||||
5 + (5 - (card.value as i32))
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
fn find_useless_card(&self, view: &BorrowedGameView, hand: &Vec<CardPossibilityTable>) -> Option<usize> {
|
||||
let mut set: HashSet<Card> = HashSet::new();
|
||||
|
||||
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);
|
||||
}
|
||||
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)| {
|
||||
view.board.probability_is_playable(card_table) == 1.0
|
||||
card_table.probability_is_playable(view.board) == 1.0
|
||||
}).collect::<Vec<_>>();
|
||||
|
||||
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
|
||||
// the higher the play_score, the better to play
|
||||
let mut play_score = -1.0;
|
||||
|
@ -688,11 +664,43 @@ impl PlayerStrategy for InformationPlayerStrategy {
|
|||
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 =
|
||||
view.board.total_cards
|
||||
- (COLORS.len() * VALUES.len()) as u32
|
||||
- (view.board.num_players * view.board.hand_size);
|
||||
|
||||
// TODO: use the useless card discard as information!
|
||||
|
||||
if view.board.discard_size() <= discard_threshold {
|
||||
// if anything is totally useless, discard it
|
||||
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 self.someone_else_can_play(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 let Some(i) = self.find_useless_card(view, &private_info) {
|
||||
return TurnChoice::Discard(i);
|
||||
|
@ -722,7 +735,7 @@ impl PlayerStrategy for InformationPlayerStrategy {
|
|||
});
|
||||
let my_compval =
|
||||
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();
|
||||
|
||||
if my_compval > compval {
|
||||
|
|
Loading…
Reference in a new issue