feat(generator): add maze backtracking generator

This commit is contained in:
2024-11-06 13:58:16 +02:00
parent 838116ae3f
commit 9871df523e
6 changed files with 209 additions and 7 deletions

89
src/generator.rs Normal file
View File

@@ -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<Hex>,
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(&current, 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");
}
}

View File

@@ -1,3 +1,4 @@
mod generator;
mod maze;
mod tile;
mod walls;

View File

@@ -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<Hex, HexTile>,
pub tiles: HashMap<Hex, HexTile>,
layout: HexLayout,
}
@@ -83,8 +83,8 @@ impl HexMaze {
/// Returns an iterator over all tiles
#[inline]
pub fn tiles(&self) -> impl Iterator<Item = (&Hex, &HexTile)> {
self.tiles.iter()
pub fn tiles(&self) -> &HashMap<Hex, HexTile> {
&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::<Vec<_>>();
let collected = maze
.tiles()
.iter()
.map(|(_, tile)| tile)
.collect::<Vec<_>>();
assert_eq!(
collected.len(),
coords.len(),
@@ -248,7 +259,11 @@ mod tests {
}
// Verify iterator
let iter_coords = maze.tiles().map(|(coord, _)| *coord).collect::<Vec<_>>();
let iter_coords = maze
.tiles()
.iter()
.map(|(coord, _)| *coord)
.collect::<Vec<_>>();
assert_eq!(
iter_coords.len(),
coords.len(),

View File

@@ -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