1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
//! Recursive-Backtracking algorithm implementation
//!
//! Recursive backtracking is fast, easy to understand and straightforward.
//! Its downsides are relatively large memory and stack size requirements.
//!
//! The algorithm works as follows:
//!
//! 1. Choose a starting point in the field (in this implementation 0,0) and make it the current cell
//! 2. Randomly choose a direction, check if the field in that direction has not yet been visited.
//!     If that is the case, make the cell in that direction the new current cell and carve a passage between the two.
//! 3. If all adjacent fields have been visited, back up to the last field with unvisited neighbors.
//! 4. The algorithm terminates when it has backed up all the way to the starting point.

use anyhow::Result;
use rand::prelude::*;
use rand_chacha::ChaChaRng;

use crate::prelude::*;

/// [`Generator`] implementation which uses the recursive-backtracking algorithm.
#[derive(Debug, Clone)]
pub struct RbGenerator {
    rng: ChaChaRng,
}

impl RbGenerator {
    /// Create a new instance.
    ///
    /// Optionally a 32 bit seed can be provided to seed the internal random generator.
    /// Giving a seed results in identical mazes being generated which omitting it sources the
    /// random generator from entropy.
    pub fn new(seed: Option<[u8; 32]>) -> RbGenerator {
        RbGenerator {
            rng: match seed {
                None => ChaChaRng::from_entropy(),
                Some(seed) => ChaChaRng::from_seed(seed),
            },
        }
    }

    /// Core algorithm implementation
    ///
    /// Carves passages in all directions in random order from the current coordinates but only
    /// if the field in that direction has not yet been processed.
    ///
    /// Returns coordinates of the goal field
    fn carve_passages_from(
        &mut self,
        maze: &mut Maze,
        current_coordinates: Coordinates,
    ) -> Coordinates {
        let mut goal_coords = maze.start;
        for i_dir in Direction::gen_random_order(&mut self.rng).iter() {
            let next_coords = current_coordinates.next(i_dir);

            if maze.are_coordinates_inside(&next_coords)
                && maze.graph.neighbors(next_coords).count() == 0
            {
                maze.graph.add_edge(current_coordinates, next_coords, ());
                if goal_coords == maze.start {
                    goal_coords = self.carve_passages_from(maze, next_coords);
                } else {
                    self.carve_passages_from(maze, next_coords);
                }
            }
        }

        if goal_coords == maze.start {
            current_coordinates
        } else {
            goal_coords
        }
    }
}

impl Generator for RbGenerator {
    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.graph.add_node(start);

        let goal = self.carve_passages_from(&mut maze, start);
        maze.goal = goal;

        Ok(maze)
    }
}

#[cfg(test)]
mod test {
    test_all_coordinates_have_fields!(super::RbGenerator);
    test_route_from_start_to_goal_exists!(super::RbGenerator);
    test_all_fields_connected!(super::RbGenerator);
    test_generation_is_deterministic!(super::RbGenerator);
}