use std::collections::{HashSet, VecDeque};
use anyhow::{Context, Result};
use rand::prelude::*;
use rand_chacha::ChaChaRng;
use crate::prelude::*;
#[derive(Debug, Clone)]
pub struct PrimsGenerator {
rng: ChaChaRng,
frontier: Vec<Coordinates>,
visited: Vec<Coordinates>,
neighbours: Vec<Coordinates>,
}
impl PrimsGenerator {
pub fn new(seed: Option<[u8; 32]>) -> PrimsGenerator {
PrimsGenerator {
rng: match seed {
None => ChaChaRng::from_entropy(),
Some(seed) => ChaChaRng::from_seed(seed),
},
frontier: Vec::new(),
visited: Vec::new(),
neighbours: Vec::new(),
}
}
fn carve_passages_from(
&mut self,
maze: &mut Maze,
current_coordinates: Coordinates,
) -> Result<()> {
self.mark_cell(maze, current_coordinates)
.with_context(|| String::from("Could not parse passages"))?;
while !self.frontier.is_empty() {
let next_coords = self.frontier[self.rng.gen_range(0, self.frontier.len())];
self.find_visited_neighbours(maze, next_coords);
if !self.neighbours.is_empty() {
let ncell = self.neighbours[self.rng.gen_range(0, self.neighbours.len())]; maze.graph.add_edge(next_coords, ncell, ()); self.mark_cell(maze, next_coords)
.with_context(|| "Could not parse passages")?; } else {
self.frontier.clear(); eprintln!("No neighbours! {:?}", next_coords);
}
}
Ok(())
}
fn mark_cell(&mut self, maze: &mut Maze, current_coordinates: Coordinates) -> Result<()> {
if !self.visited.contains(¤t_coordinates) {
self.visited.push(current_coordinates);
}
if self.frontier.contains(¤t_coordinates) {
let idx = self
.frontier
.iter()
.position(|&r| r == current_coordinates)
.ok_or_else(|| {
GenericGeneratorError::InternalError(String::from(
"Could not find coordinates in frontier list",
))
})
.with_context(|| "Could not mark cell")?;
self.frontier.remove(idx);
}
for i_dir in Direction::all().iter() {
let next_coords = current_coordinates.next(i_dir);
if maze.are_coordinates_inside(&next_coords)
&& !self.frontier.contains(&next_coords)
&& !self.visited.contains(&next_coords)
{
self.frontier.push(next_coords);
}
}
Ok(())
}
fn find_visited_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);
}
}
}
fn find_suitable_goal(&self, maze: &mut Maze, start: Coordinates) -> Coordinates {
let mut already_visited = HashSet::new();
let mut queue: VecDeque<Coordinates> = maze.graph.neighbors(start).collect();
let mut last_coords = start;
while let Some(i_coords) = queue.pop_front() {
queue.extend(
maze.graph
.neighbors(i_coords)
.filter(|c| !already_visited.contains(c)),
);
already_visited.insert(i_coords);
last_coords = i_coords;
}
last_coords
}
}
impl Generator for PrimsGenerator {
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());
self.carve_passages_from(&mut maze, start)
.with_context(|| "Could not generate maze")?;
maze.goal = self.find_suitable_goal(&mut maze, start);
Ok(maze)
}
}
#[cfg(test)]
mod test {
test_all_coordinates_have_fields!(super::PrimsGenerator);
test_route_from_start_to_goal_exists!(super::PrimsGenerator);
test_all_fields_connected!(super::PrimsGenerator);
test_generation_is_deterministic!(super::PrimsGenerator);
}