use std::collections::{BTreeSet, HashSet, VecDeque};
use anyhow::{Context, Result};
use rand::prelude::*;
use rand_chacha::ChaChaRng;
use crate::prelude::*;
const HORIZONTAL_JOIN_CHANCE: f64 = 0.5;
type EllersSet = BTreeSet<Coordinates>;
#[derive(Debug, Clone)]
pub struct EllersGenerator {
rng: ChaChaRng,
sets: Vec<EllersSet>,
graph: MazeGraph,
}
impl EllersGenerator {
pub fn new(seed: Option<[u8; 32]>) -> Self {
EllersGenerator {
rng: match seed {
None => ChaChaRng::from_entropy(),
Some(seed) => ChaChaRng::from_seed(seed),
},
sets: Vec::new(),
graph: MazeGraph::new(),
}
}
fn join_sets_of_fields(&mut self, field1: Coordinates, field2: Coordinates) -> Result<()> {
let set1 = self
.sets
.iter()
.find(|set| set.contains(&field1))
.ok_or_else(|| {
GenericGeneratorError::InternalError(format!(
"Expected to find coordinates {}",
field1
))
})?;
let set2 = self
.sets
.iter()
.find(|set| set.contains(&field2))
.ok_or_else(|| {
GenericGeneratorError::InternalError(format!(
"Expected to find coordinates {}",
field2
))
})?;
if set1 != set2 {
let index1 = self
.sets
.iter()
.position(|set| set == set1)
.ok_or_else(|| {
GenericGeneratorError::InternalError(String::from(
"Could not determine position of set",
))
})?;
let index2 = self
.sets
.iter()
.position(|set| set == set2)
.ok_or_else(|| {
GenericGeneratorError::InternalError(String::from(
"Could not determine position of set",
))
})?;
self.sets[index1] = set1.union(set2).cloned().collect();
self.sets[index2] = EllersSet::new();
self.graph.add_edge(field1, field2, ());
}
Ok(())
}
fn init_fields_first_row(&mut self, width: i32) {
self.sets = (0..width)
.map(|x| {
let mut set = EllersSet::new();
set.insert(Coordinates::new(x, 0));
set
})
.collect();
}
fn randomly_join_fields(&mut self, current_y: i32) -> Result<()> {
for i_x in 0..(self.sets.len() - 1) {
if self.rng.gen_bool(HORIZONTAL_JOIN_CHANCE) {
self.join_sets_of_fields(
(i_x as i32, current_y).into(),
(i_x as i32 + 1, current_y).into(),
)
.with_context(|| "Could not randomly join fields")?;
}
}
Ok(())
}
fn create_downward_connections(&mut self, current_y: i32) {
for i_set in &mut self.sets {
if !i_set.is_empty() {
let bottom_most_fields: Vec<_> =
i_set.iter().filter(|c| c.y == current_y).cloned().collect();
let count = if !bottom_most_fields.is_empty() {
1
} else {
self.rng.gen_range(1, bottom_most_fields.len())
};
for coordinates in bottom_most_fields.choose_multiple(&mut self.rng, count) {
let next_coords = coordinates.next(&Direction::South);
i_set.insert(next_coords);
self.graph.add_edge(*coordinates, next_coords, ());
}
}
}
}
fn flesh_out_next_row(&mut self, current_y: i32) -> Result<()> {
let next_y = current_y + 1;
for i_x in 0..self.sets.len() {
let coordinates = (i_x as i32, next_y).into();
if !self.sets.iter().any(|set| set.contains(&coordinates)) {
self.sets
.iter_mut()
.find(|set| set.is_empty())
.ok_or_else(|| {
GenericGeneratorError::InternalError(String::from("No empty set found"))
})?
.insert(coordinates);
}
}
Ok(())
}
fn join_last_rows(&mut self, current_y: i32) -> Result<()> {
for i_x in 0..(self.sets.len() - 1) {
self.join_sets_of_fields(
(i_x as i32, current_y).into(),
(i_x as i32 + 1, current_y).into(),
)
.with_context(|| "Could not join last rows")?;
}
Ok(())
}
fn find_suitable_goal(&self, start: Coordinates) -> Coordinates {
let mut already_visited = HashSet::new();
let mut queue: VecDeque<Coordinates> = self.graph.neighbors(start).collect();
let mut last_coords = start;
while let Some(i_coords) = queue.pop_front() {
queue.extend(
self.graph
.neighbors(i_coords)
.filter(|c| !already_visited.contains(c)),
);
already_visited.insert(i_coords);
last_coords = i_coords;
}
last_coords
}
}
impl Generator for EllersGenerator {
fn generate(&mut self, width: i32, height: i32) -> Result<Maze> {
self.graph = MazeGraph::with_capacity((width * height) as usize, 0);
self.init_fields_first_row(width);
for y in 0..height {
self.randomly_join_fields(y)
.with_context(|| "Could not generate maze")?;
self.create_downward_connections(y);
self.flesh_out_next_row(y)
.with_context(|| "Could not generate maze")?;
}
self.join_last_rows(height - 1)
.with_context(|| "Could not generate maze")?;
let start = (0, 0).into();
let goal = (0, 0).into();
let mut maze = Maze::new(width, height, start, goal);
maze.graph = self.graph.clone();
maze.goal = self.find_suitable_goal(start);
Ok(maze)
}
}
#[cfg(test)]
mod test {
test_all_coordinates_have_fields!(super::EllersGenerator);
test_route_from_start_to_goal_exists!(super::EllersGenerator);
test_all_fields_connected!(super::EllersGenerator);
test_generation_is_deterministic!(super::EllersGenerator);
}