use anyhow::{Context, Result};
use rand::prelude::*;
use rand_chacha::ChaChaRng;
use crate::prelude::*;
#[derive(Debug, Clone, Copy)]
pub enum GrowingTreeSelectionMethod {
Random,
MostRecent,
First,
}
#[derive(Debug, Clone)]
pub struct GrowingTreeGenerator {
rng: ChaChaRng,
pub selection_method: GrowingTreeSelectionMethod,
cell_stack: Vec<Coordinates>,
visited: Vec<Coordinates>,
neighbours: Vec<Coordinates>,
}
impl GrowingTreeGenerator {
pub fn new(seed: Option<[u8; 32]>) -> GrowingTreeGenerator {
GrowingTreeGenerator {
rng: match seed {
None => ChaChaRng::from_entropy(),
Some(seed) => ChaChaRng::from_seed(seed),
},
selection_method: GrowingTreeSelectionMethod::First,
cell_stack: Vec::new(),
visited: Vec::new(),
neighbours: Vec::new(),
}
}
fn carve_passages_from(
&mut self,
maze: &mut Maze,
start_coordinates: Coordinates,
) -> Result<Coordinates> {
let mut current_coordinates = start_coordinates;
let mut goal_coordinates = current_coordinates;
let mut max_q = 0;
self.cell_stack.clear();
self.cell_stack.push(current_coordinates);
self.visited.push(current_coordinates);
while !self.cell_stack.is_empty() {
self.find_unvisited_neighbours(maze, current_coordinates);
if self.neighbours.is_empty() {
if self.cell_stack.contains(¤t_coordinates) {
let idx = self
.cell_stack
.iter()
.position(|&r| r == current_coordinates)
.ok_or_else(|| {
GenericGeneratorError::InternalError(String::from(
"Could not find position of coordinates in cell_stack",
))
})
.with_context(|| "Could not carve passages")?;
self.cell_stack.remove(idx);
}
if self.cell_stack.is_empty() {
continue;
}
current_coordinates = match self.selection_method {
GrowingTreeSelectionMethod::MostRecent => self
.cell_stack
.pop()
.ok_or_else(|| {
GenericGeneratorError::InternalError(String::from(
"Could not pop most recent cell from cell_stack",
))
})
.with_context(|| "Could not carve passage")?,
GrowingTreeSelectionMethod::Random => {
self.cell_stack[self.rng.gen_range(0, self.cell_stack.len())]
}
GrowingTreeSelectionMethod::First => self.cell_stack.remove(0),
};
} else {
let next_coords = self.neighbours[self.rng.gen_range(0, self.neighbours.len())];
maze.graph.add_edge(current_coordinates, next_coords, ()); self.cell_stack.push(next_coords);
current_coordinates = next_coords;
self.visited.push(current_coordinates);
if self.cell_stack.len() > max_q {
max_q = self.cell_stack.len();
goal_coordinates = current_coordinates;
}
}
}
Ok(goal_coordinates)
}
fn find_unvisited_neighbours(&mut self, maze: &mut Maze, current_coordinates: Coordinates) {
self.neighbours.clear();
for i_dir in Direction::all().iter() {
let next_coords = current_coordinates.next(i_dir);
if maze.are_coordinates_inside(&next_coords) && !self.visited.contains(&next_coords) {
self.neighbours.push(next_coords);
}
}
}
}
impl Generator for GrowingTreeGenerator {
fn generate(&mut self, width: i32, height: i32) -> Result<Maze> {
let start = (0, 0).into();
let mut maze = Maze::new(width, height, start, (0, 0).into());
maze.goal = self
.carve_passages_from(&mut maze, start)
.with_context(|| "Could not generate maze")?;
Ok(maze)
}
}
#[cfg(test)]
mod test {
test_all_coordinates_have_fields!(super::GrowingTreeGenerator);
test_route_from_start_to_goal_exists!(super::GrowingTreeGenerator);
test_all_fields_connected!(super::GrowingTreeGenerator);
test_generation_is_deterministic!(super::GrowingTreeGenerator);
}