Can generate random boats layout
This commit is contained in:
		
							
								
								
									
										1
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @@ -1019,6 +1019,7 @@ dependencies = [ | ||||
|  "clap", | ||||
|  "env_logger", | ||||
|  "log", | ||||
|  "rand", | ||||
|  "serde", | ||||
|  "serde_json", | ||||
|  "uuid", | ||||
|   | ||||
| @@ -16,4 +16,5 @@ actix-cors = "0.6.2" | ||||
| actix = "0.13.0" | ||||
| actix-web-actors = "4.1.0" | ||||
| actix-rt = "2.7.0" | ||||
| uuid = { version = "1.1.2", features = ["v4"] } | ||||
| uuid = { version = "1.1.2", features = ["v4"] } | ||||
| rand = "0.8.5" | ||||
| @@ -1,20 +1,66 @@ | ||||
| use std::io::ErrorKind; | ||||
|  | ||||
| use rand::{Rng, RngCore}; | ||||
|  | ||||
| use crate::data::GameRules; | ||||
|  | ||||
| #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Copy)] | ||||
| pub enum BoatDirection { | ||||
|     Left, | ||||
|     Right, | ||||
|     Top, | ||||
|     Bottom, | ||||
|     Up, | ||||
|     Down, | ||||
|  | ||||
|     UpLeft, | ||||
|     UpRight, | ||||
|     BottomLeft, | ||||
|     BottomRight, | ||||
| } | ||||
|  | ||||
| const _BOATS_DIRECTION: [BoatDirection; 4] = [ | ||||
|     BoatDirection::Left, | ||||
|     BoatDirection::Right, | ||||
|     BoatDirection::Up, | ||||
|     BoatDirection::Down, | ||||
| ]; | ||||
|  | ||||
| const _ALL_DIRECTIONS: [BoatDirection; 8] = [ | ||||
|     BoatDirection::Left, | ||||
|     BoatDirection::Right, | ||||
|     BoatDirection::Up, | ||||
|     BoatDirection::Down, | ||||
|     BoatDirection::UpLeft, | ||||
|     BoatDirection::UpRight, | ||||
|     BoatDirection::BottomLeft, | ||||
|     BoatDirection::BottomRight, | ||||
| ]; | ||||
|  | ||||
| impl BoatDirection { | ||||
|     pub fn boat_directions() -> &'static [BoatDirection] { | ||||
|         &_BOATS_DIRECTION | ||||
|     } | ||||
|  | ||||
|     pub fn all_directions() -> &'static [BoatDirection] { | ||||
|         &_ALL_DIRECTIONS | ||||
|     } | ||||
|  | ||||
|     pub fn is_valid_boat_direction(&self) -> bool { | ||||
|         matches!( | ||||
|             self, | ||||
|             BoatDirection::Left | BoatDirection::Right | BoatDirection::Up | BoatDirection::Down | ||||
|         ) | ||||
|     } | ||||
|  | ||||
|     pub fn shift_coordinates(&self, coordinates: Coordinates, nth: usize) -> Coordinates { | ||||
|         let shift = match self { | ||||
|             BoatDirection::Left => (1, 0), | ||||
|             BoatDirection::Right => (-1, 0), | ||||
|             BoatDirection::Top => (0, -1), | ||||
|             BoatDirection::Bottom => (0, 1), | ||||
|             BoatDirection::Up => (0, -1), | ||||
|             BoatDirection::Down => (0, 1), | ||||
|             BoatDirection::UpLeft => (-1, -1), | ||||
|             BoatDirection::UpRight => (1, -1), | ||||
|             BoatDirection::BottomLeft => (-1, 1), | ||||
|             BoatDirection::BottomRight => (1, 1), | ||||
|         }; | ||||
|  | ||||
|         Coordinates::new( | ||||
| @@ -24,22 +70,31 @@ impl BoatDirection { | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Copy, Eq, PartialEq)] | ||||
| #[derive( | ||||
| serde::Serialize, serde::Deserialize, Debug, Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, | ||||
| )] | ||||
| pub struct Coordinates { | ||||
|     x: i32, | ||||
|     y: i32, | ||||
|     x: i32, | ||||
| } | ||||
|  | ||||
| impl Coordinates { | ||||
|     pub fn new<E>(x: E, y: E) -> Self | ||||
|     where | ||||
|         E: Into<i32>, | ||||
|         where | ||||
|             E: Into<i32>, | ||||
|     { | ||||
|         Self { | ||||
|             x: x.into(), | ||||
|             y: y.into(), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn is_valid(&self, rules: &GameRules) -> bool { | ||||
|         self.x >= 0 | ||||
|             && self.y >= 0 | ||||
|             && self.x < rules.map_width as i32 | ||||
|             && self.y < rules.map_height as i32 | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Copy)] | ||||
| @@ -50,6 +105,7 @@ pub struct BoatPosition { | ||||
| } | ||||
|  | ||||
| impl BoatPosition { | ||||
|     /// Get all the coordinates occupied by the boat | ||||
|     pub fn all_coordinates(&self) -> Vec<Coordinates> { | ||||
|         let mut positions = Vec::with_capacity(self.len); | ||||
|         for i in 0..self.len { | ||||
| @@ -57,27 +113,160 @@ impl BoatPosition { | ||||
|         } | ||||
|         positions | ||||
|     } | ||||
|  | ||||
|     /// Get all the coordinates around the boats | ||||
|     pub fn neighbor_coordinates(&self, rules: &GameRules) -> Vec<Coordinates> { | ||||
|         let boat_positions = self.all_coordinates(); | ||||
|  | ||||
|         let mut neighbors = Vec::with_capacity(self.len); | ||||
|         for i in 0..self.len { | ||||
|             let boat_point = self.direction.shift_coordinates(self.start, i); | ||||
|             for dir in BoatDirection::all_directions() { | ||||
|                 let curr_point = dir.shift_coordinates(boat_point, 1); | ||||
|                 if !neighbors.contains(&curr_point) | ||||
|                     && !boat_positions.contains(&curr_point) | ||||
|                     && curr_point.is_valid(rules) | ||||
|                 { | ||||
|                     neighbors.push(curr_point); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         neighbors | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] | ||||
| pub struct BoatsLayout(Vec<BoatPosition>); | ||||
|  | ||||
| impl BoatsLayout { | ||||
|     pub fn gen_random_for_rules(rules: &GameRules) -> Self { | ||||
|         todo!() | ||||
|     /// Generate random boats layout for given game rules | ||||
|     pub fn gen_random_for_rules(rules: &GameRules) -> std::io::Result<Self> { | ||||
|         let mut boats = Self(Vec::with_capacity(rules.boats_list().len())); | ||||
|         let mut rng = rand::thread_rng(); | ||||
|  | ||||
|         for boat in rules.boats_list() { | ||||
|             let mut attempt = 0; | ||||
|             loop { | ||||
|                 attempt += 1; | ||||
|  | ||||
|                 let directions = BoatDirection::boat_directions(); | ||||
|                 let position = BoatPosition { | ||||
|                     start: Coordinates::new( | ||||
|                         (rng.next_u32() % rules.map_width as u32) as i32, | ||||
|                         (rng.next_u32() % rules.map_height as u32) as i32, | ||||
|                     ), | ||||
|                     len: boat, | ||||
|                     direction: directions[rng.gen::<usize>() % directions.len()], | ||||
|                 }; | ||||
|  | ||||
|                 if boats.is_acceptable_boat_position(&position, &rules) { | ||||
|                     boats.0.push(position); | ||||
|                     break; | ||||
|                 } | ||||
|  | ||||
|                 if attempt >= rules.map_width * rules.map_height { | ||||
|                     return Err(std::io::Error::new(ErrorKind::Other, "Un-usable game rules!")); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         Ok(boats) | ||||
|     } | ||||
|  | ||||
|     fn is_acceptable_boat_position(&self, pos: &BoatPosition, rules: &GameRules) -> bool { | ||||
|         // Check if boat coordinates are valid | ||||
|         if pos.all_coordinates().iter().any(|c| !c.is_valid(rules)) { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         // Check if the boat would cross another boat | ||||
|         if pos | ||||
|             .all_coordinates() | ||||
|             .iter() | ||||
|             .any(|c| self.find_boat_at_position(*c).is_some()) | ||||
|         { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         // Check if the boat touch another boat in a configuration where is it forbidden | ||||
|         if !rules.boats_can_touch | ||||
|             && pos | ||||
|             .neighbor_coordinates(&rules) | ||||
|             .iter() | ||||
|             .any(|c| self.find_boat_at_position(*c).is_some()) | ||||
|         { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         true | ||||
|     } | ||||
|  | ||||
|     pub fn errors(&self, rules: &GameRules) -> Vec<&str> { | ||||
|         let mut errors = vec![]; | ||||
|  | ||||
|         // Check the number of boats | ||||
|         if self.0.len() != rules.boats_list().len() { | ||||
|             errors.push("The number of boats is invalid!"); | ||||
|         } | ||||
|  | ||||
|         // Check the length of the boats | ||||
|         let mut len = self.0.iter() | ||||
|             .map(|l| l.len) | ||||
|             .collect::<Vec<_>>(); | ||||
|         len.sort(); | ||||
|         let mut boats = rules.boats_list(); | ||||
|         boats.sort(); | ||||
|  | ||||
|         if len != boats { | ||||
|             errors.push("Boats lens mismatch!"); | ||||
|         } | ||||
|  | ||||
|         for boat_i in 0..self.0.len() { | ||||
|             // Check boat coordinates | ||||
|             let boat_i_coordinates = self.0[boat_i].all_coordinates(); | ||||
|             if boat_i_coordinates.iter().any(|c| !c.is_valid(&rules)) { | ||||
|                 errors.push("A boat goes outside the game map!"); | ||||
|             } | ||||
|  | ||||
|             for boat_j in 0..self.0.len() { | ||||
|                 if boat_i == boat_j { | ||||
|                     continue; | ||||
|                 } | ||||
|  | ||||
|                 let boat_j_coords = self.0[boat_j].all_coordinates(); | ||||
|                 if boat_i_coordinates.iter().any(|c| boat_j_coords.contains(c)) { | ||||
|                     errors.push("A collision between two boats has been detected!"); | ||||
|                 } | ||||
|  | ||||
|                 if !rules.boats_can_touch && self.0[boat_i].neighbor_coordinates(&rules).iter() | ||||
|                     .any(|c| boat_j_coords.contains(c)) { | ||||
|                     errors.push("A collision between two boats has been detected!"); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|  | ||||
|         errors | ||||
|     } | ||||
|  | ||||
|     pub fn find_boat_at_position(&self, pos: Coordinates) -> Option<&BoatPosition> { | ||||
|         self.0.iter().find(|f| f.all_coordinates().contains(&pos)) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod test { | ||||
|     use crate::data::boats_layout::{BoatDirection, BoatPosition, Coordinates}; | ||||
|     use crate::data::{BotType, GameRules, PlayConfiguration}; | ||||
|     use crate::data::boats_layout::{BoatDirection, BoatPosition, BoatsLayout, Coordinates}; | ||||
|     use crate::data::game_map::GameMap; | ||||
|     use crate::game::Game; | ||||
|  | ||||
|     #[test] | ||||
|     fn get_boat_coordinates() { | ||||
|         let position = BoatPosition { | ||||
|             start: Coordinates { x: 1, y: 1 }, | ||||
|             len: 3, | ||||
|             direction: BoatDirection::Bottom, | ||||
|             direction: BoatDirection::Down, | ||||
|         }; | ||||
|         let coordinates = position.all_coordinates(); | ||||
|         assert_eq!( | ||||
| @@ -89,4 +278,109 @@ mod test { | ||||
|             ] | ||||
|         ) | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn get_boat_neighbor_coordinates() { | ||||
|         let rules = GameRules::random_players_rules(); | ||||
|         let position = BoatPosition { | ||||
|             start: Coordinates { x: 0, y: 1 }, | ||||
|             len: 3, | ||||
|             direction: BoatDirection::Down, | ||||
|         }; | ||||
|         println!("{:?}", position.all_coordinates()); | ||||
|         let mut coordinates = position.neighbor_coordinates(&rules); | ||||
|         coordinates.sort(); | ||||
|         assert_eq!( | ||||
|             coordinates, | ||||
|             vec![ | ||||
|                 Coordinates::new(0, 0), | ||||
|                 Coordinates::new(1, 0), | ||||
|                 Coordinates::new(1, 1), | ||||
|                 Coordinates::new(1, 2), | ||||
|                 Coordinates::new(1, 3), | ||||
|                 Coordinates::new(0, 4), | ||||
|                 Coordinates::new(1, 4), | ||||
|             ] | ||||
|         ) | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn generate_random_boats_layout() { | ||||
|         let rules = GameRules::random_players_rules(); | ||||
|         let boats = BoatsLayout::gen_random_for_rules(&rules).unwrap(); | ||||
|         assert!(boats.errors(&rules).is_empty()); | ||||
|         let game = GameMap::new(rules, boats); | ||||
|         game.print_map(); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn impossible_map() { | ||||
|         let mut rules = GameRules::random_players_rules(); | ||||
|         rules.map_height = PlayConfiguration::default().min_map_height; | ||||
|         rules.map_width = PlayConfiguration::default().min_map_width; | ||||
|         let mut boats = Vec::new(); | ||||
|         for i in 0..PlayConfiguration::default().max_boats_number { | ||||
|             boats.push(PlayConfiguration::default().max_boat_len); | ||||
|         } | ||||
|         rules.set_boats_list(&boats); | ||||
|         BoatsLayout::gen_random_for_rules(&rules).unwrap_err(); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn empty_boats_layout() { | ||||
|         let rules = GameRules::random_players_rules(); | ||||
|         let boats = BoatsLayout(vec![]); | ||||
|         assert!(!boats.errors(&rules).is_empty()); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn invalid_number_of_boats() { | ||||
|         let rules = GameRules::random_players_rules(); | ||||
|         let mut boats = BoatsLayout::gen_random_for_rules(&rules).unwrap(); | ||||
|         boats.0.remove(0); | ||||
|         assert!(!boats.errors(&rules).is_empty()); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn boats_crossing() { | ||||
|         let rules = GameRules::random_players_rules(); | ||||
|         let mut boats = BoatsLayout::gen_random_for_rules(&rules).unwrap(); | ||||
|         boats.0[0].start = boats.0[1].start; | ||||
|         assert!(!boats.errors(&rules).is_empty()); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn boats_touching_in_forbidden_configuration() { | ||||
|         let rules = GameRules { | ||||
|             map_width: 5, | ||||
|             map_height: 5, | ||||
|             boats_str: "1,1".to_string(), | ||||
|             boats_can_touch: false, | ||||
|             player_continue_on_hit: false, | ||||
|             bot_type: BotType::Random, | ||||
|         }; | ||||
|  | ||||
|         let mut boats = BoatsLayout(vec![ | ||||
|             BoatPosition { | ||||
|                 start: Coordinates::new(0, 0), | ||||
|                 len: 1, | ||||
|                 direction: BoatDirection::Left, | ||||
|             }, | ||||
|             BoatPosition { | ||||
|                 start: Coordinates::new(0, 1), | ||||
|                 len: 1, | ||||
|                 direction: BoatDirection::Left, | ||||
|             }, | ||||
|         ]); | ||||
|         boats.0[0].start = boats.0[1].start; | ||||
|         assert!(!boats.errors(&rules).is_empty()); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn boats_outside_map() { | ||||
|         let rules = GameRules::random_players_rules(); | ||||
|         let mut boats = BoatsLayout::gen_random_for_rules(&rules).unwrap(); | ||||
|         boats.0[0].start = Coordinates::new(-1, -1); | ||||
|         assert!(!boats.errors(&rules).is_empty()); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| use crate::data::boats_layout::BoatsLayout; | ||||
| use crate::data::boats_layout::{BoatsLayout, Coordinates}; | ||||
| use crate::data::GameRules; | ||||
|  | ||||
| enum MapCellContent { | ||||
| @@ -13,7 +13,7 @@ impl MapCellContent { | ||||
|     fn letter(&self) -> &'static str { | ||||
|         match self { | ||||
|             MapCellContent::Invalid => "!", | ||||
|             MapCellContent::Nothing => " ", | ||||
|             MapCellContent::Nothing => ".", | ||||
|             MapCellContent::TouchedBoat => "T", | ||||
|             MapCellContent::Boat => "B", | ||||
|             MapCellContent::FailedStrike => "X", | ||||
| @@ -34,15 +34,24 @@ impl GameMap { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn get_cell_content(&self, x: usize, y: usize) -> MapCellContent { | ||||
|         // TODO : improve this | ||||
|     pub fn get_cell_content(&self, c: Coordinates) -> MapCellContent { | ||||
|         //TODO : improve this | ||||
|  | ||||
|         if self.boats_config.find_boat_at_position(c).is_some() { | ||||
|             return MapCellContent::Boat; | ||||
|         } | ||||
|  | ||||
|         return MapCellContent::Nothing; | ||||
|     } | ||||
|  | ||||
|     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(x, y).letter()); | ||||
|                 print!( | ||||
|                     "{} ", | ||||
|                     self.get_cell_content(Coordinates::new(x as i32, y as i32)) | ||||
|                         .letter() | ||||
|                 ); | ||||
|             } | ||||
|             println!(); | ||||
|         } | ||||
|   | ||||
| @@ -27,6 +27,11 @@ impl GameRules { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Set the list of boats for this configuration | ||||
|     pub fn set_boats_list(&mut self, boats: &[usize]) { | ||||
|         self.boats_str = boats.iter().map(usize::to_string).collect::<Vec<_>>().join(","); | ||||
|     } | ||||
|  | ||||
|     /// Get the list of boats for this configuration | ||||
|     pub fn boats_list(&self) -> Vec<usize> { | ||||
|         self.boats_str | ||||
|   | ||||
		Reference in New Issue
	
	Block a user