From 3f5492b2bb67326be43cd7c5ba02ccf0ba1ae0e3 Mon Sep 17 00:00:00 2001 From: Justin Wernick Date: Tue, 19 Apr 2022 21:27:56 +0200 Subject: Refile for merging repos --- 2019-worms/src/game/map.rs | 49 ++++++++ 2019-worms/src/game/player.rs | 259 +++++++++++++++++++++++++++++++++++++++++ 2019-worms/src/game/powerup.rs | 6 + 3 files changed, 314 insertions(+) create mode 100644 2019-worms/src/game/map.rs create mode 100644 2019-worms/src/game/player.rs create mode 100644 2019-worms/src/game/powerup.rs (limited to '2019-worms/src/game') diff --git a/2019-worms/src/game/map.rs b/2019-worms/src/game/map.rs new file mode 100644 index 0000000..84ec99a --- /dev/null +++ b/2019-worms/src/game/map.rs @@ -0,0 +1,49 @@ +use crate::constants::*; +use crate::geometry::*; + +#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)] +pub struct Map { + pub cells: [u64; MAP_U64S], +} + +impl Map { + fn internal_index(p: Point2d) -> Option<(usize, usize)> { + if p.y < 0 || p.y as usize >= MAP_SIZE { + None + } else { + let row_data = &MAP_ROW_SIZE[p.y as usize]; + if p.x < row_data.x_offset as i8 || p.x as usize >= row_data.x_offset + row_data.len() { + None + } else { + let global_bit = row_data.start_bit + p.x as usize - row_data.x_offset; + let integer = global_bit / 64; + let bit = global_bit % 64; + Some((integer, bit)) + } + } + } + + pub fn at(&self, p: Point2d) -> Option { + Map::internal_index(p).map(|(integer, bit)| { + let mask = 1 << bit; + self.cells[integer] & mask != 0 + }) + } + + pub fn set(&mut self, p: Point2d) { + if let Some((integer, bit)) = Map::internal_index(p) { + let mask = 1 << bit; + self.cells[integer] |= mask; + } else { + panic!("Tried to set an out of bounds bit, {:?}", p); + } + } + pub fn clear(&mut self, p: Point2d) { + if let Some((integer, bit)) = Map::internal_index(p) { + let mask = !(1 << bit); + self.cells[integer] &= mask; + } else { + panic!("Tried to set an out of bounds bit, {:?}", p); + } + } +} diff --git a/2019-worms/src/game/player.rs b/2019-worms/src/game/player.rs new file mode 100644 index 0000000..0874c76 --- /dev/null +++ b/2019-worms/src/game/player.rs @@ -0,0 +1,259 @@ +use crate::geometry::*; +use arrayvec::ArrayVec; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct Player { + pub moves_score: i32, + pub active_worm: usize, + pub select_moves: u8, + pub worms: ArrayVec<[Worm; 3]>, +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct Worm { + pub id: i32, + pub health: i32, + pub position: Point2d, + pub bombs: u8, + pub snowballs: u8, + pub rounds_until_unfrozen: u8, +} + +impl Player { + pub fn find_worm_position(&self, id: i32) -> Option { + self.worms.iter().position(|w| w.id == id) + } + + pub fn find_worm(&self, id: i32) -> Option<&Worm> { + self.worms.iter().find(|w| w.id == id) + } + + pub fn find_worm_mut(&mut self, id: i32) -> Option<&mut Worm> { + self.worms.iter_mut().find(|w| w.id == id) + } + + pub fn active_worm(&self) -> Option<&Worm> { + self.worms.get(self.active_worm) + } + + pub fn active_worm_mut(&mut self) -> Option<&mut Worm> { + self.worms.get_mut(self.active_worm) + } + + pub fn health(&self) -> i32 { + self.worms.iter().map(|w| w.health).sum() + } + + pub fn max_worm_health(&self) -> i32 { + self.worms.iter().map(|w| w.health).max().unwrap_or(0) + } + + pub fn clear_dead_worms(&mut self) { + for worm_index in (0..self.worms.len()).rev() { + if self.worms[worm_index].health <= 0 { + self.worms.remove(worm_index); + if self.active_worm >= worm_index { + self.active_worm = if self.active_worm > 0 { + self.active_worm - 1 + } else if self.worms.len() > 0 { + self.worms.len() - 1 + } else { + 0 + }; + } + } + } + } + + pub fn next_active_worm(&mut self) { + self.active_worm = (self.active_worm + 1) + .checked_rem(self.worms.len()) + .unwrap_or(0); + } + + fn health_score(&self) -> i32 { + self.health() / 3 + } + + pub fn score(&self) -> i32 { + self.moves_score + self.health_score() + } + + pub fn active_worm_is_frozen(&self) -> bool { + self.active_worm() + .map(|worm| worm.rounds_until_unfrozen > 0) + .unwrap_or(false) + } + + pub fn active_worm_is_frozen_after_tick(&self) -> bool { + self.active_worm() + .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)] +mod test { + use super::*; + + #[test] + fn clear_dead_worms_after_active_worm() { + let mut worms = ArrayVec::new(); + worms.push(Worm { + id: 1, + health: 50, + position: Point2d::new(0, 0), + rounds_until_unfrozen: 0, + bombs: 0, + snowballs: 0, + }); + worms.push(Worm { + id: 2, + health: 10, + position: Point2d::new(0, 0), + rounds_until_unfrozen: 0, + bombs: 0, + snowballs: 0, + }); + worms.push(Worm { + id: 3, + health: -2, + position: Point2d::new(0, 0), + rounds_until_unfrozen: 0, + bombs: 0, + snowballs: 0, + }); + let mut player = Player { + active_worm: 1, + moves_score: 0, + select_moves: 0, + worms, + }; + + player.clear_dead_worms(); + + assert_eq!(2, player.worms.len()); + assert_eq!(1, player.active_worm); + + assert_eq!(1, player.worms[0].id); + assert_eq!(2, player.worms[1].id); + } + + #[test] + fn clear_dead_worms_before_active_worm() { + let mut worms = ArrayVec::new(); + worms.push(Worm { + id: 1, + health: 0, + position: Point2d::new(0, 0), + rounds_until_unfrozen: 0, + bombs: 0, + snowballs: 0, + }); + worms.push(Worm { + id: 2, + health: 10, + position: Point2d::new(0, 0), + rounds_until_unfrozen: 0, + bombs: 0, + snowballs: 0, + }); + worms.push(Worm { + id: 3, + health: 2, + position: Point2d::new(0, 0), + rounds_until_unfrozen: 0, + bombs: 0, + snowballs: 0, + }); + let mut player = Player { + active_worm: 1, + moves_score: 0, + worms, + select_moves: 0, + }; + + player.clear_dead_worms(); + + assert_eq!(2, player.worms.len()); + assert_eq!(0, player.active_worm); + + assert_eq!(2, player.worms[0].id); + assert_eq!(3, player.worms[1].id); + } + + #[test] + fn clear_dead_worms_before_active_worm_wrapping() { + let mut worms = ArrayVec::new(); + worms.push(Worm { + id: 1, + health: 0, + position: Point2d::new(0, 0), + rounds_until_unfrozen: 0, + bombs: 0, + snowballs: 0, + }); + worms.push(Worm { + id: 2, + health: 10, + position: Point2d::new(0, 0), + rounds_until_unfrozen: 0, + bombs: 0, + snowballs: 0, + }); + worms.push(Worm { + id: 3, + health: 2, + position: Point2d::new(0, 0), + rounds_until_unfrozen: 0, + bombs: 0, + snowballs: 0, + }); + let mut player = Player { + active_worm: 0, + moves_score: 0, + worms, + select_moves: 0, + }; + + player.clear_dead_worms(); + + assert_eq!(2, player.worms.len()); + assert_eq!(1, player.active_worm); + + assert_eq!(2, player.worms[0].id); + assert_eq!(3, player.worms[1].id); + } + + #[test] + fn clear_last_dead_worm() { + let mut worms = ArrayVec::new(); + worms.push(Worm { + id: 1, + health: -10, + position: Point2d::new(0, 0), + rounds_until_unfrozen: 0, + bombs: 0, + snowballs: 0, + }); + let mut player = Player { + active_worm: 0, + moves_score: 0, + worms, + select_moves: 0, + }; + + player.clear_dead_worms(); + + assert_eq!(0, player.worms.len()); + // active worm is undefined in this case, but clearing the worms must not panic. + } +} diff --git a/2019-worms/src/game/powerup.rs b/2019-worms/src/game/powerup.rs new file mode 100644 index 0000000..47e73a1 --- /dev/null +++ b/2019-worms/src/game/powerup.rs @@ -0,0 +1,6 @@ +use crate::geometry::*; + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct Powerup { + pub position: Point2d, +} -- cgit v1.2.3