diff --git a/Cargo.lock b/Cargo.lock index 053c1d3..026468f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,29 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "glam" version = "0.27.0" @@ -16,6 +39,7 @@ name = "hexlab" version = "0.1.0" dependencies = [ "hexx", + "rand", "serde", "serde_json", ] @@ -36,12 +60,27 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "libc" +version = "0.2.161" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" + [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + [[package]] name = "proc-macro2" version = "1.0.89" @@ -60,6 +99,36 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "ryu" version = "1.0.18" @@ -114,3 +183,30 @@ name = "unicode-ident" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index e690751..301ba45 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ license = "MIT OR Apache-2.0" [dependencies] hexx = { version = "0.18" } +rand = "0.8" serde = { version = "1.0", features = ["derive"], optional = true } [features] diff --git a/src/generator.rs b/src/generator.rs new file mode 100644 index 0000000..6048d4d --- /dev/null +++ b/src/generator.rs @@ -0,0 +1,89 @@ +use std::collections::HashSet; + +use hexx::{EdgeDirection, Hex}; +use rand::{rngs::ThreadRng, seq::SliceRandom, thread_rng}; + +use crate::HexMaze; + +#[derive(Debug, Clone, Copy)] +pub enum GeneratorType { + BackTracking, +} + +impl HexMaze { + pub fn generate(&mut self, generator_type: GeneratorType) { + match generator_type { + GeneratorType::BackTracking => self.generate_backtracking(), + } + } + + pub fn generate_backtracking(&mut self) { + if self.is_empty() { + return; + } + let start = *self.tiles().keys().next().unwrap(); + + let mut visited = HashSet::new(); + let mut rng = thread_rng(); + self.recursive_backtrack(start, &mut visited, &mut rng); + } + + fn recursive_backtrack( + &mut self, + current: Hex, + visited: &mut HashSet, + rng: &mut ThreadRng, + ) { + visited.insert(current); + + let mut directions = EdgeDirection::ALL_DIRECTIONS; + directions.shuffle(rng); + + for direction in directions { + let neighbor = current + direction; + + if let Some(_) = self.get_tile(&neighbor) { + if !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 hexx::HexLayout; + + use super::*; + + #[test] + fn backtracking_generation() { + let mut maze = HexMaze::default().with_radius(2); + + // Before generation + for tile in maze.tiles.values() { + assert_eq!(tile.walls.as_bits(), 0b111111); + } + + // Generate using backtracking + maze.generate(GeneratorType::BackTracking); + + // After generation + let all_walls = maze + .tiles + .values() + .all(|tile| tile.walls.as_bits() == 0b111111); + assert!(!all_walls, "Some walls should be removed"); + } + + #[test] + fn empty_maze() { + let mut maze = HexMaze::with_layout(HexLayout::default()); + maze.generate(GeneratorType::BackTracking); + assert!(maze.is_empty(), "Empty maze should remain empty"); + } +} diff --git a/src/lib.rs b/src/lib.rs index e320cbf..267a1f5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +mod generator; mod maze; mod tile; mod walls; diff --git a/src/maze.rs b/src/maze.rs index 14c5095..28e9001 100644 --- a/src/maze.rs +++ b/src/maze.rs @@ -8,7 +8,7 @@ use super::{HexTile, Walls}; #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Debug, Clone, Default)] pub struct HexMaze { - tiles: HashMap, + pub tiles: HashMap, layout: HexLayout, } @@ -83,8 +83,8 @@ impl HexMaze { /// Returns an iterator over all tiles #[inline] - pub fn tiles(&self) -> impl Iterator { - self.tiles.iter() + pub fn tiles(&self) -> &HashMap { + &self.tiles } /// Returns the number of tiles in the maze @@ -98,6 +98,13 @@ impl HexMaze { pub fn is_empty(&self) -> bool { self.tiles.is_empty() } + + #[inline] + pub fn remove_tile_wall(&mut self, coord: &Hex, direction: EdgeDirection) { + if let Some(tile) = self.tiles.get_mut(coord) { + tile.walls.remove(direction); + } + } } #[cfg(test)] @@ -165,7 +172,11 @@ mod tests { } // Test iterator - let collected = maze.tiles().map(|(_, tile)| tile).collect::>(); + let collected = maze + .tiles() + .iter() + .map(|(_, tile)| tile) + .collect::>(); assert_eq!( collected.len(), coords.len(), @@ -248,7 +259,11 @@ mod tests { } // Verify iterator - let iter_coords = maze.tiles().map(|(coord, _)| *coord).collect::>(); + let iter_coords = maze + .tiles() + .iter() + .map(|(coord, _)| *coord) + .collect::>(); assert_eq!( iter_coords.len(), coords.len(), diff --git a/src/walls.rs b/src/walls.rs index 9e81004..69b6e08 100644 --- a/src/walls.rs +++ b/src/walls.rs @@ -17,13 +17,13 @@ impl Walls { /// Adds a wall in the specified direction #[inline] pub fn add(&mut self, direction: EdgeDirection) { - self.0 |= Self::from(direction).0 + self.0 |= Self::from(direction).0; } /// Removes a wall in the specified direction #[inline] pub fn remove(&mut self, direction: EdgeDirection) { - self.0 &= !Self::from(direction).0 + self.0 &= !Self::from(direction).0; } /// Returns true if there is a wall in the specified direction