From 02ffb781b37e9a3d35c87da5f7764491bbeaece2 Mon Sep 17 00:00:00 2001 From: Jeff Wu Date: Sun, 13 Mar 2016 11:02:08 -0700 Subject: [PATCH] finish up cheating strategy --- src/game.rs | 59 ++++++++++++++++------- src/simulator.rs | 8 +-- src/strategies/cheating.rs | 99 ++++++++++++++++++++++++++------------ src/strategies/examples.rs | 20 ++++---- 4 files changed, 124 insertions(+), 62 deletions(-) diff --git a/src/game.rs b/src/game.rs index 805a886..a68be96 100644 --- a/src/game.rs +++ b/src/game.rs @@ -377,6 +377,29 @@ impl BoardState { } } + // best possible value we can get for firework of that color, + // based on looking at discard + fireworks + pub fn highest_attainable(&self, color: &Color) -> Value { + let firework = self.fireworks.get(color).unwrap(); + if firework.complete() { + return FINAL_VALUE; + } + let desired = firework.desired_value().unwrap(); + + for value in VALUES.iter() { + if *value < desired { + // already have these cards + continue + } + let needed_card = Card::new(color, value.clone()); + if self.discard.has_all(&needed_card) { + // already discarded all of these + return value - 1; + } + } + return FINAL_VALUE; + } + // is never going to play, based on discard + fireworks pub fn is_unplayable(&self, card: &Card) -> bool { let firework = self.fireworks.get(card.color).unwrap(); @@ -387,24 +410,26 @@ impl BoardState { if card.value < desired { true } else { - let mut playable = true; - for value in VALUES.iter() { - if *value < desired { - // already have these cards - continue - } else if *value > card.value { - // don't care about these cards - break - } else { - // need these cards - let needed_card = Card::new(card.color, value.clone()); - if self.discard.has_all(&needed_card) { - // already discarded all of these - playable = false; - } - } + card.value > self.highest_attainable(&card.color) + } + } + } + + // cannot be discarded without sacrificing score, based on discard + fireworks + pub fn is_undiscardable(&self, card: &Card) -> bool { + let firework = self.fireworks.get(card.color).unwrap(); + if firework.complete() { + false + } else { + let desired = firework.desired_value().unwrap(); + if card.value < desired { + false + } else { + if card.value > self.highest_attainable(&card.color) { + false + } else { + self.discard.remaining(&card) == 1 } - playable } } } diff --git a/src/simulator.rs b/src/simulator.rs index 7aaa47c..a669813 100644 --- a/src/simulator.rs +++ b/src/simulator.rs @@ -4,11 +4,11 @@ use std::collections::HashMap; // Trait to implement for any valid Hanabi strategy // State management is done by the simulator, to avoid cheating pub trait Strategy { - fn decide(&mut self, &Player, &GameStateView) -> TurnChoice; + fn decide(&mut self, &GameStateView) -> TurnChoice; fn update(&mut self, &Turn, &GameStateView); } pub trait StrategyConfig { - fn initialize(&self, &Player, &GameStateView) -> Box; + fn initialize(&self, Player, &GameStateView) -> Box; } pub fn simulate_once<'a>(opts: &GameOptions, strat_configs: &Vec>) -> Score { @@ -21,7 +21,7 @@ pub fn simulate_once<'a>(opts: &GameOptions, strat_configs: &Vec(opts: &GameOptions, strat_configs: &Vec StrategyConfig for CheatingStrategyConfig { - fn initialize(&self, _: &Player, _: &GameStateView) -> Box { + fn initialize(&self, player: Player, _: &GameStateView) -> Box { Box::new(CheatingStrategy { player_states_cheat: self.player_states_cheat.clone(), + me: player, }) } } pub struct CheatingStrategy { player_states_cheat: Rc>>, + me: Player, } -impl Strategy for CheatingStrategy { - fn decide(&mut self, me: &Player, view: &GameStateView) -> TurnChoice { - let next = view.board.player_to_left(&me); +impl CheatingStrategy { + // help next player cheat! + fn inform_next_player_cards(&self, view: &GameStateView) { + let next = view.board.player_to_left(&self.me); self.player_states_cheat.borrow_mut().insert( next, view.other_player_states.get(&next).unwrap().hand.clone() ); - if view.board.turn == 1 { - TurnChoice::Hint(Hint { - player: next, + } + // give a throwaway hint - we only do this when we have nothing to do + fn throwaway_hint(&self, view: &GameStateView) -> TurnChoice { + TurnChoice::Hint(Hint { + player: view.board.player_to_left(&self.me), hinted: Hinted::Value(1) - }) - } else { - let states = self.player_states_cheat.borrow(); - let my_cards = states.get(me).unwrap(); - let mut playable_cards = my_cards.iter().filter(|card| { - view.board.is_playable(card) - }).peekable(); - if playable_cards.peek() == None { - TurnChoice::Discard(0) - } else { - let mut play_card = playable_cards.next().unwrap(); + }) + } +} +impl Strategy for CheatingStrategy { + fn decide(&mut self, view: &GameStateView) -> TurnChoice { + self.inform_next_player_cards(view); + if view.board.turn == 1 { + // don't know my cards yet, just give a random hint + return self.throwaway_hint(view); + } - let mut next_card_opt = playable_cards.next(); - while let Some(next_card) = next_card_opt { - if next_card.value < play_card.value { - play_card = next_card; - } - next_card_opt = playable_cards.next(); + let states = self.player_states_cheat.borrow(); + let my_cards = states.get(&self.me).unwrap(); + let mut playable_cards = my_cards.iter().filter(|card| { + view.board.is_playable(card) + }).peekable(); + + if playable_cards.peek() == None { + for card in my_cards { + if view.board.is_unplayable(card) { + let index = my_cards.iter().position(|iter_card| { + card == iter_card + }).unwrap(); + return TurnChoice::Discard(index); } - - let index = my_cards.iter().position(|card| { - card == play_card - }).unwrap(); - TurnChoice::Play(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 { + return self.throwaway_hint(view); + } + TurnChoice::Discard(0) + } else { + // play the lowest playable card + let mut play_card = playable_cards.next().unwrap(); + + let mut next_card_opt = playable_cards.next(); + while let Some(next_card) = next_card_opt { + if next_card.value < play_card.value { + play_card = next_card; + } + next_card_opt = playable_cards.next(); + } + + let index = my_cards.iter().position(|card| { + card == play_card + }).unwrap(); + TurnChoice::Play(index) } } fn update(&mut self, _: &Turn, _: &GameStateView) { diff --git a/src/strategies/examples.rs b/src/strategies/examples.rs index e0e9e15..16137ba 100644 --- a/src/strategies/examples.rs +++ b/src/strategies/examples.rs @@ -7,13 +7,13 @@ use rand::{self, Rng}; #[derive(Clone)] pub struct AlwaysPlayConfig; impl StrategyConfig for AlwaysPlayConfig { - fn initialize(&self, _: &Player, _: &GameStateView) -> Box { + fn initialize(&self, _: Player, _: &GameStateView) -> Box { Box::new(AlwaysPlay) } } pub struct AlwaysPlay; impl Strategy for AlwaysPlay { - fn decide(&mut self, _: &Player, _: &GameStateView) -> TurnChoice { + fn decide(&mut self, _: &GameStateView) -> TurnChoice { TurnChoice::Play(0) } fn update(&mut self, _: &Turn, _: &GameStateView) { @@ -25,13 +25,13 @@ impl Strategy for AlwaysPlay { #[derive(Clone)] pub struct AlwaysDiscardConfig; impl StrategyConfig for AlwaysDiscardConfig { - fn initialize(&self, _: &Player, _: &GameStateView) -> Box { + fn initialize(&self, _: Player, _: &GameStateView) -> Box { Box::new(AlwaysDiscard) } } pub struct AlwaysDiscard; impl Strategy for AlwaysDiscard { - fn decide(&mut self, _: &Player, _: &GameStateView) -> TurnChoice { + fn decide(&mut self, _: &GameStateView) -> TurnChoice { TurnChoice::Discard(0) } fn update(&mut self, _: &Turn, _: &GameStateView) { @@ -48,20 +48,22 @@ pub struct RandomStrategyConfig { } impl StrategyConfig for RandomStrategyConfig { - fn initialize(&self, _: &Player, _: &GameStateView) -> Box { + fn initialize(&self, player: Player, _: &GameStateView) -> Box { Box::new(RandomStrategy { hint_probability: self.hint_probability, play_probability: self.play_probability, + me: player, }) } } pub struct RandomStrategy { - pub hint_probability: f64, - pub play_probability: f64, + hint_probability: f64, + play_probability: f64, + me: Player, } impl Strategy for RandomStrategy { - fn decide(&mut self, me: &Player, view: &GameStateView) -> TurnChoice { + fn decide(&mut self, view: &GameStateView) -> TurnChoice { let p = rand::random::(); if p < self.hint_probability { if view.board.hints_remaining > 0 { @@ -74,7 +76,7 @@ impl Strategy for RandomStrategy { } }; TurnChoice::Hint(Hint { - player: view.board.player_to_left(&me), + player: view.board.player_to_left(&self.me), hinted: hinted, }) } else {