use crate::geometry::*; use crate::command::Command; use crate::json; use crate::constants::*; use arrayvec::ArrayVec; #[derive(Clone)] pub struct GameBoard { pub players: [Player; 2], pub powerups: ArrayVec<[Powerup; 2]>, pub map: Map, pub outcome: SimulationOutcome } #[derive(Debug, PartialEq, Eq, Clone)] pub struct Player { pub active_worm: usize, pub worms: ArrayVec<[Worm; 3]> } #[derive(Debug, PartialEq, Eq, Clone)] pub struct Worm { pub id: i32, pub health: i32, pub position: Point2d, pub weapon_damage: i32, pub weapon_range: u8 } #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum Powerup { Health(Point2d, i32) } #[derive(Clone)] pub struct Map { pub cells: [u64; MAP_U64S] } #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum SimulationOutcome { PlayerWon(usize), Draw, Continue, } 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(), worms: json.my_player.worms.iter().map(|w| Worm { id: w.id, health: w.health, position: Point2d::new(w.position.x, w.position.y), weapon_damage: commando_damage, weapon_range: commando_range }).collect() }; let opponent = Player { active_worm: 0, worms: json.opponents.iter().flat_map(|o| &o.worms).map(|w| Worm { id: w.id, health: w.health, position: Point2d::new(w.position.x, w.position.y), weapon_damage: commando_damage, weapon_range: commando_range }).collect() }; let mut map = Map { cells: [0; MAP_U64S] }; for cell in json.map.iter().flatten() { if cell.cell_type == json::CellType::Dirt { map.set(Point2d::new(cell.x, cell.y)) } } GameBoard { players: [player, opponent], powerups: json.map.iter().flatten().filter_map(|c| { c.powerup.clone().map(|p| Powerup::Health(Point2d::new(c.x, c.y), p.value)) }).collect(), map, outcome: SimulationOutcome::Continue } } pub fn update(&mut self, _json: json::State) { // TODO // What can change? // - Worm health (and dead worms die) // - Active worms += 1 // - The worms may move // - The powerups may be taken // - The map cells may change from dirt to not dirt } pub fn simulate(&mut self, moves: [Command; 2]) -> SimulationOutcome { match moves { [Command::Move(x1, y1), Command::Move(x2, y2)] if x1 == x2 && y1 == y2 => { // TODO: Get this from some sort of config rather let damage = 20; debug_assert_eq!(Some(false), self.map.at(Point2d::new(x1, y1)), "Movement target wasn't empty, ({}, {})", x1, y1); // Worms have a 50% chance of swapping places // here. I'm treating that as an edge case that I // don't need to handle for now. for player in &mut self.players { player.active_worm_mut().health -= damage; } }, _ => { for player_index in 0..moves.len() { if let Command::Move(x, y) = moves[player_index] { debug_assert_eq!(Some(false), self.map.at(Point2d::new(x, y)), "Movement target wasn't empty, ({}, {})", x, y); let worm = self.players[player_index].active_worm_mut(); debug_assert!( (worm.position.x - x).abs() <= 1 && (worm.position.y - y).abs() <= 1, "Tried to move too far away, ({}, {})", x, y ); worm.position.x = x; worm.position.y = y; } } } } for player_index in 0..moves.len() { if let Command::Dig(x, y) = moves[player_index] { debug_assert!( Some(true) == self.map.at(Point2d::new(x, y)) || (player_index == 1 && moves[0] == Command::Dig(x, y)), "Tried to dig through air, ({}, {})", x, y ); debug_assert!{ (self.players[player_index].active_worm().position.x - x).abs() <= 1 && (self.players[player_index].active_worm().position.y - y).abs() <= 1, "Tried to dig too far away, ({}, {})", x, y }; self.map.clear(Point2d::new(x, y)); } } for player_index in 0..moves.len() { if let Command::Shoot(dir) = moves[player_index] { let (center, weapon_range, weapon_damage) = { let worm = self.players[player_index].active_worm(); (worm.position, worm.weapon_range, worm.weapon_damage) }; let diff = dir.as_vec(); let range = if dir.is_diagonal() { ((weapon_range as f32 + 1.) / 2f32.sqrt()).floor() as i8 } else { weapon_range as i8 }; for distance in 1..=range { let target = center + diff * distance; match self.map.at(target) { Some(false) => { let target_worm: Option<&mut Worm> = self.players.iter_mut() .flat_map(|p| p.worms.iter_mut()) .find(|w| w.position == target); if let Some(target_worm) = target_worm { target_worm.health -= weapon_damage; break; } }, _ => break } } } } for player in &mut self.players { // Remove dead worms for worm_index in (0..player.worms.len()).rev() { if player.worms[worm_index].health <= 0 { player.worms.remove(worm_index); if player.active_worm >= worm_index { if player.active_worm > 0 { player.active_worm -= 1; } else { player.active_worm = player.worms.len()-1; } } } } // Update the active worm player.active_worm = (player.active_worm + 1) % player.worms.len(); } self.outcome = match (self.players[0].worms.len(), self.players[1].worms.len()) { (0, 0) => SimulationOutcome::Draw, (_, 0) => SimulationOutcome::PlayerWon(0), (0, _) => SimulationOutcome::PlayerWon(1), _ => SimulationOutcome::Continue }; self.outcome } pub fn find_target(&self, center: Point2d, dir: Direction, weapon_range: u8) -> Option<&Worm> { let diff = dir.as_vec(); let range = if dir.is_diagonal() { ((weapon_range as f32 + 1.) / 2f32.sqrt()).floor() as i8 } else { weapon_range as i8 }; let mut target_worm: Option<&Worm> = None; for distance in 1..=range { let target = center + diff * distance; match self.map.at(target) { Some(false) => { target_worm = self.players.iter() .flat_map(|p| p.worms.iter()) .find(|w| w.position == target); if target_worm.is_some() { break; } }, _ => break } } target_worm } } impl Player { pub fn find_worm(&self, id: i32) -> Option<&Worm> { self.worms .iter() .find(|w| w.id == id) } pub fn active_worm(&self) -> &Worm { &self.worms[self.active_worm] } fn active_worm_mut(&mut self) -> &mut Worm { &mut self.worms[self.active_worm] } } impl Map { pub fn at(&self, p: Point2d) -> Option { 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; let mask = 1 << bit; Some(self.cells[integer] & mask != 0) } } } fn set(&mut self, p: Point2d) { if p.y < 0 || p.y as usize >= MAP_SIZE { debug_assert!(false, "Tried to set an out of bounds bit, {:?}", p); } 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() { debug_assert!(false, "Tried to set an out of bounds bit, {:?}", p); } 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; let mask = 1 << bit; self.cells[integer] |= mask; } } } fn clear(&mut self, p: Point2d) { if p.y < 0 || p.y as usize >= MAP_SIZE { debug_assert!(false, "Tried to set an out of bounds bit, {:?}", p); } 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() { debug_assert!(false, "Tried to set an out of bounds bit, {:?}", p); } 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; let mask = !(1 << bit); self.cells[integer] &= mask; } } } }