Can generate random boats layout
This commit is contained in:
parent
5a19763d73
commit
e17b64a416
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
|
||||
|
Loading…
Reference in New Issue
Block a user