summaryrefslogtreecommitdiff
path: root/src/game.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/game.rs')
-rw-r--r--src/game.rs379
1 files changed, 158 insertions, 221 deletions
diff --git a/src/game.rs b/src/game.rs
index 4e2f7a9..11108e3 100644
--- a/src/game.rs
+++ b/src/game.rs
@@ -34,9 +34,6 @@ pub enum SimulationOutcome {
impl GameBoard {
pub fn new(json: json::State) -> GameBoard {
- let commando_damage = json.my_player.worms[0].weapon.damage;
- let commando_range = json.my_player.worms[0].weapon.range;
-
let player = Player {
active_worm: json.active_worm_index().unwrap(),
moves_score: json.my_player.score - json.my_player.health_score(),
@@ -49,8 +46,6 @@ impl GameBoard {
id: w.id,
health: w.health,
position: Point2d::new(w.position.x, w.position.y),
- weapon_damage: commando_damage,
- weapon_range: commando_range,
rounds_until_unfrozen: w.rounds_until_unfrozen,
bombs: w.banana_bombs.as_ref().map(|b| b.count).unwrap_or(0),
snowballs: w.snowballs.as_ref().map(|b| b.count).unwrap_or(0),
@@ -70,8 +65,6 @@ impl GameBoard {
id: w.id,
health: w.health,
position: Point2d::new(w.position.x, w.position.y),
- weapon_damage: commando_damage,
- weapon_range: commando_range,
rounds_until_unfrozen: w.rounds_until_unfrozen,
bombs: if w.profession == json::WormType::Agent {
STARTING_BOMBS
@@ -110,9 +103,8 @@ impl GameBoard {
.iter()
.flatten()
.filter_map(|c| {
- c.powerup.as_ref().map(|p| Powerup {
+ c.powerup.as_ref().map(|_p| Powerup {
position: Point2d::new(c.x, c.y),
- value: p.value,
})
})
.collect(),
@@ -138,25 +130,24 @@ impl GameBoard {
}
}
- // TODO: Good enough for now, but what if these worms are
- // frozen? Then it looks like the move was a banana, but it
- // actually does nothing.
- if json
- .opponents
- .iter()
- .any(|o| o.previous_command.starts_with("banana"))
- {
- for worm in &mut self.players[1].worms {
- worm.bombs = worm.bombs.saturating_sub(1);
+ if !self.players[1].active_worm_is_frozen() {
+ if json
+ .opponents
+ .iter()
+ .any(|o| o.previous_command.starts_with("banana"))
+ {
+ for worm in &mut self.players[1].worms {
+ worm.bombs = worm.bombs.saturating_sub(1);
+ }
}
- }
- if json
- .opponents
- .iter()
- .any(|o| o.previous_command.starts_with("snowball"))
- {
- for worm in &mut self.players[1].worms {
- worm.snowballs = worm.snowballs.saturating_sub(1);
+ if json
+ .opponents
+ .iter()
+ .any(|o| o.previous_command.starts_with("snowball"))
+ {
+ for worm in &mut self.players[1].worms {
+ worm.snowballs = worm.snowballs.saturating_sub(1);
+ }
}
}
@@ -171,9 +162,8 @@ impl GameBoard {
.iter()
.flatten()
.filter_map(|c| {
- c.powerup.as_ref().map(|p| Powerup {
+ c.powerup.as_ref().map(|_p| Powerup {
position: Point2d::new(c.x, c.y),
- value: p.value,
})
})
.collect();
@@ -251,7 +241,8 @@ impl GameBoard {
// TODO: Question order of actions on the forums: https://forum.entelect.co.za/t/possible-bug-in-order-of-moves/822
self.simulate_snowballs(actions);
// This check needs to happen again because the worm may have
- // been frozen on the previous command.
+ // been frozen on the previous command. This will probably be
+ // removed after the bug is fixed.
let actions = self.identify_actions(moves);
self.simulate_shoots(actions);
@@ -372,7 +363,7 @@ impl GameBoard {
self.powerups.retain(|power| {
if power.position == worm.position {
- worm.health += power.value;
+ worm.health += HEALTH_PACK_VALUE;
false
} else {
true
@@ -511,14 +502,13 @@ impl GameBoard {
'players_loop: for player_index in 0..actions.len() {
if let Action::Shoot(dir) = actions[player_index] {
if let Some(worm) = self.players[player_index].active_worm() {
- let (center, weapon_range, weapon_damage) =
- { (worm.position, worm.weapon_range, worm.weapon_damage) };
+ let center = worm.position;
let diff = dir.as_vec();
let range = if dir.is_diagonal() {
- ((f32::from(weapon_range) + 1.) / 2f32.sqrt()).floor() as i8
+ SHOOT_RANGE_DIAGONAL
} else {
- weapon_range as i8
+ SHOOT_RANGE
};
for distance in 1..=range {
@@ -531,9 +521,9 @@ impl GameBoard {
.find(|w| w.position == target);
if let Some(target_worm) = target_own_worm {
- target_worm.health -= weapon_damage;
+ target_worm.health -= SHOOT_DAMAGE;
self.players[player_index].moves_score -=
- weapon_damage * ATTACK_SCORE_MULTIPLIER;
+ SHOOT_DAMAGE * ATTACK_SCORE_MULTIPLIER;
if target_worm.health <= 0 {
self.players[player_index].moves_score -= KILL_SCORE;
}
@@ -547,9 +537,9 @@ impl GameBoard {
.find(|w| w.position == target);
if let Some(target_worm) = target_opponent_worm {
- target_worm.health -= weapon_damage;
+ target_worm.health -= SHOOT_DAMAGE;
self.players[player_index].moves_score +=
- weapon_damage * ATTACK_SCORE_MULTIPLIER;
+ SHOOT_DAMAGE * ATTACK_SCORE_MULTIPLIER;
if target_worm.health <= 0 {
self.players[player_index].moves_score += KILL_SCORE;
}
@@ -585,217 +575,164 @@ impl GameBoard {
(player_index + 1) % 2
}
- pub fn valid_selects(&self, player_index: usize) -> ArrayVec<[i32; 2]> {
- if self.players[player_index].select_moves > 0 {
- self.players[player_index]
- .worms
- .iter()
- .enumerate()
- .filter(|(p, _w)| self.players[player_index].active_worm != *p)
- .map(|(_p, w)| w.id)
- .collect()
- } else {
- ArrayVec::new()
- }
- }
-
- pub fn valid_moves_for_worm(&self, worm: &Worm) -> ArrayVec<[Action; 8]> {
- Direction::ALL
- .iter()
- .map(Direction::as_vec)
- .map(|d| worm.position + d)
- .filter(|p| !self.occupied_cells.contains(p))
- .filter_map(|p| match self.map.at(p) {
- Some(false) => Some(Action::Move(p)),
- Some(true) => Some(Action::Dig(p)),
- _ => None,
- })
- .collect()
- }
+ fn selects_iter(&self, player_index: usize) -> impl Iterator<Item = (Option<i32>, &Worm)> {
+ let no_select = self.players[player_index]
+ .active_worm()
+ .into_iter()
+ .map(|w| (None, w));
- pub fn valid_move_commands(&self, player_index: usize) -> ArrayVec<[Command; 24]> {
- let active = self.players[player_index].active_worm();
- let no_select = active
+ let has_select_moves = self.players[player_index].select_moves > 0;
+ let active_worm_index = self.players[player_index].active_worm;
+ let selects = self.players[player_index]
+ .worms
.iter()
- .flat_map(|w| self.valid_moves_for_worm(w))
- .map(Command::new);
+ .enumerate()
+ .filter(move |(p, _w)| has_select_moves && active_worm_index != *p)
+ .map(|(_p, w)| (Some(w.id), w));
- self.valid_selects(player_index)
- .iter()
- .flat_map(|select_worm| self.players[player_index].find_worm(*select_worm))
- .flat_map(move |w| {
- self.valid_moves_for_worm(w)
- .into_iter()
- .map(move |a| Command::with_select(w.id, a))
- })
- .chain(no_select)
- .collect()
+ no_select.chain(selects)
}
- pub fn valid_shoot_commands(&self, player_index: usize) -> ArrayVec<[Command; 24]> {
- let all_dirs = Direction::ALL;
- let no_select = all_dirs.iter().map(|d| Command::new(Action::Shoot(*d)));
-
- self.valid_selects(player_index)
- .iter()
- .flat_map(|select_worm| {
- all_dirs
+ fn pruned_valid_move_commands(&self, player_index: usize) -> ArrayVec<[Command; 8]> {
+ self.players[player_index]
+ .active_worm()
+ .into_iter()
+ .flat_map(|worm| {
+ // TODO: If you aren't on lava, don't step onto the lava
+ Direction::ALL
.iter()
- .map(move |d| Command::with_select(*select_worm, Action::Shoot(*d)))
+ .map(Direction::as_vec)
+ .map(move |d| worm.position + d)
+ .filter(|p| !self.occupied_cells.contains(p))
+ .filter_map(|p| match self.map.at(p) {
+ Some(false) => Some(Action::Move(p)),
+ Some(true) => Some(Action::Dig(p)),
+ _ => None,
+ })
+ .map(Command::new)
})
- .chain(no_select)
.collect()
}
- pub fn valid_bomb_commands(&self, player_index: usize) -> Vec<Command> {
- let agent_worm = self.players[player_index]
- .worms
- .iter()
- .enumerate()
- .find(|(_p, w)| w.bombs > 0);
- match agent_worm {
- Some((worm_i, worm)) => {
- let select = if worm_i == self.players[player_index].active_worm {
- None
- } else {
- Some(worm.id)
- };
-
- if select.is_none() || self.players[player_index].select_moves > 0 {
- let mut result = Vec::with_capacity((BOMB_RANGE * 2 + 1).pow(2) as usize - 12);
-
- for y in worm.position.y - BOMB_RANGE..=worm.position.y + BOMB_RANGE {
- for x in worm.position.x - BOMB_RANGE..=worm.position.x + BOMB_RANGE {
- let target = Point2d::new(x, y);
- if self.map.at(target).is_some()
- && (worm.position - target).magnitude_squared()
- < (BOMB_RANGE + 1).pow(2)
- {
- result.push(Command {
- worm: select,
- action: Action::Bomb(target),
- });
- }
+ fn pruned_valid_bomb_commands(&self, player_index: usize) -> Vec<Command> {
+ self.selects_iter(player_index)
+ .filter(|(_, worm)| worm.bombs > 0)
+ .flat_map(|(select, worm)| {
+ let mut result = Vec::with_capacity((BOMB_RANGE * 2 + 1).pow(2) as usize - 12);
+
+ // TODO: Don't push the ones where you're hurt by the bomb
+ // TODO: Only push the ones that hurt an opponent worm
+ for y in worm.position.y - BOMB_RANGE..=worm.position.y + BOMB_RANGE {
+ for x in worm.position.x - BOMB_RANGE..=worm.position.x + BOMB_RANGE {
+ let target = Point2d::new(x, y);
+ if self.map.at(target).is_some()
+ && (worm.position - target).magnitude_squared()
+ < (BOMB_RANGE + 1).pow(2)
+ {
+ result.push(Command {
+ worm: select,
+ action: Action::Bomb(target),
+ });
}
}
-
- result
- } else {
- Vec::new()
}
- }
- None => Vec::new(),
- }
- }
-
- pub fn valid_snowball_commands(&self, player_index: usize) -> Vec<Command> {
- let tech_worm = self.players[player_index]
- .worms
- .iter()
- .enumerate()
- .find(|(_p, w)| w.snowballs > 0);
- match tech_worm {
- Some((worm_i, worm)) => {
- let select = if worm_i == self.players[player_index].active_worm {
- None
- } else {
- Some(worm.id)
- };
- if select.is_none() || self.players[player_index].select_moves > 0 {
- let mut result =
- Vec::with_capacity((SNOWBALL_RANGE * 2 + 1).pow(2) as usize - 12); // -12 is from 3 on each corner
+ result
+ })
+ .collect()
+ }
- for y in worm.position.y - SNOWBALL_RANGE..=worm.position.y + SNOWBALL_RANGE {
- for x in worm.position.x - SNOWBALL_RANGE..=worm.position.x + SNOWBALL_RANGE
+ fn pruned_valid_snowball_commands(&self, player_index: usize) -> Vec<Command> {
+ self.selects_iter(player_index)
+ .filter(|(_, worm)| worm.snowballs > 0)
+ .flat_map(|(select, worm)| {
+ let mut result = Vec::with_capacity((SNOWBALL_RANGE * 2 + 1).pow(2) as usize - 12);
+
+ // TODO: Don't push the ones where you're freezing yourself
+ // TODO: Only push the ones that freeze an opponent worm
+ for y in worm.position.y - SNOWBALL_RANGE..=worm.position.y + SNOWBALL_RANGE {
+ for x in worm.position.x - SNOWBALL_RANGE..=worm.position.x + SNOWBALL_RANGE {
+ let target = Point2d::new(x, y);
+ if self.map.at(target).is_some()
+ && (worm.position - target).magnitude_squared()
+ < (SNOWBALL_RANGE + 1).pow(2)
{
- let target = Point2d::new(x, y);
- if self.map.at(target).is_some()
- && (worm.position - target).magnitude_squared()
- < (SNOWBALL_RANGE + 1).pow(2)
- {
- result.push(Command {
- worm: select,
- action: Action::Snowball(target),
- });
- }
+ result.push(Command {
+ worm: select,
+ action: Action::Snowball(target),
+ });
}
}
-
- result
- } else {
- Vec::new()
}
- }
- None => Vec::new(),
- }
- }
- // TODO: encorporate this for earlier filtering
- pub fn sensible_shoot_commands(
- &self,
- player_index: usize,
- center: Point2d<i8>,
- weapon_range: u8,
- ) -> ArrayVec<[Command; 8]> {
- let range = weapon_range as i8;
- let dir_range = ((f32::from(weapon_range) + 1.) / 2f32.sqrt()).floor() as i8;
-
- self.players[GameBoard::opponent(player_index)]
- .worms
- .iter()
- .filter_map(|w| {
- let diff = w.position - center;
- if diff.x == 0 && diff.y.abs() <= range {
- if diff.y > 0 {
- Some((Direction::South, diff.y))
- } else {
- Some((Direction::North, -diff.y))
- }
- } else if diff.y == 0 && diff.x.abs() <= range {
- if diff.x > 0 {
- Some((Direction::East, diff.x))
- } else {
- Some((Direction::West, -diff.x))
- }
- } else if diff.x.abs() == diff.y.abs() && diff.x.abs() <= dir_range {
- match (diff.x > 0, diff.y > 0) {
- (true, true) => Some((Direction::SouthEast, diff.x)),
- (false, true) => Some((Direction::SouthWest, -diff.x)),
- (true, false) => Some((Direction::NorthEast, diff.x)),
- (false, false) => Some((Direction::NorthWest, -diff.x)),
- }
- } else {
- None
- }
+ result
})
- .filter(|(dir, range)| {
- let diff = dir.as_vec();
- // NB: This is up to range EXCLUSIVE, so if there's
- // anything in the way, even another good shot, skip.
- !(1..*range).any(|distance| {
- self.map.at(center + diff * distance) != Some(false)
- && !self
- .players
- .iter()
- .flat_map(|p| p.worms.iter())
- .any(|w| w.position == center + diff * distance)
- })
+ .collect()
+ }
+
+ fn pruned_valid_shoot_commands(&self, player_index: usize) -> Vec<Command> {
+ self.selects_iter(player_index)
+ .flat_map(|(select, worm)| {
+ self.players[GameBoard::opponent(player_index)]
+ .worms
+ .iter()
+ .filter_map(move |w| {
+ let diff = w.position - worm.position;
+ if diff.x == 0 && diff.y.abs() <= SHOOT_RANGE {
+ if diff.y > 0 {
+ Some((Direction::South, diff.y))
+ } else {
+ Some((Direction::North, -diff.y))
+ }
+ } else if diff.y == 0 && diff.x.abs() <= SHOOT_RANGE {
+ if diff.x > 0 {
+ Some((Direction::East, diff.x))
+ } else {
+ Some((Direction::West, -diff.x))
+ }
+ } else if diff.x.abs() == diff.y.abs()
+ && diff.x.abs() <= SHOOT_RANGE_DIAGONAL
+ {
+ match (diff.x > 0, diff.y > 0) {
+ (true, true) => Some((Direction::SouthEast, diff.x)),
+ (false, true) => Some((Direction::SouthWest, -diff.x)),
+ (true, false) => Some((Direction::NorthEast, diff.x)),
+ (false, false) => Some((Direction::NorthWest, -diff.x)),
+ }
+ } else {
+ None
+ }
+ })
+ .filter(move |(dir, range)| {
+ let diff = dir.as_vec();
+ // NB: This is up to range EXCLUSIVE, so if there's
+ // anything in the way, even another good shot, skip.
+ !(1..*range).any(|distance| {
+ self.map.at(worm.position + diff * distance) != Some(false)
+ && !self
+ .players
+ .iter()
+ .flat_map(|p| p.worms.iter())
+ .any(|w| w.position == worm.position + diff * distance)
+ })
+ })
+ .map(move |(dir, _range)| Command {
+ worm: select,
+ action: Action::Shoot(dir),
+ })
})
- .map(|(dir, _range)| Command::new(Action::Shoot(dir)))
.collect()
}
- // TODO: If all of this can be iterators, I can pass in the final filter to this function and only collect once.
- pub fn valid_moves(&self, player_index: usize) -> Vec<Command> {
+ pub fn pruned_valid_moves(&self, player_index: usize) -> Vec<Command> {
if self.players[player_index].active_worm_is_frozen_after_tick() {
vec![Command::new(Action::DoNothing)]
} else {
- self.valid_shoot_commands(player_index)
+ self.pruned_valid_shoot_commands(player_index)
.iter()
- .chain(self.valid_move_commands(player_index).iter())
- .chain(self.valid_bomb_commands(player_index).iter())
- .chain(self.valid_snowball_commands(player_index).iter())
+ .chain(self.pruned_valid_move_commands(player_index).iter())
+ .chain(self.pruned_valid_bomb_commands(player_index).iter())
+ .chain(self.pruned_valid_snowball_commands(player_index).iter())
.chain([Command::new(Action::DoNothing)].iter())
.cloned()
.collect()