use crate::data::boats_layout::{BoatsLayout, Coordinates}; use crate::data::{BoatPosition, CurrentGameMapStatus, EndGameMap, GameRules}; #[derive(Debug, Copy, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)] pub enum FireResult { Missed, Hit, Sunk, Rejected, AlreadyTargetedPosition, } #[derive(Debug, Copy, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)] pub enum MapCellContent { Invalid, Nothing, TouchedBoat, SunkBoat, Boat, FailedStrike, } impl MapCellContent { pub fn letter(&self) -> &'static str { match self { MapCellContent::Invalid => "!", MapCellContent::Nothing => ".", MapCellContent::TouchedBoat => "T", MapCellContent::SunkBoat => "S", MapCellContent::Boat => "B", MapCellContent::FailedStrike => "x", } } } pub struct GameMap { rules: GameRules, boats_config: BoatsLayout, failed_strikes: Vec, successful_strikes: Vec, sunk_boats: Vec, } impl GameMap { pub fn new(rules: GameRules, boats_config: BoatsLayout) -> Self { Self { rules, boats_config, failed_strikes: vec![], successful_strikes: vec![], sunk_boats: vec![], } } pub fn get_cell_content(&self, c: Coordinates) -> MapCellContent { if !c.is_valid(&self.rules) { return MapCellContent::Invalid; } if self.failed_strikes.contains(&c) { return MapCellContent::FailedStrike; } if let Some(b) = self.boats_config.find_boat_at_position(c) { if !self.successful_strikes.contains(&c) { return MapCellContent::Boat; } if self.sunk_boats.contains(b) { return MapCellContent::SunkBoat; } return MapCellContent::TouchedBoat; } MapCellContent::Nothing } pub fn fire(&mut self, c: Coordinates) -> FireResult { if !c.is_valid(&self.rules) { return FireResult::Rejected; } if self.failed_strikes.contains(&c) || self.successful_strikes.contains(&c) { return FireResult::AlreadyTargetedPosition; } match self.boats_config.find_boat_at_position(c) { None => { self.failed_strikes.push(c); FireResult::Missed } Some(b) => { self.successful_strikes.push(c); if !b .all_coordinates() .iter() .all(|c| self.successful_strikes.contains(c)) { return FireResult::Hit; } self.sunk_boats.push(*b); if !self.rules.boats_can_touch { for c in b.neighbor_coordinates(&self.rules) { if !self.failed_strikes.contains(&c) { self.failed_strikes.push(c); } } } FireResult::Sunk } } } pub fn are_all_boat_sunk(&self) -> bool { self.sunk_boats.len() == self.boats_config.number_of_boats() } pub fn print_map(&self) { for y in 0..self.rules.map_height { for x in 0..self.rules.map_width { print!( "{} ", self.get_cell_content(Coordinates::new(x as i32, y as i32)) .letter() ); } println!(); } } pub fn current_map_status(&self, for_opponent: bool) -> CurrentGameMapStatus { CurrentGameMapStatus { boats: match for_opponent { true => BoatsLayout::new_invalid(), false => self.boats_config.clone(), }, successful_strikes: self.successful_strikes.clone(), failed_strikes: self.failed_strikes.clone(), sunk_boats: self.sunk_boats.clone(), } } pub fn final_map(&self) -> EndGameMap { EndGameMap { boats: self.boats_config.clone(), grid: (0..self.rules.map_height) .map(|y| { (0..self.rules.map_width) .map(|x| self.get_cell_content(Coordinates::new(x as i32, y as i32))) .collect::>() }) .collect::>(), } } }