mirror of
https://github.com/kristoferssolo/hexlab.git
synced 2025-10-21 19:40:34 +00:00
Merge branch 'feature/add-traits'
This commit is contained in:
commit
dcbe06fb8c
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -2375,7 +2375,7 @@ checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
|
||||
|
||||
[[package]]
|
||||
name = "hexlab"
|
||||
version = "0.4.0"
|
||||
version = "0.5.0"
|
||||
dependencies = [
|
||||
"bevy",
|
||||
"bevy_reflect",
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "hexlab"
|
||||
authors = ["Kristofers Solo <dev@kristofers.xyz>"]
|
||||
version = "0.4.0"
|
||||
version = "0.5.0"
|
||||
edition = "2021"
|
||||
description = "A hexagonal maze generation and manipulation library"
|
||||
repository = "https://github.com/kristoferssolo/hexlab"
|
||||
|
||||
@ -1,25 +1,5 @@
|
||||
use crate::{GeneratorType, HexMaze};
|
||||
use crate::{errors::MazeBuilderError, GeneratorType, Maze};
|
||||
use hexx::Hex;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum MazeBuilderError {
|
||||
/// Occurs when attempting to build a maze without specifying a radius.
|
||||
#[error("Radius must be specified to build a maze")]
|
||||
NoRadius,
|
||||
|
||||
/// Occurs when the specified radius is too large.
|
||||
#[error("Radius {0} is too large. Maximum allowed radius is {1}")]
|
||||
RadiusTooLarge(u32, u32),
|
||||
|
||||
/// Occurs when the specified start position is outside the maze bounds.
|
||||
#[error("Start position {0:?} is outside maze bounds")]
|
||||
InvalidStartPosition(Hex),
|
||||
|
||||
/// Occurs when maze generation fails.
|
||||
#[error("Failed to generate maze: {0}")]
|
||||
GenerationError(String),
|
||||
}
|
||||
|
||||
/// A builder pattern for creating hexagonal mazes.
|
||||
///
|
||||
@ -85,7 +65,7 @@ pub struct MazeBuilder {
|
||||
|
||||
impl MazeBuilder {
|
||||
/// Creates a new [`MazeBuilder`] instance with default settings.
|
||||
#[cfg_attr(not(debug_assertions), inline)]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
@ -101,7 +81,7 @@ impl MazeBuilder {
|
||||
/// # Arguments
|
||||
///
|
||||
/// - `radius` - The number of tiles from the center to the edge of the hexagon.
|
||||
#[cfg_attr(not(debug_assertions), inline)]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn with_radius(mut self, radius: u16) -> Self {
|
||||
self.radius = Some(radius);
|
||||
@ -115,7 +95,7 @@ impl MazeBuilder {
|
||||
/// # Arguments
|
||||
///
|
||||
/// - `seed` - The random seed value.
|
||||
#[cfg_attr(not(debug_assertions), inline)]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn with_seed(mut self, seed: u64) -> Self {
|
||||
self.seed = Some(seed);
|
||||
@ -129,7 +109,7 @@ impl MazeBuilder {
|
||||
/// # Arguments
|
||||
///
|
||||
/// - `generator_type` - The maze generation algorithm to use.
|
||||
#[cfg_attr(not(debug_assertions), inline)]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn with_generator(mut self, generator_type: GeneratorType) -> Self {
|
||||
self.generator_type = generator_type;
|
||||
@ -140,7 +120,7 @@ impl MazeBuilder {
|
||||
/// # Arguments
|
||||
///
|
||||
/// - `pos` - The hexagonal coordinates for the starting position.
|
||||
#[cfg_attr(not(debug_assertions), inline)]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn with_start_position(mut self, pos: Hex) -> Self {
|
||||
self.start_position = Some(pos);
|
||||
@ -172,12 +152,12 @@ impl MazeBuilder {
|
||||
/// let maze = result.unwrap();
|
||||
/// assert!(!maze.is_empty());
|
||||
/// ```
|
||||
pub fn build(self) -> Result<HexMaze, MazeBuilderError> {
|
||||
pub fn build(self) -> Result<Maze, MazeBuilderError> {
|
||||
let radius = self.radius.ok_or(MazeBuilderError::NoRadius)?;
|
||||
let mut maze = create_hex_maze(radius);
|
||||
|
||||
if let Some(start_pos) = self.start_position {
|
||||
if maze.get_tile(&start_pos).is_none() {
|
||||
if maze.get(&start_pos).is_none() {
|
||||
return Err(MazeBuilderError::InvalidStartPosition(start_pos));
|
||||
}
|
||||
}
|
||||
@ -190,8 +170,9 @@ impl MazeBuilder {
|
||||
Ok(maze)
|
||||
}
|
||||
}
|
||||
pub fn create_hex_maze(radius: u16) -> HexMaze {
|
||||
let mut maze = HexMaze::new();
|
||||
|
||||
pub fn create_hex_maze(radius: u16) -> Maze {
|
||||
let mut maze = Maze::new();
|
||||
let radius = i32::from(radius);
|
||||
|
||||
for q in -radius..=radius {
|
||||
@ -199,7 +180,7 @@ pub fn create_hex_maze(radius: u16) -> HexMaze {
|
||||
let r2 = radius.min(-q + radius);
|
||||
for r in r1..=r2 {
|
||||
let pos = Hex::new(q, r);
|
||||
maze.add_tile(pos);
|
||||
maze.insert(pos);
|
||||
}
|
||||
}
|
||||
|
||||
@ -269,7 +250,7 @@ mod test {
|
||||
Hex::new(1, -2),
|
||||
];
|
||||
for pos in expected_positions.iter() {
|
||||
assert!(maze.get_tile(pos).is_some(), "Expected tile at {:?}", pos);
|
||||
assert!(maze.get(pos).is_some(), "Expected tile at {:?}", pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
23
src/errors.rs
Normal file
23
src/errors.rs
Normal file
@ -0,0 +1,23 @@
|
||||
use hexx::Hex;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error, PartialEq, Eq)]
|
||||
pub enum MazeBuilderError {
|
||||
/// Occurs when attempting to build a maze without specifying a radius.
|
||||
#[error("Radius must be specified to build a maze")]
|
||||
NoRadius,
|
||||
|
||||
/// Occurs when the specified start position is outside the maze bounds.
|
||||
#[error("Start position {0:?} is outside maze bounds")]
|
||||
InvalidStartPosition(Hex),
|
||||
|
||||
/// Occurs when maze generation fails.
|
||||
#[error("Failed to generate maze: {0}")]
|
||||
GenerationError(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Error, PartialEq, Eq)]
|
||||
pub enum MazeError {
|
||||
#[error("Invalid coordinate: {0:?}")]
|
||||
InvalidCoordinate(Hex),
|
||||
}
|
||||
@ -1,9 +1,9 @@
|
||||
use crate::HexMaze;
|
||||
use crate::Maze;
|
||||
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<Hex>, seed: Option<u64>) {
|
||||
pub(super) fn generate_backtracking(maze: &mut Maze, start_pos: Option<Hex>, seed: Option<u64>) {
|
||||
if maze.is_empty() {
|
||||
return;
|
||||
}
|
||||
@ -21,7 +21,7 @@ pub(super) fn generate_backtracking(maze: &mut HexMaze, start_pos: Option<Hex>,
|
||||
}
|
||||
|
||||
fn recursive_backtrack<R: Rng>(
|
||||
maze: &mut HexMaze,
|
||||
maze: &mut Maze,
|
||||
current: Hex,
|
||||
visited: &mut HashSet<Hex>,
|
||||
rng: &mut R,
|
||||
@ -32,9 +32,9 @@ fn recursive_backtrack<R: 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());
|
||||
if maze.get(&neighbor).is_some() && !visited.contains(&neighbor) {
|
||||
let _ = maze.remove_tile_wall(¤t, direction);
|
||||
let _ = maze.remove_tile_wall(&neighbor, direction.const_neg());
|
||||
recursive_backtrack(maze, neighbor, visited, rng);
|
||||
}
|
||||
}
|
||||
@ -100,7 +100,7 @@ mod test {
|
||||
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() {
|
||||
if !walls.contains(&dir) && maze.get(&neighbor).is_some() {
|
||||
to_visit.push(neighbor);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
mod backtrack;
|
||||
use crate::HexMaze;
|
||||
use crate::Maze;
|
||||
use backtrack::generate_backtracking;
|
||||
#[cfg(feature = "bevy")]
|
||||
use bevy::prelude::*;
|
||||
@ -16,7 +16,7 @@ pub enum GeneratorType {
|
||||
RecursiveBacktracking,
|
||||
}
|
||||
impl GeneratorType {
|
||||
pub fn generate(&self, maze: &mut HexMaze, start_pos: Option<Hex>, seed: Option<u64>) {
|
||||
pub fn generate(&self, maze: &mut Maze, start_pos: Option<Hex>, seed: Option<u64>) {
|
||||
match self {
|
||||
Self::RecursiveBacktracking => generate_backtracking(maze, start_pos, seed),
|
||||
}
|
||||
|
||||
21
src/lib.rs
21
src/lib.rs
@ -7,7 +7,6 @@
|
||||
//! - Efficient bit-flag representation of walls
|
||||
//! - Multiple maze generation algorithms
|
||||
//! - Maze builder pattern for easy maze creation
|
||||
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
@ -36,7 +35,7 @@
|
||||
//! .build()
|
||||
//! .expect("Failed to create maze");
|
||||
//!
|
||||
//! assert!(maze.get_tile(&Hex::new(1, -1)).is_some());
|
||||
//! assert!(maze.get(&Hex::new(1, -1)).is_some());
|
||||
//!```
|
||||
//!
|
||||
//! Manipulating walls:
|
||||
@ -45,24 +44,28 @@
|
||||
//! 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));
|
||||
//! assert!(!walls.insert(EdgeDirection::FLAT_NORTH));
|
||||
//! assert!(walls.contains(&EdgeDirection::FLAT_NORTH));
|
||||
//! assert!(!walls.contains(&EdgeDirection::FLAT_SOUTH));
|
||||
//!```
|
||||
mod builder;
|
||||
pub mod errors;
|
||||
mod generator;
|
||||
mod maze;
|
||||
mod tile;
|
||||
pub mod traits;
|
||||
mod walls;
|
||||
|
||||
pub use builder::{MazeBuilder, MazeBuilderError};
|
||||
pub use builder::MazeBuilder;
|
||||
pub use errors::*;
|
||||
pub use generator::GeneratorType;
|
||||
pub use maze::HexMaze;
|
||||
pub use tile::HexTile;
|
||||
pub use maze::Maze;
|
||||
pub use tile::Tile;
|
||||
pub use traits::*;
|
||||
pub use walls::Walls;
|
||||
|
||||
/// Prelude module containing commonly used types
|
||||
pub mod prelude {
|
||||
pub use super::{GeneratorType, HexMaze, HexTile, MazeBuilder, MazeBuilderError, Walls};
|
||||
pub use super::{errors::*, traits::*, GeneratorType, Maze, MazeBuilder, Tile, Walls};
|
||||
pub use hexx::{EdgeDirection, Hex, HexLayout};
|
||||
}
|
||||
|
||||
253
src/maze.rs
253
src/maze.rs
@ -1,4 +1,8 @@
|
||||
use super::{HexTile, Walls};
|
||||
use super::{Tile, Walls};
|
||||
use crate::{
|
||||
errors::MazeError,
|
||||
traits::{TilePosition, WallStorage},
|
||||
};
|
||||
#[cfg(feature = "bevy")]
|
||||
use bevy::prelude::*;
|
||||
#[cfg(feature = "bevy_reflect")]
|
||||
@ -12,15 +16,14 @@ 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(bevy_reflect::Reflect))]
|
||||
#[cfg_attr(feature = "bevy", derive(Component))]
|
||||
#[cfg_attr(feature = "bevy", reflect(Component))]
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub struct HexMaze(HashMap<Hex, HexTile>);
|
||||
pub struct Maze(HashMap<Hex, Tile>);
|
||||
|
||||
impl HexMaze {
|
||||
impl Maze {
|
||||
/// Creates a new empty maze
|
||||
///
|
||||
/// # Examples
|
||||
@ -28,18 +31,23 @@ impl HexMaze {
|
||||
/// ```
|
||||
/// use hexlab::prelude::*;
|
||||
///
|
||||
/// let maze = HexMaze::new();
|
||||
/// let maze = Maze::new();
|
||||
///
|
||||
/// assert!(maze.is_empty());
|
||||
/// assert_eq!(maze.len(), 0);
|
||||
/// ```
|
||||
#[cfg_attr(not(debug_assertions), inline)]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Adds a new tile at the specified coordinates
|
||||
/// Inserts a new tile at the specified coordinates.
|
||||
///
|
||||
/// If the map did not have this key present, [`None`] is returned.
|
||||
///
|
||||
/// If the map did have this key present, the value is updated, and the old
|
||||
/// value is returned.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
@ -50,43 +58,48 @@ impl HexMaze {
|
||||
/// ```
|
||||
/// use hexlab::prelude::*;
|
||||
///
|
||||
/// let mut maze = HexMaze::new();
|
||||
/// let mut maze = Maze::new();
|
||||
/// let coord = Hex::ZERO;
|
||||
/// maze.add_tile(coord);
|
||||
///
|
||||
/// assert_eq!(maze.len(), 1);
|
||||
/// assert!(!maze.is_empty());
|
||||
/// assert_eq!(maze.insert(coord), None);
|
||||
/// assert_eq!(maze.insert(coord), Some(Tile::new(coord)));
|
||||
/// ```
|
||||
pub fn add_tile(&mut self, coords: Hex) {
|
||||
let tile = HexTile::new(coords);
|
||||
self.0.insert(coords, tile);
|
||||
pub fn insert(&mut self, coords: Hex) -> Option<Tile> {
|
||||
let tile = Tile::new(coords);
|
||||
self.0.insert(coords, tile)
|
||||
}
|
||||
|
||||
/// Adds a wall in the specified direction at the given coordinates.
|
||||
/// Adds a new tile at the specified coordinates.
|
||||
///
|
||||
/// If the map did not have this key present, [`None`] is returned.
|
||||
///
|
||||
/// If the map did have this key present, the value is updated, and the old
|
||||
/// value is returned.
|
||||
///
|
||||
/// It is recommended to use [`insert`].
|
||||
///
|
||||
/// [`insert`]: Maze::insert
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// - `coord` - The hexagonal coordinates of the tile.
|
||||
/// - `direction` - The direction in which to add the wall.
|
||||
/// - `coords` - The hexagonal coordinates where the tile should be added.
|
||||
/// - `tile` - The tile to insert to.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use hexlab::prelude::*;
|
||||
///
|
||||
/// let mut maze = HexMaze::new();
|
||||
/// let mut maze = Maze::new();
|
||||
/// let coord = Hex::ZERO;
|
||||
/// maze.add_tile(coord);
|
||||
/// let tile1 = Tile::new(coord);
|
||||
/// let tile2 = Tile::new(Hex::new(1, 1));
|
||||
///
|
||||
/// maze.add_wall(coord, EdgeDirection::FLAT_NORTH);
|
||||
/// let walls = maze.get_walls(&coord);
|
||||
/// assert!(walls.is_some());
|
||||
/// assert!(walls.unwrap().contains(EdgeDirection::FLAT_NORTH));
|
||||
/// assert_eq!(maze.insert_with_tile(coord, tile1.clone()), None);
|
||||
/// assert_eq!(maze.insert_with_tile(coord, tile2), Some(tile1));
|
||||
/// ```
|
||||
pub fn add_wall(&mut self, coord: Hex, direction: EdgeDirection) {
|
||||
if let Some(tile) = self.0.get_mut(&coord) {
|
||||
tile.walls.add(direction);
|
||||
}
|
||||
pub fn insert_with_tile(&mut self, coords: Hex, tile: Tile) -> Option<Tile> {
|
||||
self.0.insert(coords, tile)
|
||||
}
|
||||
|
||||
/// Returns a reference to the tile at the specified coordinates.
|
||||
@ -100,20 +113,26 @@ impl HexMaze {
|
||||
/// ```
|
||||
/// use hexlab::prelude::*;
|
||||
///
|
||||
/// let mut maze = HexMaze::new();
|
||||
/// let mut maze = Maze::new();
|
||||
/// let coord = Hex::ZERO;
|
||||
/// maze.add_tile(coord);
|
||||
/// maze.insert(coord);
|
||||
///
|
||||
/// assert!(maze.get_tile(&coord).is_some());
|
||||
/// assert!(maze.get_tile(&Hex::new(1, 1)).is_none());
|
||||
/// assert!(maze.get(&coord).is_some());
|
||||
/// assert!(maze.get(&Hex::new(1, 1)).is_none());
|
||||
/// ```
|
||||
#[cfg_attr(not(debug_assertions), inline)]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn get_tile(&self, coord: &Hex) -> Option<&HexTile> {
|
||||
pub fn get(&self, coord: &Hex) -> Option<&Tile> {
|
||||
self.0.get(coord)
|
||||
}
|
||||
|
||||
/// Returns an optional reference to the walls at the specified coordinates.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn get_mut(&mut self, coord: &Hex) -> Option<&mut Tile> {
|
||||
self.0.get_mut(coord)
|
||||
}
|
||||
|
||||
/// Returns an optional mutable reference to the walls at the specified coordinates.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
@ -124,16 +143,43 @@ impl HexMaze {
|
||||
/// ```
|
||||
/// use hexlab::prelude::*;
|
||||
///
|
||||
/// let mut maze = HexMaze::new();
|
||||
/// let mut maze = Maze::new();
|
||||
/// let coord = Hex::new(0, 0);
|
||||
/// maze.add_tile(coord);
|
||||
/// maze.insert(coord);
|
||||
///
|
||||
/// maze.add_wall(coord, EdgeDirection::FLAT_NORTH);
|
||||
/// maze.add_tile_wall(&coord, EdgeDirection::FLAT_NORTH);
|
||||
/// let walls = maze.get_walls(&coord).unwrap();
|
||||
/// assert!(walls.contains(EdgeDirection::FLAT_NORTH));
|
||||
/// assert!(walls.contains(&EdgeDirection::FLAT_NORTH));
|
||||
/// ```
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn get_walls(&self, coord: &Hex) -> Option<&Walls> {
|
||||
self.0.get(coord).map(HexTile::walls)
|
||||
self.0.get(coord).map(Tile::walls)
|
||||
}
|
||||
|
||||
/// Returns an optional mutable reference to the walls at the specified coordinates.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// - `coord` - The hexagonal coordinates of the tile whose walls to retrieve.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use hexlab::prelude::*;
|
||||
///
|
||||
/// let mut maze = Maze::new();
|
||||
/// let coord = Hex::new(0, 0);
|
||||
/// maze.insert(coord);
|
||||
///
|
||||
/// maze.add_tile_wall(&coord, EdgeDirection::FLAT_NORTH);
|
||||
/// let mut walls = maze.get_walls_mut(&coord).unwrap();
|
||||
/// assert!(walls.remove(EdgeDirection::FLAT_NORTH));
|
||||
/// ```
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn get_walls_mut(&mut self, coord: &Hex) -> Option<&mut Walls> {
|
||||
self.0.get_mut(coord).map(Tile::walls_mut)
|
||||
}
|
||||
|
||||
/// Returns the number of tiles in the maze.
|
||||
@ -143,16 +189,16 @@ impl HexMaze {
|
||||
/// ```
|
||||
/// use hexlab::prelude::*;
|
||||
///
|
||||
/// let mut maze = HexMaze::new();
|
||||
/// let mut maze = Maze::new();
|
||||
/// assert_eq!(maze.len(), 0);
|
||||
///
|
||||
/// maze.add_tile(Hex::new(0, 0));
|
||||
/// maze.insert(Hex::new(0, 0));
|
||||
/// assert_eq!(maze.len(), 1);
|
||||
///
|
||||
/// maze.add_tile(Hex::new(1, -1));
|
||||
/// maze.insert(Hex::new(1, -1));
|
||||
/// assert_eq!(maze.len(), 2);
|
||||
/// ```
|
||||
#[cfg_attr(not(debug_assertions), inline)]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn len(&self) -> usize {
|
||||
self.0.len()
|
||||
@ -165,54 +211,147 @@ impl HexMaze {
|
||||
/// ```
|
||||
/// use hexlab::prelude::*;
|
||||
///
|
||||
/// let mut maze = HexMaze::new();
|
||||
/// let mut maze = Maze::new();
|
||||
/// assert!(maze.is_empty());
|
||||
///
|
||||
/// maze.add_tile(Hex::ZERO);
|
||||
/// maze.insert(Hex::ZERO);
|
||||
/// assert!(!maze.is_empty());
|
||||
/// ```
|
||||
#[cfg_attr(not(debug_assertions), inline)]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
|
||||
/// Adds 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.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `MazeError::InvalidCoordinate` if the specified coordinate does not exist in the maze.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use hexlab::prelude::*;
|
||||
///
|
||||
/// // Create a maze with a single tile at the origin
|
||||
/// let mut tile = Tile::new(Hex::ZERO);
|
||||
/// tile.walls_mut().toggle(Walls::all_directions());
|
||||
/// let mut maze = Maze::from([tile]);
|
||||
///
|
||||
/// // Initially, the tile should have no walls
|
||||
/// assert!(maze.get_walls(&Hex::ZERO).unwrap().is_empty());
|
||||
///
|
||||
/// // Add a wall to the north
|
||||
/// assert!(maze.add_tile_wall(&Hex::ZERO, EdgeDirection::FLAT_NORTH).is_ok());
|
||||
///
|
||||
/// // Check that the wall was added
|
||||
/// let walls = maze.get_walls(&Hex::ZERO).unwrap();
|
||||
/// assert!(walls.contains(&EdgeDirection::FLAT_NORTH));
|
||||
/// assert_eq!(walls.count(), 1);
|
||||
///
|
||||
/// // Adding the same wall again should return true (no change)
|
||||
/// assert_eq!(maze.add_tile_wall(&Hex::ZERO, EdgeDirection::FLAT_NORTH), Ok(true));
|
||||
///
|
||||
/// // Adding a wall to a non-existent tile should return an error
|
||||
/// let invalid_coord = Hex::new(1, 1);
|
||||
/// assert_eq!(
|
||||
/// maze.add_tile_wall(&invalid_coord, EdgeDirection::FLAT_NORTH),
|
||||
/// Err(MazeError::InvalidCoordinate(invalid_coord))
|
||||
/// );
|
||||
/// ```
|
||||
pub fn add_tile_wall(
|
||||
&mut self,
|
||||
coord: &Hex,
|
||||
direction: EdgeDirection,
|
||||
) -> Result<bool, MazeError> {
|
||||
self.0
|
||||
.get_mut(coord)
|
||||
.map(|tile| tile.walls.insert(direction))
|
||||
.ok_or(MazeError::InvalidCoordinate(*coord))
|
||||
}
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `MazeError::InvalidCoordinate` if the specified coordinate does not exist in the maze.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use hexlab::prelude::*;
|
||||
///
|
||||
/// let mut maze = HexMaze::new();
|
||||
/// let mut maze = Maze::new();
|
||||
/// let coord = Hex::ZERO;
|
||||
/// maze.add_tile(coord);
|
||||
/// maze.insert(coord);
|
||||
///
|
||||
/// maze.add_wall(coord, EdgeDirection::FLAT_NORTH);
|
||||
/// maze.add_tile_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));
|
||||
/// 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);
|
||||
}
|
||||
pub fn remove_tile_wall(
|
||||
&mut self,
|
||||
coord: &Hex,
|
||||
direction: EdgeDirection,
|
||||
) -> Result<bool, MazeError> {
|
||||
self.0
|
||||
.get_mut(coord)
|
||||
.map(|tile| tile.walls.remove(direction))
|
||||
.ok_or(MazeError::InvalidCoordinate(*coord))
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for HexMaze {
|
||||
type Target = HashMap<Hex, HexTile>;
|
||||
impl FromIterator<Hex> for Maze {
|
||||
fn from_iter<T: IntoIterator<Item = Hex>>(iter: T) -> Self {
|
||||
Self(iter.into_iter().map(|hex| (hex, Tile::new(hex))).collect())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<Tile> for Maze {
|
||||
fn from_iter<T: IntoIterator<Item = Tile>>(iter: T) -> Self {
|
||||
Self(iter.into_iter().map(|tile| (tile.pos(), tile)).collect())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<(Hex, Tile)> for Maze {
|
||||
fn from_iter<T: IntoIterator<Item = (Hex, Tile)>>(iter: T) -> Self {
|
||||
Self(iter.into_iter().collect())
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> From<[Hex; N]> for Maze {
|
||||
fn from(value: [Hex; N]) -> Self {
|
||||
value.into_iter().collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> From<[Tile; N]> for Maze {
|
||||
fn from(value: [Tile; N]) -> Self {
|
||||
value.into_iter().collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Maze {
|
||||
type Target = HashMap<Hex, Tile>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for HexMaze {
|
||||
impl DerefMut for Maze {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
|
||||
179
src/tile.rs
179
src/tile.rs
@ -1,4 +1,7 @@
|
||||
use super::Walls;
|
||||
#[cfg(feature = "bevy_reflect")]
|
||||
use crate::traits::WorldPositionable;
|
||||
use crate::traits::{TilePosition, WallStorage};
|
||||
#[cfg(feature = "bevy")]
|
||||
use bevy::prelude::*;
|
||||
use hexx::Hex;
|
||||
@ -9,18 +12,90 @@ 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(bevy_reflect::Reflect))]
|
||||
#[cfg_attr(feature = "bevy", derive(Component))]
|
||||
#[cfg_attr(feature = "bevy", reflect(Component))]
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub struct HexTile {
|
||||
pub struct Tile {
|
||||
pub(crate) pos: Hex,
|
||||
pub(crate) walls: Walls,
|
||||
}
|
||||
|
||||
impl HexTile {
|
||||
impl TilePosition for Tile {
|
||||
/// Returns position of the tile
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use hexlab::prelude::*;
|
||||
///
|
||||
/// let tile = Tile::new(Hex::new(2, -2));
|
||||
/// assert_eq!(tile.pos(), Hex::new(2, -2));
|
||||
/// ```
|
||||
#[inline]
|
||||
fn pos(&self) -> Hex {
|
||||
self.pos
|
||||
}
|
||||
}
|
||||
|
||||
impl WallStorage for Tile {
|
||||
/// Returns an immutable reference to the tile's walls
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use hexlab::prelude::*;
|
||||
///
|
||||
/// let tile = Tile::new(Hex::ZERO);
|
||||
/// assert_eq!(*tile.walls(), Walls::default());
|
||||
/// ```
|
||||
#[inline]
|
||||
fn walls(&self) -> &Walls {
|
||||
&self.walls
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the tile's walls
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use hexlab::prelude::*;
|
||||
///
|
||||
/// let tile = Tile::new(Hex::ZERO);
|
||||
/// assert_eq!(*tile.walls(), Walls::default());
|
||||
/// ```
|
||||
#[inline]
|
||||
fn walls_mut(&mut self) -> &mut Walls {
|
||||
&mut self.walls
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "bevy_reflect")]
|
||||
impl WorldPositionable for Tile {
|
||||
/// Converts the tile's position to a 2D vector based on the given layout.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// - `layout` - The hexagonal layout used for conversion.
|
||||
#[inline]
|
||||
fn to_vec2(&self, layout: &HexLayout) -> glam::Vec2 {
|
||||
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.
|
||||
#[inline]
|
||||
fn to_vec3(&self, layout: &HexLayout) -> glam::Vec3 {
|
||||
let pos = self.to_vec2(layout);
|
||||
glam::Vec3::new(pos.x, 0., pos.y)
|
||||
}
|
||||
}
|
||||
|
||||
impl Tile {
|
||||
/// Creates a new tile with the given position and default walls.
|
||||
///
|
||||
/// # Arguments
|
||||
@ -32,7 +107,7 @@ impl HexTile {
|
||||
/// ```
|
||||
/// use hexlab::prelude::*;
|
||||
///
|
||||
/// let tile = HexTile::new(Hex::new(1, -1));
|
||||
/// let tile = Tile::new(Hex::new(1, -1));
|
||||
/// assert_eq!(tile.pos(), Hex::new(1, -1));
|
||||
/// assert_eq!(*tile.walls(), Walls::default());
|
||||
/// ```
|
||||
@ -43,68 +118,9 @@ impl HexTile {
|
||||
walls: Walls::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
&self.walls
|
||||
}
|
||||
|
||||
/// 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
|
||||
///
|
||||
/// - `layout` - The hexagonal layout used for conversion.
|
||||
#[cfg(feature = "bevy_reflect")]
|
||||
#[cfg_attr(not(debug_assertions), inline)]
|
||||
#[must_use]
|
||||
pub fn to_vec2(&self, layout: &HexLayout) -> glam::Vec2 {
|
||||
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")]
|
||||
#[cfg_attr(not(debug_assertions), inline)]
|
||||
#[must_use]
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Hex> for HexTile {
|
||||
impl From<Hex> for Tile {
|
||||
fn from(value: Hex) -> Self {
|
||||
Self {
|
||||
pos: value,
|
||||
@ -113,7 +129,7 @@ impl From<Hex> for HexTile {
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for HexTile {
|
||||
impl Display for Tile {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "({},{})", self.pos.x, self.pos.y)
|
||||
}
|
||||
@ -130,19 +146,6 @@ mod test {
|
||||
Hex::new(rng.gen(), rng.gen())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tile_modification() {
|
||||
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));
|
||||
|
||||
tile.walls.add(EdgeDirection::FLAT_TOP);
|
||||
assert!(tile.walls.contains(EdgeDirection::FLAT_TOP));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn different_positions() {
|
||||
let positions = [Hex::ZERO, Hex::new(1, 0), Hex::new(-1, 1), Hex::new(2, -2)];
|
||||
@ -150,7 +153,7 @@ mod test {
|
||||
// Create tiles at different positions
|
||||
let tiles = positions
|
||||
.iter()
|
||||
.map(|&pos| HexTile::new(pos))
|
||||
.map(|&pos| Tile::new(pos))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Verify each tile has correct position
|
||||
@ -170,7 +173,7 @@ mod test {
|
||||
];
|
||||
|
||||
for pos in extreme_positions {
|
||||
let tile = HexTile::new(pos);
|
||||
let tile = Tile::new(pos);
|
||||
assert_eq!(tile.pos, pos);
|
||||
}
|
||||
}
|
||||
@ -178,7 +181,7 @@ mod test {
|
||||
#[test]
|
||||
fn hex_tile_creation_and_properties() {
|
||||
let hex = random_hex();
|
||||
let tile = HexTile::new(hex);
|
||||
let tile = Tile::new(hex);
|
||||
|
||||
assert_eq!(tile.pos(), hex);
|
||||
assert!(tile.walls().is_enclosed());
|
||||
@ -187,7 +190,7 @@ mod test {
|
||||
#[test]
|
||||
fn hex_tile_from_hex() {
|
||||
let hex = random_hex();
|
||||
let tile = HexTile::from(hex);
|
||||
let tile = Tile::from(hex);
|
||||
|
||||
assert_eq!(tile.pos, hex);
|
||||
assert_eq!(tile.walls, Walls::default());
|
||||
@ -196,7 +199,7 @@ mod test {
|
||||
#[test]
|
||||
fn hex_hex_into_tile() {
|
||||
let hex = random_hex();
|
||||
let tile: HexTile = hex.into();
|
||||
let tile: Tile = hex.into();
|
||||
|
||||
assert_eq!(tile.pos, hex);
|
||||
assert_eq!(tile.walls, Walls::default());
|
||||
@ -204,16 +207,16 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn hex_tile_display() {
|
||||
let tile = HexTile::new(Hex::new(3, -3));
|
||||
let tile = Tile::new(Hex::new(3, -3));
|
||||
assert_eq!(format!("{tile}"), "(3,-3)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hex_tile_wall_modifications() {
|
||||
let mut tile = HexTile::new(Hex::ZERO);
|
||||
let mut tile = Tile::new(Hex::ZERO);
|
||||
|
||||
for direction in EdgeDirection::ALL_DIRECTIONS {
|
||||
tile.walls.add(direction);
|
||||
tile.walls.insert(direction);
|
||||
}
|
||||
assert_eq!(tile.walls.count(), 6);
|
||||
|
||||
@ -231,7 +234,7 @@ mod test {
|
||||
#[test]
|
||||
fn hex_tile_to_vec2() {
|
||||
let layout = HexLayout::default();
|
||||
let tile = HexTile::new(Hex::new(1, 0));
|
||||
let tile = Tile::new(Hex::new(1, 0));
|
||||
let vec2 = tile.to_vec2(&layout);
|
||||
assert_eq!(vec2, Vec2::new(1.5, -0.8660254));
|
||||
}
|
||||
@ -239,7 +242,7 @@ mod test {
|
||||
#[test]
|
||||
fn hex_tile_to_vec3() {
|
||||
let layout = HexLayout::default();
|
||||
let tile = HexTile::new(Hex::new(0, 1));
|
||||
let tile = Tile::new(Hex::new(0, 1));
|
||||
let vec3 = tile.to_vec3(&layout);
|
||||
assert_eq!(vec3, Vec3::new(0.0, 0.0, -1.7320508));
|
||||
}
|
||||
|
||||
22
src/traits.rs
Normal file
22
src/traits.rs
Normal file
@ -0,0 +1,22 @@
|
||||
use crate::Walls;
|
||||
use hexx::Hex;
|
||||
|
||||
pub trait TilePosition {
|
||||
/// Returns position of the tile
|
||||
#[must_use]
|
||||
fn pos(&self) -> Hex;
|
||||
}
|
||||
|
||||
#[cfg(feature = "bevy_reflect")]
|
||||
pub trait WorldPositionable {
|
||||
#[must_use]
|
||||
fn to_vec2(&self, layout: &hexx::HexLayout) -> glam::Vec2;
|
||||
#[must_use]
|
||||
fn to_vec3(&self, layout: &hexx::HexLayout) -> glam::Vec3;
|
||||
}
|
||||
|
||||
pub trait WallStorage {
|
||||
#[must_use]
|
||||
fn walls(&self) -> &Walls;
|
||||
fn walls_mut(&mut self) -> &mut Walls;
|
||||
}
|
||||
162
src/walls.rs
162
src/walls.rs
@ -16,15 +16,15 @@ use hexx::EdgeDirection;
|
||||
///
|
||||
/// // Create a hexagon with all walls
|
||||
/// let walls = Walls::new();
|
||||
/// assert!(walls.is_closed());
|
||||
/// assert!(walls.is_enclosed());
|
||||
///
|
||||
/// // 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);
|
||||
/// walls.insert(EdgeDirection::FLAT_NORTH);
|
||||
/// walls.insert(EdgeDirection::FLAT_SOUTH);
|
||||
/// assert_eq!(walls.count(), 2);
|
||||
/// ```
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
@ -47,7 +47,7 @@ impl Walls {
|
||||
/// let walls = Walls::new();
|
||||
/// assert!(walls.is_enclosed());
|
||||
/// ```
|
||||
#[cfg_attr(not(debug_assertions), inline)]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
@ -63,7 +63,7 @@ impl Walls {
|
||||
/// let walls = Walls::empty();
|
||||
/// assert!(walls.is_empty());
|
||||
/// ```
|
||||
#[cfg_attr(not(debug_assertions), inline)]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn empty() -> Self {
|
||||
Self(0)
|
||||
@ -79,17 +79,21 @@ impl Walls {
|
||||
/// let walls = Walls::empty();
|
||||
/// assert!(walls.is_empty());
|
||||
/// ```
|
||||
#[cfg_attr(not(debug_assertions), inline)]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn is_empty(&self) -> bool {
|
||||
self.0 == 0
|
||||
}
|
||||
|
||||
/// Adds a wall in the specified direction.
|
||||
/// Insert a wall in the specified direction.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// - `direction` - The direction in which to add the wall.
|
||||
/// - `direction` - The direction in which to insert the wall.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns `true` if a wall was present, `false` otherwise.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@ -99,22 +103,24 @@ impl Walls {
|
||||
/// let mut walls = Walls::empty();
|
||||
/// assert_eq!(walls.count(), 0);
|
||||
///
|
||||
/// walls.add(1);
|
||||
/// assert!(!walls.insert(1));
|
||||
/// assert_eq!(walls.count(), 1);
|
||||
///
|
||||
/// walls.add(1);
|
||||
/// assert!(walls.insert(1));
|
||||
/// assert_eq!(walls.count(), 1);
|
||||
///
|
||||
/// walls.add(EdgeDirection::FLAT_NORTH);
|
||||
/// assert!(!walls.insert(EdgeDirection::FLAT_NORTH));
|
||||
/// assert_eq!(walls.count(), 2);
|
||||
///
|
||||
/// ```
|
||||
#[cfg_attr(not(debug_assertions), inline)]
|
||||
pub fn add<T>(&mut self, direction: T)
|
||||
#[inline]
|
||||
pub fn insert<T>(&mut self, direction: T) -> bool
|
||||
where
|
||||
T: Into<Self> + Copy,
|
||||
{
|
||||
self.0 |= direction.into().0;
|
||||
let mask = direction.into().0;
|
||||
let was_present = self.0 & mask != 0;
|
||||
self.0 |= mask;
|
||||
was_present
|
||||
}
|
||||
|
||||
/// Removes a wall in the specified direction.
|
||||
@ -123,6 +129,10 @@ impl Walls {
|
||||
///
|
||||
/// - `direction` - The direction from which to remove the wall.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns `true` if a wall was present and removed, `false` otherwise.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
@ -139,16 +149,15 @@ impl Walls {
|
||||
/// assert!(walls.remove(EdgeDirection::FLAT_NORTH));
|
||||
/// assert_eq!(walls.count(), 4);
|
||||
/// ```
|
||||
#[cfg_attr(not(debug_assertions), inline)]
|
||||
#[inline]
|
||||
pub fn remove<T>(&mut self, direction: T) -> bool
|
||||
where
|
||||
T: Into<Self> + Copy,
|
||||
{
|
||||
let was_removed = self.contains(direction);
|
||||
if was_removed {
|
||||
self.0 &= !direction.into().0;
|
||||
}
|
||||
was_removed
|
||||
let mask = direction.into().0;
|
||||
let was_present = self.0 & mask != 0;
|
||||
self.0 &= !mask;
|
||||
was_present
|
||||
}
|
||||
|
||||
/// Checks if there is a wall in the specified direction.
|
||||
@ -163,17 +172,17 @@ impl Walls {
|
||||
/// use hexlab::prelude::*;
|
||||
///
|
||||
/// let mut walls = Walls::empty();
|
||||
/// walls.add(EdgeDirection::FLAT_NORTH);
|
||||
/// walls.insert(EdgeDirection::FLAT_NORTH);
|
||||
///
|
||||
/// assert!(walls.contains(EdgeDirection::FLAT_NORTH));
|
||||
/// assert!(!walls.contains(EdgeDirection::FLAT_SOUTH));
|
||||
/// assert!(walls.contains(&EdgeDirection::FLAT_NORTH));
|
||||
/// assert!(!walls.contains(&EdgeDirection::FLAT_SOUTH));
|
||||
/// ```
|
||||
#[cfg_attr(not(debug_assertions), inline)]
|
||||
pub fn contains<T>(&self, other: T) -> bool
|
||||
#[inline]
|
||||
pub fn contains<T>(&self, direction: &T) -> bool
|
||||
where
|
||||
T: Into<Self> + Copy,
|
||||
{
|
||||
self.0 & other.into().0 != 0
|
||||
self.0 & (*direction).into().0 != 0
|
||||
}
|
||||
|
||||
/// Returns the raw bit representation of the walls
|
||||
@ -189,7 +198,7 @@ impl Walls {
|
||||
/// let walls = Walls::empty();
|
||||
/// assert_eq!(walls.as_bits(), 0);
|
||||
/// ```
|
||||
#[cfg_attr(not(debug_assertions), inline)]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn as_bits(&self) -> u8 {
|
||||
self.0
|
||||
@ -205,13 +214,13 @@ impl Walls {
|
||||
/// let mut walls = Walls::empty();
|
||||
/// assert!(walls.is_empty());
|
||||
///
|
||||
/// walls.add(0);
|
||||
/// walls.insert(0);
|
||||
/// assert_eq!(walls.count(), 1);
|
||||
///
|
||||
/// walls.add(1);
|
||||
/// walls.insert(1);
|
||||
/// assert_eq!(walls.count(), 2);
|
||||
/// ```
|
||||
#[cfg_attr(not(debug_assertions), inline)]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn count(&self) -> u8 {
|
||||
u8::try_from(self.0.count_ones()).unwrap_or_default()
|
||||
@ -226,7 +235,7 @@ impl Walls {
|
||||
///
|
||||
/// assert_eq!(Walls::all_directions().as_bits(), 0b11_1111);
|
||||
/// ```
|
||||
#[cfg_attr(not(debug_assertions), inline)]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub const fn all_directions() -> Self {
|
||||
Self(0b11_1111)
|
||||
@ -262,29 +271,10 @@ impl Walls {
|
||||
where
|
||||
T: Into<Self> + Copy,
|
||||
{
|
||||
let is_present = self.contains(direction);
|
||||
if is_present {
|
||||
self.remove(direction);
|
||||
} else {
|
||||
self.add(direction);
|
||||
}
|
||||
is_present
|
||||
}
|
||||
|
||||
/// 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.4.0. Use `is_enclosed()` instead.
|
||||
#[cfg_attr(not(debug_assertions), inline)]
|
||||
#[must_use]
|
||||
#[deprecated(since = "0.4.0", note = "use `walls::Walls::is_enclosed()`")]
|
||||
pub fn is_closed(&self) -> bool {
|
||||
self.is_enclosed()
|
||||
let mask = direction.into().0;
|
||||
let was_present = self.0 & mask != 0;
|
||||
self.0 ^= mask;
|
||||
was_present
|
||||
}
|
||||
|
||||
/// Checks if walls are present in all six directions.
|
||||
@ -304,7 +294,7 @@ impl Walls {
|
||||
/// walls.remove(0);
|
||||
/// assert!(!walls.is_enclosed());
|
||||
/// ```
|
||||
#[cfg_attr(not(debug_assertions), inline)]
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn is_enclosed(&self) -> bool {
|
||||
self.count() == 6
|
||||
@ -317,7 +307,7 @@ impl Walls {
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// - `other` - The walls to add, specified as a `Walls` instance or any type
|
||||
/// - `other` - The walls to insert, specified as a `Walls` instance or any type
|
||||
/// that can be converted into `Walls`.
|
||||
///
|
||||
///
|
||||
@ -329,10 +319,10 @@ impl Walls {
|
||||
/// let mut walls = Walls::empty();
|
||||
/// walls.fill([EdgeDirection::FLAT_NORTH ,EdgeDirection::FLAT_SOUTH, EdgeDirection::FLAT_SOUTH_EAST]);
|
||||
///
|
||||
/// assert!(walls.contains(EdgeDirection::FLAT_SOUTH));
|
||||
/// assert!(walls.contains(&EdgeDirection::FLAT_SOUTH));
|
||||
/// assert_eq!(walls.count(), 3);
|
||||
/// ```
|
||||
#[cfg_attr(not(debug_assertions), inline)]
|
||||
#[inline]
|
||||
pub fn fill<T>(&mut self, other: T)
|
||||
where
|
||||
T: Into<Self>,
|
||||
@ -398,15 +388,15 @@ mod test {
|
||||
#[test]
|
||||
fn as_bits_single_wall() {
|
||||
let mut walls = Walls::empty();
|
||||
walls.add(EdgeDirection::FLAT_NORTH);
|
||||
walls.insert(EdgeDirection::FLAT_NORTH);
|
||||
assert_eq!(walls.as_bits(), 0b010000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn as_bits_multiple_walls() {
|
||||
let mut walls = Walls::empty();
|
||||
walls.add(EdgeDirection::FLAT_NORTH);
|
||||
walls.add(EdgeDirection::FLAT_SOUTH);
|
||||
walls.insert(EdgeDirection::FLAT_NORTH);
|
||||
walls.insert(EdgeDirection::FLAT_SOUTH);
|
||||
assert_eq!(walls.as_bits(), 0b010010);
|
||||
}
|
||||
|
||||
@ -432,12 +422,12 @@ mod test {
|
||||
assert_eq!(walls.as_bits(), 0);
|
||||
}
|
||||
|
||||
// add
|
||||
// insert
|
||||
#[test]
|
||||
fn add_single_wall() {
|
||||
fn insert_single_wall() {
|
||||
let mut walls = Walls::empty();
|
||||
walls.add(EdgeDirection::FLAT_NORTH);
|
||||
assert!(walls.contains(EdgeDirection::FLAT_NORTH));
|
||||
walls.insert(EdgeDirection::FLAT_NORTH);
|
||||
assert!(walls.contains(&EdgeDirection::FLAT_NORTH));
|
||||
assert_eq!(walls.count(), 1);
|
||||
}
|
||||
|
||||
@ -446,30 +436,30 @@ mod test {
|
||||
fn remove_existing_wall() {
|
||||
let mut walls = Walls::new();
|
||||
assert!(walls.remove(EdgeDirection::FLAT_NORTH));
|
||||
assert!(!walls.contains(EdgeDirection::FLAT_NORTH));
|
||||
assert!(!walls.contains(&EdgeDirection::FLAT_NORTH));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_nonexistent_wall() {
|
||||
let mut walls = Walls::empty();
|
||||
assert!(!walls.remove(EdgeDirection::FLAT_NORTH));
|
||||
walls.add(EdgeDirection::FLAT_NORTH);
|
||||
walls.insert(EdgeDirection::FLAT_NORTH);
|
||||
assert!(walls.remove(EdgeDirection::FLAT_NORTH));
|
||||
}
|
||||
|
||||
// toggle
|
||||
#[test]
|
||||
fn toggle_adds_wall() {
|
||||
fn toggle_wall() {
|
||||
let mut walls = Walls::empty();
|
||||
assert!(!walls.toggle(EdgeDirection::FLAT_NORTH));
|
||||
assert!(walls.contains(EdgeDirection::FLAT_NORTH));
|
||||
assert!(walls.contains(&EdgeDirection::FLAT_NORTH));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn toggle_removes_wall() {
|
||||
let mut walls = Walls::new();
|
||||
assert!(walls.toggle(EdgeDirection::FLAT_NORTH));
|
||||
assert!(!walls.contains(EdgeDirection::FLAT_NORTH));
|
||||
assert!(!walls.contains(&EdgeDirection::FLAT_NORTH));
|
||||
}
|
||||
|
||||
// fill
|
||||
@ -477,41 +467,41 @@ mod test {
|
||||
fn fill_adds_multiple_walls() {
|
||||
let mut walls = Walls::empty();
|
||||
walls.fill([EdgeDirection::FLAT_NORTH, EdgeDirection::FLAT_SOUTH]);
|
||||
assert!(walls.contains(EdgeDirection::FLAT_NORTH));
|
||||
assert!(walls.contains(EdgeDirection::FLAT_SOUTH));
|
||||
assert!(walls.contains(&EdgeDirection::FLAT_NORTH));
|
||||
assert!(walls.contains(&EdgeDirection::FLAT_SOUTH));
|
||||
assert_eq!(walls.count(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fill_preserves_existing_walls() {
|
||||
let mut walls = Walls::empty();
|
||||
walls.add(EdgeDirection::FLAT_NORTH);
|
||||
walls.insert(EdgeDirection::FLAT_NORTH);
|
||||
walls.fill([EdgeDirection::FLAT_SOUTH, EdgeDirection::FLAT_SOUTH_EAST]);
|
||||
assert!(walls.contains(EdgeDirection::FLAT_NORTH));
|
||||
assert!(walls.contains(EdgeDirection::FLAT_SOUTH));
|
||||
assert!(walls.contains(EdgeDirection::FLAT_SOUTH_EAST));
|
||||
assert!(walls.contains(&EdgeDirection::FLAT_NORTH));
|
||||
assert!(walls.contains(&EdgeDirection::FLAT_SOUTH));
|
||||
assert!(walls.contains(&EdgeDirection::FLAT_SOUTH_EAST));
|
||||
assert_eq!(walls.count(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_edge_direction_conversion() {
|
||||
let walls: Walls = EdgeDirection::FLAT_NORTH.into();
|
||||
assert!(walls.contains(EdgeDirection::FLAT_NORTH));
|
||||
assert!(walls.contains(&EdgeDirection::FLAT_NORTH));
|
||||
assert_eq!(walls.count(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_u8_conversion() {
|
||||
let walls: Walls = 0u8.into();
|
||||
assert!(walls.contains(EdgeDirection::FLAT_SOUTH_EAST));
|
||||
assert!(walls.contains(&EdgeDirection::FLAT_SOUTH_EAST));
|
||||
assert_eq!(walls.count(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_array_conversion() {
|
||||
let walls: Walls = [EdgeDirection::FLAT_NORTH, EdgeDirection::FLAT_SOUTH].into();
|
||||
assert!(walls.contains(EdgeDirection::FLAT_NORTH));
|
||||
assert!(walls.contains(EdgeDirection::FLAT_SOUTH));
|
||||
assert!(walls.contains(&EdgeDirection::FLAT_NORTH));
|
||||
assert!(walls.contains(&EdgeDirection::FLAT_SOUTH));
|
||||
assert_eq!(walls.count(), 2);
|
||||
}
|
||||
|
||||
@ -542,8 +532,8 @@ mod test {
|
||||
];
|
||||
let walls: Walls = directions.into_iter().collect();
|
||||
assert_eq!(walls.count(), 2);
|
||||
assert!(walls.contains(EdgeDirection::FLAT_NORTH));
|
||||
assert!(walls.contains(EdgeDirection::FLAT_SOUTH));
|
||||
assert!(walls.contains(&EdgeDirection::FLAT_NORTH));
|
||||
assert!(walls.contains(&EdgeDirection::FLAT_SOUTH));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -551,14 +541,14 @@ mod test {
|
||||
let mut walls = Walls::empty();
|
||||
|
||||
// Test single bit operations
|
||||
walls.add(EdgeDirection::FLAT_NORTH);
|
||||
walls.insert(EdgeDirection::FLAT_NORTH);
|
||||
assert_eq!(walls.as_bits(), 0b010000);
|
||||
|
||||
walls.add(EdgeDirection::FLAT_SOUTH);
|
||||
walls.insert(EdgeDirection::FLAT_SOUTH);
|
||||
assert_eq!(walls.as_bits(), 0b010010);
|
||||
|
||||
// Test removing middle bit
|
||||
walls.add(EdgeDirection::FLAT_SOUTH_EAST);
|
||||
walls.insert(EdgeDirection::FLAT_SOUTH_EAST);
|
||||
assert_eq!(walls.as_bits(), 0b010011);
|
||||
walls.remove(EdgeDirection::FLAT_SOUTH);
|
||||
assert_eq!(walls.as_bits(), 0b010001);
|
||||
|
||||
@ -29,7 +29,7 @@ fn valid_start_position(#[case] start_pos: Hex) {
|
||||
.with_radius(3)
|
||||
.with_start_position(start_pos)
|
||||
.build());
|
||||
assert_some!(maze.get_tile(&start_pos));
|
||||
assert_some!(maze.get(&start_pos));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -67,13 +67,13 @@ 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 {
|
||||
fn count_accessible_neighbors(maze: &Maze, 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()
|
||||
!walls.contains(&dir) && maze.get(&neighbor).is_some()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
@ -107,7 +107,7 @@ fn maze_boundaries() {
|
||||
let pos = Hex::new(q, r);
|
||||
if q.abs() + r.abs() <= radius {
|
||||
assert!(
|
||||
maze.get_tile(&pos).is_some(),
|
||||
maze.get(&pos).is_some(),
|
||||
"Expected tile at {:?} to exist",
|
||||
pos
|
||||
);
|
||||
|
||||
@ -10,12 +10,12 @@ fn generator_type(
|
||||
#[case] start_pos: Option<Hex>,
|
||||
#[case] seed: Option<u64>,
|
||||
) {
|
||||
let mut maze = HexMaze::new();
|
||||
let mut maze = Maze::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);
|
||||
maze.insert(hex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -36,7 +36,7 @@ fn generator_type(
|
||||
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() {
|
||||
if !walls.contains(&dir) && maze.get(&neighbor).is_some() {
|
||||
to_visit.push(neighbor);
|
||||
}
|
||||
}
|
||||
@ -57,7 +57,7 @@ fn generator_type(
|
||||
|
||||
#[test]
|
||||
fn test_empty_maze() {
|
||||
let mut maze = HexMaze::new();
|
||||
let mut maze = Maze::new();
|
||||
GeneratorType::RecursiveBacktracking.generate(&mut maze, None, None);
|
||||
assert!(
|
||||
maze.is_empty(),
|
||||
|
||||
@ -2,28 +2,28 @@ use hexlab::prelude::*;
|
||||
|
||||
#[test]
|
||||
fn hex_maze_creation_and_basic_operations() {
|
||||
let mut maze = HexMaze::new();
|
||||
let mut maze = Maze::new();
|
||||
assert!(maze.is_empty());
|
||||
|
||||
let center = Hex::ZERO;
|
||||
maze.add_tile(center);
|
||||
maze.insert(center);
|
||||
assert_eq!(maze.len(), 1);
|
||||
assert!(!maze.is_empty());
|
||||
|
||||
let tile = maze.get_tile(¢er);
|
||||
let tile = maze.get(¢er);
|
||||
assert!(tile.is_some());
|
||||
assert_eq!(tile.unwrap().pos(), center);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hex_maze_wall_operations() {
|
||||
let mut maze = HexMaze::new();
|
||||
let mut maze = Maze::new();
|
||||
let center = Hex::ZERO;
|
||||
maze.add_tile(center);
|
||||
maze.insert(center);
|
||||
|
||||
// Add walls
|
||||
for direction in EdgeDirection::ALL_DIRECTIONS {
|
||||
maze.add_wall(center, direction);
|
||||
let _ = maze.add_tile_wall(¢er, direction);
|
||||
}
|
||||
|
||||
let walls = maze.get_walls(¢er).unwrap();
|
||||
@ -31,7 +31,7 @@ fn hex_maze_wall_operations() {
|
||||
|
||||
// Remove walls
|
||||
for direction in EdgeDirection::ALL_DIRECTIONS {
|
||||
maze.remove_tile_wall(¢er, direction);
|
||||
let _ = maze.remove_tile_wall(¢er, direction);
|
||||
}
|
||||
|
||||
let walls = maze.get_walls(¢er).unwrap();
|
||||
@ -40,29 +40,29 @@ fn hex_maze_wall_operations() {
|
||||
|
||||
#[test]
|
||||
fn hex_maze_multiple_tiles() {
|
||||
let mut maze = HexMaze::new();
|
||||
let mut maze = Maze::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);
|
||||
maze.insert(tile);
|
||||
}
|
||||
|
||||
assert_eq!(maze.len(), tiles.len());
|
||||
|
||||
for &tile in &tiles {
|
||||
assert!(maze.get_tile(&tile).is_some());
|
||||
assert!(maze.get(&tile).is_some());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hex_maze_edge_cases() {
|
||||
let mut maze = HexMaze::new();
|
||||
let mut maze = Maze::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);
|
||||
let _ = maze.add_tile_wall(&non_existent, EdgeDirection::FLAT_NORTH);
|
||||
let _ = maze.remove_tile_wall(&non_existent, EdgeDirection::FLAT_NORTH);
|
||||
|
||||
assert!(maze.get_tile(&non_existent).is_none());
|
||||
assert!(maze.get(&non_existent).is_none());
|
||||
assert!(maze.get_walls(&non_existent).is_none());
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user