summaryrefslogtreecommitdiff
path: root/2019-worms/src/game
diff options
context:
space:
mode:
Diffstat (limited to '2019-worms/src/game')
-rw-r--r--2019-worms/src/game/map.rs49
-rw-r--r--2019-worms/src/game/player.rs259
-rw-r--r--2019-worms/src/game/powerup.rs6
3 files changed, 314 insertions, 0 deletions
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<bool> {
+ 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<usize> {
+ 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,
+}