use crate::generators::{
GenerationStep, GenerationSteps, GenerationVisitor, MazeGenerator2D, VecGenerationVisitor,
};
use crate::grid_coord_2d::{GetCoordinateBounds2D, GridCoord2D};
use crate::visit_map_2d::VisitMap2D;
use crate::wall4_grid::Wall4Grid;
use rand::SeedableRng;
use rand::prelude::IndexedRandom;
use rand::rngs::StdRng;
pub struct RecursiveBacktracker4 {
rng_seed: u64,
}
impl Default for RecursiveBacktracker4 {
fn default() -> Self {
Self::new_random()
}
}
impl RecursiveBacktracker4 {
pub fn new_random() -> Self {
Self {
rng_seed: rand::random(),
}
}
pub fn new_from_seed(rng_seed: u64) -> Self {
if rng_seed == 0 {
Self::new_random()
} else {
Self { rng_seed }
}
}
pub fn generate(&self, width: usize, height: usize) -> Wall4Grid {
self.generate_with_steps(width, height).0
}
pub fn generate_steps(&self, width: usize, height: usize) -> GenerationSteps {
GenerationSteps::new(self.generate_with_steps(width, height).1)
}
fn generate_with_steps(&self, width: usize, height: usize) -> (Wall4Grid, Vec<GenerationStep>) {
let mut cells = Wall4Grid::new(width, height);
let mut visit_map = VisitMap2D::new_like(&cells);
let start_coordinate = GridCoord2D::default();
let mut backtrace = Vec::new();
let mut visitor = VecGenerationVisitor::default();
if width == 0 || height == 0 {
visitor.on_step(&GenerationStep::Complete);
return (cells, visitor.into_steps());
}
let rng = StdRng::seed_from_u64(self.rng_seed);
Self::backtrack(
rng,
&mut cells,
&mut visit_map,
start_coordinate,
&mut backtrace,
&mut visitor,
);
visitor.on_step(&GenerationStep::Complete);
(cells, visitor.into_steps())
}
fn backtrack<V: GenerationVisitor>(
mut rng: StdRng,
cells: &mut Wall4Grid,
visit_map: &mut VisitMap2D,
mut current_cell: GridCoord2D,
backtrace: &mut Vec<GridCoord2D>,
visitor: &mut V,
) {
debug_assert_eq!(cells.width(), visit_map.width());
debug_assert_eq!(cells.height(), visit_map.height());
loop {
if !visit_map[current_cell] {
visitor.on_step(&GenerationStep::Visit { cell: current_cell });
}
visit_map[current_cell] = true;
if let Some(selected_cell) =
Self::select_random_unvisited_neighbor(&mut rng, visit_map, current_cell)
{
backtrace.push(current_cell);
visitor.on_step(&GenerationStep::AddToFrontier { cell: current_cell });
cells.remove_wall_between(current_cell, selected_cell);
visitor.on_step(&GenerationStep::Carve {
from: current_cell,
to: selected_cell,
});
current_cell = selected_cell;
continue;
}
if let Some(cell) = backtrace.pop() {
current_cell = cell;
visitor.on_step(&GenerationStep::Backtrack { to: cell });
} else {
break;
}
}
}
fn select_random_unvisited_neighbor(
rng: &mut StdRng,
visit_map: &VisitMap2D,
current_cell: GridCoord2D,
) -> Option<GridCoord2D> {
debug_assert!(current_cell.x < visit_map.width());
debug_assert!(current_cell.y < visit_map.height());
let list = visit_map.unvisited_neighbors(current_cell);
if list.is_empty() {
return None;
}
list.choose(rng).copied()
}
}
impl MazeGenerator2D for RecursiveBacktracker4 {
fn new_random() -> Self {
RecursiveBacktracker4::new_random()
}
fn new_from_seed(rng_seed: u64) -> Self {
RecursiveBacktracker4::new_from_seed(rng_seed)
}
fn generate(&self, width: usize, height: usize) -> Wall4Grid {
self.generate(width, height)
}
fn generate_steps(&self, width: usize, height: usize) -> GenerationSteps {
self.generate_steps(width, height)
}
fn name(&self) -> &'static str {
"recursive-backtracker"
}
fn description(&self) -> &'static str {
"Depth-first backtracking generator that creates long corridors"
}
}