various cleanups, fixes
This commit is contained in:
parent
ec1fd2eb07
commit
7f5e32699e
7 changed files with 148 additions and 131 deletions
20
README.md
20
README.md
|
@ -54,16 +54,24 @@ cargo run -- -n 10000 -s 0 -t 2 -p 5 -g cheat
|
||||||
Or, if the simulation is slow (as the info strategy is),
|
Or, if the simulation is slow (as the info strategy is),
|
||||||
|
|
||||||
```
|
```
|
||||||
cargo build --release
|
cargo run --release -- -n 10000 -s 0 -t 2 -p 5 -g info
|
||||||
time ./target/release/rust_hanabi -n 10000 -s 0 -t 2 -p 5 -g info
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Results
|
## Results
|
||||||
|
|
||||||
Currently, on seeds 0-9999, we have:
|
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 | 18.5959 | 23.8846 | 24.7753 | 24.8719 |
|
information | 18.5726 | 23.8806 | 24.7722 | 24.8756 |
|
||||||
|
|
||||||
|
To reproduce:
|
||||||
|
```
|
||||||
|
n=1000000
|
||||||
|
for strategy in info cheat; do
|
||||||
|
for p in $(seq 2 5); do
|
||||||
|
time cargo run --release -- -n $n -s 0 -t 4 -p $p -g $strategy;
|
||||||
|
done
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
|
@ -125,6 +125,7 @@ impl fmt::Display for Discard {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Score = u32;
|
pub type Score = u32;
|
||||||
|
pub const PERFECT_SCORE: Score = 25;
|
||||||
|
|
||||||
#[derive(Debug,Clone)]
|
#[derive(Debug,Clone)]
|
||||||
pub struct Firework {
|
pub struct Firework {
|
||||||
|
@ -139,7 +140,7 @@ impl Firework {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn desired_value(&self) -> Option<Value> {
|
pub fn needed_value(&self) -> Option<Value> {
|
||||||
if self.complete() { None } else { Some(self.top + 1) }
|
if self.complete() { None } else { Some(self.top + 1) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -157,7 +158,7 @@ impl Firework {
|
||||||
"Attempted to place card on firework of wrong color!"
|
"Attempted to place card on firework of wrong color!"
|
||||||
);
|
);
|
||||||
assert!(
|
assert!(
|
||||||
Some(card.value) == self.desired_value(),
|
Some(card.value) == self.needed_value(),
|
||||||
"Attempted to place card of wrong value on firework!"
|
"Attempted to place card of wrong value on firework!"
|
||||||
);
|
);
|
||||||
self.top = card.value;
|
self.top = card.value;
|
||||||
|
|
27
src/game.rs
27
src/game.rs
|
@ -223,7 +223,7 @@ impl BoardState {
|
||||||
|
|
||||||
// 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 {
|
||||||
Some(card.value) == self.get_firework(card.color).desired_value()
|
Some(card.value) == self.get_firework(card.color).needed_value()
|
||||||
}
|
}
|
||||||
|
|
||||||
// best possible value we can get for firework of that color,
|
// best possible value we can get for firework of that color,
|
||||||
|
@ -233,10 +233,10 @@ impl BoardState {
|
||||||
if firework.complete() {
|
if firework.complete() {
|
||||||
return FINAL_VALUE;
|
return FINAL_VALUE;
|
||||||
}
|
}
|
||||||
let desired = firework.desired_value().unwrap();
|
let needed = firework.needed_value().unwrap();
|
||||||
|
|
||||||
for &value in VALUES.iter() {
|
for &value in VALUES.iter() {
|
||||||
if value < desired {
|
if value < needed {
|
||||||
// already have these cards
|
// already have these cards
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -255,8 +255,8 @@ impl BoardState {
|
||||||
if firework.complete() {
|
if firework.complete() {
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
let desired = firework.desired_value().unwrap();
|
let needed = firework.needed_value().unwrap();
|
||||||
if card.value < desired {
|
if card.value < needed {
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
card.value > self.highest_attainable(card.color)
|
card.value > self.highest_attainable(card.color)
|
||||||
|
@ -270,8 +270,8 @@ impl BoardState {
|
||||||
if firework.complete() {
|
if firework.complete() {
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
let desired = firework.desired_value().unwrap();
|
let needed = firework.needed_value().unwrap();
|
||||||
if card.value < desired {
|
if card.value < needed {
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
if card.value > self.highest_attainable(card.color) {
|
if card.value > self.highest_attainable(card.color) {
|
||||||
|
@ -392,6 +392,19 @@ pub trait GameView {
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn someone_else_can_play(&self) -> bool {
|
||||||
|
for player in self.get_board().get_players() {
|
||||||
|
if player != self.me() {
|
||||||
|
for card in self.get_hand(&player) {
|
||||||
|
if self.get_board().is_playable(card) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// version of game view that is borrowed. used in simulator for efficiency,
|
// version of game view that is borrowed. used in simulator for efficiency,
|
||||||
|
|
|
@ -33,7 +33,6 @@ 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,7 @@ pub fn simulate_once(
|
||||||
opts: &GameOptions,
|
opts: &GameOptions,
|
||||||
game_strategy: Box<GameStrategy>,
|
game_strategy: Box<GameStrategy>,
|
||||||
seed_opt: Option<u32>,
|
seed_opt: Option<u32>,
|
||||||
) -> Score {
|
) -> GameState {
|
||||||
|
|
||||||
let seed = seed_opt.unwrap_or(rand::thread_rng().next_u32());
|
let seed = seed_opt.unwrap_or(rand::thread_rng().next_u32());
|
||||||
|
|
||||||
|
@ -73,11 +73,10 @@ pub fn simulate_once(
|
||||||
debug!("");
|
debug!("");
|
||||||
debug!("=======================================================");
|
debug!("=======================================================");
|
||||||
debug!("Final state:\n{}", game);
|
debug!("Final state:\n{}", game);
|
||||||
let score = game.score();
|
game
|
||||||
debug!("SCORED: {:?}", score);
|
|
||||||
score
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
struct Histogram {
|
struct Histogram {
|
||||||
pub hist: HashMap<Score, u32>,
|
pub hist: HashMap<Score, u32>,
|
||||||
pub sum: Score,
|
pub sum: Score,
|
||||||
|
@ -103,6 +102,9 @@ impl Histogram {
|
||||||
pub fn get_count(&self, val: &Score) -> u32 {
|
pub fn get_count(&self, val: &Score) -> u32 {
|
||||||
*self.hist.get(&val).unwrap_or(&0)
|
*self.hist.get(&val).unwrap_or(&0)
|
||||||
}
|
}
|
||||||
|
pub fn percentage_with(&self, val: &Score) -> f32 {
|
||||||
|
self.get_count(val) as f32 / self.total_count as f32
|
||||||
|
}
|
||||||
pub fn average(&self) -> f32 {
|
pub fn average(&self) -> f32 {
|
||||||
(self.sum as f32) / (self.total_count as f32)
|
(self.sum as f32) / (self.total_count as f32)
|
||||||
}
|
}
|
||||||
|
@ -131,7 +133,7 @@ pub fn simulate<T: ?Sized>(
|
||||||
first_seed_opt: Option<u32>,
|
first_seed_opt: Option<u32>,
|
||||||
n_trials: u32,
|
n_trials: u32,
|
||||||
n_threads: u32,
|
n_threads: u32,
|
||||||
) -> f32 where T: GameStrategyConfig + Sync {
|
) where T: GameStrategyConfig + Sync {
|
||||||
|
|
||||||
let first_seed = first_seed_opt.unwrap_or(rand::thread_rng().next_u32());
|
let first_seed = first_seed_opt.unwrap_or(rand::thread_rng().next_u32());
|
||||||
|
|
||||||
|
@ -145,33 +147,40 @@ pub fn simulate<T: ?Sized>(
|
||||||
info!("Thread {} spawned: seeds {} to {}", i, start, end);
|
info!("Thread {} spawned: seeds {} to {}", i, start, end);
|
||||||
let mut non_perfect_seeds = Vec::new();
|
let mut non_perfect_seeds = Vec::new();
|
||||||
|
|
||||||
let mut histogram = Histogram::new();
|
let mut score_histogram = Histogram::new();
|
||||||
|
let mut lives_histogram = Histogram::new();
|
||||||
|
|
||||||
for seed in start..end {
|
for seed in start..end {
|
||||||
if (seed > start) && ((seed-start) % 1000 == 0) {
|
if (seed > start) && ((seed-start) % 1000 == 0) {
|
||||||
info!(
|
info!(
|
||||||
"Thread {}, Trials: {}, Average so far: {}",
|
"Thread {}, Trials: {}, Stats so far: {} score, {} lives, {}% win",
|
||||||
i, seed-start, histogram.average()
|
i, seed-start, score_histogram.average(), lives_histogram.average(),
|
||||||
|
score_histogram.percentage_with(&PERFECT_SCORE) * 100.0
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
let score = simulate_once(&opts, strat_config_ref.initialize(&opts), Some(seed));
|
let game = simulate_once(&opts, strat_config_ref.initialize(&opts), Some(seed));
|
||||||
histogram.insert(score);
|
let score = game.score();
|
||||||
if score != 25 { non_perfect_seeds.push((score, seed)); }
|
debug!("SCORED: {:?}", score);
|
||||||
|
lives_histogram.insert(game.board.lives_remaining);
|
||||||
|
score_histogram.insert(score);
|
||||||
|
if score != PERFECT_SCORE { non_perfect_seeds.push((score, seed)); }
|
||||||
}
|
}
|
||||||
info!("Thread {} done", i);
|
info!("Thread {} done", i);
|
||||||
(non_perfect_seeds, histogram)
|
(non_perfect_seeds, score_histogram, lives_histogram)
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut non_perfect_seeds : Vec<(Score,u32)> = Vec::new();
|
let mut non_perfect_seeds : Vec<(Score,u32)> = Vec::new();
|
||||||
let mut histogram = Histogram::new();
|
let mut score_histogram = Histogram::new();
|
||||||
|
let mut lives_histogram = Histogram::new();
|
||||||
for join_handle in join_handles {
|
for join_handle in join_handles {
|
||||||
let (thread_non_perfect_seeds, thread_histogram) = join_handle.join();
|
let (thread_non_perfect_seeds, thread_score_histogram, thread_lives_histogram) = join_handle.join();
|
||||||
non_perfect_seeds.extend(thread_non_perfect_seeds.iter());
|
non_perfect_seeds.extend(thread_non_perfect_seeds.iter());
|
||||||
histogram.merge(thread_histogram);
|
score_histogram.merge(thread_score_histogram);
|
||||||
|
lives_histogram.merge(thread_lives_histogram);
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("Score histogram:\n{}", histogram);
|
info!("Score histogram:\n{}", score_histogram);
|
||||||
|
|
||||||
non_perfect_seeds.sort();
|
non_perfect_seeds.sort();
|
||||||
// info!("Seeds with non-perfect score: {:?}", non_perfect_seeds);
|
// info!("Seeds with non-perfect score: {:?}", non_perfect_seeds);
|
||||||
|
@ -180,10 +189,8 @@ pub fn simulate<T: ?Sized>(
|
||||||
non_perfect_seeds.get(0).unwrap().1);
|
non_perfect_seeds.get(0).unwrap().1);
|
||||||
}
|
}
|
||||||
|
|
||||||
let percentage = (n_trials - non_perfect_seeds.len() as u32) as f32 / n_trials as f32;
|
info!("Percentage perfect: {:?}%", score_histogram.percentage_with(&PERFECT_SCORE) * 100.0);
|
||||||
info!("Percentage perfect: {:?}%", percentage * 100.0);
|
info!("Average score: {:?}", score_histogram.average());
|
||||||
let average = histogram.average();
|
info!("Average lives: {:?}", lives_histogram.average());
|
||||||
info!("Average score: {:?}", average);
|
|
||||||
average
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -134,19 +134,6 @@ impl CheatingPlayerStrategy {
|
||||||
}
|
}
|
||||||
return None
|
return None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn someone_else_can_play(&self, view: &BorrowedGameView) -> bool {
|
|
||||||
for player in view.board.get_players() {
|
|
||||||
if player != self.me {
|
|
||||||
for card in view.get_hand(&player) {
|
|
||||||
if view.board.is_playable(card) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
impl PlayerStrategy for CheatingPlayerStrategy {
|
impl PlayerStrategy for CheatingPlayerStrategy {
|
||||||
fn decide(&mut self, view: &BorrowedGameView) -> TurnChoice {
|
fn decide(&mut self, view: &BorrowedGameView) -> TurnChoice {
|
||||||
|
@ -197,7 +184,7 @@ impl PlayerStrategy for CheatingPlayerStrategy {
|
||||||
// hinting is better than discarding dead cards
|
// hinting is better than discarding dead cards
|
||||||
// (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 view.someone_else_can_play() {
|
||||||
return self.throwaway_hint(view);
|
return self.throwaway_hint(view);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,23 +123,23 @@ struct CardPossibilityPartition {
|
||||||
partition: HashMap<Card, u32>,
|
partition: HashMap<Card, u32>,
|
||||||
}
|
}
|
||||||
impl CardPossibilityPartition {
|
impl CardPossibilityPartition {
|
||||||
fn new<T>(
|
fn new(
|
||||||
index: usize, max_n_partitions: u32, card_table: &CardPossibilityTable, view: &T
|
index: usize, max_n_partitions: u32, card_table: &CardPossibilityTable, view: &OwnedGameView
|
||||||
) -> CardPossibilityPartition where T: GameView {
|
) -> CardPossibilityPartition {
|
||||||
let mut cur_block = 0;
|
let mut cur_block = 0;
|
||||||
let mut partition = HashMap::new();
|
let mut partition = HashMap::new();
|
||||||
let mut n_partitions = 0;
|
let mut n_partitions = 0;
|
||||||
|
|
||||||
let has_dead = card_table.probability_is_dead(view.get_board()) != 0.0;
|
let has_dead = card_table.probability_is_dead(&view.board) != 0.0;
|
||||||
|
|
||||||
// TODO: group things of different colors and values?
|
// TODO: group things of different colors and values?
|
||||||
let effective_max = if has_dead {
|
let mut effective_max = max_n_partitions;
|
||||||
max_n_partitions - 1
|
if has_dead {
|
||||||
} else {
|
effective_max -= 1;
|
||||||
max_n_partitions
|
|
||||||
};
|
};
|
||||||
|
|
||||||
for card in card_table.get_possibilities() {
|
for card in card_table.get_possibilities() {
|
||||||
if !view.get_board().is_dead(&card) {
|
if !view.board.is_dead(&card) {
|
||||||
partition.insert(card.clone(), cur_block);
|
partition.insert(card.clone(), cur_block);
|
||||||
cur_block = (cur_block + 1) % effective_max;
|
cur_block = (cur_block + 1) % effective_max;
|
||||||
if n_partitions < effective_max {
|
if n_partitions < effective_max {
|
||||||
|
@ -150,13 +150,24 @@ impl CardPossibilityPartition {
|
||||||
|
|
||||||
if has_dead {
|
if has_dead {
|
||||||
for card in card_table.get_possibilities() {
|
for card in card_table.get_possibilities() {
|
||||||
if view.get_board().is_dead(&card) {
|
if view.board.is_dead(&card) {
|
||||||
partition.insert(card.clone(), n_partitions);
|
partition.insert(card.clone(), n_partitions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
n_partitions += 1;
|
n_partitions += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// let mut s : String = "Partition: |".to_string();
|
||||||
|
// for i in 0..n_partitions {
|
||||||
|
// for (card, block) in partition.iter() {
|
||||||
|
// if *block == i {
|
||||||
|
// s = s + &format!(" {}", card);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// s = s + &format!(" |");
|
||||||
|
// }
|
||||||
|
// debug!("{}", s);
|
||||||
|
|
||||||
CardPossibilityPartition {
|
CardPossibilityPartition {
|
||||||
index: index,
|
index: index,
|
||||||
n_partitions: n_partitions,
|
n_partitions: n_partitions,
|
||||||
|
@ -231,13 +242,11 @@ pub struct InformationPlayerStrategy {
|
||||||
}
|
}
|
||||||
impl InformationPlayerStrategy {
|
impl InformationPlayerStrategy {
|
||||||
|
|
||||||
fn get_questions<T>(
|
fn get_questions(
|
||||||
total_info: u32,
|
total_info: u32,
|
||||||
view: &T,
|
view: &OwnedGameView,
|
||||||
hand_info: &Vec<CardPossibilityTable>,
|
hand_info: &Vec<CardPossibilityTable>,
|
||||||
) -> Vec<Box<Question>>
|
) -> Vec<Box<Question>> {
|
||||||
where T: GameView
|
|
||||||
{
|
|
||||||
let mut questions = Vec::new();
|
let mut questions = Vec::new();
|
||||||
let mut info_remaining = total_info;
|
let mut info_remaining = total_info;
|
||||||
|
|
||||||
|
@ -251,10 +260,21 @@ impl InformationPlayerStrategy {
|
||||||
*info_remaining <= 1
|
*info_remaining <= 1
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut augmented_hand_info = hand_info.iter().enumerate().map(|(i, card_table)| {
|
let mut augmented_hand_info = hand_info.iter().enumerate()
|
||||||
let p = card_table.probability_is_playable(view.get_board());
|
.filter(|&(_, card_table)| {
|
||||||
(p, card_table, i)
|
if card_table.is_determined() {
|
||||||
}).collect::<Vec<_>>();
|
false
|
||||||
|
} else if card_table.probability_is_dead(&view.board) == 1.0 {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.map(|(i, card_table)| {
|
||||||
|
let p = card_table.probability_is_playable(&view.board);
|
||||||
|
(p, card_table, i)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
// sort by probability of play, then by index
|
// sort by probability of play, then by index
|
||||||
augmented_hand_info.sort_by(|&(p1, _, i1), &(p2, _, i2)| {
|
augmented_hand_info.sort_by(|&(p1, _, i1), &(p2, _, i2)| {
|
||||||
|
@ -284,12 +304,6 @@ impl InformationPlayerStrategy {
|
||||||
// }
|
// }
|
||||||
|
|
||||||
for &(_, card_table, i) in &augmented_hand_info {
|
for &(_, card_table, i) in &augmented_hand_info {
|
||||||
if card_table.is_determined() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if card_table.probability_is_dead(view.get_board()) == 1.0 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let question = CardPossibilityPartition::new(i, info_remaining, card_table, view);
|
let question = CardPossibilityPartition::new(i, info_remaining, card_table, view);
|
||||||
if add_question(&mut questions, &mut info_remaining, question) {
|
if add_question(&mut questions, &mut info_remaining, question) {
|
||||||
return questions;
|
return questions;
|
||||||
|
@ -406,28 +420,25 @@ 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: &OwnedGameView, 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) };
|
||||||
self.get_play_score(view, card) as f32
|
|
||||||
};
|
|
||||||
card_table.weighted_score(&f)
|
card_table.weighted_score(&f)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_play_score(&self, view: &OwnedGameView, card: &Card) -> i32 {
|
fn get_play_score(&self, view: &OwnedGameView, card: &Card) -> f32 {
|
||||||
|
let mut num_with = 1;
|
||||||
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 {
|
||||||
if view.has_card(&player, card) {
|
if view.has_card(&player, card) {
|
||||||
return 1;
|
num_with += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
5 + (5 - (card.value as i32))
|
(10.0 - card.value as f32) / (num_with as f32)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_useless_cards<T>(&self, view: &T, hand: &Vec<CardPossibilityTable>) -> Vec<usize>
|
fn find_useless_cards(&self, view: &OwnedGameView, hand: &Vec<CardPossibilityTable>) -> Vec<usize> {
|
||||||
where T: GameView
|
|
||||||
{
|
|
||||||
let mut useless: HashSet<usize> = HashSet::new();
|
let mut useless: HashSet<usize> = HashSet::new();
|
||||||
let mut seen: HashMap<Card, usize> = HashMap::new();
|
let mut seen: HashMap<Card, usize> = HashMap::new();
|
||||||
|
|
||||||
|
@ -451,19 +462,6 @@ impl InformationPlayerStrategy {
|
||||||
return useless_vec;
|
return useless_vec;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn someone_else_can_play(&self, view: &OwnedGameView) -> bool {
|
|
||||||
for player in view.board.get_players() {
|
|
||||||
if player != self.me {
|
|
||||||
for card in view.get_hand(&player) {
|
|
||||||
if view.board.is_playable(card) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn take_public_info(&mut self, player: &Player) -> Vec<CardPossibilityTable> {
|
fn take_public_info(&mut self, player: &Player) -> Vec<CardPossibilityTable> {
|
||||||
self.public_info.remove(player).unwrap()
|
self.public_info.remove(player).unwrap()
|
||||||
}
|
}
|
||||||
|
@ -476,6 +474,11 @@ impl InformationPlayerStrategy {
|
||||||
self.get_player_public_info(&self.me)
|
self.get_player_public_info(&self.me)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fn get_my_public_info_mut(&mut self) -> &mut Vec<CardPossibilityTable> {
|
||||||
|
// let me = self.me.clone();
|
||||||
|
// self.get_player_public_info_mut(&me)
|
||||||
|
// }
|
||||||
|
|
||||||
fn get_player_public_info(&self, player: &Player) -> &Vec<CardPossibilityTable> {
|
fn get_player_public_info(&self, player: &Player) -> &Vec<CardPossibilityTable> {
|
||||||
self.public_info.get(player).unwrap()
|
self.public_info.get(player).unwrap()
|
||||||
}
|
}
|
||||||
|
@ -521,8 +524,7 @@ impl InformationPlayerStrategy {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: decrement weight counts for fully determined cards,
|
// TODO: decrement weight counts for fully determined cards, ahead of time
|
||||||
// ahead of time
|
|
||||||
|
|
||||||
// note: other_player could be player, as well
|
// note: other_player could be player, as well
|
||||||
// in particular, we will decrement the newly drawn card
|
// in particular, we will decrement the newly drawn card
|
||||||
|
@ -548,9 +550,7 @@ impl InformationPlayerStrategy {
|
||||||
info
|
info
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_hint_index_score<T>(&self, card_table: &CardPossibilityTable, view: &T) -> i32
|
fn get_hint_index_score(&self, card_table: &CardPossibilityTable, view: &OwnedGameView) -> i32 {
|
||||||
where T: GameView
|
|
||||||
{
|
|
||||||
if card_table.probability_is_dead(view.get_board()) == 1.0 {
|
if card_table.probability_is_dead(view.get_board()) == 1.0 {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -565,9 +565,7 @@ impl InformationPlayerStrategy {
|
||||||
return score;
|
return score;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_index_for_hint<T>(&self, info: &Vec<CardPossibilityTable>, view: &T) -> usize
|
fn get_index_for_hint(&self, info: &Vec<CardPossibilityTable>, view: &OwnedGameView) -> usize {
|
||||||
where T: GameView
|
|
||||||
{
|
|
||||||
let mut scores = info.iter().enumerate().map(|(i, card_table)| {
|
let mut scores = info.iter().enumerate().map(|(i, card_table)| {
|
||||||
let score = self.get_hint_index_score(card_table, view);
|
let score = self.get_hint_index_score(card_table, view);
|
||||||
(-score, i)
|
(-score, i)
|
||||||
|
@ -680,7 +678,6 @@ impl PlayerStrategy for InformationPlayerStrategy {
|
||||||
}).collect::<Vec<_>>();
|
}).collect::<Vec<_>>();
|
||||||
|
|
||||||
if playable_cards.len() > 0 {
|
if playable_cards.len() > 0 {
|
||||||
// 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;
|
||||||
|
@ -697,8 +694,16 @@ impl PlayerStrategy for InformationPlayerStrategy {
|
||||||
return TurnChoice::Play(play_index)
|
return TurnChoice::Play(play_index)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let discard_threshold =
|
||||||
|
view.board.total_cards
|
||||||
|
- (COLORS.len() * VALUES.len()) as u32
|
||||||
|
- (view.board.num_players * view.board.hand_size);
|
||||||
|
|
||||||
|
|
||||||
// make a possibly risky play
|
// make a possibly risky play
|
||||||
if view.board.lives_remaining > 1 {
|
if view.board.lives_remaining > 1 &&
|
||||||
|
view.board.discard_size() <= discard_threshold
|
||||||
|
{
|
||||||
let mut risky_playable_cards = private_info.iter().enumerate().filter(|&(_, card_table)| {
|
let mut risky_playable_cards = private_info.iter().enumerate().filter(|&(_, card_table)| {
|
||||||
// card is either playable or dead
|
// card is either playable or dead
|
||||||
card_table.probability_of_predicate(&|card| {
|
card_table.probability_of_predicate(&|card| {
|
||||||
|
@ -711,21 +716,16 @@ impl PlayerStrategy for InformationPlayerStrategy {
|
||||||
|
|
||||||
if risky_playable_cards.len() > 0 {
|
if risky_playable_cards.len() > 0 {
|
||||||
risky_playable_cards.sort_by(|c1, c2| {
|
risky_playable_cards.sort_by(|c1, c2| {
|
||||||
c1.2.partial_cmp(&c2.2).unwrap_or(Ordering::Equal)
|
c2.2.partial_cmp(&c1.2).unwrap_or(Ordering::Equal)
|
||||||
});
|
});
|
||||||
|
|
||||||
let maybe_play = risky_playable_cards[0];
|
let maybe_play = risky_playable_cards[0];
|
||||||
if maybe_play.2 > 0.7 {
|
if maybe_play.2 > 0.75 {
|
||||||
return TurnChoice::Play(maybe_play.0);
|
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);
|
|
||||||
|
|
||||||
let public_useless_indices = self.find_useless_cards(view, &self.get_my_public_info());
|
let public_useless_indices = self.find_useless_cards(view, &self.get_my_public_info());
|
||||||
let useless_indices = self.find_useless_cards(view, &private_info);
|
let useless_indices = self.find_useless_cards(view, &private_info);
|
||||||
|
|
||||||
|
@ -742,16 +742,11 @@ impl PlayerStrategy for InformationPlayerStrategy {
|
||||||
// hinting is better than discarding dead cards
|
// hinting is better than discarding dead cards
|
||||||
// (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 view.someone_else_can_play() {
|
||||||
return self.get_hint();
|
return self.get_hint();
|
||||||
} else {
|
|
||||||
// print!("This actually happens");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 public_useless_indices.len() > 1 {
|
if public_useless_indices.len() > 1 {
|
||||||
let info = self.get_hint_sum_info(public_useless_indices.len() as u32, view);
|
let info = self.get_hint_sum_info(public_useless_indices.len() as u32, view);
|
||||||
|
@ -760,6 +755,10 @@ impl PlayerStrategy for InformationPlayerStrategy {
|
||||||
return TurnChoice::Discard(useless_indices[0]);
|
return TurnChoice::Discard(useless_indices[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOTE: the only conditions under which we would discard a potentially useful card:
|
||||||
|
// - we have no known useless cards
|
||||||
|
// - there are no hints remaining OR nobody else can play
|
||||||
|
|
||||||
// Play the best discardable card
|
// Play the best discardable card
|
||||||
let mut compval = 0.0;
|
let mut compval = 0.0;
|
||||||
let mut index = 0;
|
let mut index = 0;
|
||||||
|
@ -792,17 +791,20 @@ impl PlayerStrategy for InformationPlayerStrategy {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TurnChoice::Discard(index) => {
|
TurnChoice::Discard(index) => {
|
||||||
|
let known_useless_indices = self.find_useless_cards(
|
||||||
|
&self.last_view, &self.get_player_public_info(&turn.player)
|
||||||
|
);
|
||||||
|
|
||||||
|
if known_useless_indices.len() > 1 {
|
||||||
|
// unwrap is safe because *if* a discard happened, and there were known
|
||||||
|
// dead cards, it must be a dead card
|
||||||
|
let value = known_useless_indices.iter().position(|&i| i == index).unwrap();
|
||||||
|
self.update_from_hint_sum(ModulusInformation::new(
|
||||||
|
known_useless_indices.len() as u32, value as u32
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
if let &TurnResult::Discard(ref card) = &turn.result {
|
if let &TurnResult::Discard(ref card) = &turn.result {
|
||||||
let public_useless_indices = self.find_useless_cards(
|
|
||||||
&self.last_view, &self.get_player_public_info(&turn.player));
|
|
||||||
if public_useless_indices.len() > 1 {
|
|
||||||
// unwrap is safe because *if* a discard happened, and there were known
|
|
||||||
// dead cards, it must be a dead card
|
|
||||||
let value = public_useless_indices.iter().position(|&i| i == index).unwrap();
|
|
||||||
self.update_from_hint_sum(ModulusInformation::new(
|
|
||||||
public_useless_indices.len() as u32, value as u32
|
|
||||||
));
|
|
||||||
}
|
|
||||||
self.update_public_info_for_discard_or_play(view, &turn.player, index, card);
|
self.update_public_info_for_discard_or_play(view, &turn.player, index, card);
|
||||||
} else {
|
} else {
|
||||||
panic!("Got turn choice {:?}, but turn result {:?}",
|
panic!("Got turn choice {:?}, but turn result {:?}",
|
||||||
|
|
Loading…
Reference in a new issue