use crate::behaviors::maze::MazeGeneration;
use crate::algorithms::MazeAlgorithm;
use crate::grid::Grid;
use crate::cell::{Coordinates, MazeType};
use crate::error::Error;
use std::collections::HashSet;
pub struct HuntAndKill;
impl MazeGeneration for HuntAndKill {
fn generate(&self, grid: &mut Grid) -> Result<(), Error> {
match grid.maze_type {
MazeType::Rhombic => {
return Err(Error::AlgorithmUnavailableForMazeType {
algorithm: MazeAlgorithm::HuntAndKill,
maze_type: MazeType::Rhombic,
});
}
_ => {} }
let mut visited = HashSet::new();
let mut current_coords = Coordinates {
x: grid.bounded_random_usize(grid.width),
y: grid.bounded_random_usize(grid.height),
};
visited.insert(current_coords);
if grid.capture_steps {
let changed_cells = HashSet::new();
self.capture_step(grid, &changed_cells);
}
loop {
while let Some(next_coords) = Self::random_unvisited_neighbor(grid, ¤t_coords, &visited) {
grid.link(current_coords, next_coords)?;
visited.insert(next_coords);
current_coords = next_coords;
if grid.capture_steps {
let mut changed_cells = HashSet::new();
changed_cells.insert(current_coords);
changed_cells.insert(next_coords);
self.capture_step(grid, &changed_cells);
}
}
if let Some((new_coords, neighbor)) = Self::find_hunt_target(grid, &visited) {
grid.link(new_coords, neighbor)?;
visited.insert(new_coords);
current_coords = new_coords;
if grid.capture_steps {
let mut changed_cells = HashSet::new();
changed_cells.insert(new_coords);
changed_cells.insert(neighbor);
self.capture_step(grid, &changed_cells);
}
} else {
break;
}
}
Ok(())
}
}
impl HuntAndKill {
fn random_unvisited_neighbor(
grid: &mut Grid,
coords: &Coordinates,
visited: &HashSet<Coordinates>,
) -> Option<Coordinates> {
if let Ok(current_cell) = grid.get(*coords) {
let neighbors: Vec<_> = current_cell
.neighbors()
.into_iter()
.filter(|neighbor| !visited.contains(neighbor))
.collect();
if neighbors.is_empty() {
None
} else {
let index = grid.bounded_random_usize(neighbors.len());
Some(neighbors[index])
}
} else {
None
}
}
fn find_hunt_target(
grid: &Grid,
visited: &HashSet<Coordinates>,
) -> Option<(Coordinates, Coordinates)> {
(0..grid.height)
.flat_map(|y| (0..grid.width).map(move |x| Coordinates { x, y }))
.find_map(|coords| {
if visited.contains(&coords) {
None
} else {
match grid.get(coords) {
Ok(current_cell) => current_cell
.neighbors()
.into_iter()
.find(|neighbor| visited.contains(neighbor))
.map(|neighbor| (coords, neighbor)),
Err(_) => None,
}
}
})
}
}
#[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());
HuntAndKill.generate(&mut grid).expect("HuntAndKill maze generation failed");
println!("\n\nHunt-and-Kill\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());
HuntAndKill.generate(&mut grid).expect("HuntAndKill maze generation failed");
println!("\n\nHunt-and-Kill\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());
HuntAndKill.generate(&mut grid).expect("HuntAndKill 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());
HuntAndKill.generate(&mut grid).expect("HuntAndKill 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());
HuntAndKill.generate(&mut grid).expect("HuntAndKill 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());
HuntAndKill.generate(&mut grid).expect("HuntAndKill maze generation failed");
assert!(grid.is_perfect_maze().unwrap());
}
Err(e) => panic!("Unexpected error running test: {:?}", e),
}
}
#[test]
fn test_hunt_and_kill_with_capture_steps() {
let start = Coordinates { x: 0, y: 0 };
let goal = Coordinates { x: 11, y: 11 };
match Grid::new(MazeType::Orthogonal, 12, 12, start, goal, true) {
Ok(mut grid) => {
assert!(!grid.is_perfect_maze().unwrap());
HuntAndKill.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),
}
}
}