diff options
author | Justin Worthe <justin@worthe-it.co.za> | 2019-08-13 22:00:10 +0200 |
---|---|---|
committer | Justin Worthe <justin@worthe-it.co.za> | 2019-08-13 22:00:10 +0200 |
commit | dabedf442d65fcaec36d0a30ed3ed2327b39a44d (patch) | |
tree | a9bbcb4e97dcb98050fd872997ace284f2f764e0 /src/game.rs | |
parent | 33b9c9e05a3693d944342753288fda824f0da13c (diff) |
Trimming down on redundant code and tweaking perf
Diffstat (limited to 'src/game.rs')
-rw-r--r-- | src/game.rs | 379 |
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() |