cheating strategy improvements
This commit is contained in:
parent
6184fcd914
commit
adaa513ff8
4 changed files with 173 additions and 59 deletions
75
src/game.rs
75
src/game.rs
|
@ -9,7 +9,7 @@ use info::*;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
pub type Color = &'static str;
|
pub type Color = &'static str;
|
||||||
pub const COLORS: [Color; 5] = ["blue", "red", "yellow", "white", "green"];
|
pub const COLORS: [Color; 5] = ["red", "yellow", "green", "blue", "white"];
|
||||||
pub fn display_color(color: Color) -> char {
|
pub fn display_color(color: Color) -> char {
|
||||||
color.chars().next().unwrap()
|
color.chars().next().unwrap()
|
||||||
}
|
}
|
||||||
|
@ -321,6 +321,7 @@ pub struct BoardState {
|
||||||
pub turn: u32,
|
pub turn: u32,
|
||||||
// // whose turn is it?
|
// // whose turn is it?
|
||||||
pub player: Player,
|
pub player: Player,
|
||||||
|
pub hand_size: u32,
|
||||||
|
|
||||||
pub hints_total: u32,
|
pub hints_total: u32,
|
||||||
pub hints_remaining: u32,
|
pub hints_remaining: u32,
|
||||||
|
@ -343,6 +344,7 @@ impl BoardState {
|
||||||
fireworks: fireworks,
|
fireworks: fireworks,
|
||||||
discard: Discard::new(),
|
discard: Discard::new(),
|
||||||
num_players: opts.num_players,
|
num_players: opts.num_players,
|
||||||
|
hand_size: opts.hand_size,
|
||||||
player: 0,
|
player: 0,
|
||||||
turn: 1,
|
turn: 1,
|
||||||
hints_total: opts.num_hints,
|
hints_total: opts.num_hints,
|
||||||
|
@ -360,10 +362,13 @@ impl BoardState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_firework(&self, color: &Color) -> &Firework {
|
||||||
|
self.fireworks.get(color).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
// returns whether a card would place on a firework
|
// returns whether a card would place on a firework
|
||||||
pub fn is_playable(&self, card: &Card) -> bool {
|
pub fn is_playable(&self, card: &Card) -> bool {
|
||||||
let firework = self.fireworks.get(card.color).unwrap();
|
Some(card.value) == self.get_firework(&card.color).desired_value()
|
||||||
Some(card.value) == firework.desired_value()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn was_played(&self, card: &Card) -> bool {
|
pub fn was_played(&self, card: &Card) -> bool {
|
||||||
|
@ -399,7 +404,7 @@ impl BoardState {
|
||||||
}
|
}
|
||||||
|
|
||||||
// is never going to play, based on discard + fireworks
|
// is never going to play, based on discard + fireworks
|
||||||
pub fn is_unplayable(&self, card: &Card) -> bool {
|
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() {
|
if firework.complete() {
|
||||||
true
|
true
|
||||||
|
@ -413,20 +418,20 @@ impl BoardState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// cannot be discarded without sacrificing score, based on discard + fireworks
|
// can be discarded without necessarily sacrificing score, based on discard + fireworks
|
||||||
pub fn is_undiscardable(&self, card: &Card) -> bool {
|
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() {
|
if firework.complete() {
|
||||||
false
|
true
|
||||||
} else {
|
} else {
|
||||||
let desired = firework.desired_value().unwrap();
|
let desired = firework.desired_value().unwrap();
|
||||||
if card.value < desired {
|
if card.value < desired {
|
||||||
false
|
true
|
||||||
} else {
|
} else {
|
||||||
if card.value > self.highest_attainable(&card.color) {
|
if card.value > self.highest_attainable(&card.color) {
|
||||||
false
|
true
|
||||||
} else {
|
} else {
|
||||||
self.discard.remaining(&card) == 1
|
self.discard.remaining(&card) != 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -454,12 +459,23 @@ impl BoardState {
|
||||||
pub fn player_to_right(&self, player: &Player) -> Player {
|
pub fn player_to_right(&self, player: &Player) -> Player {
|
||||||
(player - 1) % self.num_players
|
(player - 1) % self.num_players
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_over(&self) -> bool {
|
||||||
|
(self.lives_remaining == 0) || (self.deckless_turns_remaining == 0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl fmt::Display for BoardState {
|
impl fmt::Display for BoardState {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
if self.is_over() {
|
||||||
|
try!(f.write_str(&format!(
|
||||||
|
"Turn {} (GAME ENDED):\n", self.turn
|
||||||
|
)));
|
||||||
|
} else {
|
||||||
try!(f.write_str(&format!(
|
try!(f.write_str(&format!(
|
||||||
"Turn {} (Player {}'s turn):\n", self.turn, self.player
|
"Turn {} (Player {}'s turn):\n", self.turn, self.player
|
||||||
)));
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
let deck_size = self.deck_size();
|
let deck_size = self.deck_size();
|
||||||
try!(f.write_str(&format!(
|
try!(f.write_str(&format!(
|
||||||
"{} cards remaining in deck\n", deck_size
|
"{} cards remaining in deck\n", deck_size
|
||||||
|
@ -476,8 +492,8 @@ impl fmt::Display for BoardState {
|
||||||
"{}/{} lives remaining\n", self.lives_remaining, self.lives_total
|
"{}/{} lives remaining\n", self.lives_remaining, self.lives_total
|
||||||
)));
|
)));
|
||||||
try!(f.write_str("Fireworks:\n"));
|
try!(f.write_str("Fireworks:\n"));
|
||||||
for (_, firework) in &self.fireworks {
|
for color in COLORS.iter() {
|
||||||
try!(f.write_str(&format!(" {}\n", firework)));
|
try!(f.write_str(&format!(" {}\n", self.get_firework(color))));
|
||||||
}
|
}
|
||||||
try!(f.write_str("Discard:\n"));
|
try!(f.write_str("Discard:\n"));
|
||||||
try!(f.write_str(&format!("{}\n", self.discard)));
|
try!(f.write_str(&format!("{}\n", self.discard)));
|
||||||
|
@ -499,6 +515,26 @@ pub struct GameStateView<'a> {
|
||||||
// board state
|
// board state
|
||||||
pub board: &'a BoardState,
|
pub board: &'a BoardState,
|
||||||
}
|
}
|
||||||
|
impl <'a> GameStateView<'a> {
|
||||||
|
pub fn has_card(&self, player: &Player, card: &Card) -> bool {
|
||||||
|
assert!(self.player != *player, "Cannot query about your own cards!");
|
||||||
|
let state = self.other_player_states.get(player).unwrap();
|
||||||
|
for other_card in &state.hand {
|
||||||
|
if *card == *other_card {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
pub fn can_see(&self, card: &Card) -> bool {
|
||||||
|
for other_player in self.other_player_states.keys() {
|
||||||
|
if self.has_card(other_player, card) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// complete game state (known to nobody!)
|
// complete game state (known to nobody!)
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -511,7 +547,7 @@ impl fmt::Display for GameState {
|
||||||
try!(f.write_str("==========================\n"));
|
try!(f.write_str("==========================\n"));
|
||||||
try!(f.write_str("Hands:\n"));
|
try!(f.write_str("Hands:\n"));
|
||||||
try!(f.write_str("==========================\n"));
|
try!(f.write_str("==========================\n"));
|
||||||
for player in 0..self.board.num_players {
|
for player in self.board.get_players() {
|
||||||
let state = &self.player_states.get(&player).unwrap();
|
let state = &self.player_states.get(&player).unwrap();
|
||||||
try!(f.write_str(&format!("player {} {}\n", player, state)));
|
try!(f.write_str(&format!("player {} {}\n", player, state)));
|
||||||
}
|
}
|
||||||
|
@ -552,8 +588,7 @@ impl GameState {
|
||||||
|
|
||||||
pub fn is_over(&self) -> bool {
|
pub fn is_over(&self) -> bool {
|
||||||
// TODO: add condition that fireworks cannot be further completed?
|
// TODO: add condition that fireworks cannot be further completed?
|
||||||
(self.board.lives_remaining == 0) ||
|
self.board.is_over()
|
||||||
(self.board.deckless_turns_remaining == 0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn score(&self) -> Score {
|
pub fn score(&self) -> Score {
|
||||||
|
@ -580,11 +615,17 @@ impl GameState {
|
||||||
fn take_from_hand(&mut self, index: usize) -> Card {
|
fn take_from_hand(&mut self, index: usize) -> Card {
|
||||||
let ref mut state = self.player_states.get_mut(&self.board.player).unwrap();
|
let ref mut state = self.player_states.get_mut(&self.board.player).unwrap();
|
||||||
let (card, _) = state.take(index);
|
let (card, _) = state.take(index);
|
||||||
|
card
|
||||||
|
}
|
||||||
|
|
||||||
|
fn replenish_hand(&mut self) {
|
||||||
|
let ref mut state = self.player_states.get_mut(&self.board.player).unwrap();
|
||||||
|
if (state.hand.len() as u32) < self.board.hand_size {
|
||||||
if let Some(new_card) = self.board.deck.pop() {
|
if let Some(new_card) = self.board.deck.pop() {
|
||||||
debug!("Drew new card, {}", new_card);
|
debug!("Drew new card, {}", new_card);
|
||||||
state.place(new_card);
|
state.place(new_card);
|
||||||
}
|
}
|
||||||
card
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_choice(&mut self, choice: &TurnChoice) {
|
pub fn process_choice(&mut self, choice: &TurnChoice) {
|
||||||
|
@ -642,6 +683,8 @@ impl GameState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.replenish_hand();
|
||||||
|
|
||||||
if self.board.deck.len() == 0 {
|
if self.board.deck.len() == 0 {
|
||||||
self.board.deckless_turns_remaining -= 1;
|
self.board.deckless_turns_remaining -= 1;
|
||||||
}
|
}
|
||||||
|
|
20
src/main.rs
20
src/main.rs
|
@ -27,9 +27,11 @@ impl log::Log for SimpleLogger {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
// TODO: make a binary with command line options
|
||||||
|
|
||||||
log::set_logger(|max_log_level| {
|
log::set_logger(|max_log_level| {
|
||||||
max_log_level.set(log::LogLevelFilter::Trace);
|
// max_log_level.set(log::LogLevelFilter::Trace);
|
||||||
// max_log_level.set(log::LogLevelFilter::Info);
|
max_log_level.set(log::LogLevelFilter::Info);
|
||||||
Box::new(SimpleLogger)
|
Box::new(SimpleLogger)
|
||||||
}).unwrap();
|
}).unwrap();
|
||||||
|
|
||||||
|
@ -50,13 +52,13 @@ fn main() {
|
||||||
// },
|
// },
|
||||||
// n
|
// n
|
||||||
// );
|
// );
|
||||||
// simulator::simulate_symmetric(
|
simulator::simulate_symmetric(
|
||||||
// &opts,
|
&opts,
|
||||||
// strategies::cheating::CheatingStrategyConfig::new(),
|
|
||||||
// n
|
|
||||||
// );
|
|
||||||
simulator::simulate_symmetric_once(
|
|
||||||
&opts, Some(993),
|
|
||||||
strategies::cheating::CheatingStrategyConfig::new(),
|
strategies::cheating::CheatingStrategyConfig::new(),
|
||||||
|
n
|
||||||
);
|
);
|
||||||
|
// simulator::simulate_symmetric_once(
|
||||||
|
// &opts, Some(999),
|
||||||
|
// strategies::cheating::CheatingStrategyConfig::new(),
|
||||||
|
// );
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,8 @@ pub fn simulate_once<'a>(
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
debug!("Initial state:\n{}", game);
|
||||||
|
|
||||||
while !game.is_over() {
|
while !game.is_over() {
|
||||||
debug!("Turn {}", game.board.turn);
|
debug!("Turn {}", game.board.turn);
|
||||||
let player = game.board.player;
|
let player = game.board.player;
|
||||||
|
|
|
@ -5,11 +5,18 @@ use std::collections::HashMap;
|
||||||
use simulator::*;
|
use simulator::*;
|
||||||
use game::*;
|
use game::*;
|
||||||
|
|
||||||
// strategy that cheats by using Rc/RefCell
|
// strategy that explicitly cheats by using Rc/RefCell
|
||||||
|
// serves as a reference point for other strategies
|
||||||
|
//
|
||||||
// Plays according to the following rules:
|
// Plays according to the following rules:
|
||||||
// - if any card is playable,
|
// - if any card is playable,
|
||||||
// play the card with the lowest value
|
// play the card with the lowest value
|
||||||
// - if a card is dead, discard it
|
// - if a card is dead, discard it
|
||||||
|
// - if another player has same card in hand, discard it
|
||||||
|
// - if a card is discardable, discard it
|
||||||
|
// - if a hint exists, hint
|
||||||
|
// - discard the first card
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct CheatingStrategyConfig {
|
pub struct CheatingStrategyConfig {
|
||||||
|
@ -43,6 +50,7 @@ impl CheatingStrategy {
|
||||||
next, view.other_player_states.get(&next).unwrap().hand.clone()
|
next, view.other_player_states.get(&next).unwrap().hand.clone()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// give a throwaway hint - we only do this when we have nothing to do
|
// give a throwaway hint - we only do this when we have nothing to do
|
||||||
fn throwaway_hint(&self, view: &GameStateView) -> TurnChoice {
|
fn throwaway_hint(&self, view: &GameStateView) -> TurnChoice {
|
||||||
TurnChoice::Hint(Hint {
|
TurnChoice::Hint(Hint {
|
||||||
|
@ -50,11 +58,54 @@ impl CheatingStrategy {
|
||||||
hinted: Hinted::Value(1)
|
hinted: Hinted::Value(1)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// given a hand of cards, represents how badly it will need to play things
|
||||||
|
fn hand_play_value(&self, view: &GameStateView, hand: &Cards/*, all_viewable: HashMap<Color, <Value, usize>> */) -> 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 += 10 - card.value;
|
||||||
|
} else {
|
||||||
|
value += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
value
|
||||||
|
}
|
||||||
|
|
||||||
|
// how badly do we need to play a particular card
|
||||||
|
fn get_play_score(&self, view: &GameStateView, card: &Card) -> i32 {
|
||||||
|
let states = self.player_states_cheat.borrow();
|
||||||
|
let my_hand = states.get(&self.me).unwrap();
|
||||||
|
|
||||||
|
let my_hand_value = self.hand_play_value(view, my_hand);
|
||||||
|
|
||||||
|
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, states.get(&player).unwrap());
|
||||||
|
// 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
|
||||||
|
// maybe value 5s more?
|
||||||
|
5 + (5 - (card.value as i32))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl Strategy for CheatingStrategy {
|
impl Strategy for CheatingStrategy {
|
||||||
fn decide(&mut self, view: &GameStateView) -> TurnChoice {
|
fn decide(&mut self, view: &GameStateView) -> TurnChoice {
|
||||||
self.inform_next_player_cards(view);
|
self.inform_next_player_cards(view);
|
||||||
if view.board.turn == 1 {
|
if view.board.turn <= view.board.num_players {
|
||||||
// don't know my cards yet, just give a random hint
|
// don't know my cards yet, just give a random hint
|
||||||
return self.throwaway_hint(view);
|
return self.throwaway_hint(view);
|
||||||
}
|
}
|
||||||
|
@ -66,41 +117,57 @@ impl Strategy for CheatingStrategy {
|
||||||
}).peekable();
|
}).peekable();
|
||||||
|
|
||||||
if playable_cards.peek() == None {
|
if playable_cards.peek() == None {
|
||||||
for card in my_cards {
|
// if anything is totally useless, discard it
|
||||||
if view.board.is_unplayable(card) {
|
for (i, card) in my_cards.iter().enumerate() {
|
||||||
let index = my_cards.iter().position(|iter_card| {
|
if view.board.is_dead(card) {
|
||||||
card == iter_card
|
return TurnChoice::Discard(i);
|
||||||
}).unwrap();
|
|
||||||
return TurnChoice::Discard(index);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for card in my_cards {
|
|
||||||
if !view.board.is_undiscardable(card) {
|
|
||||||
let index = my_cards.iter().position(|iter_card| {
|
|
||||||
card == iter_card
|
|
||||||
}).unwrap();
|
|
||||||
return TurnChoice::Discard(index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// all my cards are undiscardable!
|
|
||||||
if view.board.hints_remaining > 0 {
|
if view.board.hints_remaining > 0 {
|
||||||
return self.throwaway_hint(view);
|
return self.throwaway_hint(view);
|
||||||
}
|
}
|
||||||
TurnChoice::Discard(0)
|
// All cards are plausibly useful.
|
||||||
} else {
|
// Play the best discardable card, according to the ordering induced by comparing
|
||||||
// play the lowest playable card
|
// (is in another hand, is dispensable, value)
|
||||||
let mut play_card = playable_cards.next().unwrap();
|
// The higher, the better to discard
|
||||||
|
let mut discard_card = None;
|
||||||
let mut next_card_opt = playable_cards.next();
|
let mut compval = (false, false, 0);
|
||||||
while let Some(next_card) = next_card_opt {
|
for card in my_cards {
|
||||||
if next_card.value < play_card.value {
|
let my_compval = (
|
||||||
play_card = next_card;
|
view.can_see(card),
|
||||||
|
view.board.is_dispensable(card),
|
||||||
|
card.value,
|
||||||
|
);
|
||||||
|
if my_compval > compval {
|
||||||
|
discard_card = Some(card);
|
||||||
|
compval = my_compval;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(card) = discard_card {
|
||||||
|
let index = my_cards.iter().position(|iter_card| {
|
||||||
|
card == iter_card
|
||||||
|
}).unwrap();
|
||||||
|
TurnChoice::Discard(index)
|
||||||
|
} else {
|
||||||
|
panic!("This shouldn't happen! No discardable card");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// play the best playable card
|
||||||
|
// the higher the play_score, the better to play
|
||||||
|
let mut play_card = None;
|
||||||
|
let mut play_score = -1;
|
||||||
|
|
||||||
|
while playable_cards.peek().is_some() {
|
||||||
|
let next_card = playable_cards.next().unwrap();
|
||||||
|
let next_play_score = self.get_play_score(view, next_card);
|
||||||
|
if next_play_score > play_score {
|
||||||
|
play_card = Some(next_card);
|
||||||
|
play_score = next_play_score;
|
||||||
}
|
}
|
||||||
next_card_opt = playable_cards.next();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let index = my_cards.iter().position(|card| {
|
let index = my_cards.iter().position(|card| {
|
||||||
card == play_card
|
card == play_card.unwrap()
|
||||||
}).unwrap();
|
}).unwrap();
|
||||||
TurnChoice::Play(index)
|
TurnChoice::Play(index)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue