use crate::geometry::*; use crate::command::Command; use crate::json; use arrayvec::ArrayVec; #[derive(Debug, PartialEq, Eq)] pub struct GameBoard { pub players: [Player; 2], pub powerups: ArrayVec<[Powerup; 2]>, pub map: Map, } #[derive(Debug, PartialEq, Eq)] pub struct Player { pub active_worm: usize, pub worms: ArrayVec<[Worm; 3]> } #[derive(Debug, PartialEq, Eq)] 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(Debug, PartialEq, Eq)] pub struct Map { pub size: u8, /// This is 2d, each row is size long pub cells: ArrayVec<[CellType; 2048]> } #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum CellType { Air, Dirt, DeepSpace, } #[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() }; 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: Map { size: json.map_size, cells: json.map.iter().flatten().map(|c| match c.cell_type { json::CellType::Air => CellType::Air, json::CellType::Dirt => CellType::Dirt, json::CellType::DeepSpace => CellType::DeepSpace, }).collect() } } } 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(CellType::Air), self.map.at(Point2d::new(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(CellType::Air), self.map.at(Point2d::new(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 ); worm.position.x = x; worm.position.y = y; } } } } for player_index in 0..moves.len() { if let Command::Dig(x, y) = moves[player_index] { println!("Player {} is digging {}, {}", player_index, x, y); debug_assert!( Some(CellType::Dirt) == self.map.at(Point2d::new(x, y)) || Some(CellType::Air) == self.map.at(Point2d::new(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 }; if let Some(c) = self.map.at_mut(Point2d::new(x, y)) { *c = CellType::Air; } } } 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(CellType::Air) => { 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(); } 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 } } } impl Player { pub fn find_worm(&self, id: i32) -> Option<&Worm> { self.worms .iter() .find(|w| w.id == id) } 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 { fn at(&self, p: Point2d) -> Option { if p.y < 0 || p.x < 0 || p.y as u8 >= self.size || p.x as u8 >= self.size { None } else { Some(self.cells[p.y as usize * self.size as usize + p.x as usize]) } } fn at_mut(&mut self, p: Point2d) -> Option<&mut CellType> { if p.y < 0 || p.x < 0 || p.y as u8 >= self.size || p.x as u8 >= self.size { None } else { Some(&mut self.cells[p.y as usize * self.size as usize + p.x as usize]) } } }