From cd0c5b9d8c3f6a274018e0e60a4c0b82fc3598f3 Mon Sep 17 00:00:00 2001 From: Justin Worthe Date: Fri, 9 Aug 2019 20:48:04 +0200 Subject: More filtering of silly moves --- src/command.rs | 18 ++++++++++++++++++ src/game.rs | 1 + src/game/player.rs | 8 ++++++++ src/strategy/minimax.rs | 45 ++++++++++++++++++++++++++++++++++++--------- 4 files changed, 63 insertions(+), 9 deletions(-) diff --git a/src/command.rs b/src/command.rs index c0315db..4428b4d 100644 --- a/src/command.rs +++ b/src/command.rs @@ -53,3 +53,21 @@ impl fmt::Display for Action { } } } + +impl Action { + pub fn is_attack(&self) -> bool { + use Action::*; + match self { + Shoot(_) | Bomb(_) => true, + _ => false, + } + } + + pub fn is_snowball(&self) -> bool { + use Action::*; + match self { + Snowball(_) => true, + _ => false, + } + } +} diff --git a/src/game.rs b/src/game.rs index 7ece88d..78fd965 100644 --- a/src/game.rs +++ b/src/game.rs @@ -732,6 +732,7 @@ impl GameBoard { } } + // TODO: encorporate this for earlier filtering pub fn sensible_shoot_commands( &self, player_index: usize, diff --git a/src/game/player.rs b/src/game/player.rs index 0d24e60..8b63f9f 100644 --- a/src/game/player.rs +++ b/src/game/player.rs @@ -92,6 +92,14 @@ impl Player { .map(|worm| worm.rounds_until_unfrozen > 1) .unwrap_or(false) } + + pub fn bombs(&self) -> u8 { + self.worms.iter().map(|w| w.bombs).sum() + } + + pub fn snowballs(&self) -> u8 { + self.worms.iter().map(|w| w.snowballs).sum() + } } #[cfg(test)] diff --git a/src/strategy/minimax.rs b/src/strategy/minimax.rs index 2dcd7bd..60a66b1 100644 --- a/src/strategy/minimax.rs +++ b/src/strategy/minimax.rs @@ -1,4 +1,5 @@ use crate::command::{Action, Command}; +use crate::constants::*; use crate::game::{GameBoard, SimulationOutcome}; use std::cmp; @@ -128,7 +129,7 @@ fn expand_tree(node: &mut Node, mut state: GameBoard) -> Score { if state.outcome != SimulationOutcome::Continue { score(&state) } else if let Some(commands) = node.unexplored.pop() { - // TODO: Explore preemptively doing the rollout + // TODO: Explore preemptively doing the rollout? state.simulate(commands); let score = score(&state); let unexplored = if state.outcome == SimulationOutcome::Continue { @@ -229,21 +230,37 @@ fn best_player_move(node: &Node) -> Command { fn score(state: &GameBoard) -> Score { let max_health = (state.players[0].max_worm_health() - state.players[1].max_worm_health()) as f32; + let total_health = (state.players[0].health() - state.players[1].health()) as f32; let points = (state.players[0].score() - state.players[1].score()) as f32; + let victory = match state.outcome { + SimulationOutcome::PlayerWon(0) => 1., + SimulationOutcome::PlayerWon(1) => -1., + _ => 0., + }; + + let time_to_end = MAX_ROUNDS as f32 - state.round as f32; + + let snowballs = state.players[0].snowballs() as f32 - state.players[1].snowballs() as f32; + let bombs = state.players[0].bombs() as f32 - state.players[1].bombs() as f32; const MAX_HEALTH_WEIGHT: f32 = 1.; + const TOTAL_HEALTH_WEIGHT: f32 = 1.; const POINTS_WEIGHT: f32 = 0.; const VICTORY_WEIGHT: f32 = 3000.; - // TODO: Try adding new features here, like total player health, living worms, etc + const SNOWBALL_WEIGHT: f32 = 100.; + const BOMB_WEIGHT: f32 = 100.; + + // TODO: Try adding new features here. Something about board position and ammo remaining? Ammo isn't zero sum, so is it right to represent it as such here? // TODO: Calibrate these weightings somehow? // TODO: Distance to dirt heatmap? Probably less relevant these days. Score { - val: match state.outcome { - SimulationOutcome::PlayerWon(0) => VICTORY_WEIGHT, - SimulationOutcome::PlayerWon(1) => -VICTORY_WEIGHT, - _ => max_health * MAX_HEALTH_WEIGHT + points * POINTS_WEIGHT, - }, + val: max_health * MAX_HEALTH_WEIGHT + + total_health * TOTAL_HEALTH_WEIGHT + + points * POINTS_WEIGHT + + victory * VICTORY_WEIGHT + + snowballs * SNOWBALL_WEIGHT / time_to_end + + bombs * BOMB_WEIGHT / time_to_end, } } @@ -302,7 +319,6 @@ fn pruned_moves(state: &GameBoard, player_index: usize) -> Vec { .into_iter() .filter(|command| { // TODO: Filtering out of snowball moves to only freeze opponents - // TODO: Filter bombs out to only hurt opponents // NB: These rules should pass for doing nothing, otherwise // we need some other mechanism for sticking in a do @@ -312,9 +328,20 @@ fn pruned_moves(state: &GameBoard, player_index: usize) -> Vec { let hurt_self = idle_opponent_state.players[player_index].health() < my_starting_health; let hurt_opponent = idle_opponent_state.players[opponent_index].health() < opponent_starting_health; + let frozen_opponent = idle_opponent_state.players[opponent_index] + .worms + .iter() + .any(|w| w.rounds_until_unfrozen == FREEZE_DURATION); + let is_select = command.worm.is_some(); - !hurt_self && (!is_select || hurt_opponent) + let is_attack = command.action.is_attack(); + let is_snowball = command.action.is_snowball(); + + !hurt_self + && (!is_select || hurt_opponent) + && (!is_attack || hurt_opponent) + && (!is_snowball || frozen_opponent) }) .collect() } -- cgit v1.2.3