From dd6111dce329091ded2ecff4a40bc6b25ef09d05 Mon Sep 17 00:00:00 2001 From: Kristofers Solo Date: Wed, 25 Dec 2024 17:06:22 +0200 Subject: [PATCH 01/11] docs: fix typos --- src/builder.rs | 158 ++++++++-------- src/walls.rs | 492 ++++++++++++++++++++++++++----------------------- 2 files changed, 340 insertions(+), 310 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index f62204c..be3c77b 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -24,58 +24,60 @@ pub enum MazeBuilderError { GenerationError(String), } -/// A builder pattern for creating hexagonal mazes. -/// -/// This struct provides a fluent interface for configuring and building hexagonal mazes. -/// It offers flexibility in specifying the maze size, random seed, and generation algorithm. -/// -/// # Examples -/// -/// Basic usage: -/// ```rust -/// use hexlab::prelude::*; -/// -/// let maze = MazeBuilder::new() -/// .with_radius(5) -/// .build() -/// .expect("Failed to create maze"); -/// -/// // A radius of 5 creates 61 hexagonal tiles -/// assert!(!maze.is_empty()); -/// assert_eq!(maze.len(), 91); -/// ``` -/// -/// Using a seed for reproducible results: -/// ```rust -/// use hexlab::prelude::*; -/// -/// let maze1 = MazeBuilder::new() -/// .with_radius(3) -/// .with_seed(12345) -/// .build() -/// .expect("Failed to create maze"); -/// -/// let maze2 = MazeBuilder::new() -/// .with_radius(3) -/// .with_seed(12345) -/// .build() -/// .expect("Failed to create maze"); -/// -/// // Same seed should produce identical mazes -/// assert_eq!(maze1.len(), maze2.len()); -/// assert_eq!(maze1, maze2); -/// ``` -/// -/// Specifying a custom generator: -/// ```rust -/// use hexlab::prelude::*; -/// -/// let maze = MazeBuilder::new() -/// .with_radius(7) -/// .with_generator(GeneratorType::RecursiveBacktracking) -/// .build() -/// .expect("Failed to create maze"); -/// ``` +/** +A builder pattern for creating hexagonal mazes. + +This struct provides a fluent interface for configuring and building hexagonal mazes. +It offers flexibility in specifying the maze size, random seed, and generation algorithm. + +# Examples + +Basic usage: +``` +use hexlab::prelude::*; + +let maze = MazeBuilder::new() + .with_radius(5) + .build() + .expect("Failed to create maze"); + +// A radius of 5 creates 61 hexagonal tiles +assert!(!maze.is_empty()); +assert_eq!(maze.len(), 91); +``` + +Using a seed for reproducible results: +``` +use hexlab::prelude::*; + +let maze1 = MazeBuilder::new() + .with_radius(3) + .with_seed(12345) + .build() + .expect("Failed to create maze"); + +let maze2 = MazeBuilder::new() + .with_radius(3) + .with_seed(12345) + .build() + .expect("Failed to create maze"); + +// Same seed should produce identical mazes +assert_eq!(maze1.len(), maze2.len()); +assert_eq!(maze1, maze2); +``` + +Specifying a custom generator: +``` +use hexlab::prelude::*; + +let maze = MazeBuilder::new() + .with_radius(7) + .with_generator(GeneratorType::RecursiveBacktracking) + .build() + .expect("Failed to create maze"); +``` +*/ #[allow(clippy::module_name_repetitions)] #[derive(Default)] pub struct MazeBuilder { @@ -138,31 +140,33 @@ impl MazeBuilder { self } - /// Builds the hexagonal maze based on the configured parameters. - /// - /// # Errors - /// - /// Returns [`MazeBuilderError::NoRadius`] if no radius is specified. - /// Returns [`MazeBuilderError::InvalidStartPosition`] if the start position is outside maze bounds. - /// - /// # Examples - /// - /// ```rust - /// use hexlab::prelude::*; - /// - /// // Should fail without radius - /// let result = MazeBuilder::new().build(); - /// assert!(result.is_err()); - /// - /// // Should succeed with radius - /// let result = MazeBuilder::new() - /// .with_radius(3) - /// .build(); - /// assert!(result.is_ok()); - /// - /// let maze = result.unwrap(); - /// assert!(!maze.is_empty()); - /// ``` + /** + Builds the hexagonal maze based on the configured parameters. + + # Errors + + Returns [`MazeBuilderError::NoRadius`] if no radius is specified. + Returns [`MazeBuilderError::InvalidStartPosition`] if the start position is outside maze bounds. + + # Examples + + ``` + use hexlab::prelude::*; + + // Should fail without radius + let result = MazeBuilder::new().build(); + assert!(result.is_err()); + + // Should succeed with radius + let result = MazeBuilder::new() + .with_radius(3) + .build(); + assert!(result.is_ok()); + + let maze = result.unwrap(); + assert!(!maze.is_empty()); + ``` + */ pub fn build(self) -> Result { let radius = self.radius.ok_or(MazeBuilderError::NoRadius)?; let mut maze = create_hex_maze(radius); diff --git a/src/walls.rs b/src/walls.rs index d919bfb..c4b9981 100644 --- a/src/walls.rs +++ b/src/walls.rs @@ -2,46 +2,48 @@ use bevy::prelude::*; use hexx::EdgeDirection; -/// A bit-flag representation of walls in a hexagonal tile. -/// -/// `Walls` uses an efficient bit-flag system to track the presence or absence of walls -/// along each edge of a hexagonal tile. Each of the six possible walls is represented -/// by a single bit in an 8-bit integer, allowing for fast operations and minimal memory usage. -/// -/// # Examples -/// -/// Creating and manipulating walls: -/// ```rust -/// use hexlab::prelude::*; -/// -/// // Create a hexagon with all walls -/// let walls = Walls::new(); -/// assert!(walls.is_closed()); -/// -/// // Create a hexagon with no walls -/// let mut walls = Walls::empty(); -/// assert!(walls.is_empty()); -/// -/// // Add specific walls -/// walls.add(EdgeDirection::FLAT_NORTH); -/// walls.add(EdgeDirection::FLAT_SOUTH); -/// assert_eq!(walls.count(), 2); -/// ``` -/// -/// Using walls in game logic: -/// -/// ```rust -/// use hexlab::prelude::*; -/// let mut walls = Walls::empty(); -/// -/// // Add walls to create a corner -/// walls.add(EdgeDirection::FLAT_NORTH); -/// walls.add(EdgeDirection::FLAT_SOUTH_EAST); -/// -/// // Check if a specific direction has a wall -/// assert!(walls.contains(EdgeDirection::FLAT_NORTH)); -/// assert!(!walls.contains(EdgeDirection::FLAT_SOUTH)); -/// ``` +/** +A bit-flag representation of walls in a hexagonal tile. + +`Walls` uses an efficient bit-flag system to track the presence or absence of walls +along each edge of a hexagonal tile. Each of the six possible walls is represented +by a single bit in an 8-bit integer, allowing for fast operations and minimal memory usage. + +# Examples + +Creating and manipulating walls: +``` +use hexlab::prelude::*; + +// Create a hexagon with all walls +let walls = Walls::new(); +assert!(walls.is_closed()); + +// Create a hexagon with no walls +let mut walls = Walls::empty(); +assert!(walls.is_empty()); + +// Add specific walls +walls.add(EdgeDirection::FLAT_NORTH); +walls.add(EdgeDirection::FLAT_SOUTH); +assert_eq!(walls.count(), 2); +``` + +Using walls in game logic: + +``` +use hexlab::prelude::*; +let mut walls = Walls::empty(); + +// Add walls to create a corner +walls.add(EdgeDirection::FLAT_NORTH); +walls.add(EdgeDirection::FLAT_SOUTH_EAST); + +// Check if a specific direction has a wall +assert!(walls.contains(EdgeDirection::FLAT_NORTH)); +assert!(!walls.contains(EdgeDirection::FLAT_SOUTH)); +``` +*/ #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "bevy_reflect", derive(Reflect))] #[cfg_attr(feature = "bevy", derive(Component))] @@ -50,81 +52,89 @@ use hexx::EdgeDirection; pub struct Walls(u8); impl Walls { - /// Creates a new set of walls with all edges closed. - /// - /// This is the default state where all six edges of the hexagon have walls. - /// - /// # Examples - /// - /// ```rust - /// use hexlab::prelude::*; - /// - /// let walls = Walls::new(); - /// assert!(walls.is_closed()); - /// assert_eq!(walls.count(), 6); - /// ``` + /** + Creates a new set of walls with all edges closed. + + This is the default state where all six edges of the hexagon have walls. + + # Examples + + ``` + use hexlab::prelude::*; + + let walls = Walls::new(); + assert!(walls.is_closed()); + assert_eq!(walls.count(), 6); + ``` + */ #[inline] #[must_use] pub fn new() -> Self { Self::default() } - /// Creates a new set of walls with no edges (completely open). - /// - /// # Examples - /// - /// ```rust - /// use hexlab::prelude::*; - /// - /// let walls = Walls::empty(); - /// assert!(walls.is_empty()); - /// assert_eq!(walls.count(), 0); - /// ``` + /** + Creates a new set of walls with no edges (completely open). + + # Examples + + ``` + use hexlab::prelude::*; + + let walls = Walls::empty(); + assert!(walls.is_empty()); + assert_eq!(walls.count(), 0); + ``` + */ #[inline] #[must_use] pub const fn empty() -> Self { Self(0) } - /// Checks if the walls are currently empty - /// - /// Returns `true` if all directions have no walls set. - /// # Examples - /// - /// ```rust - /// use hexlab::prelude::*; - /// - /// let walls = Walls::empty(); - /// assert!(walls.is_empty()); - /// - /// let walls = Walls::new(); - /// assert!(!walls.is_empty()); - /// ``` + /** + Checks if the walls are currently empty + + Returns `true` if all directions have no walls set. + # Examples + + ``` + use hexlab::prelude::*; + + let walls = Walls::empty(); + assert!(walls.is_empty()); + + let walls = Walls::new(); + assert!(!walls.is_empty()); + ``` + */ #[inline] #[must_use] pub const fn is_empty(&self) -> bool { self.0 == 0 } - /// Adds a wall in the specified direction - /// - /// This method uses bitwise operations to efficiently set the wall flag - /// for the given direction. Multiple walls can be added to the same hexagon. - /// - /// # Examples - /// - /// ```rust - /// use hexlab::prelude::*; - /// - /// let mut walls = Walls::empty(); - /// walls.add(EdgeDirection::FLAT_NORTH); - /// assert!(walls.contains(EdgeDirection::FLAT_NORTH)); - /// assert!(!walls.contains(EdgeDirection::FLAT_SOUTH)); - /// - /// walls.add(EdgeDirection::FLAT_SOUTH); - /// assert!(walls.contains(EdgeDirection::FLAT_SOUTH)); - /// assert_eq!(walls.count(), 2); - /// ``` + /** + Adds a wall in the specified direction + + This method uses bitwise operations to efficiently set the wall flag + for the given direction. Multiple walls can be added to the same hexagon. + + # Examples + + ``` + use hexlab::prelude::*; + + let mut walls = Walls::empty(); + walls.add(EdgeDirection::FLAT_NORTH); + assert!(walls.contains(EdgeDirection::FLAT_NORTH)); + assert!(!walls.contains(EdgeDirection::FLAT_SOUTH)); + + walls.add(EdgeDirection::FLAT_SOUTH); + assert!(walls.contains(EdgeDirection::FLAT_SOUTH)); + assert_eq!(walls.count(), 2); + ``` + */ #[inline] pub fn add(&mut self, direction: T) where @@ -133,23 +143,25 @@ impl Walls { self.0 |= direction.into().0; } - /// Removes a wall in the specified direction - /// - /// Returns `true` if a wall was actually removed, `false` if there was no wall - /// in the specified direction. - /// - /// # Exmaples - /// - /// ```rust - /// use hexlab::prelude::*; - /// - /// let mut walls = Walls::new(); - /// assert!(walls.remove(EdgeDirection::FLAT_NORTH)); - /// assert!(!walls.contains(EdgeDirection::FLAT_NORTH)); - /// - /// // Removing a non-existent wall returns false - /// assert!(!walls.remove(EdgeDirection::FLAT_NORTH)); - /// ``` + /** + Removes a wall in the specified direction + + Returns `true` if a wall was actually removed, `false` if there was no wall + in the specified direction. + + # Examples + + ``` + use hexlab::prelude::*; + + let mut walls = Walls::new(); + assert!(walls.remove(EdgeDirection::FLAT_NORTH)); + assert!(!walls.contains(EdgeDirection::FLAT_NORTH)); + + // Removing a non-existent wall returns false + assert!(!walls.remove(EdgeDirection::FLAT_NORTH)); + ``` + */ #[inline] pub fn remove(&mut self, direction: T) -> bool where @@ -162,21 +174,23 @@ impl Walls { was_removed } - /// Returns true if there is a wall in the specified direction - /// - /// Uses efficient bitwise operations to check for the presence of a wall. - /// - /// # Exmaples - /// - /// ```rust - /// use hexlab::prelude::*; - /// - /// let mut walls = Walls::empty(); - /// - /// walls.add(EdgeDirection::FLAT_NORTH); - /// assert!(walls.contains(EdgeDirection::FLAT_NORTH)); - /// assert!(!walls.contains(EdgeDirection::FLAT_SOUTH)); - /// ``` + /** + Returns true if there is a wall in the specified direction + + Uses efficient bitwise operations to check for the presence of a wall. + + # Examples + + ``` + use hexlab::prelude::*; + + let mut walls = Walls::empty(); + + walls.add(EdgeDirection::FLAT_NORTH); + assert!(walls.contains(EdgeDirection::FLAT_NORTH)); + assert!(!walls.contains(EdgeDirection::FLAT_SOUTH)); + ``` + */ #[inline] pub fn contains(&self, other: T) -> bool where @@ -185,90 +199,98 @@ impl Walls { self.0 & other.into().0 != 0 } - /// Returns the raw bit representation of the walls - /// - /// This method provides access to the underlying bit flags for advanced usage. - /// The bits are ordered according to the `EdgeDirection` indices. - /// - /// # Exmaples - /// - /// ```rust - /// use hexlab::prelude::*; - /// - /// let mut walls = Walls::new(); - /// - /// assert_eq!(walls.as_bits(), 0b111111); - /// ``` + /** + Returns the raw bit representation of the walls + + This method provides access to the underlying bit flags for advanced usage. + The bits are ordered according to the `EdgeDirection` indices. + + # Examples + + ``` + use hexlab::prelude::*; + + let mut walls = Walls::new(); + + assert_eq!(walls.as_bits(), 0b111111); + ``` + */ #[inline] #[must_use] pub const fn as_bits(&self) -> u8 { self.0 } - /// Returns the total number of walls present - /// - /// Efficiently counts the number of set bits in the internal representation. - /// - /// # Exmaples - /// - /// ```rust - /// use hexlab::prelude::*; - /// - /// let mut walls = Walls::empty(); - /// - /// assert_eq!(walls.count(), 0); - /// - /// walls.add(EdgeDirection::FLAT_NORTH); - /// walls.add(EdgeDirection::FLAT_SOUTH); - /// assert_eq!(walls.count(), 2); - /// ``` + /** + Returns the total number of walls present + + Efficiently counts the number of set bits in the internal representation. + + # Examples + + ``` + use hexlab::prelude::*; + + let mut walls = Walls::empty(); + + assert_eq!(walls.count(), 0); + + walls.add(EdgeDirection::FLAT_NORTH); + walls.add(EdgeDirection::FLAT_SOUTH); + assert_eq!(walls.count(), 2); + ``` + */ #[inline] #[must_use] pub fn count(&self) -> u8 { u8::try_from(self.0.count_ones()).unwrap_or_default() } - /// Returns all possible directions as a `Walls` value - /// - /// This represents a hexagon with walls in all six directions. - /// - /// # Exmaples - /// - /// ```rust - /// use hexlab::prelude::*; - /// - /// let all_walls = Walls::all_directions(); - /// - /// assert_eq!(all_walls.count(), 6); - /// assert!(all_walls.is_closed()); - /// ``` + /** + Returns all possible directions as a `Walls` value + + This represents a hexagon with walls in all six directions. + + # Examples + + ``` + use hexlab::prelude::*; + + let all_walls = Walls::all_directions(); + + assert_eq!(all_walls.count(), 6); + assert!(all_walls.is_closed()); + ``` + */ #[inline] #[must_use] pub const fn all_directions() -> Self { Self(0b11_1111) } - /// Toggles a wall in the specified direction. - /// - /// If a wall exists in the given direction, it will be removed. - /// If no wall exists, one will be added. - /// Returns the previous state (`true` if a wall was present). - /// - /// # Examples - /// - /// ```rust - /// use hexlab::prelude::*; - /// - /// let mut walls = Walls::empty(); - /// - /// assert!(!walls.toggle(EdgeDirection::FLAT_NORTH)); // Returns false, wall was not present - /// assert!(walls.contains(EdgeDirection::FLAT_NORTH)); // Wall is now present - /// - /// let mut walls = Walls::new(); - /// - /// assert!(walls.toggle(EdgeDirection::FLAT_NORTH)); // Returns true, wall was present - /// assert!(!walls.contains(EdgeDirection::FLAT_NORTH)); // Wall is now removed - /// ``` + /** + Toggles a wall in the specified direction. + + If a wall exists in the given direction, it will be removed. + If no wall exists, one will be added. + Returns the previous state (`true` if a wall was present). + + # Examples + + ``` + use hexlab::prelude::*; + + let mut walls = Walls::empty(); + + assert!(!walls.toggle(EdgeDirection::FLAT_NORTH)); // Returns false, wall was not present + assert!(walls.contains(EdgeDirection::FLAT_NORTH)); // Wall is now present + + let mut walls = Walls::new(); + + assert!(walls.toggle(EdgeDirection::FLAT_NORTH)); // Returns true, wall was present + assert!(!walls.contains(EdgeDirection::FLAT_NORTH)); // Wall is now removed + ``` + */ pub fn toggle(&mut self, direction: T) -> bool where T: Into + Copy, @@ -282,50 +304,54 @@ impl Walls { is_present } - /// Checks if walls are present in all six directions. - /// - /// Returns `true` if the hexagon has all possible walls, making it completely enclosed. - /// - /// # Examples - /// - /// ```rust - /// use hexlab::prelude::*; - /// - /// let walls = Walls::new(); - /// assert!(walls.is_closed()); - /// - /// let mut walls = Walls::empty(); - /// assert!(!walls.is_closed()); - /// // Add all walls manually - /// for direction in EdgeDirection::iter() { - /// walls.add(direction); - /// } - /// assert!(walls.is_closed()); - /// ``` + /** + Checks if walls are present in all six directions. + + Returns `true` if the hexagon has all possible walls, making it completely enclosed. + + # Examples + + ``` + use hexlab::prelude::*; + + let walls = Walls::new(); + assert!(walls.is_closed()); + + let mut walls = Walls::empty(); + assert!(!walls.is_closed()); + // Add all walls manually + for direction in EdgeDirection::iter() { + walls.add(direction); + } + assert!(walls.is_closed()); + ``` + */ #[inline] #[must_use] pub fn is_closed(&self) -> bool { self.count() == 6 } - /// Sets walls for multiple directions at once. - /// - /// This method efficiently adds multiple walls in a single operation while - /// preserving any existing walls not specified in the input. - /// - /// # Examples - /// - /// ```rust - /// use hexlab::prelude::*; - /// - /// let mut walls = Walls::empty(); - /// walls.add(EdgeDirection::FLAT_NORTH); - /// - /// walls.fill([EdgeDirection::FLAT_SOUTH, EdgeDirection::FLAT_SOUTH_EAST]); - /// - /// assert!(walls.contains(EdgeDirection::FLAT_SOUTH)); - /// assert_eq!(walls.count(), 3); - /// ``` + /** + Sets walls for multiple directions at once. + + This method efficiently adds multiple walls in a single operation while + preserving any existing walls not specified in the input. + + # Examples + + ``` + use hexlab::prelude::*; + + let mut walls = Walls::empty(); + walls.add(EdgeDirection::FLAT_NORTH); + + walls.fill([EdgeDirection::FLAT_SOUTH, EdgeDirection::FLAT_SOUTH_EAST]); + + assert!(walls.contains(EdgeDirection::FLAT_SOUTH)); + assert_eq!(walls.count(), 3); + ``` + */ #[inline] pub fn fill(&mut self, other: T) where From 7e5abb9a79c21597ddca1bdc2da49d43a4531d77 Mon Sep 17 00:00:00 2001 From: Kristofers Solo Date: Wed, 25 Dec 2024 17:07:27 +0200 Subject: [PATCH 02/11] chore: bump version --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4340e83..1f3a04c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2295,7 +2295,7 @@ checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" [[package]] name = "hexlab" -version = "0.3.0" +version = "0.3.1" dependencies = [ "bevy", "hexx", diff --git a/Cargo.toml b/Cargo.toml index 794822a..13f3e3f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "hexlab" authors = ["Kristofers Solo "] -version = "0.3.0" +version = "0.3.1" edition = "2021" description = "A hexagonal maze generation and manipulation library" repository = "https://github.com/kristoferssolo/hexlab" From 83f2e47e2763dfaf4a1839303ba4c57ce7f60c10 Mon Sep 17 00:00:00 2001 From: Kristofers Solo Date: Wed, 25 Dec 2024 18:01:20 +0200 Subject: [PATCH 03/11] fix: typo --- src/hex_maze.rs | 2 +- src/hex_tile.rs | 2 +- src/walls.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/hex_maze.rs b/src/hex_maze.rs index 965c291..e3a7b28 100644 --- a/src/hex_maze.rs +++ b/src/hex_maze.rs @@ -84,7 +84,7 @@ impl DerefMut for HexMaze { } #[cfg(test)] -mod tests { +mod test { use super::*; #[test] diff --git a/src/hex_tile.rs b/src/hex_tile.rs index f9421e2..93fd99d 100644 --- a/src/hex_tile.rs +++ b/src/hex_tile.rs @@ -73,7 +73,7 @@ impl Display for HexTile { } #[cfg(test)] -mod tests { +mod test { use hexx::EdgeDirection; use super::*; diff --git a/src/walls.rs b/src/walls.rs index c4b9981..e03fa63 100644 --- a/src/walls.rs +++ b/src/walls.rs @@ -396,7 +396,7 @@ impl Default for Walls { } #[cfg(test)] -mod tests { +mod test { use super::*; // all_directions From cd4f369108490bbd8224269f97db76fdfc914cf2 Mon Sep 17 00:00:00 2001 From: Kristofers Solo Date: Wed, 25 Dec 2024 19:00:28 +0200 Subject: [PATCH 04/11] docs: update --- src/builder.rs | 180 ++++++++++++------------ src/hex_maze.rs | 38 ++++- src/hex_tile.rs | 19 ++- src/walls.rs | 362 ++++++++++++++---------------------------------- 4 files changed, 246 insertions(+), 353 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index be3c77b..c20b4b6 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -24,60 +24,59 @@ pub enum MazeBuilderError { GenerationError(String), } -/** -A builder pattern for creating hexagonal mazes. - -This struct provides a fluent interface for configuring and building hexagonal mazes. -It offers flexibility in specifying the maze size, random seed, and generation algorithm. - -# Examples - -Basic usage: -``` -use hexlab::prelude::*; - -let maze = MazeBuilder::new() - .with_radius(5) - .build() - .expect("Failed to create maze"); - -// A radius of 5 creates 61 hexagonal tiles -assert!(!maze.is_empty()); -assert_eq!(maze.len(), 91); -``` - -Using a seed for reproducible results: -``` -use hexlab::prelude::*; - -let maze1 = MazeBuilder::new() - .with_radius(3) - .with_seed(12345) - .build() - .expect("Failed to create maze"); - -let maze2 = MazeBuilder::new() - .with_radius(3) - .with_seed(12345) - .build() - .expect("Failed to create maze"); - -// Same seed should produce identical mazes -assert_eq!(maze1.len(), maze2.len()); -assert_eq!(maze1, maze2); -``` - -Specifying a custom generator: -``` -use hexlab::prelude::*; - -let maze = MazeBuilder::new() - .with_radius(7) - .with_generator(GeneratorType::RecursiveBacktracking) - .build() - .expect("Failed to create maze"); -``` -*/ +/// A builder pattern for creating hexagonal mazes. +/// +/// This struct provides a fluent interface for configuring and building hexagonal mazes. +/// It offers flexibility in specifying the maze size, random seed, generation algorithm, +/// and starting position. +/// +/// # Examples +/// +/// Basic usage: +/// ``` +/// use hexlab::prelude::*; +/// +/// let maze = MazeBuilder::new() +/// .with_radius(5) +/// .build() +/// .expect("Failed to create maze"); +/// +/// // A radius of 5 creates 61 hexagonal tiles +/// assert!(!maze.is_empty()); +/// assert_eq!(maze.len(), 91); +/// ``` +/// +/// Using a seed for reproducible results: +/// ``` +/// use hexlab::prelude::*; +/// +/// let maze1 = MazeBuilder::new() +/// .with_radius(3) +/// .with_seed(12345) +/// .build() +/// .expect("Failed to create maze"); +/// +/// let maze2 = MazeBuilder::new() +/// .with_radius(3) +/// .with_seed(12345) +/// .build() +/// .expect("Failed to create maze"); +/// +/// // Same seed should produce identical mazes +/// assert_eq!(maze1.len(), maze2.len()); +/// assert_eq!(maze1, maze2); +/// ``` +/// +/// Specifying a custom generator: +/// ``` +/// use hexlab::prelude::*; +/// +/// let maze = MazeBuilder::new() +/// .with_radius(7) +/// .with_generator(GeneratorType::RecursiveBacktracking) +/// .build() +/// .expect("Failed to create maze"); +/// ``` #[allow(clippy::module_name_repetitions)] #[derive(Default)] pub struct MazeBuilder { @@ -88,7 +87,7 @@ pub struct MazeBuilder { } impl MazeBuilder { - /// Creates a new [`MazeBuilder`] instance. + /// Creates a new [`MazeBuilder`] instance with default settings. #[inline] #[must_use] pub fn new() -> Self { @@ -97,9 +96,14 @@ impl MazeBuilder { /// Sets the radius for the hexagonal maze. /// + /// The radius determines the size of the maze, specifically the number of tiles + /// from the center (0,0) to the edge of the hexagon, not including the center tile. + /// For example, a radius of 3 would create a maze with 3 tiles from center to edge, + /// resulting in a total diameter of 7 tiles (3 + 1 + 3). + /// /// # Arguments /// - /// * `radius` - The size of the maze (number of tiles along one edge). + /// - `radius` - The number of tiles from the center to the edge of the hexagon. #[inline] #[must_use] pub const fn with_radius(mut self, radius: u32) -> Self { @@ -109,9 +113,11 @@ impl MazeBuilder { /// Sets the random seed for maze generation. /// + /// Using the same seed will produce identical mazes, allowing for reproducible results. + /// /// # Arguments /// - /// * `seed` - The random seed value. + /// - `seed` - The random seed value. #[inline] #[must_use] pub const fn with_seed(mut self, seed: u64) -> Self { @@ -125,14 +131,18 @@ impl MazeBuilder { /// /// # Arguments /// - /// * `generator_type` - The maze generation algorithm to use. + /// - `generator_type` - The maze generation algorithm to use. #[inline] #[must_use] pub const fn with_generator(mut self, generator_type: GeneratorType) -> Self { self.generator_type = generator_type; self } - + /// Sets the starting position for maze generation. + /// + /// # Arguments + /// + /// - `pos` - The hexagonal coordinates for the starting position. #[inline] #[must_use] pub const fn with_start_position(mut self, pos: Hex) -> Self { @@ -140,33 +150,31 @@ impl MazeBuilder { self } - /** - Builds the hexagonal maze based on the configured parameters. - - # Errors - - Returns [`MazeBuilderError::NoRadius`] if no radius is specified. - Returns [`MazeBuilderError::InvalidStartPosition`] if the start position is outside maze bounds. - - # Examples - - ``` - use hexlab::prelude::*; - - // Should fail without radius - let result = MazeBuilder::new().build(); - assert!(result.is_err()); - - // Should succeed with radius - let result = MazeBuilder::new() - .with_radius(3) - .build(); - assert!(result.is_ok()); - - let maze = result.unwrap(); - assert!(!maze.is_empty()); - ``` - */ + /// Builds the hexagonal maze based on the configured parameters. + /// + /// # Errors + /// + /// Returns [`MazeBuilderError::NoRadius`] if no radius is specified. + /// Returns [`MazeBuilderError::InvalidStartPosition`] if the start position is outside maze bounds. + /// + /// # Examples + /// + /// ``` + /// use hexlab::prelude::*; + /// + /// // Should fail without radius + /// let result = MazeBuilder::new().build(); + /// assert!(result.is_err()); + /// + /// // Should succeed with radius + /// let result = MazeBuilder::new() + /// .with_radius(3) + /// .build(); + /// assert!(result.is_ok()); + /// + /// let maze = result.unwrap(); + /// assert!(!maze.is_empty()); + /// ``` pub fn build(self) -> Result { let radius = self.radius.ok_or(MazeBuilderError::NoRadius)?; let mut maze = create_hex_maze(radius); diff --git a/src/hex_maze.rs b/src/hex_maze.rs index e3a7b28..8186e69 100644 --- a/src/hex_maze.rs +++ b/src/hex_maze.rs @@ -8,7 +8,10 @@ use hexx::{EdgeDirection, Hex}; use std::collections::HashMap; use std::ops::{Deref, DerefMut}; -/// Represents a hexagonal maze with tiles and walls +/// Represents a hexagonal maze with tiles and walls. +/// +/// This struct stores the layout of a hexagonal maze, including the positions +/// of tiles and their associated walls. #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "bevy_reflect", derive(Reflect))] #[cfg_attr(feature = "bevy", derive(Component))] @@ -25,44 +28,67 @@ impl HexMaze { } /// Adds a new tile at the specified coordinates + /// + /// # Arguments + /// + /// - `coords` - The hexagonal coordinates where the tile should be added. pub fn add_tile(&mut self, coords: Hex) { let tile = HexTile::new(coords); self.0.insert(coords, tile); } - /// Adds a wall in the specified direction at the given coordinates + /// Adds a wall in the specified direction at the given coordinates. + /// + /// # Arguments + /// + /// - `coord` - The hexagonal coordinates of the tile. + /// - `direction` - The direction in which to add the wall. pub fn add_wall(&mut self, coord: Hex, direction: EdgeDirection) { if let Some(tile) = self.0.get_mut(&coord) { tile.walls.add(direction); } } - /// Returns a reference to the tile at the specified coordinates + /// Returns a reference to the tile at the specified coordinates. + /// + /// # Arguments + /// + /// - `coord` - The hexagonal coordinates of the tile to retrieve. #[inline] #[must_use] pub fn get_tile(&self, coord: &Hex) -> Option<&HexTile> { self.0.get(coord) } - /// Returns a reference to the walls at the specified coordinates + /// Returns an optional reference to the walls at the specified coordinates. + /// + /// # Arguments + /// + /// - `coord` - The hexagonal coordinates of the tile whose walls to retrieve. pub fn get_walls(&self, coord: &Hex) -> Option<&Walls> { self.0.get(coord).map(HexTile::walls) } - /// Returns the number of tiles in the maze + /// Returns the number of tiles in the maze. #[inline] #[must_use] pub fn len(&self) -> usize { self.0.len() } - /// Returns true if the maze is empty + /// Returns `true` if the maze contains no tiles. #[inline] #[must_use] pub fn is_empty(&self) -> bool { self.0.is_empty() } + /// Removes a wall from a tile in the specified direction. + /// + /// # Arguments + /// + /// - `coord` - The hexagonal coordinates of the tile. + /// - `direction` - The direction of the wall to remove. pub fn remove_tile_wall(&mut self, coord: &Hex, direction: EdgeDirection) { if let Some(tile) = self.0.get_mut(coord) { tile.walls.remove(direction); diff --git a/src/hex_tile.rs b/src/hex_tile.rs index 93fd99d..8ccef70 100644 --- a/src/hex_tile.rs +++ b/src/hex_tile.rs @@ -7,6 +7,8 @@ use hexx::HexLayout; use std::fmt::Display; /// Represents a single hexagonal tile in the maze +/// +/// Each tile has a position and a set of walls defining its boundaries. #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "bevy_reflect", derive(Reflect))] #[cfg_attr(feature = "bevy", derive(Component))] @@ -18,7 +20,11 @@ pub struct HexTile { } impl HexTile { - /// Creates a new tile with pos and default walls + /// Creates a new tile with the given position and default walls. + /// + /// # Arguments + /// + /// - `pos` - The hexagonal coordinates of the tile. #[must_use] pub fn new(pos: Hex) -> Self { Self { @@ -40,7 +46,11 @@ impl HexTile { pub const fn pos(&self) -> Hex { self.pos } - + /// Converts the tile's position to a 2D vector based on the given layout. + /// + /// # Arguments + /// + /// - `layout` - The hexagonal layout used for conversion. #[cfg(feature = "bevy_reflect")] #[inline] #[must_use] @@ -48,6 +58,11 @@ impl HexTile { layout.hex_to_world_pos(self.pos) } + /// Converts the tile's position to a 3D vector based on the given layout. + /// + /// # Arguments + /// + /// - `layout` - The hexagonal layout used for conversion. #[cfg(feature = "bevy_reflect")] #[inline] #[must_use] diff --git a/src/walls.rs b/src/walls.rs index e03fa63..102735b 100644 --- a/src/walls.rs +++ b/src/walls.rs @@ -2,48 +2,31 @@ use bevy::prelude::*; use hexx::EdgeDirection; -/** -A bit-flag representation of walls in a hexagonal tile. - -`Walls` uses an efficient bit-flag system to track the presence or absence of walls -along each edge of a hexagonal tile. Each of the six possible walls is represented -by a single bit in an 8-bit integer, allowing for fast operations and minimal memory usage. - -# Examples - -Creating and manipulating walls: -``` -use hexlab::prelude::*; - -// Create a hexagon with all walls -let walls = Walls::new(); -assert!(walls.is_closed()); - -// Create a hexagon with no walls -let mut walls = Walls::empty(); -assert!(walls.is_empty()); - -// Add specific walls -walls.add(EdgeDirection::FLAT_NORTH); -walls.add(EdgeDirection::FLAT_SOUTH); -assert_eq!(walls.count(), 2); -``` - -Using walls in game logic: - -``` -use hexlab::prelude::*; -let mut walls = Walls::empty(); - -// Add walls to create a corner -walls.add(EdgeDirection::FLAT_NORTH); -walls.add(EdgeDirection::FLAT_SOUTH_EAST); - -// Check if a specific direction has a wall -assert!(walls.contains(EdgeDirection::FLAT_NORTH)); -assert!(!walls.contains(EdgeDirection::FLAT_SOUTH)); -``` -*/ +/// A bit-flag representation of walls in a hexagonal tile. +/// +/// `Walls` uses an efficient bit-flag system to track the presence or absence of walls +/// along each edge of a hexagonal tile. Each of the six possible walls is represented +/// by a single bit in an 8-bit integer, allowing for fast operations and minimal memory usage. +/// +/// # Examples +/// +/// Creating and manipulating walls: +/// ``` +/// use hexlab::prelude::*; +/// +/// // Create a hexagon with all walls +/// let walls = Walls::new(); +/// assert!(walls.is_closed()); +/// +/// // Create a hexagon with no walls +/// let mut walls = Walls::empty(); +/// assert!(walls.is_empty()); +/// +/// // Add specific walls +/// walls.add(EdgeDirection::FLAT_NORTH); +/// walls.add(EdgeDirection::FLAT_SOUTH); +/// assert_eq!(walls.count(), 2); +/// ``` #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "bevy_reflect", derive(Reflect))] #[cfg_attr(feature = "bevy", derive(Component))] @@ -52,89 +35,34 @@ assert!(!walls.contains(EdgeDirection::FLAT_SOUTH)); pub struct Walls(u8); impl Walls { - /** - Creates a new set of walls with all edges closed. - - This is the default state where all six edges of the hexagon have walls. - - # Examples - - ``` - use hexlab::prelude::*; - - let walls = Walls::new(); - assert!(walls.is_closed()); - assert_eq!(walls.count(), 6); - ``` - */ + /// Creates a new set of walls with all edges closed. + /// + /// This is the default state where all six edges of the hexagon have walls. #[inline] #[must_use] pub fn new() -> Self { Self::default() } - /** - Creates a new set of walls with no edges (completely open). - - # Examples - - ``` - use hexlab::prelude::*; - - let walls = Walls::empty(); - assert!(walls.is_empty()); - assert_eq!(walls.count(), 0); - ``` - */ + /// Creates a new set of walls with no edges (completely open). #[inline] #[must_use] pub const fn empty() -> Self { Self(0) } - /** - Checks if the walls are currently empty - - Returns `true` if all directions have no walls set. - # Examples - - ``` - use hexlab::prelude::*; - - let walls = Walls::empty(); - assert!(walls.is_empty()); - - let walls = Walls::new(); - assert!(!walls.is_empty()); - ``` - */ + /// Checks if the walls are currently empty (no walls present). #[inline] #[must_use] pub const fn is_empty(&self) -> bool { self.0 == 0 } - /** - Adds a wall in the specified direction - - This method uses bitwise operations to efficiently set the wall flag - for the given direction. Multiple walls can be added to the same hexagon. - - # Examples - - ``` - use hexlab::prelude::*; - - let mut walls = Walls::empty(); - walls.add(EdgeDirection::FLAT_NORTH); - assert!(walls.contains(EdgeDirection::FLAT_NORTH)); - assert!(!walls.contains(EdgeDirection::FLAT_SOUTH)); - - walls.add(EdgeDirection::FLAT_SOUTH); - assert!(walls.contains(EdgeDirection::FLAT_SOUTH)); - assert_eq!(walls.count(), 2); - ``` - */ + /// Adds a wall in the specified direction. + /// + /// # Arguments + /// + /// 0 `direction` - The direction in which to add the wall. #[inline] pub fn add(&mut self, direction: T) where @@ -143,25 +71,11 @@ impl Walls { self.0 |= direction.into().0; } - /** - Removes a wall in the specified direction - - Returns `true` if a wall was actually removed, `false` if there was no wall - in the specified direction. - - # Examples - - ``` - use hexlab::prelude::*; - - let mut walls = Walls::new(); - assert!(walls.remove(EdgeDirection::FLAT_NORTH)); - assert!(!walls.contains(EdgeDirection::FLAT_NORTH)); - - // Removing a non-existent wall returns false - assert!(!walls.remove(EdgeDirection::FLAT_NORTH)); - ``` - */ + /// Removes a wall in the specified direction. + /// + /// # Arguments + /// + /// - `direction` - The direction from which to remove the wall. #[inline] pub fn remove(&mut self, direction: T) -> bool where @@ -174,23 +88,11 @@ impl Walls { was_removed } - /** - Returns true if there is a wall in the specified direction - - Uses efficient bitwise operations to check for the presence of a wall. - - # Examples - - ``` - use hexlab::prelude::*; - - let mut walls = Walls::empty(); - - walls.add(EdgeDirection::FLAT_NORTH); - assert!(walls.contains(EdgeDirection::FLAT_NORTH)); - assert!(!walls.contains(EdgeDirection::FLAT_SOUTH)); - ``` - */ + /// Checks if there is a wall in the specified direction. + /// + /// # Arguments + /// + /// - `other` - The direction to check for a wall. #[inline] pub fn contains(&self, other: T) -> bool where @@ -199,98 +101,39 @@ impl Walls { self.0 & other.into().0 != 0 } - /** - Returns the raw bit representation of the walls - - This method provides access to the underlying bit flags for advanced usage. - The bits are ordered according to the `EdgeDirection` indices. - - # Examples - - ``` - use hexlab::prelude::*; - - let mut walls = Walls::new(); - - assert_eq!(walls.as_bits(), 0b111111); - ``` - */ + /// Returns the raw bit representation of the walls #[inline] #[must_use] pub const fn as_bits(&self) -> u8 { self.0 } - /** - Returns the total number of walls present - - Efficiently counts the number of set bits in the internal representation. - - # Examples - - ``` - use hexlab::prelude::*; - - let mut walls = Walls::empty(); - - assert_eq!(walls.count(), 0); - - walls.add(EdgeDirection::FLAT_NORTH); - walls.add(EdgeDirection::FLAT_SOUTH); - assert_eq!(walls.count(), 2); - ``` - */ + /// Returns the total number of walls present #[inline] #[must_use] pub fn count(&self) -> u8 { u8::try_from(self.0.count_ones()).unwrap_or_default() } - /** - Returns all possible directions as a `Walls` value - - This represents a hexagon with walls in all six directions. - - # Examples - - ``` - use hexlab::prelude::*; - - let all_walls = Walls::all_directions(); - - assert_eq!(all_walls.count(), 6); - assert!(all_walls.is_closed()); - ``` - */ + /// Returns a `Walls` value representing all possible directions. #[inline] #[must_use] pub const fn all_directions() -> Self { Self(0b11_1111) } - /** - Toggles a wall in the specified direction. - - If a wall exists in the given direction, it will be removed. - If no wall exists, one will be added. - Returns the previous state (`true` if a wall was present). - - # Examples - - ``` - use hexlab::prelude::*; - - let mut walls = Walls::empty(); - - assert!(!walls.toggle(EdgeDirection::FLAT_NORTH)); // Returns false, wall was not present - assert!(walls.contains(EdgeDirection::FLAT_NORTH)); // Wall is now present - - let mut walls = Walls::new(); - - assert!(walls.toggle(EdgeDirection::FLAT_NORTH)); // Returns true, wall was present - assert!(!walls.contains(EdgeDirection::FLAT_NORTH)); // Wall is now removed - ``` - */ + /// Toggles a wall in the specified direction. + /// + /// If a wall exists in the given direction, it will be removed. + /// If no wall exists, one will be added. + /// + /// # Arguments + /// + /// - `direction` - The direction in which to toggle the wall. + /// + /// # Returns + /// + /// The previous state (`true` if a wall was present before toggling, `false` otherwise). pub fn toggle(&mut self, direction: T) -> bool where T: Into + Copy, @@ -304,54 +147,55 @@ impl Walls { is_present } - /** - Checks if walls are present in all six directions. - - Returns `true` if the hexagon has all possible walls, making it completely enclosed. - - # Examples - - ``` - use hexlab::prelude::*; - - let walls = Walls::new(); - assert!(walls.is_closed()); - - let mut walls = Walls::empty(); - assert!(!walls.is_closed()); - // Add all walls manually - for direction in EdgeDirection::iter() { - walls.add(direction); - } - assert!(walls.is_closed()); - ``` - */ + /// Checks if walls are present in all six directions. + /// + /// # Returns + /// + /// `true` if the hexagon has all possible walls, making it completely enclosed. + /// + /// # Deprecated + /// + /// This method is deprecated since version 0.3.1. Use `is_enclosed()` instead. #[inline] #[must_use] + #[deprecated(since = "0.3.1", note = "use `walls::Walls::is_enclosed()`")] pub fn is_closed(&self) -> bool { + self.is_enclosed() + } + + /// Checks if walls are present in all six directions. + /// + /// # Returns + /// + /// `true` if the hexagon has all possible walls, making it completely enclosed. + #[inline] + #[must_use] + pub fn is_enclosed(&self) -> bool { self.count() == 6 } - /** - Sets walls for multiple directions at once. - - This method efficiently adds multiple walls in a single operation while - preserving any existing walls not specified in the input. - - # Examples - - ``` - use hexlab::prelude::*; - - let mut walls = Walls::empty(); - walls.add(EdgeDirection::FLAT_NORTH); - - walls.fill([EdgeDirection::FLAT_SOUTH, EdgeDirection::FLAT_SOUTH_EAST]); - - assert!(walls.contains(EdgeDirection::FLAT_SOUTH)); - assert_eq!(walls.count(), 3); - ``` - */ + /// Sets walls for multiple directions at once. + /// + /// This method efficiently adds multiple walls in a single operation while + /// preserving any existing walls not specified in the input. + /// + /// # Arguments + /// + /// - `other` - The walls to add, specified as a `Walls` instance or any type + /// that can be converted into `Walls`. + /// + /// + /// # Examples + /// + /// ``` + /// use hexlab::prelude::*; + /// + /// let mut walls = Walls::empty(); + /// walls.fill([EdgeDirection::FLAT_NORTH ,EdgeDirection::FLAT_SOUTH, EdgeDirection::FLAT_SOUTH_EAST]); + /// + /// assert!(walls.contains(EdgeDirection::FLAT_SOUTH)); + /// assert_eq!(walls.count(), 3); + /// ``` #[inline] pub fn fill(&mut self, other: T) where From 389c8ee1fd6d5482e18b973fc5bb1286b478cd39 Mon Sep 17 00:00:00 2001 From: Kristofers Solo Date: Wed, 25 Dec 2024 19:23:42 +0200 Subject: [PATCH 05/11] docs: add README.md --- Cargo.toml | 1 + README.md | 89 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/builder.rs | 4 +-- src/lib.rs | 36 ++++++++++++++++++++ 4 files changed, 128 insertions(+), 2 deletions(-) create mode 100644 README.md diff --git a/Cargo.toml b/Cargo.toml index 13f3e3f..21be02e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ categories = [ "data-structures", ] exclude = ["/.github", "/.gitignore", "/tests", "*.png", "*.md"] +readme = "README.md" [dependencies] bevy = { version = "0.15", optional = true } diff --git a/README.md b/README.md new file mode 100644 index 0000000..5c23a20 --- /dev/null +++ b/README.md @@ -0,0 +1,89 @@ +# Hexlab + + + +- [Features](#features) +- [Installation](#installation) +- [Getting Started](#getting-started) +- [Usage](#usage) +- [Documentation](#documentation) +- [Contributing](#contributing) +- [Acknowledgements](#acknowledgements) +- [License](#license) + + + +Hexlab is a Rust library for generating and manipulating hexagonal mazes. + +## Features + +- Create hexagonal mazes of configurable size +- Customizable maze properties (radius, start position, seed) +- Efficient bit-flag representation of walls for optimized memory usage +- Multiple maze generation algorithms (WIP) +- Maze builder pattern for easy and flexible maze creation + +## Installation + +Add `hexlab` as a dependency: + +```sh +cargo add hexlab +``` + +## Getting Started + +```rust +use hexlab::prelude::*; + +fn main() { + // Create a new maze with radius 5 + let maze = MazeBuilder::new() + .with_radius(5) + .build() + .expect("Failed to create maze"); + println!("Maze size: {}", maze.len()); +} +``` + +## Usage + +```rust +use hexlab::prelude::*; + +// Create a new maze +let maze = MazeBuilder::new() + .with_radius(5) + .build() + .expect("Failed to create maze"); + +// Get a specific tile +let tile = maze.get_tile(&Hex::new(1, -1)).unwrap(); + +// Check if a wall exists +let has_wall = tile.walls().contains(EdgeDirection::FLAT_NORTH); +``` + +## Documentation + +Full documentation is available at [docs.rs](https://docs.rs/hexlab). + +## Contributing + +Contributions are welcome! Please feel free to submit a Pull Request. + +## Acknowledgements + +Hexlab relies on the excellent [hexx](https://github.com/ManevilleF/hexx) +library for handling hexagonal grid mathematics, coordinates, and related +operations. We're grateful for the robust foundation it provides for working +with hexagonal grids. + +## License + +This project is dual-licensed under either: + +- MIT License ([LICENSE-MIT](LICENSE-MIT) or [http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT)) +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)) + +at your option. diff --git a/src/builder.rs b/src/builder.rs index c20b4b6..2670165 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -168,8 +168,8 @@ impl MazeBuilder { /// /// // Should succeed with radius /// let result = MazeBuilder::new() - /// .with_radius(3) - /// .build(); + /// .with_radius(3) + /// .build(); /// assert!(result.is_ok()); /// /// let maze = result.unwrap(); diff --git a/src/lib.rs b/src/lib.rs index fd59fad..7dead81 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,38 @@ +//! Hexlab is a library for generating and manipulating hexagonal mazes. +//! +//! # Features +//! +//! - Create hexagonal mazes of configurable size +//! - Customizable maze properties (radius, start position, seed) +//! - Efficient bit-flag representation of walls +//! - Multiple maze generation algorithms +//! - Maze builder pattern for easy maze creation + +//! +//! # Examples +//! +//! Here's a quick example to create a simple hexagonal maze: +//! +//!``` +//! use hexlab::prelude::*; +//! +//! // Create a new maze +//! let maze = MazeBuilder::new() +//! .with_radius(5) +//! .build() +//! .expect("Failed to create maze"); +//! +//! // Get a specific tile +//! let tile = maze.get_tile(&Hex::new(1, -1)).unwrap(); +//! +//! // Check if a wall exists +//! let has_wall = tile.walls().contains(EdgeDirection::FLAT_NORTH); +//!``` +//! +//! # Acknowledgements +//! +//! Hexlab relies on the excellent [hexx](https://github.com/ManevilleF/hexx) library for handling +//! hexagonal grid mathematics, coordinates, and related operations. mod builder; mod generator; mod hex_maze; @@ -10,6 +45,7 @@ pub use hex_maze::HexMaze; pub use hex_tile::HexTile; pub use walls::Walls; +/// Prelude module containing commonly used types pub mod prelude { pub use super::{GeneratorType, HexMaze, HexTile, MazeBuilder, MazeBuilderError, Walls}; pub use hexx::{EdgeDirection, Hex, HexLayout}; From 7cacf92014159be616ed889415ab60a62c60bb95 Mon Sep 17 00:00:00 2001 From: Kristofers Solo Date: Wed, 25 Dec 2024 20:18:12 +0200 Subject: [PATCH 06/11] test(builder): 100% builder tests --- Cargo.lock | 139 ++++++++++++++++++++++++++ Cargo.toml | 6 +- src/builder.rs | 246 ++++++++++++----------------------------------- src/generator.rs | 2 +- src/hex_maze.rs | 8 +- src/hex_tile.rs | 8 +- src/lib.rs | 36 +++++-- src/walls.rs | 24 ++--- tests/builder.rs | 139 ++++++++++++++++++++++++++ 9 files changed, 390 insertions(+), 218 deletions(-) create mode 100644 tests/builder.rs diff --git a/Cargo.lock b/Cargo.lock index 1f3a04c..e5dc3b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1441,6 +1441,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "claims" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bba18ee93d577a8428902687bcc2b6b45a56b1981a1f6d779731c86cc4c5db18" + [[package]] name = "clang-sys" version = "1.8.1" @@ -2008,6 +2014,21 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.31" @@ -2015,6 +2036,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -2023,6 +2045,17 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + [[package]] name = "futures-io" version = "0.3.31" @@ -2042,6 +2075,53 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + [[package]] name = "gethostname" version = "0.4.3" @@ -2298,8 +2378,10 @@ name = "hexlab" version = "0.3.1" dependencies = [ "bevy", + "claims", "hexx", "rand", + "rstest", "serde", "thiserror 2.0.3", ] @@ -3162,6 +3244,12 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "piper" version = "0.2.4" @@ -3436,6 +3524,12 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "relative-path" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" + [[package]] name = "renderdoc-sys" version = "1.1.0" @@ -3471,12 +3565,51 @@ version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" +[[package]] +name = "rstest" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a2c585be59b6b5dd66a9d2084aa1d8bd52fbdb806eafdeffb52791147862035" +dependencies = [ + "futures", + "futures-timer", + "rstest_macros", + "rustc_version", +] + +[[package]] +name = "rstest_macros" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "825ea780781b15345a146be27eaefb05085e337e869bff01b4306a4fd4a9ad5a" +dependencies = [ + "cfg-if", + "glob", + "proc-macro-crate", + "proc-macro2", + "quote", + "regex", + "relative-path", + "rustc_version", + "syn", + "unicode-ident", +] + [[package]] name = "rustc-hash" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "0.38.42" @@ -3543,6 +3676,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2fdfc24bc566f839a2da4c4295b82db7d25a24253867d5c64355abb5799bdbe" +[[package]] +name = "semver" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba" + [[package]] name = "send_wrapper" version = "0.6.0" diff --git a/Cargo.toml b/Cargo.toml index 21be02e..1137472 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,8 +25,9 @@ rand = "0.8" serde = { version = "1.0", features = ["derive"], optional = true } thiserror = "2.0" - [dev-dependencies] +claims = "0.8" +rstest = "0.23" [features] default = [] @@ -53,3 +54,6 @@ pedantic = "warn" nursery = "warn" unwrap_used = "warn" expect_used = "warn" + +[package.metadata.nextest] +slow-timeout = { period = "120s", terminate-after = 3 } diff --git a/src/builder.rs b/src/builder.rs index 2670165..169c7ea 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -80,7 +80,7 @@ pub enum MazeBuilderError { #[allow(clippy::module_name_repetitions)] #[derive(Default)] pub struct MazeBuilder { - radius: Option, + radius: Option, seed: Option, generator_type: GeneratorType, start_position: Option, @@ -88,7 +88,7 @@ pub struct MazeBuilder { impl MazeBuilder { /// Creates a new [`MazeBuilder`] instance with default settings. - #[inline] + #[cfg_attr(not(debug_assertions), inline)] #[must_use] pub fn new() -> Self { Self::default() @@ -104,9 +104,9 @@ impl MazeBuilder { /// # Arguments /// /// - `radius` - The number of tiles from the center to the edge of the hexagon. - #[inline] + #[cfg_attr(not(debug_assertions), inline)] #[must_use] - pub const fn with_radius(mut self, radius: u32) -> Self { + pub const fn with_radius(mut self, radius: u16) -> Self { self.radius = Some(radius); self } @@ -118,7 +118,7 @@ impl MazeBuilder { /// # Arguments /// /// - `seed` - The random seed value. - #[inline] + #[cfg_attr(not(debug_assertions), inline)] #[must_use] pub const fn with_seed(mut self, seed: u64) -> Self { self.seed = Some(seed); @@ -132,7 +132,7 @@ impl MazeBuilder { /// # Arguments /// /// - `generator_type` - The maze generation algorithm to use. - #[inline] + #[cfg_attr(not(debug_assertions), inline)] #[must_use] pub const fn with_generator(mut self, generator_type: GeneratorType) -> Self { self.generator_type = generator_type; @@ -143,7 +143,7 @@ impl MazeBuilder { /// # Arguments /// /// - `pos` - The hexagonal coordinates for the starting position. - #[inline] + #[cfg_attr(not(debug_assertions), inline)] #[must_use] pub const fn with_start_position(mut self, pos: Hex) -> Self { self.start_position = Some(pos); @@ -200,9 +200,9 @@ impl MazeBuilder { } } } -fn create_hex_maze(radius: u32) -> HexMaze { +fn create_hex_maze(radius: u16) -> HexMaze { let mut maze = HexMaze::new(); - let radius = i32::try_from(radius).unwrap_or(5); + let radius = i32::from(radius); for q in -radius..=radius { let r1 = (-radius).max(-q - radius); @@ -218,194 +218,68 @@ fn create_hex_maze(radius: u32) -> HexMaze { #[cfg(test)] mod test { - use hexx::EdgeDirection; - use super::*; - - /// Helper function to count the number of tiles for a given radius - fn calculate_hex_tiles(radius: u32) -> usize { - let r = radius as i32; - (3 * r * r + 3 * r + 1) as usize - } + use claims::assert_gt; + use rstest::rstest; #[test] - fn new_builder() { + fn maze_builder_new() { let builder = MazeBuilder::new(); - assert!(builder.radius.is_none()); - assert!(builder.seed.is_none()); - assert!(builder.start_position.is_none()); + assert_eq!(builder.radius, None); + assert_eq!(builder.seed, None); + assert_eq!(builder.generator_type, GeneratorType::default()); + assert_eq!(builder.start_position, None); + } + + #[rstest] + #[case(0, 1)] // Minimum size is 1 tile + #[case(1, 7)] + #[case(2, 19)] + #[case(3, 37)] + #[case(10, 331)] + #[case(100, 30301)] + fn create_hex_maze_various_radii(#[case] radius: u16, #[case] expected_size: usize) { + let maze = create_hex_maze(radius); + assert_eq!(maze.len(), expected_size); } #[test] - fn builder_with_radius() { - let radius = 5; - let maze = MazeBuilder::new().with_radius(radius).build().unwrap(); + fn create_hex_maze_large_radius() { + let large_radius = 1000; + let maze = create_hex_maze(large_radius); + assert_gt!(maze.len(), 0); - assert_eq!(maze.len(), calculate_hex_tiles(radius)); - assert!(maze.get_tile(&Hex::ZERO).is_some()); + // Calculate expected size for this radius + let expected_size = 3 * (large_radius as usize).pow(2) + 3 * large_radius as usize + 1; + assert_eq!(maze.len(), expected_size); } #[test] - fn builder_without_radius() { - let maze = MazeBuilder::new().build(); - assert!(matches!(maze, Err(MazeBuilderError::NoRadius))); - } - - #[test] - fn builder_with_seed() { - let radius = 3; - let seed = 12345; - - let maze1 = MazeBuilder::new() - .with_radius(radius) - .with_seed(seed) - .build() - .unwrap(); - - let maze2 = MazeBuilder::new() - .with_radius(radius) - .with_seed(seed) - .build() - .unwrap(); - - // Same seed should produce identical mazes - assert_eq!(maze1, maze2); - } - - #[test] - fn different_seeds_produce_different_mazes() { - let radius = 3; - - let maze1 = MazeBuilder::new() - .with_radius(radius) - .with_seed(12345) - .build() - .unwrap(); - - let maze2 = MazeBuilder::new() - .with_radius(radius) - .with_seed(54321) - .build() - .unwrap(); - - // Different seeds should produce different mazes - assert_ne!(maze1, maze2); - } - - #[test] - fn maze_connectivity() { - let radius = 3; - let maze = MazeBuilder::new().with_radius(radius).build().unwrap(); - - // Helper function to count accessible neighbors - fn count_accessible_neighbors(maze: &HexMaze, pos: Hex) -> usize { - EdgeDirection::ALL_DIRECTIONS - .iter() - .filter(|&&dir| { - let neighbor = pos + dir; - if let Some(walls) = maze.get_walls(&pos) { - !walls.contains(dir) && maze.get_tile(&neighbor).is_some() - } else { - false - } - }) - .count() - } - - // Check that each tile has at least one connection - for &pos in maze.keys() { - let accessible_neighbors = count_accessible_neighbors(&maze, pos); - assert!( - accessible_neighbors > 0, - "Tile at {:?} has no accessible neighbors", - pos - ); - } - } - - #[test] - fn start_position() { - let radius = 3; - let start_pos = Hex::new(1, 1); - - let maze = MazeBuilder::new() - .with_radius(radius) - .with_start_position(start_pos) - .build() - .unwrap(); - - assert!(maze.get_tile(&start_pos).is_some()); - } - - #[test] - fn invalid_start_position() { - let maze = MazeBuilder::new() - .with_radius(3) - .with_start_position(Hex::new(10, 10)) - .build(); - - assert!(matches!( - maze, - Err(MazeBuilderError::InvalidStartPosition(_)) - )); - } - - #[test] - fn maze_boundaries() { - let radius = 3; - let maze = MazeBuilder::new().with_radius(radius).build().unwrap(); - - // Test that tiles exist within the radius - for q in -(radius as i32)..=(radius as i32) { - for r in -(radius as i32)..=(radius as i32) { - let pos = Hex::new(q, r); - if q.abs() + r.abs() <= radius as i32 { - assert!( - maze.get_tile(&pos).is_some(), - "Expected tile at {:?} to exist", - pos - ); - } - } - } - } - - #[test] - fn different_radii() { - for radius in 1..=5 { - let maze = MazeBuilder::new().with_radius(radius).build().unwrap(); - - assert_eq!( - maze.len(), - calculate_hex_tiles(radius), - "Incorrect number of tiles for radius {}", - radius - ); - } - } - - #[test] - fn wall_consistency() { - let radius = 3; - let maze = MazeBuilder::new().with_radius(radius).build().unwrap(); - - // Check that if tile A has no wall to tile B, - // then tile B has no wall to tile A - for &pos in maze.keys() { - for &dir in &EdgeDirection::ALL_DIRECTIONS { - let neighbor = pos + dir; - if let (Some(walls), Some(neighbor_walls)) = - (maze.get_walls(&pos), maze.get_walls(&neighbor)) - { - assert_eq!( - walls.contains(dir), - neighbor_walls.contains(dir.const_neg()), - "Wall inconsistency between {:?} and {:?}", - pos, - neighbor - ); - } - } + fn create_hex_maze_tile_positions() { + let maze = create_hex_maze(2); + let expected_positions = [ + Hex::new(0, 0), + Hex::new(1, -1), + Hex::new(1, 0), + Hex::new(0, 1), + Hex::new(-1, 1), + Hex::new(-1, 0), + Hex::new(0, -1), + Hex::new(2, -2), + Hex::new(2, -1), + Hex::new(2, 0), + Hex::new(1, 1), + Hex::new(0, 2), + Hex::new(-1, 2), + Hex::new(-2, 2), + Hex::new(-2, 1), + Hex::new(-2, 0), + Hex::new(-1, -1), + Hex::new(0, -2), + Hex::new(1, -2), + ]; + for pos in expected_positions.iter() { + assert!(maze.get_tile(pos).is_some(), "Expected tile at {:?}", pos); } } } diff --git a/src/generator.rs b/src/generator.rs index 579a69b..082787a 100644 --- a/src/generator.rs +++ b/src/generator.rs @@ -11,7 +11,7 @@ use crate::HexMaze; #[cfg_attr(feature = "bevy_reflect", derive(Reflect))] #[cfg_attr(feature = "bevy", derive(Component))] #[cfg_attr(feature = "bevy", reflect(Component))] -#[derive(Debug, Clone, Copy, Default)] +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] pub enum GeneratorType { #[default] RecursiveBacktracking, diff --git a/src/hex_maze.rs b/src/hex_maze.rs index 8186e69..e41957a 100644 --- a/src/hex_maze.rs +++ b/src/hex_maze.rs @@ -21,7 +21,7 @@ pub struct HexMaze(HashMap); impl HexMaze { /// Creates a new empty maze - #[inline] + #[cfg_attr(not(debug_assertions), inline)] #[must_use] pub fn new() -> Self { Self::default() @@ -54,7 +54,7 @@ impl HexMaze { /// # Arguments /// /// - `coord` - The hexagonal coordinates of the tile to retrieve. - #[inline] + #[cfg_attr(not(debug_assertions), inline)] #[must_use] pub fn get_tile(&self, coord: &Hex) -> Option<&HexTile> { self.0.get(coord) @@ -70,14 +70,14 @@ impl HexMaze { } /// Returns the number of tiles in the maze. - #[inline] + #[cfg_attr(not(debug_assertions), inline)] #[must_use] pub fn len(&self) -> usize { self.0.len() } /// Returns `true` if the maze contains no tiles. - #[inline] + #[cfg_attr(not(debug_assertions), inline)] #[must_use] pub fn is_empty(&self) -> bool { self.0.is_empty() diff --git a/src/hex_tile.rs b/src/hex_tile.rs index 8ccef70..863ce91 100644 --- a/src/hex_tile.rs +++ b/src/hex_tile.rs @@ -34,14 +34,14 @@ impl HexTile { } /// Returns a reference to the tile's walls - #[inline] + #[cfg_attr(not(debug_assertions), inline)] #[must_use] pub const fn walls(&self) -> &Walls { &self.walls } /// Returns position of the tile - #[inline] + #[cfg_attr(not(debug_assertions), inline)] #[must_use] pub const fn pos(&self) -> Hex { self.pos @@ -52,7 +52,7 @@ impl HexTile { /// /// - `layout` - The hexagonal layout used for conversion. #[cfg(feature = "bevy_reflect")] - #[inline] + #[cfg_attr(not(debug_assertions), inline)] #[must_use] pub fn to_vec2(&self, layout: &HexLayout) -> Vec2 { layout.hex_to_world_pos(self.pos) @@ -64,7 +64,7 @@ impl HexTile { /// /// - `layout` - The hexagonal layout used for conversion. #[cfg(feature = "bevy_reflect")] - #[inline] + #[cfg_attr(not(debug_assertions), inline)] #[must_use] pub fn to_vec3(&self, layout: &HexLayout) -> Vec3 { let pos = self.to_vec2(layout); diff --git a/src/lib.rs b/src/lib.rs index 7dead81..2fb8c48 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,23 +16,39 @@ //!``` //! use hexlab::prelude::*; //! -//! // Create a new maze //! let maze = MazeBuilder::new() -//! .with_radius(5) +//! .with_radius(3) //! .build() //! .expect("Failed to create maze"); //! -//! // Get a specific tile -//! let tile = maze.get_tile(&Hex::new(1, -1)).unwrap(); -//! -//! // Check if a wall exists -//! let has_wall = tile.walls().contains(EdgeDirection::FLAT_NORTH); +//! assert_eq!(maze.len(), 37); // A radius of 3 should create 37 tiles //!``` //! -//! # Acknowledgements +//! Customizing maze generation: //! -//! Hexlab relies on the excellent [hexx](https://github.com/ManevilleF/hexx) library for handling -//! hexagonal grid mathematics, coordinates, and related operations. +//!``` +//! use hexlab::prelude::*; +//! +//! let maze = MazeBuilder::new() +//! .with_radius(2) +//! .with_seed(12345) +//! .with_start_position(Hex::new(1, -1)) +//! .build() +//! .expect("Failed to create maze"); +//! +//! assert!(maze.get_tile(&Hex::new(1, -1)).is_some()); +//!``` +//! +//! Manipulating walls: +//! +//!``` +//! use hexlab::prelude::*; +//! +//! let mut walls = Walls::empty(); +//! walls.add(EdgeDirection::FLAT_NORTH); +//! assert!(walls.contains(EdgeDirection::FLAT_NORTH)); +//! assert!(!walls.contains(EdgeDirection::FLAT_SOUTH)); +//!``` mod builder; mod generator; mod hex_maze; diff --git a/src/walls.rs b/src/walls.rs index 102735b..54af979 100644 --- a/src/walls.rs +++ b/src/walls.rs @@ -38,21 +38,21 @@ impl Walls { /// Creates a new set of walls with all edges closed. /// /// This is the default state where all six edges of the hexagon have walls. - #[inline] + #[cfg_attr(not(debug_assertions), inline)] #[must_use] pub fn new() -> Self { Self::default() } /// Creates a new set of walls with no edges (completely open). - #[inline] + #[cfg_attr(not(debug_assertions), inline)] #[must_use] pub const fn empty() -> Self { Self(0) } /// Checks if the walls are currently empty (no walls present). - #[inline] + #[cfg_attr(not(debug_assertions), inline)] #[must_use] pub const fn is_empty(&self) -> bool { self.0 == 0 @@ -63,7 +63,7 @@ impl Walls { /// # Arguments /// /// 0 `direction` - The direction in which to add the wall. - #[inline] + #[cfg_attr(not(debug_assertions), inline)] pub fn add(&mut self, direction: T) where T: Into + Copy, @@ -76,7 +76,7 @@ impl Walls { /// # Arguments /// /// - `direction` - The direction from which to remove the wall. - #[inline] + #[cfg_attr(not(debug_assertions), inline)] pub fn remove(&mut self, direction: T) -> bool where T: Into + Copy, @@ -93,7 +93,7 @@ impl Walls { /// # Arguments /// /// - `other` - The direction to check for a wall. - #[inline] + #[cfg_attr(not(debug_assertions), inline)] pub fn contains(&self, other: T) -> bool where T: Into + Copy, @@ -102,21 +102,21 @@ impl Walls { } /// Returns the raw bit representation of the walls - #[inline] + #[cfg_attr(not(debug_assertions), inline)] #[must_use] pub const fn as_bits(&self) -> u8 { self.0 } /// Returns the total number of walls present - #[inline] + #[cfg_attr(not(debug_assertions), inline)] #[must_use] pub fn count(&self) -> u8 { u8::try_from(self.0.count_ones()).unwrap_or_default() } /// Returns a `Walls` value representing all possible directions. - #[inline] + #[cfg_attr(not(debug_assertions), inline)] #[must_use] pub const fn all_directions() -> Self { Self(0b11_1111) @@ -156,7 +156,7 @@ impl Walls { /// # Deprecated /// /// This method is deprecated since version 0.3.1. Use `is_enclosed()` instead. - #[inline] + #[cfg_attr(not(debug_assertions), inline)] #[must_use] #[deprecated(since = "0.3.1", note = "use `walls::Walls::is_enclosed()`")] pub fn is_closed(&self) -> bool { @@ -168,7 +168,7 @@ impl Walls { /// # Returns /// /// `true` if the hexagon has all possible walls, making it completely enclosed. - #[inline] + #[cfg_attr(not(debug_assertions), inline)] #[must_use] pub fn is_enclosed(&self) -> bool { self.count() == 6 @@ -196,7 +196,7 @@ impl Walls { /// assert!(walls.contains(EdgeDirection::FLAT_SOUTH)); /// assert_eq!(walls.count(), 3); /// ``` - #[inline] + #[cfg_attr(not(debug_assertions), inline)] pub fn fill(&mut self, other: T) where T: Into, diff --git a/tests/builder.rs b/tests/builder.rs new file mode 100644 index 0000000..832781c --- /dev/null +++ b/tests/builder.rs @@ -0,0 +1,139 @@ +use claims::{assert_err, assert_gt, assert_matches, assert_ok, assert_some}; +use hexlab::prelude::*; +use rstest::rstest; + +#[rstest] +#[case(1, 7)] +#[case(2, 19)] +#[case(3, 37)] +#[case(4, 61)] +#[case(5, 91)] +fn maze_size(#[case] radius: u16, #[case] expected_size: usize) { + let maze = assert_ok!(MazeBuilder::new().with_radius(radius).build()); + assert_eq!(maze.len(), expected_size); +} + +#[test] +fn builder_without_radius() { + let result = MazeBuilder::new().build(); + assert_err!(&result); + assert_matches!(result, Err(MazeBuilderError::NoRadius)); +} + +#[rstest] +#[case(Hex::ZERO)] +#[case(Hex::new(1,-1))] +#[case(Hex::new(-2,1))] +fn valid_start_position(#[case] start_pos: Hex) { + let maze = assert_ok!(MazeBuilder::new() + .with_radius(3) + .with_start_position(start_pos) + .build()); + assert_some!(maze.get_tile(&start_pos)); +} + +#[test] +fn invalid_start_position() { + let maze = MazeBuilder::new() + .with_radius(3) + .with_start_position(Hex::new(10, 10)) + .build(); + + assert_err!(&maze); + assert_matches!(maze, Err(MazeBuilderError::InvalidStartPosition(_))); +} + +#[test] +fn maze_with_seed() { + let maze1 = assert_ok!(MazeBuilder::new().with_radius(3).with_seed(12345).build()); + let maze2 = assert_ok!(MazeBuilder::new().with_radius(3).with_seed(12345).build()); + + assert_eq!(maze1, maze2, "Mazes with the same seed should be identical"); +} + +#[test] +fn different_seeds_produce_different_mazes() { + let maze1 = assert_ok!(MazeBuilder::new().with_radius(3).with_seed(12345).build()); + let maze2 = assert_ok!(MazeBuilder::new().with_radius(3).with_seed(54321).build()); + + assert_ne!( + maze1, maze2, + "Mazes with different seeds should be different" + ); +} + +#[test] +fn maze_connectivity() { + let maze = assert_ok!(MazeBuilder::new().with_radius(3).build()); + + // Helper function to count accessible neighbors + fn count_accessible_neighbors(maze: &HexMaze, pos: Hex) -> usize { + hexx::EdgeDirection::ALL_DIRECTIONS + .iter() + .filter(|&&dir| { + let neighbor = pos + dir; + if let Some(walls) = maze.get_walls(&pos) { + !walls.contains(dir) && maze.get_tile(&neighbor).is_some() + } else { + false + } + }) + .count() + } + + // Check that each tile has at least one connection + for &pos in maze.keys() { + let accessible_neighbors = count_accessible_neighbors(&maze, pos); + claims::assert_gt!( + accessible_neighbors, + 0, + "Tile at {:?} has no accessible neighbors", + pos + ); + } +} + +#[test] +fn generator_type() { + let maze = assert_ok!(MazeBuilder::new() + .with_radius(3) + .with_generator(GeneratorType::RecursiveBacktracking) + .build()); + claims::assert_gt!(maze.len(), 0); +} + +#[test] +fn maze_boundaries() { + let radius = 3; + let maze = MazeBuilder::new() + .with_radius(radius as u16) + .build() + .unwrap(); + + // Test that tiles exist within the radius + for q in -radius..=radius { + for r in -radius..=radius { + let pos = Hex::new(q, r); + if q.abs() + r.abs() <= radius { + assert!( + maze.get_tile(&pos).is_some(), + "Expected tile at {:?} to exist", + pos + ); + } + } + } +} + +#[rstest] +#[case(GeneratorType::RecursiveBacktracking)] +fn generate_maze_with_different_types(#[case] generator: GeneratorType) { + // TODO: Add more generator types when they become available + + let maze = assert_ok!(MazeBuilder::new() + .with_radius(3) + .with_generator(generator) + .build()); + + assert_gt!(maze.len(), 0); +} From 6660b4613d51fc53501813ac4284d1731319af60 Mon Sep 17 00:00:00 2001 From: Kristofers Solo Date: Wed, 25 Dec 2024 20:51:45 +0200 Subject: [PATCH 07/11] test(generator): 89% coverage --- src/builder.rs | 18 ++---- src/generator.rs | 55 ------------------ src/generator/backtrack.rs | 111 +++++++++++++++++++++++++++++++++++++ src/generator/mod.rs | 24 ++++++++ tests/generator.rs | 66 ++++++++++++++++++++++ 5 files changed, 205 insertions(+), 69 deletions(-) delete mode 100644 src/generator.rs create mode 100644 src/generator/backtrack.rs create mode 100644 src/generator/mod.rs create mode 100644 tests/generator.rs diff --git a/src/builder.rs b/src/builder.rs index 169c7ea..3a7c798 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -1,7 +1,4 @@ -use crate::{ - generator::{generate_backtracking, GeneratorType}, - HexMaze, -}; +use crate::{GeneratorType, HexMaze}; use hexx::Hex; use thiserror::Error; @@ -186,21 +183,14 @@ impl MazeBuilder { } if !maze.is_empty() { - self.generate_maze(&mut maze); + self.generator_type + .generate(&mut maze, self.start_position, self.seed); } Ok(maze) } - - fn generate_maze(&self, maze: &mut HexMaze) { - match self.generator_type { - GeneratorType::RecursiveBacktracking => { - generate_backtracking(maze, self.start_position, self.seed); - } - } - } } -fn create_hex_maze(radius: u16) -> HexMaze { +pub(crate) fn create_hex_maze(radius: u16) -> HexMaze { let mut maze = HexMaze::new(); let radius = i32::from(radius); diff --git a/src/generator.rs b/src/generator.rs deleted file mode 100644 index 082787a..0000000 --- a/src/generator.rs +++ /dev/null @@ -1,55 +0,0 @@ -#[cfg(feature = "bevy_reflect")] -use bevy::prelude::*; -use hexx::{EdgeDirection, Hex}; -use rand::{rngs::StdRng, seq::SliceRandom, thread_rng, Rng, RngCore, SeedableRng}; -use std::collections::HashSet; - -use crate::HexMaze; - -#[allow(clippy::module_name_repetitions)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "bevy_reflect", derive(Reflect))] -#[cfg_attr(feature = "bevy", derive(Component))] -#[cfg_attr(feature = "bevy", reflect(Component))] -#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] -pub enum GeneratorType { - #[default] - RecursiveBacktracking, -} - -pub fn generate_backtracking(maze: &mut HexMaze, start_pos: Option, seed: Option) { - if maze.is_empty() { - return; - } - - let start = start_pos.unwrap_or(Hex::ZERO); - - let mut visited = HashSet::new(); - - let mut rng: Box = seed.map_or_else( - || Box::new(thread_rng()) as Box, - |seed| Box::new(StdRng::seed_from_u64(seed)) as Box, - ); - - recursive_backtrack(maze, start, &mut visited, &mut rng); -} - -fn recursive_backtrack( - maze: &mut HexMaze, - current: Hex, - visited: &mut HashSet, - rng: &mut R, -) { - visited.insert(current); - let mut directions = EdgeDirection::ALL_DIRECTIONS; - directions.shuffle(rng); - - for direction in directions { - let neighbor = current + direction; - if maze.get_tile(&neighbor).is_some() && !visited.contains(&neighbor) { - maze.remove_tile_wall(¤t, direction); - maze.remove_tile_wall(&neighbor, direction.const_neg()); - recursive_backtrack(maze, neighbor, visited, rng); - } - } -} diff --git a/src/generator/backtrack.rs b/src/generator/backtrack.rs new file mode 100644 index 0000000..e59c045 --- /dev/null +++ b/src/generator/backtrack.rs @@ -0,0 +1,111 @@ +use crate::HexMaze; +use hexx::{EdgeDirection, Hex}; +use rand::{rngs::StdRng, seq::SliceRandom, thread_rng, Rng, RngCore, SeedableRng}; +use std::collections::HashSet; + +pub(super) fn generate_backtracking(maze: &mut HexMaze, start_pos: Option, seed: Option) { + if maze.is_empty() { + return; + } + + let start = start_pos.unwrap_or(Hex::ZERO); + + let mut visited = HashSet::new(); + + let mut rng: Box = seed.map_or_else( + || Box::new(thread_rng()) as Box, + |seed| Box::new(StdRng::seed_from_u64(seed)) as Box, + ); + + recursive_backtrack(maze, start, &mut visited, &mut rng); +} + +fn recursive_backtrack( + maze: &mut HexMaze, + current: Hex, + visited: &mut HashSet, + rng: &mut R, +) { + visited.insert(current); + let mut directions = EdgeDirection::ALL_DIRECTIONS; + directions.shuffle(rng); + + for direction in directions { + let neighbor = current + direction; + if maze.get_tile(&neighbor).is_some() && !visited.contains(&neighbor) { + maze.remove_tile_wall(¤t, direction); + maze.remove_tile_wall(&neighbor, direction.const_neg()); + recursive_backtrack(maze, neighbor, visited, rng); + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::builder::create_hex_maze; + use rstest::rstest; + + #[rstest] + #[case(Hex::ZERO)] + #[case(Hex::new(1, -1))] + #[case(Hex::new(-2, 2))] + fn recursive_backtrack_start_visited(#[case] start: Hex) { + let mut maze = create_hex_maze(3); + let mut rng = StdRng::seed_from_u64(12345); + let mut visited = HashSet::new(); + + recursive_backtrack(&mut maze, start, &mut visited, &mut rng); + + assert!(visited.contains(&start), "Start position should be visited"); + } + + #[rstest] + #[case(Hex::ZERO)] + #[case(Hex::new(1, -1))] + #[case(Hex::new(-2, 2))] + fn recursive_backtrack_walls_removed(#[case] start: Hex) { + let mut maze = create_hex_maze(3); + let mut rng = StdRng::seed_from_u64(12345); + let mut visited = HashSet::new(); + + recursive_backtrack(&mut maze, start, &mut visited, &mut rng); + + for &pos in maze.keys() { + let walls = maze.get_walls(&pos).unwrap(); + assert!( + walls.count() < 6, + "At least one wall should be removed for each tile" + ); + } + } + + #[rstest] + #[case(Hex::ZERO)] + #[case(Hex::new(1, -1))] + #[case(Hex::new(-2, 2))] + fn recursive_backtrack_connectivity(#[case] start: Hex) { + let mut maze = create_hex_maze(3); + let mut rng = StdRng::seed_from_u64(12345); + let mut visited = HashSet::new(); + + recursive_backtrack(&mut maze, start, &mut visited, &mut rng); + + let mut to_visit = vec![start]; + let mut connected = HashSet::new(); + while let Some(current) = to_visit.pop() { + if !connected.insert(current) { + continue; + } + for dir in EdgeDirection::ALL_DIRECTIONS { + let neighbor = current + dir; + if let Some(walls) = maze.get_walls(¤t) { + if !walls.contains(dir) && maze.get_tile(&neighbor).is_some() { + to_visit.push(neighbor); + } + } + } + } + assert_eq!(connected.len(), maze.len(), "All tiles should be connected"); + } +} diff --git a/src/generator/mod.rs b/src/generator/mod.rs new file mode 100644 index 0000000..771af0c --- /dev/null +++ b/src/generator/mod.rs @@ -0,0 +1,24 @@ +mod backtrack; +use crate::HexMaze; +use backtrack::generate_backtracking; +#[cfg(feature = "bevy_reflect")] +use bevy::prelude::*; +use hexx::Hex; + +#[allow(clippy::module_name_repetitions)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "bevy_reflect", derive(Reflect))] +#[cfg_attr(feature = "bevy", derive(Component))] +#[cfg_attr(feature = "bevy", reflect(Component))] +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +pub enum GeneratorType { + #[default] + RecursiveBacktracking, +} +impl GeneratorType { + pub fn generate(&self, maze: &mut HexMaze, start_pos: Option, seed: Option) { + match self { + Self::RecursiveBacktracking => generate_backtracking(maze, start_pos, seed), + } + } +} diff --git a/tests/generator.rs b/tests/generator.rs new file mode 100644 index 0000000..b823266 --- /dev/null +++ b/tests/generator.rs @@ -0,0 +1,66 @@ +use hexlab::prelude::*; +use rstest::rstest; + +#[rstest] +#[case(GeneratorType::RecursiveBacktracking, None, None)] +#[case(GeneratorType::RecursiveBacktracking, Some(Hex::new(1, -1)), None)] +#[case(GeneratorType::RecursiveBacktracking, None, Some(12345))] +fn generator_type( + #[case] generator: GeneratorType, + #[case] start_pos: Option, + #[case] seed: Option, +) { + let mut maze = HexMaze::new(); + for q in -3..=3 { + for r in -3..=3 { + let hex = Hex::new(q, r); + if hex.length() <= 3 { + maze.add_tile(hex); + } + } + } + let initial_size = maze.len(); + + generator.generate(&mut maze, start_pos, seed); + + assert_eq!(maze.len(), initial_size, "Maze size should not change"); + + // Check maze connectivity + let start = start_pos.unwrap_or(Hex::ZERO); + let mut to_visit = vec![start]; + let mut visited = std::collections::HashSet::new(); + while let Some(current) = to_visit.pop() { + if !visited.insert(current) { + continue; + } + for dir in EdgeDirection::ALL_DIRECTIONS { + let neighbor = current + dir; + if let Some(walls) = maze.get_walls(¤t) { + if !walls.contains(dir) && maze.get_tile(&neighbor).is_some() { + to_visit.push(neighbor); + } + } + } + } + assert_eq!(visited.len(), maze.len(), "All tiles should be connected"); + + // Check that each tile has at least one open wall + for &pos in maze.keys() { + let walls = maze.get_walls(&pos).unwrap(); + assert!( + walls.count() < 6, + "Tile at {:?} should have at least one open wall", + pos + ); + } +} + +#[test] +fn test_empty_maze() { + let mut maze = HexMaze::new(); + GeneratorType::RecursiveBacktracking.generate(&mut maze, None, None); + assert!( + maze.is_empty(), + "Empty maze should remain empty after generation" + ); +} From 012d1e5cca30d5b1a9ddf523f0804419b3aaeb5e Mon Sep 17 00:00:00 2001 From: Kristofers Solo Date: Wed, 25 Dec 2024 20:56:22 +0200 Subject: [PATCH 08/11] refactor: rename files --- src/builder.rs | 2 +- src/lib.rs | 8 ++++---- src/{hex_maze.rs => maze.rs} | 1 + src/{hex_tile.rs => tile.rs} | 1 + 4 files changed, 7 insertions(+), 5 deletions(-) rename src/{hex_maze.rs => maze.rs} (99%) rename src/{hex_tile.rs => tile.rs} (99%) diff --git a/src/builder.rs b/src/builder.rs index 3a7c798..4c5b6a2 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -190,7 +190,7 @@ impl MazeBuilder { Ok(maze) } } -pub(crate) fn create_hex_maze(radius: u16) -> HexMaze { +pub fn create_hex_maze(radius: u16) -> HexMaze { let mut maze = HexMaze::new(); let radius = i32::from(radius); diff --git a/src/lib.rs b/src/lib.rs index 2fb8c48..f0f9145 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -51,14 +51,14 @@ //!``` mod builder; mod generator; -mod hex_maze; -mod hex_tile; +mod maze; +mod tile; mod walls; pub use builder::{MazeBuilder, MazeBuilderError}; pub use generator::GeneratorType; -pub use hex_maze::HexMaze; -pub use hex_tile::HexTile; +pub use maze::HexMaze; +pub use tile::HexTile; pub use walls::Walls; /// Prelude module containing commonly used types diff --git a/src/hex_maze.rs b/src/maze.rs similarity index 99% rename from src/hex_maze.rs rename to src/maze.rs index e41957a..204b587 100644 --- a/src/hex_maze.rs +++ b/src/maze.rs @@ -12,6 +12,7 @@ use std::ops::{Deref, DerefMut}; /// /// This struct stores the layout of a hexagonal maze, including the positions /// of tiles and their associated walls. +#[allow(clippy::module_name_repetitions)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "bevy_reflect", derive(Reflect))] #[cfg_attr(feature = "bevy", derive(Component))] diff --git a/src/hex_tile.rs b/src/tile.rs similarity index 99% rename from src/hex_tile.rs rename to src/tile.rs index 863ce91..85cd3fe 100644 --- a/src/hex_tile.rs +++ b/src/tile.rs @@ -9,6 +9,7 @@ use std::fmt::Display; /// Represents a single hexagonal tile in the maze /// /// Each tile has a position and a set of walls defining its boundaries. +#[allow(clippy::module_name_repetitions)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "bevy_reflect", derive(Reflect))] #[cfg_attr(feature = "bevy", derive(Component))] From 43a669dee82bbbdfb279efd3e763a6be0a131c49 Mon Sep 17 00:00:00 2001 From: Kristofers Solo Date: Wed, 25 Dec 2024 21:24:55 +0200 Subject: [PATCH 09/11] test(maze): 88% coverage --- src/maze.rs | 277 ++++++++++++++++++----------------------------- tests/builder.rs | 9 -- tests/maze.rs | 68 ++++++++++++ 3 files changed, 175 insertions(+), 179 deletions(-) create mode 100644 tests/maze.rs diff --git a/src/maze.rs b/src/maze.rs index 204b587..0ed198b 100644 --- a/src/maze.rs +++ b/src/maze.rs @@ -22,6 +22,17 @@ pub struct HexMaze(HashMap); impl HexMaze { /// Creates a new empty maze + /// + /// # Examples + /// + /// ``` + /// use hexlab::prelude::*; + /// + /// let maze = HexMaze::new(); + /// + /// assert!(maze.is_empty()); + /// assert_eq!(maze.len(), 0); + /// ``` #[cfg_attr(not(debug_assertions), inline)] #[must_use] pub fn new() -> Self { @@ -33,6 +44,20 @@ impl HexMaze { /// # Arguments /// /// - `coords` - The hexagonal coordinates where the tile should be added. + /// + /// # Examples + /// + /// ``` + /// use hexlab::prelude::*; + /// + /// let mut maze = HexMaze::new(); + /// let coord = Hex::ZERO; + /// maze.add_tile(coord); + /// + /// assert_eq!(maze.len(), 1); + /// assert!(!maze.is_empty()); + /// assert!(!maze.get_tile(&coord).is_some()); + /// ``` pub fn add_tile(&mut self, coords: Hex) { let tile = HexTile::new(coords); self.0.insert(coords, tile); @@ -44,6 +69,20 @@ impl HexMaze { /// /// - `coord` - The hexagonal coordinates of the tile. /// - `direction` - The direction in which to add the wall. + /// + /// # Examples + /// + /// ``` + /// use hexlab::prelude::*; + /// + /// let mut maze = HexMaze::new(); + /// let coord = Hex::ZERO; + /// maze.add_tile(coord); + /// + /// maze.add_wall(coord, EdgeDirection::FLAT_NORTH); + /// let walls = maze.get_walls(&coord).unwrap(); + /// assert!(walls.contains(EdgeDirection::FLAT_NORTH)); + /// ``` pub fn add_wall(&mut self, coord: Hex, direction: EdgeDirection) { if let Some(tile) = self.0.get_mut(&coord) { tile.walls.add(direction); @@ -55,6 +94,18 @@ impl HexMaze { /// # Arguments /// /// - `coord` - The hexagonal coordinates of the tile to retrieve. + /// + /// # Examples + /// + /// ``` + /// use hexlab::prelude::*; + /// + /// let mut maze = HexMaze::new(); + /// let coord = Hex::ZERO; + /// maze.add_tile(coord); + /// + /// assert!(!maze.get_tile(&coord).is_some()); + /// ``` #[cfg_attr(not(debug_assertions), inline)] #[must_use] pub fn get_tile(&self, coord: &Hex) -> Option<&HexTile> { @@ -66,11 +117,40 @@ impl HexMaze { /// # Arguments /// /// - `coord` - The hexagonal coordinates of the tile whose walls to retrieve. + /// + /// # Examples + /// + /// ``` + /// use hexlab::prelude::*; + /// + /// let mut maze = HexMaze::new(); + /// let coord = Hex::new(0, 0); + /// maze.add_tile(coord); + /// + /// maze.add_wall(coord, EdgeDirection::FLAT_NORTH); + /// let walls = maze.get_walls(&coord).unwrap(); + /// assert!(walls.contains(EdgeDirection::FLAT_NORTH)); + /// ``` pub fn get_walls(&self, coord: &Hex) -> Option<&Walls> { self.0.get(coord).map(HexTile::walls) } /// Returns the number of tiles in the maze. + /// + /// # Examples + /// + /// ``` + /// use hexlab::prelude::*; + /// + /// let mut maze = HexMaze::new(); + /// assert_eq!(maze.len(), 0); + /// + /// maze.add_tile(Hex::new(0, 0)); + /// assert_eq!(maze.len(), 1); + /// + /// maze.add_tile(Hex::new(1, -1)); + /// assert_eq!(maze.len(), 2); + /// ``` #[cfg_attr(not(debug_assertions), inline)] #[must_use] pub fn len(&self) -> usize { @@ -78,6 +158,18 @@ impl HexMaze { } /// Returns `true` if the maze contains no tiles. + /// + /// # Examples + /// + /// ``` + /// use hexlab::prelude::*; + /// + /// let mut maze = HexMaze::new(); + /// assert!(maze.is_empty()); + /// + /// maze.add_tile(Hex::ZERO); + /// assert!(!maze.is_empty()); + /// ``` #[cfg_attr(not(debug_assertions), inline)] #[must_use] pub fn is_empty(&self) -> bool { @@ -90,6 +182,21 @@ impl HexMaze { /// /// - `coord` - The hexagonal coordinates of the tile. /// - `direction` - The direction of the wall to remove. + /// # Examples + /// + /// ``` + /// use hexlab::prelude::*; + /// + /// let mut maze = HexMaze::new(); + /// let coord = Hex::ZERO; + /// maze.add_tile(coord); + /// + /// maze.add_wall(coord, EdgeDirection::FLAT_NORTH); + /// maze.remove_tile_wall(&coord, EdgeDirection::FLAT_NORTH); + /// + /// let walls = maze.get_walls(&coord).unwrap(); + /// assert!(!walls.contains(EdgeDirection::FLAT_NORTH)); + /// ``` pub fn remove_tile_wall(&mut self, coord: &Hex, direction: EdgeDirection) { if let Some(tile) = self.0.get_mut(coord) { tile.walls.remove(direction); @@ -109,173 +216,3 @@ impl DerefMut for HexMaze { &mut self.0 } } - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn new_maze() { - let maze = HexMaze::default(); - assert!(maze.is_empty(), "New maze should be empty"); - assert_eq!(maze.len(), 0, "New maze should have zero tiles"); - } - - #[test] - fn add_tile() { - let mut maze = HexMaze::default(); - let coords = [Hex::ZERO, Hex::new(1, -1), Hex::new(-1, 1)]; - - // Add tiles - for &coord in &coords { - maze.add_tile(coord); - assert!( - maze.get_tile(&coord).is_some(), - "Tile should exist after adding" - ); - } - - assert_eq!( - maze.len(), - coords.len(), - "Maze should contain all added tiles" - ); - } - - #[test] - fn wall_operations() { - let mut maze = HexMaze::default(); - let coord = Hex::ZERO; - maze.add_tile(coord); - - // Test adding walls - let directions = [ - EdgeDirection::FLAT_TOP, - EdgeDirection::FLAT_BOTTOM, - EdgeDirection::POINTY_TOP_RIGHT, - ]; - - for &direction in &directions { - maze.add_wall(coord, direction); - assert!( - maze.get_walls(&coord).unwrap().contains(direction), - "Wall should exist after adding" - ); - } - } - - #[test] - fn tile_iteration() { - let mut maze = HexMaze::default(); - let coords = [Hex::ZERO, Hex::new(1, 0), Hex::new(0, 1)]; - - // Add tiles - for &coord in &coords { - maze.add_tile(coord); - } - - // Test iterator - let collected = maze.iter().map(|(_, tile)| tile).collect::>(); - assert_eq!( - collected.len(), - coords.len(), - "Iterator should yield all tiles" - ); - } - - #[test] - fn maze_clone() { - let mut maze = HexMaze::default(); - let coord = Hex::ZERO; - maze.add_tile(coord); - maze.add_wall(coord, EdgeDirection::FLAT_TOP); - - // Test cloning - let cloned_maze = maze.clone(); - assert_eq!( - maze.len(), - cloned_maze.len(), - "Cloned maze should have same size" - ); - assert!( - cloned_maze - .get_walls(&coord) - .unwrap() - .contains(EdgeDirection::FLAT_TOP), - "Cloned maze should preserve wall state" - ); - } - - #[test] - fn empty_tile_operations() { - let mut maze = HexMaze::default(); - let coord = Hex::ZERO; - - // Operations on non-existent tile - assert!( - maze.get_tile(&coord).is_none(), - "Should return None for non-existent tile" - ); - assert!( - maze.get_walls(&coord).is_none(), - "Should return None for non-existent walls" - ); - - // Adding wall to non-existent tile should not panic - maze.add_wall(coord, EdgeDirection::FLAT_TOP); - } - - #[test] - fn maze_boundaries() { - let mut maze = HexMaze::default(); - let extreme_coords = [ - Hex::new(i32::MAX, i32::MIN), - Hex::new(i32::MIN, i32::MAX), - Hex::new(0, i32::MAX), - Hex::new(0, i32::MIN), - Hex::new(i32::MAX, 0), - Hex::new(i32::MIN, 0), - ]; - - // Test with extreme coordinates - for &coord in &extreme_coords { - maze.add_tile(coord); - assert!( - maze.get_tile(&coord).is_some(), - "Should handle extreme coordinates" - ); - } - } - - #[test] - fn iterator_consistency() { - let mut maze = HexMaze::default(); - let coords = [Hex::ZERO, Hex::new(1, -1), Hex::new(-1, 1)]; - - // Add tiles - for &coord in &coords { - maze.add_tile(coord); - } - - // Verify iterator - let iter_coords = maze.iter().map(|(coord, _)| *coord).collect::>(); - assert_eq!( - iter_coords.len(), - coords.len(), - "Iterator should yield all coordinates" - ); - - for coord in coords { - assert!( - iter_coords.contains(&coord), - "Iterator should contain all added coordinates" - ); - } - } - - #[test] - fn empty_maze() { - let maze = HexMaze::default(); - assert!(maze.is_empty(), "New maze should be empty"); - } -} diff --git a/tests/builder.rs b/tests/builder.rs index 832781c..df33480 100644 --- a/tests/builder.rs +++ b/tests/builder.rs @@ -93,15 +93,6 @@ fn maze_connectivity() { } } -#[test] -fn generator_type() { - let maze = assert_ok!(MazeBuilder::new() - .with_radius(3) - .with_generator(GeneratorType::RecursiveBacktracking) - .build()); - claims::assert_gt!(maze.len(), 0); -} - #[test] fn maze_boundaries() { let radius = 3; diff --git a/tests/maze.rs b/tests/maze.rs new file mode 100644 index 0000000..89fc225 --- /dev/null +++ b/tests/maze.rs @@ -0,0 +1,68 @@ +use hexlab::prelude::*; + +#[test] +fn hex_maze_creation_and_basic_operations() { + let mut maze = HexMaze::new(); + assert!(maze.is_empty()); + + let center = Hex::ZERO; + maze.add_tile(center); + assert_eq!(maze.len(), 1); + assert!(!maze.is_empty()); + + let tile = maze.get_tile(¢er); + assert!(tile.is_some()); + assert_eq!(tile.unwrap().pos(), center); +} + +#[test] +fn hex_maze_wall_operations() { + let mut maze = HexMaze::new(); + let center = Hex::ZERO; + maze.add_tile(center); + + // Add walls + for direction in EdgeDirection::ALL_DIRECTIONS { + maze.add_wall(center, direction); + } + + let walls = maze.get_walls(¢er).unwrap(); + assert_eq!(walls.count(), 6); + + // Remove walls + for direction in EdgeDirection::ALL_DIRECTIONS { + maze.remove_tile_wall(¢er, direction); + } + + let walls = maze.get_walls(¢er).unwrap(); + assert_eq!(walls.count(), 0); +} + +#[test] +fn hex_maze_multiple_tiles() { + let mut maze = HexMaze::new(); + let tiles = [Hex::ZERO, Hex::new(1, -1), Hex::new(0, 1), Hex::new(-1, 1)]; + + for &tile in &tiles { + maze.add_tile(tile); + } + + assert_eq!(maze.len(), tiles.len()); + + for &tile in &tiles { + assert!(maze.get_tile(&tile).is_some()); + } +} + +#[test] +fn hex_maze_edge_cases() { + let mut maze = HexMaze::new(); + let non_existent = Hex::new(10, 10); + + // Operations on non-existent tiles should not panic + maze.add_wall(non_existent, EdgeDirection::FLAT_NORTH); + maze.remove_tile_wall(&non_existent, EdgeDirection::FLAT_NORTH); + + assert!(maze.get_tile(&non_existent).is_none()); + assert!(maze.get_walls(&non_existent).is_none()); +} From 9740ce1a5a198b9b27381bd97bde950eb994a0c4 Mon Sep 17 00:00:00 2001 From: Kristofers Solo Date: Wed, 25 Dec 2024 22:57:13 +0200 Subject: [PATCH 10/11] test(walls): 93% coverage --- Cargo.lock | 71 ++++++++------- Cargo.toml | 22 ++++- src/generator/mod.rs | 8 +- src/maze.rs | 19 ++-- src/tile.rs | 212 +++++++++++++++++++++++-------------------- src/walls.rs | 152 +++++++++++++++++++++++++++++-- 6 files changed, 325 insertions(+), 159 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e5dc3b6..8a5b356 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1347,18 +1347,18 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytemuck" -version = "1.20.0" +version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b37c88a63ffd85d15b406896cc343916d7cf57838a847b3a6f2ca5d39a5695a" +checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcfcc3cd946cb52f0bbfdbbcfa2f4e24f75ebb6c0e1002f7c25904fada18b9ec" +checksum = "3fa76293b4f7bb636ab88fd78228235b5248b4d05cc589aed610f954af5d7c7a" dependencies = [ "proc-macro2", "quote", @@ -1399,9 +1399,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.3" +version = "1.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27f657647bcff5394bf56c7317665bbf790a137a50eaaa5c6bfbb9e27a518f2d" +checksum = "c31a0499c1dc64f458ad13872de75c0eb7e3fdb0e67964610c914b034fc5956e" dependencies = [ "jobserver", "libc", @@ -1525,9 +1525,9 @@ dependencies = [ [[package]] name = "const_panic" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "013b6c2c3a14d678f38cd23994b02da3a1a1b6a5d1eedddfe63a5a5f11b13a81" +checksum = "53857514f72ee4a2b583de67401e3ff63a5472ca4acf289d09a9ea7636dfec17" [[package]] name = "const_soft_float" @@ -1677,18 +1677,18 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.13" +version = "0.5.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-deque" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -1705,9 +1705,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.20" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" @@ -1951,9 +1951,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" +checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" [[package]] name = "font-types" @@ -2297,13 +2297,13 @@ dependencies = [ [[package]] name = "gpu-descriptor" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c08c1f623a8d0b722b8b99f821eb0ba672a1618f0d3b16ddbee1cedd2dd8557" +checksum = "dcf29e94d6d243368b7a56caa16bc213e4f9f8ed38c4d9557069527b5d5281ca" dependencies = [ "bitflags 2.6.0", "gpu-descriptor-types", - "hashbrown 0.14.5", + "hashbrown 0.15.1", ] [[package]] @@ -2375,10 +2375,13 @@ checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" [[package]] name = "hexlab" -version = "0.3.1" +version = "0.4.0" dependencies = [ "bevy", + "bevy_reflect", + "bevy_utils", "claims", + "glam", "hexx", "rand", "rstest", @@ -2698,9 +2701,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" dependencies = [ "adler2", "simd-adler32", @@ -2708,9 +2711,9 @@ dependencies = [ [[package]] name = "naga" -version = "23.0.0" +version = "23.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d5941e45a15b53aad4375eedf02033adb7a28931eedc31117faffa52e6a857e" +checksum = "364f94bc34f61332abebe8cad6f6cd82a5b65cff22c828d05d0968911462ca4f" dependencies = [ "arrayvec", "bit-set 0.8.0", @@ -3269,9 +3272,9 @@ checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "png" -version = "0.17.15" +version = "0.17.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67582bd5b65bdff614270e2ea89a1cf15bef71245cc1e5f7ea126977144211d" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" dependencies = [ "bitflags 1.3.2", "crc32fast", @@ -3710,9 +3713,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.133" +version = "1.0.134" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d" dependencies = [ "itoa", "memchr", @@ -3938,9 +3941,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8" dependencies = [ "tinyvec_macros", ] @@ -4085,9 +4088,9 @@ checksum = "0e13db2e0ccd5e14a544e8a246ba2312cd25223f616442d7f2cb0e3db614236e" [[package]] name = "unicode-bidi" -version = "0.3.17" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-bidi-mirroring" @@ -4755,9 +4758,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winit" -version = "0.30.5" +version = "0.30.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0be9e76a1f1077e04a411f0b989cbd3c93339e1771cb41e71ac4aee95bfd2c67" +checksum = "dba50bc8ef4b6f1a75c9274fb95aa9a8f63fbc66c56f391bd85cf68d51e7b1a3" dependencies = [ "android-activity", "atomic-waker", diff --git a/Cargo.toml b/Cargo.toml index 1137472..add2d6d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "hexlab" authors = ["Kristofers Solo "] -version = "0.3.1" +version = "0.4.0" edition = "2021" description = "A hexagonal maze generation and manipulation library" repository = "https://github.com/kristoferssolo/hexlab" @@ -19,11 +19,20 @@ exclude = ["/.github", "/.gitignore", "/tests", "*.png", "*.md"] readme = "README.md" [dependencies] -bevy = { version = "0.15", optional = true } hexx = { version = "0.19" } rand = "0.8" serde = { version = "1.0", features = ["derive"], optional = true } thiserror = "2.0" +bevy = { version = "0.15", optional = true } +bevy_utils = { version = "0.15", optional = true } +glam = { version = "0.29", optional = true } + + +[dependencies.bevy_reflect] +version = "0.15" +optional = true +default-features = false +features = ["glam"] [dev-dependencies] claims = "0.8" @@ -32,8 +41,13 @@ rstest = "0.23" [features] default = [] serde = ["dep:serde", "hexx/serde"] -bevy = ["bevy_reflect"] -bevy_reflect = ["dep:bevy", "hexx/bevy_reflect"] +bevy = ["dep:bevy", "bevy_reflect"] +bevy_reflect = [ + "dep:bevy_reflect", + "dep:bevy_utils", + "hexx/bevy_reflect", + "dep:glam", +] full = ["serde", "bevy"] [profile.dev] diff --git a/src/generator/mod.rs b/src/generator/mod.rs index 771af0c..2ae801c 100644 --- a/src/generator/mod.rs +++ b/src/generator/mod.rs @@ -1,15 +1,13 @@ mod backtrack; use crate::HexMaze; use backtrack::generate_backtracking; -#[cfg(feature = "bevy_reflect")] -use bevy::prelude::*; use hexx::Hex; #[allow(clippy::module_name_repetitions)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "bevy_reflect", derive(Reflect))] -#[cfg_attr(feature = "bevy", derive(Component))] -#[cfg_attr(feature = "bevy", reflect(Component))] +#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))] +#[cfg_attr(feature = "bevy", derive(bevy::Component))] +#[cfg_attr(feature = "bevy", reflect(bevy::Component))] #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] pub enum GeneratorType { #[default] diff --git a/src/maze.rs b/src/maze.rs index 0ed198b..e3a7c6b 100644 --- a/src/maze.rs +++ b/src/maze.rs @@ -1,8 +1,6 @@ use super::{HexTile, Walls}; #[cfg(feature = "bevy_reflect")] -use bevy::prelude::*; -#[cfg(feature = "bevy_reflect")] -use bevy::utils::HashMap; +use bevy_utils::HashMap; use hexx::{EdgeDirection, Hex}; #[cfg(not(feature = "bevy_reflect"))] use std::collections::HashMap; @@ -14,9 +12,9 @@ use std::ops::{Deref, DerefMut}; /// of tiles and their associated walls. #[allow(clippy::module_name_repetitions)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "bevy_reflect", derive(Reflect))] -#[cfg_attr(feature = "bevy", derive(Component))] -#[cfg_attr(feature = "bevy", reflect(Component))] +#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))] +#[cfg_attr(feature = "bevy", derive(bevy::Component))] +#[cfg_attr(feature = "bevy", reflect(bevy::Component))] #[derive(Debug, Clone, Default, PartialEq, Eq)] pub struct HexMaze(HashMap); @@ -56,7 +54,6 @@ impl HexMaze { /// /// assert_eq!(maze.len(), 1); /// assert!(!maze.is_empty()); - /// assert!(!maze.get_tile(&coord).is_some()); /// ``` pub fn add_tile(&mut self, coords: Hex) { let tile = HexTile::new(coords); @@ -80,8 +77,9 @@ impl HexMaze { /// maze.add_tile(coord); /// /// maze.add_wall(coord, EdgeDirection::FLAT_NORTH); - /// let walls = maze.get_walls(&coord).unwrap(); - /// assert!(walls.contains(EdgeDirection::FLAT_NORTH)); + /// let walls = maze.get_walls(&coord); + /// assert!(walls.is_some()); + /// assert!(walls.unwrap().contains(EdgeDirection::FLAT_NORTH)); /// ``` pub fn add_wall(&mut self, coord: Hex, direction: EdgeDirection) { if let Some(tile) = self.0.get_mut(&coord) { @@ -104,7 +102,8 @@ impl HexMaze { /// let coord = Hex::ZERO; /// maze.add_tile(coord); /// - /// assert!(!maze.get_tile(&coord).is_some()); + /// assert!(maze.get_tile(&coord).is_some()); + /// assert!(maze.get_tile(&Hex::new(1, 1)).is_none()); /// ``` #[cfg_attr(not(debug_assertions), inline)] #[must_use] diff --git a/src/tile.rs b/src/tile.rs index 85cd3fe..26bb4a7 100644 --- a/src/tile.rs +++ b/src/tile.rs @@ -1,6 +1,4 @@ use super::Walls; -#[cfg(feature = "bevy_reflect")] -use bevy::prelude::*; use hexx::Hex; #[cfg(feature = "bevy_reflect")] use hexx::HexLayout; @@ -11,7 +9,7 @@ use std::fmt::Display; /// Each tile has a position and a set of walls defining its boundaries. #[allow(clippy::module_name_repetitions)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "bevy_reflect", derive(Reflect))] +#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))] #[cfg_attr(feature = "bevy", derive(Component))] #[cfg_attr(feature = "bevy", reflect(Component))] #[derive(Debug, Clone, Default, PartialEq, Eq)] @@ -26,6 +24,16 @@ impl HexTile { /// # Arguments /// /// - `pos` - The hexagonal coordinates of the tile. + /// + /// # Examples + /// + /// ``` + /// use hexlab::prelude::*; + /// + /// let tile = HexTile::new(Hex::new(1, -1)); + /// assert_eq!(tile.pos(), Hex::new(1, -1)); + /// assert_eq!(*tile.walls(), Walls::default()); + /// ``` #[must_use] pub fn new(pos: Hex) -> Self { Self { @@ -35,6 +43,15 @@ impl HexTile { } /// Returns a reference to the tile's walls + /// + /// # Examples + /// + /// ``` + /// use hexlab::prelude::*; + /// + /// let tile = HexTile::new(Hex::ZERO); + /// assert_eq!(*tile.walls(), Walls::default()); + /// ``` #[cfg_attr(not(debug_assertions), inline)] #[must_use] pub const fn walls(&self) -> &Walls { @@ -42,11 +59,21 @@ impl HexTile { } /// Returns position of the tile + /// + /// # Examples + /// + /// ``` + /// use hexlab::prelude::*; + /// + /// let tile = HexTile::new(Hex::new(2, -2)); + /// assert_eq!(tile.pos(), Hex::new(2, -2)); + /// ``` #[cfg_attr(not(debug_assertions), inline)] #[must_use] pub const fn pos(&self) -> Hex { self.pos } + /// Converts the tile's position to a 2D vector based on the given layout. /// /// # Arguments @@ -55,7 +82,7 @@ impl HexTile { #[cfg(feature = "bevy_reflect")] #[cfg_attr(not(debug_assertions), inline)] #[must_use] - pub fn to_vec2(&self, layout: &HexLayout) -> Vec2 { + pub fn to_vec2(&self, layout: &HexLayout) -> glam::Vec2 { layout.hex_to_world_pos(self.pos) } @@ -67,7 +94,9 @@ impl HexTile { #[cfg(feature = "bevy_reflect")] #[cfg_attr(not(debug_assertions), inline)] #[must_use] - pub fn to_vec3(&self, layout: &HexLayout) -> Vec3 { + pub fn to_vec3(&self, layout: &HexLayout) -> glam::Vec3 { + use glam::Vec3; + let pos = self.to_vec2(layout); Vec3::new(pos.x, 0., pos.y) } @@ -90,76 +119,26 @@ impl Display for HexTile { #[cfg(test)] mod test { - use hexx::EdgeDirection; - use super::*; + use hexx::EdgeDirection; + use rand::{thread_rng, Rng}; - #[test] - fn new_tile() { - let pos = Hex::ZERO; - let tile = HexTile::new(pos); - - assert_eq!(tile.pos, pos, "Position should match constructor argument"); - assert_eq!( - tile.walls, - Walls::default(), - "Walls should be initialized to default" - ); - } - - #[test] - fn tile_walls_accessor() { - let pos = Hex::new(1, -1); - let tile = HexTile::new(pos); - - // Test walls accessor method - let walls_ref = tile.walls(); - assert_eq!( - walls_ref, &tile.walls, - "Walls accessor should return reference to walls" - ); + fn random_hex() -> Hex { + let mut rng = thread_rng(); + Hex::new(rng.gen(), rng.gen()) } #[test] fn tile_modification() { - let pos = Hex::new(2, 3); - let mut tile = HexTile::new(pos); + let hex = random_hex(); + let mut tile = HexTile::new(hex); // Modify walls tile.walls.remove(EdgeDirection::FLAT_TOP); - assert!( - !tile.walls.contains(EdgeDirection::FLAT_TOP), - "Wall should be removed" - ); + assert!(!tile.walls.contains(EdgeDirection::FLAT_TOP)); tile.walls.add(EdgeDirection::FLAT_TOP); - assert!( - tile.walls.contains(EdgeDirection::FLAT_TOP), - "Wall should be added back" - ); - } - - #[test] - fn tile_clone() { - let pos = Hex::new(0, -2); - let tile = HexTile::new(pos); - - // Test Clone trait - let cloned_tile = tile.clone(); - assert_eq!(tile, cloned_tile, "Cloned tile should equal original"); - } - - #[test] - fn tile_debug() { - let pos = Hex::ZERO; - let tile = HexTile::new(pos); - - // Test Debug trait - let debug_string = format!("{:?}", tile); - assert!( - debug_string.contains("HexTile"), - "Debug output should contain struct name" - ); + assert!(tile.walls.contains(EdgeDirection::FLAT_TOP)); } #[test] @@ -174,38 +153,10 @@ mod test { // Verify each tile has correct position for (tile, &pos) in tiles.iter().zip(positions.iter()) { - assert_eq!( - tile.pos, pos, - "Tile position should match constructor argument" - ); + assert_eq!(tile.pos, pos); } } - #[test] - fn tile_equality() { - let pos1 = Hex::new(1, 1); - let pos2 = Hex::new(1, 1); - let pos3 = Hex::new(2, 1); - - let tile1 = HexTile::new(pos1); - let tile2 = HexTile::new(pos2); - let tile3 = HexTile::new(pos3); - - assert_eq!(tile1, tile2, "Tiles with same position should be equal"); - assert_ne!( - tile1, tile3, - "Tiles with different positions should not be equal" - ); - - // Test with modified walls - let mut tile4 = HexTile::new(pos1); - tile4.walls.remove(EdgeDirection::FLAT_TOP); - assert_ne!( - tile1, tile4, - "Tiles with different walls should not be equal" - ); - } - #[test] fn hex_boundaries() { // Test with extreme coordinate values @@ -218,10 +169,77 @@ mod test { for pos in extreme_positions { let tile = HexTile::new(pos); - assert_eq!( - tile.pos, pos, - "Tile should handle extreme coordinate values" - ); + assert_eq!(tile.pos, pos); + } + } + + #[test] + fn hex_tile_creation_and_properties() { + let hex = random_hex(); + let tile = HexTile::new(hex); + + assert_eq!(tile.pos(), hex); + assert!(tile.walls().is_enclosed()); + } + + #[test] + fn hex_tile_from_hex() { + let hex = random_hex(); + let tile = HexTile::from(hex); + + assert_eq!(tile.pos, hex); + assert_eq!(tile.walls, Walls::default()); + } + + #[test] + fn hex_hex_into_tile() { + let hex = random_hex(); + let tile: HexTile = hex.into(); + + assert_eq!(tile.pos, hex); + assert_eq!(tile.walls, Walls::default()); + } + + #[test] + fn hex_tile_display() { + let tile = HexTile::new(Hex::new(3, -3)); + assert_eq!(format!("{tile}"), "(3,-3)"); + } + + #[test] + fn hex_tile_wall_modifications() { + let mut tile = HexTile::new(Hex::ZERO); + + for direction in EdgeDirection::ALL_DIRECTIONS { + tile.walls.add(direction); + } + assert_eq!(tile.walls.count(), 6); + + for direction in EdgeDirection::ALL_DIRECTIONS { + tile.walls.remove(direction); + } + assert_eq!(tile.walls.count(), 0); + } + + #[cfg(feature = "bevy_reflect")] + mod bevy_tests { + use super::*; + use glam::{Vec2, Vec3}; + + #[test] + fn hex_tile_to_vec2() { + let layout = HexLayout::default(); + let tile = HexTile::new(Hex::new(1, 0)); + let vec2 = tile.to_vec2(&layout); + assert_eq!(vec2, Vec2::new(1.5, -0.8660254)); + } + + #[test] + fn hex_tile_to_vec3() { + let layout = HexLayout::default(); + let tile = HexTile::new(Hex::new(0, 1)); + let vec3 = tile.to_vec3(&layout); + assert_eq!(vec3, Vec3::new(0.0, 0.0, -1.7320508)); } } } diff --git a/src/walls.rs b/src/walls.rs index 54af979..83493e7 100644 --- a/src/walls.rs +++ b/src/walls.rs @@ -1,5 +1,3 @@ -#[cfg(feature = "bevy_reflect")] -use bevy::prelude::*; use hexx::EdgeDirection; /// A bit-flag representation of walls in a hexagonal tile. @@ -28,7 +26,7 @@ use hexx::EdgeDirection; /// assert_eq!(walls.count(), 2); /// ``` #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "bevy_reflect", derive(Reflect))] +#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))] #[cfg_attr(feature = "bevy", derive(Component))] #[cfg_attr(feature = "bevy", reflect(Component))] #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -38,6 +36,15 @@ impl Walls { /// Creates a new set of walls with all edges closed. /// /// This is the default state where all six edges of the hexagon have walls. + /// + /// # Examples + /// + /// ``` + /// use hexlab::prelude::*; + /// + /// let walls = Walls::new(); + /// assert!(walls.is_enclosed()); + /// ``` #[cfg_attr(not(debug_assertions), inline)] #[must_use] pub fn new() -> Self { @@ -45,6 +52,15 @@ impl Walls { } /// Creates a new set of walls with no edges (completely open). + /// + /// # Examples + /// + /// ``` + /// use hexlab::prelude::*; + /// + /// let walls = Walls::empty(); + /// assert!(walls.is_empty()); + /// ``` #[cfg_attr(not(debug_assertions), inline)] #[must_use] pub const fn empty() -> Self { @@ -52,6 +68,15 @@ impl Walls { } /// Checks if the walls are currently empty (no walls present). + /// + /// # Examples + /// + /// ``` + /// use hexlab::prelude::*; + /// + /// let walls = Walls::empty(); + /// assert!(walls.is_empty()); + /// ``` #[cfg_attr(not(debug_assertions), inline)] #[must_use] pub const fn is_empty(&self) -> bool { @@ -62,7 +87,26 @@ impl Walls { /// /// # Arguments /// - /// 0 `direction` - The direction in which to add the wall. + /// - `direction` - The direction in which to add the wall. + /// + /// # Examples + /// + /// ``` + /// use hexlab::prelude::*; + /// + /// let mut walls = Walls::empty(); + /// assert_eq!(walls.count(), 0); + /// + /// walls.add(1); + /// assert_eq!(walls.count(), 1); + /// + /// walls.add(1); + /// assert_eq!(walls.count(), 1); + /// + /// walls.add(EdgeDirection::FLAT_NORTH); + /// assert_eq!(walls.count(), 2); + /// + /// ``` #[cfg_attr(not(debug_assertions), inline)] pub fn add(&mut self, direction: T) where @@ -76,6 +120,23 @@ impl Walls { /// # Arguments /// /// - `direction` - The direction from which to remove the wall. + /// + /// # Examples + /// + /// ``` + /// use hexlab::prelude::*; + /// + /// let mut walls = Walls::new(); + /// + /// assert!(walls.remove(1)); + /// assert_eq!(walls.count(), 5); + /// + /// assert!(!walls.remove(1)); + /// assert_eq!(walls.count(), 5); + /// + /// assert!(walls.remove(EdgeDirection::FLAT_NORTH)); + /// assert_eq!(walls.count(), 4); + /// ``` #[cfg_attr(not(debug_assertions), inline)] pub fn remove(&mut self, direction: T) -> bool where @@ -93,6 +154,18 @@ impl Walls { /// # Arguments /// /// - `other` - The direction to check for a wall. + /// + /// # Examples + /// + /// ``` + /// use hexlab::prelude::*; + /// + /// let mut walls = Walls::empty(); + /// walls.add(EdgeDirection::FLAT_NORTH); + /// + /// assert!(walls.contains(EdgeDirection::FLAT_NORTH)); + /// assert!(!walls.contains(EdgeDirection::FLAT_SOUTH)); + /// ``` #[cfg_attr(not(debug_assertions), inline)] pub fn contains(&self, other: T) -> bool where @@ -102,6 +175,18 @@ impl Walls { } /// Returns the raw bit representation of the walls + /// + /// # Examples + /// + /// ``` + /// use hexlab::prelude::*; + /// + /// let walls = Walls::new(); + /// assert_eq!(walls.as_bits(), 0b11_1111); + /// + /// let walls = Walls::empty(); + /// assert_eq!(walls.as_bits(), 0); + /// ``` #[cfg_attr(not(debug_assertions), inline)] #[must_use] pub const fn as_bits(&self) -> u8 { @@ -109,6 +194,21 @@ impl Walls { } /// Returns the total number of walls present + /// + /// # Examples + /// + /// ``` + /// use hexlab::prelude::*; + /// + /// let mut walls = Walls::empty(); + /// assert!(walls.is_empty()); + /// + /// walls.add(0); + /// assert_eq!(walls.count(), 1); + /// + /// walls.add(1); + /// assert_eq!(walls.count(), 2); + /// ``` #[cfg_attr(not(debug_assertions), inline)] #[must_use] pub fn count(&self) -> u8 { @@ -116,6 +216,14 @@ impl Walls { } /// Returns a `Walls` value representing all possible directions. + /// + /// # Examples + /// + /// ``` + /// use hexlab::prelude::*; + /// + /// assert_eq!(Walls::all_directions().as_bits(), 0b11_1111); + /// ``` #[cfg_attr(not(debug_assertions), inline)] #[must_use] pub const fn all_directions() -> Self { @@ -134,6 +242,20 @@ impl Walls { /// # Returns /// /// The previous state (`true` if a wall was present before toggling, `false` otherwise). + /// + /// # Examples + /// + /// ``` + /// use hexlab::prelude::*; + /// + /// let mut walls = Walls::empty(); + /// + /// assert!(!walls.toggle(0)); + /// assert_eq!(walls.count(), 1); + /// + /// assert!(walls.toggle(0)); + /// assert_eq!(walls.count(), 0); + /// ``` pub fn toggle(&mut self, direction: T) -> bool where T: Into + Copy, @@ -155,10 +277,10 @@ impl Walls { /// /// # Deprecated /// - /// This method is deprecated since version 0.3.1. Use `is_enclosed()` instead. + /// This method is deprecated since version 0.4.0. Use `is_enclosed()` instead. #[cfg_attr(not(debug_assertions), inline)] #[must_use] - #[deprecated(since = "0.3.1", note = "use `walls::Walls::is_enclosed()`")] + #[deprecated(since = "0.4.0", note = "use `walls::Walls::is_enclosed()`")] pub fn is_closed(&self) -> bool { self.is_enclosed() } @@ -168,6 +290,18 @@ impl Walls { /// # Returns /// /// `true` if the hexagon has all possible walls, making it completely enclosed. + /// + /// # Examples + /// + /// ``` + /// use hexlab::prelude::*; + /// + /// let mut walls = Walls::new(); + /// assert!(walls.is_enclosed()); + /// + /// walls.remove(0); + /// assert!(!walls.is_enclosed()); + /// ``` #[cfg_attr(not(debug_assertions), inline)] #[must_use] pub fn is_enclosed(&self) -> bool { @@ -247,7 +381,7 @@ mod test { #[test] fn all_directions_creates_closed_walls() { let walls = Walls::all_directions(); - assert!(walls.is_closed()); + assert!(walls.is_enclosed()); assert!(!walls.is_empty()); assert_eq!(walls.as_bits(), 0b111111); } @@ -284,7 +418,7 @@ mod test { #[test] fn new_created_closed_walls() { let walls = Walls::new(); - assert!(walls.is_closed()); + assert!(walls.is_enclosed()); assert_eq!(walls.as_bits(), 0b111111); } @@ -393,7 +527,7 @@ mod test { #[test] fn default_creates_closed_walls() { let walls = Walls::default(); - assert!(walls.is_closed()); + assert!(walls.is_enclosed()); assert_eq!(walls.as_bits(), 0b111111); } From a562333b9080b34d85cc214d93ab73e565ed938c Mon Sep 17 00:00:00 2001 From: Kristofers Solo Date: Wed, 25 Dec 2024 23:08:41 +0200 Subject: [PATCH 11/11] fix(bevy): imports --- Cargo.toml | 12 ++++++++++++ src/generator/mod.rs | 6 ++++-- src/maze.rs | 6 ++++-- src/tile.rs | 2 ++ src/walls.rs | 2 ++ 5 files changed, 24 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index add2d6d..db7cec4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,6 +63,18 @@ panic = "abort" # Smaller binary size all-features = true rustdoc-args = ["--cfg", "docsrs"] +[profile.dev.package."*"] +opt-level = 3 + +# Override some settings for native builds. +[profile.release-native] +# Default to release profile values. +inherits = "release" +# Optimize with performance in mind. +opt-level = 3 +# Keep debug information in the binary. +strip = "none" + [lints.clippy] pedantic = "warn" nursery = "warn" diff --git a/src/generator/mod.rs b/src/generator/mod.rs index 2ae801c..105b8f6 100644 --- a/src/generator/mod.rs +++ b/src/generator/mod.rs @@ -1,13 +1,15 @@ mod backtrack; use crate::HexMaze; use backtrack::generate_backtracking; +#[cfg(feature = "bevy")] +use bevy::prelude::*; use hexx::Hex; #[allow(clippy::module_name_repetitions)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))] -#[cfg_attr(feature = "bevy", derive(bevy::Component))] -#[cfg_attr(feature = "bevy", reflect(bevy::Component))] +#[cfg_attr(feature = "bevy", derive(Component))] +#[cfg_attr(feature = "bevy", reflect(Component))] #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] pub enum GeneratorType { #[default] diff --git a/src/maze.rs b/src/maze.rs index e3a7c6b..301fbc5 100644 --- a/src/maze.rs +++ b/src/maze.rs @@ -1,4 +1,6 @@ use super::{HexTile, Walls}; +#[cfg(feature = "bevy")] +use bevy::prelude::*; #[cfg(feature = "bevy_reflect")] use bevy_utils::HashMap; use hexx::{EdgeDirection, Hex}; @@ -13,8 +15,8 @@ use std::ops::{Deref, DerefMut}; #[allow(clippy::module_name_repetitions)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))] -#[cfg_attr(feature = "bevy", derive(bevy::Component))] -#[cfg_attr(feature = "bevy", reflect(bevy::Component))] +#[cfg_attr(feature = "bevy", derive(Component))] +#[cfg_attr(feature = "bevy", reflect(Component))] #[derive(Debug, Clone, Default, PartialEq, Eq)] pub struct HexMaze(HashMap); diff --git a/src/tile.rs b/src/tile.rs index 26bb4a7..743ff5f 100644 --- a/src/tile.rs +++ b/src/tile.rs @@ -1,4 +1,6 @@ use super::Walls; +#[cfg(feature = "bevy")] +use bevy::prelude::*; use hexx::Hex; #[cfg(feature = "bevy_reflect")] use hexx::HexLayout; diff --git a/src/walls.rs b/src/walls.rs index 83493e7..190b02a 100644 --- a/src/walls.rs +++ b/src/walls.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "bevy")] +use bevy::prelude::*; use hexx::EdgeDirection; /// A bit-flag representation of walls in a hexagonal tile.