mirror of
https://github.com/kristoferssolo/hexlab.git
synced 2025-10-21 19:40:34 +00:00
Merge branch 'feature/builder'
This commit is contained in:
commit
2631408d04
87
Cargo.lock
generated
87
Cargo.lock
generated
@ -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",
|
||||||
|
|||||||
33
Cargo.toml
33
Cargo.toml
@ -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
393
src/builder.rs
Normal 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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
112
src/generator.rs
112
src/generator.rs
@ -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(¤t, 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(¤t, 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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -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};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
10
src/walls.rs
10
src/walls.rs
@ -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)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user