use crate::behaviors::maze::MazeGeneration;
use crate::grid::Grid;
use crate::cell::Coordinates;
use crate::error::Error;
use std::collections::{BinaryHeap, HashSet};
use rand::Rng;
#[derive(Eq, PartialEq)]
struct FrontierCell {
coords: Coordinates,
weight: u32, }
impl Ord for FrontierCell {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
other.weight.cmp(&self.weight)
}
}
impl PartialOrd for FrontierCell {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
pub struct Prims;
impl MazeGeneration for Prims {
fn generate(&self, grid: &mut Grid) -> Result<(), Error> {
let mut visited: HashSet<Coordinates> = HashSet::new();
let mut frontier: BinaryHeap<FrontierCell> = BinaryHeap::new();
let mut rng = rand::thread_rng();
let start_coords;
loop {
let x = grid.bounded_random_usize(grid.width);
let y = grid.bounded_random_usize(grid.height);
if grid.has_cell(x, y) {
start_coords = Coordinates { x, y };
break;
}
}
visited.insert(start_coords);
if let Ok(start_cell) = grid.get(start_coords) {
for &neighbor_coords in start_cell.neighbors().iter() {
frontier.push(FrontierCell {
coords: neighbor_coords,
weight: rng.gen(), });
}
}
if grid.capture_steps {
let changed_cells = HashSet::new();
self.capture_step(grid, &changed_cells);
}
while let Some(FrontierCell { coords, .. }) = frontier.pop() {
if visited.contains(&coords) {
continue; }
visited.insert(coords);
let (visited_neighbors, unvisited_neighbors) = if let Ok(cell) = grid.get(coords) {
let visited_neighbors: Vec<Coordinates> = cell
.neighbors()
.into_iter()
.filter(|neighbor| visited.contains(neighbor))
.collect();
let unvisited_neighbors: Vec<Coordinates> = cell
.neighbors()
.into_iter()
.filter(|neighbor| !visited.contains(neighbor))
.collect();
(visited_neighbors, unvisited_neighbors)
} else {
continue;
};
if !visited_neighbors.is_empty() {
let neighbor_index = grid.bounded_random_usize(visited_neighbors.len());
let neighbor_coords = visited_neighbors[neighbor_index];
grid.link(coords, neighbor_coords)?;
if grid.capture_steps {
let mut changed_cells = HashSet::new();
changed_cells.insert(coords);
changed_cells.insert(neighbor_coords);
self.capture_step(grid, &changed_cells);
}
}
for neighbor_coords in unvisited_neighbors {
frontier.push(FrontierCell {
coords: neighbor_coords,
weight: rng.gen(), });
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cell::{MazeType, Coordinates};
#[test]
fn generate_and_print_5_x_5_orthogonal_maze() {
match Grid::new(MazeType::Orthogonal, 4, 4, Coordinates { x: 0, y: 0 }, Coordinates { x: 3, y: 3 }, false) {
Ok(mut grid) => {
assert!(!grid.is_perfect_maze().unwrap());
Prims.generate(&mut grid).expect("Prim's maze generation failed");
println!("\n\nPrim's\n\n{}\n\n", grid.to_asci());
assert!(grid.is_perfect_maze().unwrap());
}
Err(e) => panic!("Unexpected error running test: {:?}", e),
}
}
#[test]
fn generate_and_print_12_x_6_orthogonal_maze() {
match Grid::new(MazeType::Orthogonal, 12, 6, Coordinates { x: 0, y: 0 }, Coordinates { x: 11, y: 5 }, false) {
Ok(mut grid) => {
assert!(!grid.is_perfect_maze().unwrap());
Prims.generate(&mut grid).expect("Prim's maze generation failed");
println!("\n\nPrim's\n\n{}\n\n", grid.to_asci());
assert!(grid.is_perfect_maze().unwrap());
}
Err(e) => panic!("Unexpected error running test: {:?}", e),
}
}
#[test]
fn generate_5_x_5_delta_maze() {
match Grid::new(MazeType::Delta, 4, 4, Coordinates { x: 0, y: 0 }, Coordinates { x: 3, y: 3 }, false) {
Ok(mut grid) => {
assert!(!grid.is_perfect_maze().unwrap());
Prims.generate(&mut grid).expect("Prim's maze generation failed");
assert!(grid.is_perfect_maze().unwrap());
}
Err(e) => panic!("Unexpected error running test: {:?}", e),
}
}
#[test]
fn generate_12_x_6_delta_maze() {
match Grid::new(MazeType::Delta, 12, 6, Coordinates { x: 0, y: 0 }, Coordinates { x: 11, y: 5 }, false) {
Ok(mut grid) => {
assert!(!grid.is_perfect_maze().unwrap());
Prims.generate(&mut grid).expect("Prim's maze generation failed");
assert!(grid.is_perfect_maze().unwrap());
}
Err(e) => panic!("Unexpected error running test: {:?}", e),
}
}
#[test]
fn generate_5_x_5_sigma_maze() {
match Grid::new(MazeType::Sigma, 4, 4, Coordinates { x: 0, y: 0 }, Coordinates { x: 3, y: 3 }, false) {
Ok(mut grid) => {
assert!(!grid.is_perfect_maze().unwrap());
Prims.generate(&mut grid).expect("Prim's maze generation failed");
assert!(grid.is_perfect_maze().unwrap());
}
Err(e) => panic!("Unexpected error running test: {:?}", e),
}
}
#[test]
fn generate_12_x_6_sigma_maze() {
match Grid::new(MazeType::Sigma, 12, 6, Coordinates { x: 0, y: 0 }, Coordinates { x: 11, y: 5 }, false) {
Ok(mut grid) => {
assert!(!grid.is_perfect_maze().unwrap());
Prims.generate(&mut grid).expect("Prim's maze generation failed");
assert!(grid.is_perfect_maze().unwrap());
}
Err(e) => panic!("Unexpected error running test: {:?}", e),
}
}
#[test]
fn test_prims_with_capture_steps() {
let start = Coordinates { x: 0, y: 0 };
let goal = Coordinates { x: 19, y: 19 };
match Grid::new(MazeType::Orthogonal, 20, 20, start, goal, true) {
Ok(mut grid) => {
assert!(!grid.is_perfect_maze().unwrap());
Prims.generate(&mut grid).expect("Maze generation failed");
assert!(grid.is_perfect_maze().unwrap());
assert!(grid.generation_steps.is_some());
let steps = grid.generation_steps.as_ref().unwrap(); assert!(!steps.is_empty());
let has_linked_cells = steps.iter().any(|step| {
step.cells.iter().filter_map(|opt| opt.as_ref()).any(|cell| !cell.linked.is_empty())
});
assert!(has_linked_cells, "No cells were linked during maze generation");
let has_open_walls = steps.iter().any(|step| {
step.cells.iter().filter_map(|opt| opt.as_ref()).any(|cell| !cell.open_walls.is_empty())
});
assert!(has_open_walls, "No cells have open walls in generation steps");
}
Err(e) => panic!("Unexpected error generating grid: {:?}", e),
}
}
#[test]
fn generate_12_x_24_rhombic_prims() {
match Grid::new(MazeType::Rhombic, 12, 24, Coordinates { x: 0, y: 0 }, Coordinates { x: 5, y: 23 }, false) {
Ok(mut grid) => {
assert!(!grid.is_perfect_maze().unwrap());
Prims.generate(&mut grid).expect("Prims maze generation failed");
assert!(grid.is_perfect_maze().unwrap());
}
Err(e) => panic!("Unexpected error running test: {:?}", e),
}
}
}