summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJustin Worthe <justin@worthe-it.co.za>2019-08-09 20:48:04 +0200
committerJustin Worthe <justin@worthe-it.co.za>2019-08-09 20:48:04 +0200
commitcd0c5b9d8c3f6a274018e0e60a4c0b82fc3598f3 (patch)
treef67d209f7f23552ebefbfbd29e60596d344fa2c1
parent74becec945c19bad637cb2760ca30da9b4d395f8 (diff)
More filtering of silly moves
-rw-r--r--src/command.rs18
-rw-r--r--src/game.rs1
-rw-r--r--src/game/player.rs8
-rw-r--r--src/strategy/minimax.rs45
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<Command> {
.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<Command> {
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()
}