Merge branch 'feature/builder'

This commit is contained in:
Kristofers Solo 2024-11-13 16:36:48 +02:00
commit 2631408d04
7 changed files with 521 additions and 121 deletions

87
Cargo.lock generated
View File

@ -148,7 +148,7 @@ dependencies = [
"ndk-context", "ndk-context",
"ndk-sys 0.6.0+11769913", "ndk-sys 0.6.0+11769913",
"num_enum", "num_enum",
"thiserror", "thiserror 1.0.68",
] ]
[[package]] [[package]]
@ -341,7 +341,7 @@ dependencies = [
"petgraph", "petgraph",
"ron", "ron",
"serde", "serde",
"thiserror", "thiserror 1.0.68",
"thread_local", "thread_local",
"uuid", "uuid",
] ]
@ -359,7 +359,7 @@ dependencies = [
"bevy_utils", "bevy_utils",
"console_error_panic_hook", "console_error_panic_hook",
"downcast-rs", "downcast-rs",
"thiserror", "thiserror 1.0.68",
"wasm-bindgen", "wasm-bindgen",
"web-sys", "web-sys",
] ]
@ -389,7 +389,7 @@ dependencies = [
"parking_lot", "parking_lot",
"ron", "ron",
"serde", "serde",
"thiserror", "thiserror 1.0.68",
"uuid", "uuid",
"wasm-bindgen", "wasm-bindgen",
"wasm-bindgen-futures", "wasm-bindgen-futures",
@ -438,7 +438,7 @@ dependencies = [
"bytemuck", "bytemuck",
"encase", "encase",
"serde", "serde",
"thiserror", "thiserror 1.0.68",
"wgpu-types", "wgpu-types",
] ]
@ -478,7 +478,7 @@ dependencies = [
"radsort", "radsort",
"serde", "serde",
"smallvec", "smallvec",
"thiserror", "thiserror 1.0.68",
] ]
[[package]] [[package]]
@ -526,7 +526,7 @@ dependencies = [
"nonmax", "nonmax",
"petgraph", "petgraph",
"serde", "serde",
"thiserror", "thiserror 1.0.68",
] ]
[[package]] [[package]]
@ -563,7 +563,7 @@ dependencies = [
"bevy_time", "bevy_time",
"bevy_utils", "bevy_utils",
"gilrs", "gilrs",
"thiserror", "thiserror 1.0.68",
] ]
[[package]] [[package]]
@ -629,7 +629,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"smallvec", "smallvec",
"thiserror", "thiserror 1.0.68",
] ]
[[package]] [[package]]
@ -658,7 +658,7 @@ dependencies = [
"bevy_reflect", "bevy_reflect",
"bevy_utils", "bevy_utils",
"smol_str", "smol_str",
"thiserror", "thiserror 1.0.68",
] ]
[[package]] [[package]]
@ -740,7 +740,7 @@ dependencies = [
"rand", "rand",
"serde", "serde",
"smallvec", "smallvec",
"thiserror", "thiserror 1.0.68",
] ]
[[package]] [[package]]
@ -801,7 +801,7 @@ dependencies = [
"serde", "serde",
"smallvec", "smallvec",
"smol_str", "smol_str",
"thiserror", "thiserror 1.0.68",
"uuid", "uuid",
] ]
@ -860,7 +860,7 @@ dependencies = [
"send_wrapper", "send_wrapper",
"serde", "serde",
"smallvec", "smallvec",
"thiserror", "thiserror 1.0.68",
"wasm-bindgen", "wasm-bindgen",
"web-sys", "web-sys",
"wgpu", "wgpu",
@ -894,7 +894,7 @@ dependencies = [
"bevy_transform", "bevy_transform",
"bevy_utils", "bevy_utils",
"serde", "serde",
"thiserror", "thiserror 1.0.68",
"uuid", "uuid",
] ]
@ -921,7 +921,7 @@ dependencies = [
"guillotiere", "guillotiere",
"radsort", "radsort",
"rectangle-pack", "rectangle-pack",
"thiserror", "thiserror 1.0.68",
] ]
[[package]] [[package]]
@ -983,7 +983,7 @@ dependencies = [
"bevy_window", "bevy_window",
"glyph_brush_layout", "glyph_brush_layout",
"serde", "serde",
"thiserror", "thiserror 1.0.68",
] ]
[[package]] [[package]]
@ -997,7 +997,7 @@ dependencies = [
"bevy_reflect", "bevy_reflect",
"bevy_utils", "bevy_utils",
"crossbeam-channel", "crossbeam-channel",
"thiserror", "thiserror 1.0.68",
] ]
[[package]] [[package]]
@ -1011,7 +1011,7 @@ dependencies = [
"bevy_hierarchy", "bevy_hierarchy",
"bevy_math", "bevy_math",
"bevy_reflect", "bevy_reflect",
"thiserror", "thiserror 1.0.68",
] ]
[[package]] [[package]]
@ -1041,7 +1041,7 @@ dependencies = [
"nonmax", "nonmax",
"smallvec", "smallvec",
"taffy", "taffy",
"thiserror", "thiserror 1.0.68",
] ]
[[package]] [[package]]
@ -1258,7 +1258,7 @@ dependencies = [
"polling", "polling",
"rustix", "rustix",
"slab", "slab",
"thiserror", "thiserror 1.0.68",
] ]
[[package]] [[package]]
@ -1606,7 +1606,7 @@ dependencies = [
"const_panic", "const_panic",
"encase_derive", "encase_derive",
"glam", "glam",
"thiserror", "thiserror 1.0.68",
] ]
[[package]] [[package]]
@ -1966,7 +1966,7 @@ checksum = "6f56f6318968d03c18e1bcf4857ff88c61157e9da8e47c5f29055d60e1228884"
dependencies = [ dependencies = [
"log", "log",
"presser", "presser",
"thiserror", "thiserror 1.0.68",
"winapi", "winapi",
"windows 0.52.0", "windows 0.52.0",
] ]
@ -2034,7 +2034,7 @@ dependencies = [
"com", "com",
"libc", "libc",
"libloading 0.8.5", "libloading 0.8.5",
"thiserror", "thiserror 1.0.68",
"widestring", "widestring",
"winapi", "winapi",
] ]
@ -2063,13 +2063,14 @@ checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
[[package]] [[package]]
name = "hexlab" name = "hexlab"
version = "0.1.0" version = "0.1.1"
dependencies = [ dependencies = [
"bevy", "bevy",
"hexx", "hexx",
"rand", "rand",
"rand_chacha", "rand_chacha",
"serde", "serde",
"thiserror 2.0.3",
] ]
[[package]] [[package]]
@ -2176,7 +2177,7 @@ dependencies = [
"combine", "combine",
"jni-sys", "jni-sys",
"log", "log",
"thiserror", "thiserror 1.0.68",
"walkdir", "walkdir",
"windows-sys 0.45.0", "windows-sys 0.45.0",
] ]
@ -2405,7 +2406,7 @@ dependencies = [
"rustc-hash", "rustc-hash",
"spirv", "spirv",
"termcolor", "termcolor",
"thiserror", "thiserror 1.0.68",
"unicode-xid", "unicode-xid",
] ]
@ -2424,7 +2425,7 @@ dependencies = [
"regex", "regex",
"regex-syntax 0.8.5", "regex-syntax 0.8.5",
"rustc-hash", "rustc-hash",
"thiserror", "thiserror 1.0.68",
"tracing", "tracing",
"unicode-ident", "unicode-ident",
] ]
@ -2440,7 +2441,7 @@ dependencies = [
"log", "log",
"ndk-sys 0.5.0+25.2.9519653", "ndk-sys 0.5.0+25.2.9519653",
"num_enum", "num_enum",
"thiserror", "thiserror 1.0.68",
] ]
[[package]] [[package]]
@ -2455,7 +2456,7 @@ dependencies = [
"ndk-sys 0.6.0+11769913", "ndk-sys 0.6.0+11769913",
"num_enum", "num_enum",
"raw-window-handle", "raw-window-handle",
"thiserror", "thiserror 1.0.68",
] ]
[[package]] [[package]]
@ -3156,7 +3157,7 @@ checksum = "d1fceb9d127d515af1586d8d0cc601e1245bdb0af38e75c865a156290184f5b3"
dependencies = [ dependencies = [
"cpal", "cpal",
"lewton", "lewton",
"thiserror", "thiserror 1.0.68",
] ]
[[package]] [[package]]
@ -3397,7 +3398,16 @@ version = "1.0.68"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02dd99dc800bbb97186339685293e1cc5d9df1f8fae2d0aecd9ff1c77efea892" checksum = "02dd99dc800bbb97186339685293e1cc5d9df1f8fae2d0aecd9ff1c77efea892"
dependencies = [ dependencies = [
"thiserror-impl", "thiserror-impl 1.0.68",
]
[[package]]
name = "thiserror"
version = "2.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa"
dependencies = [
"thiserror-impl 2.0.3",
] ]
[[package]] [[package]]
@ -3411,6 +3421,17 @@ dependencies = [
"syn 2.0.87", "syn 2.0.87",
] ]
[[package]]
name = "thiserror-impl"
version = "2.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.87",
]
[[package]] [[package]]
name = "thread_local" name = "thread_local"
version = "1.1.8" version = "1.1.8"
@ -3749,7 +3770,7 @@ dependencies = [
"raw-window-handle", "raw-window-handle",
"rustc-hash", "rustc-hash",
"smallvec", "smallvec",
"thiserror", "thiserror 1.0.68",
"web-sys", "web-sys",
"wgpu-hal", "wgpu-hal",
"wgpu-types", "wgpu-types",
@ -3793,7 +3814,7 @@ dependencies = [
"renderdoc-sys", "renderdoc-sys",
"rustc-hash", "rustc-hash",
"smallvec", "smallvec",
"thiserror", "thiserror 1.0.68",
"wasm-bindgen", "wasm-bindgen",
"web-sys", "web-sys",
"wgpu-types", "wgpu-types",

View File

@ -1,12 +1,21 @@
[package] [package]
name = "hexlab" name = "hexlab"
authors = ["Kristofers Solo <dev@kristofers.xyz>"] authors = ["Kristofers Solo <dev@kristofers.xyz>"]
version = "0.1.0" version = "0.1.1"
edition = "2021" edition = "2021"
description = "A hexagonal maze library" description = "A hexagonal maze generation and manipulation library"
repository = "https://github.com/kristoferssolo/hexlab" repository = "https://github.com/kristoferssolo/hexlab"
documentation = "https://docs.rs/hexlab"
homepage = "https://github.com/kristoferssolo/hexlab"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
keywords = ["maze", "hex", "hexagons"] keywords = ["maze", "hex", "hexagons", "generation", "game"]
categories = [
"algorithms",
"game-development",
"mathematics",
"data-structures",
]
exclude = ["/.github", "/.gitignore", "/tests", "*.png", "*.md"]
[dependencies] [dependencies]
bevy = { version = "0.14", optional = true } bevy = { version = "0.14", optional = true }
@ -14,10 +23,26 @@ hexx = { version = "0.18" }
rand = "0.8" rand = "0.8"
rand_chacha = "0.3" rand_chacha = "0.3"
serde = { version = "1.0", features = ["derive"], optional = true } serde = { version = "1.0", features = ["derive"], optional = true }
thiserror = "2.0"
[dev-dependencies]
[features] [features]
default = [] default = []
serde = ["dep:serde", "hexx/serde", "rand_chacha/serde"] serde = ["dep:serde", "hexx/serde", "rand_chacha/serde"]
bevy = ["dep:bevy", "hexx/bevy_reflect"] bevy = ["dep:bevy", "hexx/bevy_reflect"]
full = ["serde", "bevy"]
[dev-dependencies] [profile.dev]
opt-level = 1 # Better compile times with some optimization
[profile.release]
opt-level = 3
lto = "thin"
strip = true # Smaller binary size
panic = "abort" # Smaller binary size
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]

393
src/builder.rs Normal file
View File

@ -0,0 +1,393 @@
use crate::{
generator::{generate_backtracking, GeneratorType},
HexMaze,
};
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.
///
/// 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");
/// ```
#[derive(Default)]
pub struct MazeBuilder {
radius: Option<u32>,
seed: Option<u64>,
generator_type: GeneratorType,
start_position: Option<Hex>,
}
impl MazeBuilder {
/// Creates a new [`MazeBuilder`] instance.
#[inline]
pub fn new() -> Self {
Self::default()
}
/// Sets the radius for the hexagonal maze.
///
/// # Arguments
///
/// * `radius` - The size of the maze (number of tiles along one edge).
#[inline]
pub fn with_radius(mut self, radius: u32) -> Self {
self.radius = Some(radius);
self
}
/// Sets the random seed for maze generation.
///
/// # Arguments
///
/// * `seed` - The random seed value.
#[inline]
pub fn with_seed(mut self, seed: u64) -> Self {
self.seed = Some(seed);
self
}
/// Sets the generator algorithm for maze creation.
///
/// Different generators may produce different maze patterns and characteristics.
///
/// # Arguments
///
/// * `generator_type` - The maze generation algorithm to use.
#[inline]
pub fn with_generator(mut self, generator_type: GeneratorType) -> Self {
self.generator_type = generator_type;
self
}
#[inline]
pub fn with_start_position(mut self, pos: Hex) -> Self {
self.start_position = Some(pos);
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());
/// ```
pub fn build(self) -> Result<HexMaze, MazeBuilderError> {
let radius = self.radius.ok_or(MazeBuilderError::NoRadius)?;
let mut maze = self.create_hex_maze(radius);
if let Some(start_pos) = self.start_position {
if maze.get_tile(&start_pos).is_none() {
return Err(MazeBuilderError::InvalidStartPosition(start_pos));
}
}
if !maze.is_empty() {
self.generate_maze(&mut maze);
}
Ok(maze)
}
fn create_hex_maze(&self, radius: u32) -> HexMaze {
let mut maze = HexMaze::new();
let radius = radius as i32;
for q in -radius..=radius {
let r1 = (-radius).max(-q - radius);
let r2 = radius.min(-q + radius);
for r in r1..=r2 {
let pos = Hex::new(q, r);
maze.add_tile(pos);
}
}
maze
}
fn generate_maze(&self, maze: &mut HexMaze) {
match self.generator_type {
GeneratorType::RecursiveBacktracking => {
generate_backtracking(maze, self.start_position, self.seed)
}
}
}
}
#[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
}
#[test]
fn new_builder() {
let builder = MazeBuilder::new();
assert!(builder.radius.is_none());
assert!(builder.seed.is_none());
assert!(builder.start_position.is_none());
}
#[test]
fn builder_with_radius() {
let radius = 5;
let maze = MazeBuilder::new().with_radius(radius).build().unwrap();
assert_eq!(maze.len(), calculate_hex_tiles(radius));
assert!(maze.get_tile(&Hex::ZERO).is_some());
}
#[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
);
}
}
}
}
}

View File

@ -1,101 +1,49 @@
use std::collections::HashSet; use std::collections::HashSet;
use hexx::{EdgeDirection, Hex}; use hexx::{EdgeDirection, Hex};
use rand::{seq::SliceRandom, thread_rng, Rng, SeedableRng}; use rand::{seq::SliceRandom, thread_rng, Rng, RngCore, SeedableRng};
use rand_chacha::ChaCha8Rng; use rand_chacha::ChaCha8Rng;
use crate::HexMaze; use crate::HexMaze;
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy, Default)]
pub enum GeneratorType { pub enum GeneratorType {
BackTracking, #[default]
RecursiveBacktracking,
} }
impl HexMaze { pub(crate) fn generate_backtracking(maze: &mut HexMaze, start_pos: Option<Hex>, seed: Option<u64>) {
pub fn generate(&mut self, generator_type: GeneratorType) { if maze.is_empty() {
match generator_type { return;
GeneratorType::BackTracking => self.generate_backtracking(),
}
} }
pub fn generate_from_seed(&mut self, generator_type: GeneratorType, seed: u64) { let start = start_pos.unwrap_or(Hex::ZERO);
match generator_type {
GeneratorType::BackTracking => self.generate_backtracking_from_seed(seed),
}
}
pub fn generate_backtracking(&mut self) { let mut visited = HashSet::new();
if self.is_empty() {
return;
}
let start = *self.keys().next().unwrap();
let mut visited = HashSet::new(); let mut rng: Box<dyn RngCore> = match seed {
let mut rng = thread_rng(); Some(seed) => Box::new(ChaCha8Rng::seed_from_u64(seed)),
self.recursive_backtrack(start, &mut visited, &mut rng); None => Box::new(thread_rng()),
} };
recursive_backtrack(maze, start, &mut visited, &mut rng);
}
pub fn generate_backtracking_from_seed(&mut self, seed: u64) { fn recursive_backtrack<R: Rng>(
if self.is_empty() { maze: &mut HexMaze,
return; current: Hex,
} visited: &mut HashSet<Hex>,
// let start = *self.keys().next().unwrap(); rng: &mut R,
let start = Hex::ZERO; ) {
visited.insert(current);
let mut directions = EdgeDirection::ALL_DIRECTIONS;
directions.shuffle(rng);
let mut visited = HashSet::new(); for direction in directions {
let mut rng = ChaCha8Rng::seed_from_u64(seed); let neighbor = current + direction;
self.recursive_backtrack(start, &mut visited, &mut rng); if maze.get_tile(&neighbor).is_some() && !visited.contains(&neighbor) {
} maze.remove_tile_wall(&current, direction);
maze.remove_tile_wall(&neighbor, direction.const_neg());
fn recursive_backtrack<R: Rng>( recursive_backtrack(maze, neighbor, visited, rng);
&mut self,
current: Hex,
visited: &mut HashSet<Hex>,
rng: &mut R,
) {
visited.insert(current);
let mut directions = EdgeDirection::ALL_DIRECTIONS;
directions.shuffle(rng);
for direction in directions {
let neighbor = current + direction;
if self.get_tile(&neighbor).is_some() && !visited.contains(&neighbor) {
self.remove_tile_wall(&current, direction);
self.remove_tile_wall(&neighbor, direction.const_neg());
self.recursive_backtrack(neighbor, visited, rng);
}
} }
} }
} }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn backtracking_generation() {
let mut maze = HexMaze::with_radius(2);
// Before generation
for tile in maze.values() {
assert_eq!(tile.walls.as_bits(), 0b111111);
}
// Generate using backtracking
maze.generate(GeneratorType::BackTracking);
// After generation
let all_walls = maze.values().all(|tile| tile.walls.as_bits() == 0b111111);
assert!(!all_walls, "Some walls should be removed");
}
#[test]
fn empty_maze() {
let mut maze = HexMaze::default();
maze.generate(GeneratorType::BackTracking);
assert!(maze.is_empty(), "Empty maze should remain empty");
}
}

View File

@ -1,13 +1,16 @@
mod builder;
mod generator; mod generator;
mod maze; mod maze;
mod tile; mod tile;
mod walls; mod walls;
pub use builder::{MazeBuilder, MazeBuilderError};
pub use generator::GeneratorType;
pub use maze::HexMaze; pub use maze::HexMaze;
pub use tile::HexTile; pub use tile::HexTile;
pub use walls::Walls; pub use walls::Walls;
pub mod prelude { pub mod prelude {
pub use super::{HexMaze, HexTile, Walls}; pub use super::{GeneratorType, HexMaze, HexTile, MazeBuilder, MazeBuilderError, Walls};
pub use hexx::{EdgeDirection, Hex, HexLayout}; pub use hexx::{EdgeDirection, Hex, HexLayout};
} }

View File

@ -9,7 +9,7 @@ use super::{HexTile, Walls};
/// Represents a hexagonal maze with tiles and walls /// Represents a hexagonal maze with tiles and walls
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct HexMaze(HashMap<Hex, HexTile>); pub struct HexMaze(HashMap<Hex, HexTile>);
impl HexMaze { impl HexMaze {

View File

@ -49,6 +49,16 @@ impl From<EdgeDirection> for Walls {
} }
} }
impl From<[EdgeDirection; 6]> for Walls {
fn from(value: [EdgeDirection; 6]) -> Self {
let mut walls = 0u8;
for direction in value {
walls |= 1 << direction.index();
}
Self(walls)
}
}
impl From<u8> for Walls { impl From<u8> for Walls {
fn from(value: u8) -> Self { fn from(value: u8) -> Self {
Self(1 << value) Self(1 << value)