├── maze_tui ├── painters │ ├── src │ │ ├── lib.rs │ │ ├── rgb.rs │ │ ├── distance.rs │ │ └── runs.rs │ └── Cargo.toml ├── solvers │ ├── src │ │ ├── lib.rs │ │ └── solve.rs │ └── Cargo.toml ├── run_tui │ ├── src │ │ ├── main.rs │ │ └── run.rs │ └── Cargo.toml ├── Cargo.toml ├── maze │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── monitor │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── print │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── builders │ ├── src │ │ ├── lib.rs │ │ ├── arena.rs │ │ ├── disjoint.rs │ │ ├── grid.rs │ │ ├── kruskal.rs │ │ ├── recursive_backtracker.rs │ │ ├── prim.rs │ │ ├── recursive_subdivision.rs │ │ ├── modify.rs │ │ ├── eller.rs │ │ ├── hunt_kill.rs │ │ ├── wilson_carver.rs │ │ └── wilson_adder.rs │ └── Cargo.toml ├── tables │ ├── Cargo.toml │ └── src │ │ └── lib.rs └── res │ ├── arena.txt │ ├── kruskal.txt │ ├── prim.txt │ ├── grid.txt │ ├── recursive_backtracker.txt │ ├── wilson_carver.txt │ ├── hunt_kill.txt │ ├── eller.txt │ ├── wilson_adder.txt │ ├── recursive_subdivision.txt │ └── instructions.txt ├── images ├── demo.gif ├── splash.png ├── tetrad.png ├── arena-loop.gif ├── eller-loop.gif ├── gather-demo.gif ├── grid-loop.gif ├── hunt-demo.gif ├── prim-loop.gif ├── rdfs-loop.gif ├── solver-loop.gif ├── wilson-demo.png ├── wilson-loop.gif ├── corners-demo.gif ├── fractal-loop.gif ├── kruskal-loop.gif ├── unicode-walls.png ├── games-showcase.png ├── hunt-kill-loop.gif ├── maze-data-flow.png ├── rdfs-solver-demo.png ├── dimension-showcase.png ├── modification-demo.png ├── wilson-walls-loop.gif ├── measure-runs-animated.gif ├── measure-runs-static.png ├── measure-distance-static.png └── measure-distance-animated.gif ├── .gitignore ├── LICENSE └── README.md /maze_tui/painters/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod distance; 2 | pub mod rgb; 3 | pub mod runs; 4 | -------------------------------------------------------------------------------- /images/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agl-alexglopez/maze-tui/HEAD/images/demo.gif -------------------------------------------------------------------------------- /images/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agl-alexglopez/maze-tui/HEAD/images/splash.png -------------------------------------------------------------------------------- /images/tetrad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agl-alexglopez/maze-tui/HEAD/images/tetrad.png -------------------------------------------------------------------------------- /images/arena-loop.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agl-alexglopez/maze-tui/HEAD/images/arena-loop.gif -------------------------------------------------------------------------------- /images/eller-loop.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agl-alexglopez/maze-tui/HEAD/images/eller-loop.gif -------------------------------------------------------------------------------- /images/gather-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agl-alexglopez/maze-tui/HEAD/images/gather-demo.gif -------------------------------------------------------------------------------- /images/grid-loop.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agl-alexglopez/maze-tui/HEAD/images/grid-loop.gif -------------------------------------------------------------------------------- /images/hunt-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agl-alexglopez/maze-tui/HEAD/images/hunt-demo.gif -------------------------------------------------------------------------------- /images/prim-loop.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agl-alexglopez/maze-tui/HEAD/images/prim-loop.gif -------------------------------------------------------------------------------- /images/rdfs-loop.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agl-alexglopez/maze-tui/HEAD/images/rdfs-loop.gif -------------------------------------------------------------------------------- /images/solver-loop.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agl-alexglopez/maze-tui/HEAD/images/solver-loop.gif -------------------------------------------------------------------------------- /images/wilson-demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agl-alexglopez/maze-tui/HEAD/images/wilson-demo.png -------------------------------------------------------------------------------- /images/wilson-loop.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agl-alexglopez/maze-tui/HEAD/images/wilson-loop.gif -------------------------------------------------------------------------------- /images/corners-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agl-alexglopez/maze-tui/HEAD/images/corners-demo.gif -------------------------------------------------------------------------------- /images/fractal-loop.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agl-alexglopez/maze-tui/HEAD/images/fractal-loop.gif -------------------------------------------------------------------------------- /images/kruskal-loop.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agl-alexglopez/maze-tui/HEAD/images/kruskal-loop.gif -------------------------------------------------------------------------------- /images/unicode-walls.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agl-alexglopez/maze-tui/HEAD/images/unicode-walls.png -------------------------------------------------------------------------------- /images/games-showcase.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agl-alexglopez/maze-tui/HEAD/images/games-showcase.png -------------------------------------------------------------------------------- /images/hunt-kill-loop.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agl-alexglopez/maze-tui/HEAD/images/hunt-kill-loop.gif -------------------------------------------------------------------------------- /images/maze-data-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agl-alexglopez/maze-tui/HEAD/images/maze-data-flow.png -------------------------------------------------------------------------------- /images/rdfs-solver-demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agl-alexglopez/maze-tui/HEAD/images/rdfs-solver-demo.png -------------------------------------------------------------------------------- /images/dimension-showcase.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agl-alexglopez/maze-tui/HEAD/images/dimension-showcase.png -------------------------------------------------------------------------------- /images/modification-demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agl-alexglopez/maze-tui/HEAD/images/modification-demo.png -------------------------------------------------------------------------------- /images/wilson-walls-loop.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agl-alexglopez/maze-tui/HEAD/images/wilson-walls-loop.gif -------------------------------------------------------------------------------- /images/measure-runs-animated.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agl-alexglopez/maze-tui/HEAD/images/measure-runs-animated.gif -------------------------------------------------------------------------------- /images/measure-runs-static.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agl-alexglopez/maze-tui/HEAD/images/measure-runs-static.png -------------------------------------------------------------------------------- /maze_tui/solvers/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod bfs; 2 | pub mod dfs; 3 | pub mod floodfs; 4 | pub mod rdfs; 5 | pub mod solve; 6 | -------------------------------------------------------------------------------- /images/measure-distance-static.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agl-alexglopez/maze-tui/HEAD/images/measure-distance-static.png -------------------------------------------------------------------------------- /images/measure-distance-animated.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agl-alexglopez/maze-tui/HEAD/images/measure-distance-animated.gif -------------------------------------------------------------------------------- /maze_tui/run_tui/src/main.rs: -------------------------------------------------------------------------------- 1 | mod run; 2 | mod tui; 3 | 4 | fn main() -> tui::Result<()> { 5 | let status = run::run(); 6 | status?; 7 | Ok(()) 8 | } 9 | -------------------------------------------------------------------------------- /maze_tui/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "run_tui", 4 | "maze", 5 | "monitor", 6 | "builders", 7 | "tables", 8 | "solvers", 9 | "painters", 10 | "print", 11 | ] 12 | resolver = "2" 13 | -------------------------------------------------------------------------------- /maze_tui/maze/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "maze" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | rand = "0.8.5" 10 | -------------------------------------------------------------------------------- /maze_tui/monitor/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "monitor" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | maze = { path = "../maze" } 10 | -------------------------------------------------------------------------------- /maze_tui/print/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "print" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | maze = { path = "../maze" } 10 | crossterm = "0.26.1" 11 | -------------------------------------------------------------------------------- /maze_tui/builders/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod arena; 2 | pub mod build; 3 | pub(crate) mod disjoint; 4 | pub mod eller; 5 | pub mod grid; 6 | pub mod hunt_kill; 7 | pub mod kruskal; 8 | pub mod modify; 9 | pub mod prim; 10 | pub mod recursive_backtracker; 11 | pub mod recursive_subdivision; 12 | pub mod wilson_adder; 13 | pub mod wilson_carver; 14 | -------------------------------------------------------------------------------- /maze_tui/builders/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "builders" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | maze = { path = "../maze" } 10 | monitor = { path = "../monitor" } 11 | print = { path = "../print" } 12 | rand = "0.8.5" 13 | crossterm = "0.26.1" 14 | ratatui = "0.24" 15 | 16 | -------------------------------------------------------------------------------- /maze_tui/tables/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tables" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | maze = { path = "../maze" } 10 | monitor = { path = "../monitor" } 11 | solvers = { path = "../solvers" } 12 | builders = { path = "../builders" } 13 | painters = { path = "../painters" } 14 | rand = "0.8.5" 15 | -------------------------------------------------------------------------------- /maze_tui/solvers/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "solvers" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | maze = { path = "../maze" } 10 | monitor = { path = "../monitor" } 11 | builders = { path = "../builders" } 12 | print = { path = "../print" } 13 | crossterm = "0.26.1" 14 | rand = "0.8.5" 15 | ratatui = "0.24" 16 | -------------------------------------------------------------------------------- /maze_tui/painters/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "painters" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | maze = { path = "../maze" } 10 | monitor = { path = "../monitor" } 11 | builders = { path = "../builders" } 12 | solvers = { path = "../solvers" } 13 | print = { path = "../print" } 14 | crossterm = "0.26.1" 15 | rand = "0.8.5" 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | # Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | 16 | .gdb_history 17 | 18 | .DS_Store 19 | -------------------------------------------------------------------------------- /maze_tui/run_tui/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "run_tui" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | maze = { path = "../maze" } 10 | builders = { path = "../builders" } 11 | solvers = { path = "../solvers" } 12 | monitor = { path = "../monitor" } 13 | tables = { path = "../tables" } 14 | print = { path = "../print" } 15 | crossterm = "0.27" 16 | ratatui = "0.24" 17 | tui-textarea = "0.3" 18 | rand = "0.8.5" 19 | crossbeam-channel = "0.5" 20 | -------------------------------------------------------------------------------- /maze_tui/monitor/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::HashMap, 3 | sync::{Arc, Mutex}, 4 | }; 5 | 6 | #[derive(Default)] 7 | pub struct MaxMap { 8 | pub max: u64, 9 | pub distances: HashMap, 10 | } 11 | 12 | impl MaxMap { 13 | pub fn new(p: maze::Point, m: u64) -> Self { 14 | Self { 15 | max: m, 16 | distances: HashMap::from([(p, m)]), 17 | } 18 | } 19 | } 20 | 21 | pub struct Monitor { 22 | pub maze: maze::Maze, 23 | pub win: Option, 24 | pub win_path: Vec<(maze::Point, maze::Square)>, 25 | pub map: MaxMap, 26 | pub count: usize, 27 | } 28 | 29 | impl Monitor { 30 | pub fn new(boxed_maze: maze::Maze) -> Arc> { 31 | Arc::new(Mutex::new(Self { 32 | maze: boxed_maze, 33 | win: None, 34 | win_path: Vec::default(), 35 | map: MaxMap::default(), 36 | count: 0, 37 | })) 38 | } 39 | } 40 | 41 | pub type MazeMonitor = Arc>; 42 | -------------------------------------------------------------------------------- /maze_tui/painters/src/rgb.rs: -------------------------------------------------------------------------------- 1 | pub struct ThreadGuide { 2 | pub bias: usize, 3 | pub color_i: usize, 4 | pub cache: maze::Square, 5 | pub p: maze::Point, 6 | } 7 | 8 | pub type SpeedUnit = u64; 9 | 10 | pub type Rgb = [u8; 3]; 11 | 12 | pub const PAINT: maze::Square = 0b0001_0000_0000_0000_0000_0000_0000; 13 | pub const PAINT_MASK: maze::Square = 0b1111_1111_1111_1111_1111_1111; 14 | pub const MEASURED: maze::Square = 0b0010_0000_0000_0000_0000_0000_0000; 15 | pub const MEASURED_MASKS: [maze::Square; 4] = [0x1000000, 0x2000000, 0x4000000, 0x8000000]; 16 | pub const NUM_PAINTERS: usize = 4; 17 | pub const ANIMATION_SPEEDS: [SpeedUnit; 8] = [0, 10000, 5000, 2000, 1000, 500, 250, 50]; 18 | pub const RED_SHIFT: maze::Square = 16; 19 | pub const GREEN_SHIFT: maze::Square = 8; 20 | pub const NUM_DIRECTIONS: u16 = 4; 21 | 22 | #[inline] 23 | pub fn has_paint_vals(square: maze::Square) -> bool { 24 | (square & PAINT_MASK) != 0 25 | } 26 | 27 | #[inline] 28 | pub fn is_measured(square: maze::Square) -> bool { 29 | (square & MEASURED) != 0 30 | } 31 | -------------------------------------------------------------------------------- /maze_tui/builders/src/arena.rs: -------------------------------------------------------------------------------- 1 | use crate::build; 2 | use maze; 3 | 4 | // Pure data driven algorithm with no display. 5 | 6 | pub fn generate_maze(monitor: monitor::MazeMonitor) { 7 | let mut lk = match monitor.lock() { 8 | Ok(l) => l, 9 | Err(_) => print::maze_panic!("uncontested lock failure"), 10 | }; 11 | build::fill_maze_with_walls(&mut lk.maze); 12 | for r in 1..lk.maze.rows() - 1 { 13 | for c in 1..lk.maze.cols() - 1 { 14 | build::build_path(&mut lk.maze, maze::Point { row: r, col: c }); 15 | } 16 | } 17 | } 18 | 19 | // History tracked for later playback and animation. 20 | 21 | pub fn generate_history(monitor: monitor::MazeMonitor) { 22 | let mut lk = match monitor.lock() { 23 | Ok(l) => l, 24 | Err(_) => print::maze_panic!("builder could not take lock"), 25 | }; 26 | build::fill_maze_history_with_walls(&mut lk.maze); 27 | for r in 1..lk.maze.rows() - 1 { 28 | for c in 1..lk.maze.cols() - 1 { 29 | build::build_path_history(&mut lk.maze, maze::Point { row: r, col: c }); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Alex G. Lopez 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /maze_tui/res/arena.txt: -------------------------------------------------------------------------------- 1 | │ ▁ ▁ ▁ ▁ ▁ 2 | │ ╱ ╱╲ ╱╲ ╲ ╱╲ ╲ ╱╲ ╲ ▁ ╱ ╱╲ 3 | │ ╱ ╱ ╲ ╱ ╲ ╲ ╱ ╲ ╲ ╱ ╲ ╲ ╱╲▁╲ ╱ ╱ ╲ 4 | │ ╱ ╱ ╱╲ ╲ ╱ ╱╲ ╲ ╲ ╱ ╱╲ ╲ ╲ ╱ ╱╲ ╲ ╲▁╱ ╱ ╱╱ ╱ ╱╲ ╲ 5 | │ ╱ ╱ ╱╲ ╲ ╲ ╱ ╱ ╱╲ ╲▁╲ ╱ ╱ ╱╲ ╲▁╲ ╱ ╱ ╱╲ ╲▁▁▁╱ ╱╱ ╱ ╱╲ ╲ ╲ 6 | │ ╱ ╱ ╱ ╲ ╲ ╲ ╱ ╱ ╱▁╱ ╱ ╱ ╱ ╱▁╱▁ ╲╱▁╱ ╱ ╱ ╱ ╲╱▁▁▁▁╱╱ ╱ ╱ ╲ ╲ ╲ 7 | │ ╱ ╱ ╱▁▁▁╱ ╱╲ ╲ ╱ ╱ ╱▁▁╲╱ ╱ ╱ ╱▁▁▁▁╱╲ ╱ ╱ ╱ ╱ ╱ ╱╱ ╱ ╱▁▁▁╱ ╱╲ ╲ 8 | │ ╱ ╱ ╱▁▁▁▁▁╱ ╱╲ ╲ ╱ ╱ ╱▁▁▁▁▁╱ ╱ ╱╲▁▁▁▁╲╱ ╱ ╱ ╱ ╱ ╱ ╱╱ ╱ ╱▁▁▁▁▁╱ ╱╲ ╲ 9 | │ ╱ ╱▁▁▁▁▁▁▁▁▁╱╲ ╲ ╲ ╱ ╱ ╱╲ ╲ ╲ ╱ ╱ ╱▁▁▁▁▁▁ ╱ ╱ ╱ ╱ ╱ ╱╱ ╱▁▁▁▁▁▁▁▁▁╱╲ ╲ ╲ 10 | │╱ ╱ ╱▁ ▁▁╲ ╲▁╲╱ ╱ ╱ ╲ ╲ ╲╱ ╱ ╱▁▁▁▁▁▁▁╲╱ ╱ ╱ ╱ ╱ ╱╱ ╱ ╱▁ ▁▁╲ ╲▁╲ 11 | │╲▁╲▁▁▁╲ ╱▁▁▁▁╱▁╱╲╱▁╱ ╲▁╲╱╲╱▁▁▁▁▁▁▁▁▁▁╱╲╱▁╱ ╲╱▁╱ ╲▁╲▁▁▁╲ ╱▁▁▁▁╱▁╱ 12 | 13 | (scroll with <↓>/<↑>, toggle info ) 14 | 15 | This is just an open arena of paths. Try different solver algorithms to see some pretty colors. 16 | 17 | │for all squares 18 | │ 19 | │ if a square is not part of the perimeter 20 | │ 21 | │ make the square a path 22 | 23 | -------------------------------------------------------------------------------- /maze_tui/builders/src/disjoint.rs: -------------------------------------------------------------------------------- 1 | pub struct DisjointSet { 2 | parent_set: Vec, 3 | set_rank: Vec, 4 | } 5 | 6 | impl DisjointSet { 7 | pub fn new(num_sets: usize) -> Self { 8 | Self { 9 | parent_set: (0..num_sets).collect(), 10 | set_rank: vec![0; num_sets], 11 | } 12 | } 13 | 14 | pub fn find(&mut self, mut id: usize) -> usize { 15 | let mut compress_path = Vec::new(); 16 | while self.parent_set[id] != id { 17 | compress_path.push(id); 18 | id = self.parent_set[id]; 19 | } 20 | while let Some(child) = compress_path.pop() { 21 | self.parent_set[child] = id; 22 | } 23 | id 24 | } 25 | 26 | pub fn made_union(&mut self, a: usize, b: usize) -> bool { 27 | let x = self.find(a); 28 | let y = self.find(b); 29 | if x == y { 30 | return false; 31 | } 32 | match self.set_rank[x].cmp(&self.set_rank[y]) { 33 | std::cmp::Ordering::Greater => { 34 | self.parent_set[y] = x; 35 | } 36 | std::cmp::Ordering::Less => { 37 | self.parent_set[x] = y; 38 | } 39 | std::cmp::Ordering::Equal => { 40 | self.parent_set[x] = y; 41 | self.set_rank[y] += 1; 42 | } 43 | } 44 | true 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /maze_tui/res/kruskal.txt: -------------------------------------------------------------------------------- 1 | │ ▁▁▁ ▁▁▁ ▁▁▁▁▁▁▁▁▁ ▁▁▁ ▁▁▁ ▁▁▁▁▁▁▁▁ ▁▁▁ ▁▁▁ ▁▁▁▁▁▁▁▁ ▁▁▁ 2 | │╱╲ ╲╱╲ ╲ ╱╲ ▁▁ ╲╲ ╲╱╲ ╲╱╲ ▁▁▁▁╲╱╲ ╲╱╲ ╲ ╱╲ ▁▁ ╲╱╲ ╲ 3 | │╲ ╲ ╲╱ ╱ ╲ ╲ ╲╱╲ ╲╲ ╲ ╲ ╲ ╲ ╲▁▁▁▁▁ ╲ ╲╱ ╱▁▁ ╲ ╲╱╲ ╲ ╲ ╲ 4 | │ ╲ ╲ ▁▁▁▔▔╲ ╲ ▁ ▁▁╲╲ ╲ ╲ ╲ ╲▁▁▁▁▁ ╲ ╲ ▁▁▁ ╲ ╲ ▁▁ ╲ ╲ ╲ 5 | │ ╲ ╲ ╲▁ ╲ ╲ ╲ ╲╲ ╲╲ ╲ ╲▁╲ ╲╱▁▁▁▁╱╲ ╲ ╲ ╲▁ ╲ ╲ ╲ ╲ ╲ ╲ ╲ ╲▁▁▁▁▁ 6 | │ ╲ ╲▁▁╲╲ ╲▁▁╲ ╲▁▁╲╲▁▁╲╲ ╲▁▁▁▁▁▁▁╲▁▁▁▁╲ ╲ ╲ ╲▁▁╲╲ ╲▁▁╲ ╲▁▁╲ ╲▁▁╲ ╲▁▁▁▁▁▁▁╲ 7 | │ ╲╱▁▁╱ ╲╱▁▁╱╲╱▁▁╱╱▁▁╱ ╲╱▁▁▁▁▁▁▁╱╲▁▁▁▁▁▁▁▁▁╲╱▁▁╱ ╲╱▁▁╱╲╱▁▁╱╲╱▁▁╱╲╱▁▁▁▁▁▁▁╱ 8 | │ ╲╱▁▁▁▁▁▁▁▁▁╱ 9 | 10 | (scroll with <↓>/<↑>, toggle info ) 11 | 12 | I lump Kruskal, Prim, and Eller together as quite similar but their visual animations are different. Kruskal reasons about the cells and walls in a maze in terms of Disjoint Sets. The algorithm goes something like this. 13 | 14 | │load all square into a disjoint set as single unique sets 15 | │ 16 | │shuffle all the walls in the maze randomly 17 | │ 18 | │for every wall in the maze 19 | │ 20 | │ if the current wall seperates a square above and below 21 | │ 22 | │ if a disjoint set union find by rank merges these squares 23 | │ 24 | │ break the wall between these squares and join them 25 | │ 26 | │ else if the current wall seperates a square left and right 27 | │ 28 | │ if a disjoint set union find by rank merges these squares 29 | │ 30 | │ break the wall between these squares and join them 31 | 32 | -------------------------------------------------------------------------------- /maze_tui/res/prim.txt: -------------------------------------------------------------------------------- 1 | │ ▁▁▁▁▁▁ ▁▁ 2 | │ ╱$$$$$$$ ╱$▕ 3 | │▕ $$▁▁ $$ ▁▁▁▁▁ ▏ ╱ ▁▁▁▁▁ ▁▁▁ 4 | │▕ $$ ╲ $$╱$$$$$$╱$$ ╱$$$$$$╱$$$$ '$ 5 | │▕ $$$$$$$╱$$▁▁ $▕$$ ▕ $$▁ $$▁ $$ 6 | │▕ $$▁▁▁▁▕ $$ ╲▁▕$$ ▕ $$ ╲ $$ ╲ $$ 7 | │▕ $$ ▕ $$ ▕$$ ▕ $$ ▕ $$ ▕ $$ 8 | │▕ $$ ▕ $$ ▕$$ ▕ $$ ▕ $$ ▕ $$ 9 | │▕▁▁╱ ▕▁▁╱ ▕▁▁╱▕▁▁╱ ▕▁▁╱ ▕▁▁╱ 10 | 11 | (scroll with <↓>/<↑>, toggle info ) 12 | 13 | There are many versions of Prim's algorithm: simplified, true, and truest are three that I am aware of. I went with true Prim's algorithm. Prim's algorithm is part of a family of algorithms that focus on sets or costs for generating the maze. If you enjoy Prim's check out Kruskal's and Eller's algorithms next. Here is the algorithm. 14 | 15 | │load all path cell into a lookup table and give each a random cost 16 | │ 17 | │choose a random starting cell 18 | │ 19 | │enqueue this cell into a min priority queue by cost 20 | │ 21 | │while the min priority queue is not empty 22 | │ 23 | │ mark the current cell as visited 24 | │ 25 | │ set cell MIN with cost = INFINITY 26 | │ 27 | │ for each valid neighbor 28 | │ 29 | │ if this neighbor has a lower cost than MIN 30 | │ 31 | │ MIN = neighbor 32 | │ 33 | │ if MIN is not equal to INFINITY 34 | │ 35 | │ break the wall between current and MIN joining squares 36 | │ 37 | │ push MIN into the min priority queue 38 | │ 39 | │ else 40 | │ 41 | │ pop from the min priority 42 | 43 | -------------------------------------------------------------------------------- /maze_tui/res/grid.txt: -------------------------------------------------------------------------------- 1 | │ ▁▁▁ ▁▁▁ ▁▁▁ ▁▁▁▁▁ 2 | │ ╱ ╱╲ ╱ ╱╲ ╱ ╱╲ ╱ ╱::╲ 3 | │ ╱ ╱:╱▁ ╱ ╱::╲ ╱ ╱:╱ ╱ ╱:╱╲:╲ 4 | │ ╱ ╱:╱ ╱╲ ╱ ╱:╱╲:╲ ╱ ╱:╱ ╱ ╱:╱╲ ╲:╲ 5 | │ ╱ ╱:╱▁╱::╲ ╱ ╱:╱ ╱:╱ ╱ ╱::╲ ▁▁▁ ╱ ╱:╱ ╲▁╲:╲ 6 | │╱▁▁╱:╱ ╲╱╲:╲ ╱▁▁╱:╱ ╱:╱▁▁▁ ╱▁▁╱:╱╲:╲ ╱╲ ╱▁▁╱:╱ ╱ ╱:╱ 7 | │╲ ╲:╲▔▔▔ ╱:╱ ╲ ╲:╲╱:::::╱ ╲▁▁╲╱ ╲:╲╱:╱ ╲ ╲:╲ ╱ ╱:╱ 8 | │ ╲ ╲:╲╱ ╱:╱ ╲ ╲::╱▔▔▔▔ ╲▁▁╲::╱ ╲ ╲:╲╱ ╱:╱ 9 | │ ╲ ╲:╲╱:╱ ╲ ╲:╲ ╱ ╱:╱ ╲ ╲:╲╱:╱ 10 | │ ╲ ╲::╱ ╲ ╲:╲ ╱▁▁╱:╱ ╲ ╲::╱ 11 | │ ╲▁▁╲╱ ╲▁▁╲╱ ╲▁▁╲╱ ╲▁▁╲╱ 12 | 13 | (scroll with <↓>/<↑>, toggle info ) 14 | 15 | This algorithm is my own addition to the repository because I wanted something chaotic for the solving threads to race through. This algorithm is a modified recursive depth first search. It works as follows. 16 | 17 | Keep in mind that this is not a perfect maze. 18 | 19 | 20 | │mark a random starting point and push it onto a stack 21 | │ 22 | │set LIMIT to be the maximum length to travel in one direction 23 | │ 24 | │while the stack is not empty 25 | │ 26 | │ mark the CURRENT square as visited 27 | │ 28 | │ for every neighboring square divided by a wall in random order 29 | │ 30 | │ if the neighbor NEXT is valid and not seen 31 | │ 32 | │ while run is less than LIMIT and CURRENT is valid 33 | │ 34 | │ break wall between CURRENT and NEXT 35 | │ 36 | │ mark NEXT as visited 37 | │ 38 | │ push next onto the stack 39 | │ 40 | │ CURRENT becomes NEXT 41 | │ 42 | │ continue outer loop 43 | │ 44 | │ if no neighbor was found 45 | │ 46 | │ pop from the stack 47 | 48 | -------------------------------------------------------------------------------- /maze_tui/res/recursive_backtracker.txt: -------------------------------------------------------------------------------- 1 | │ ▁▁▁ ▁ 2 | │ ╱ ▁ ╲ ▁▁▁ ▁▁▁▁ ▁▁ ▁▁ ▁▁▁▁ ▁▁▁ (▁)▁ ▁▁ ▁▁▁ 3 | │ ╱ ▁ ▁╱╱ -▁)╱ ▁▁╱╱ ╱╱ ╱╱ ▁▁╱(▁-< ╱ ╱| |╱ ╱╱ -▁) 4 | │╱▁╱▕▁| ╲▁▁╱ ╲▁▁╱ ╲▁,▁╱╱▁╱ ╱▁▁▁╱╱▁╱ |▁▁▁╱ ╲▁▁╱ 5 | │ ▁▁▁ ▁▁ ▁▁ ▁▁ 6 | │ ╱ ▁ ) ▁▁▁ ▁ ▁▁▁▁ ╱ ╱▁▁ ╱ ╱▁ ▁▁▁▁ ▁▁▁ ▁ ▁▁▁▁ ╱ ╱▁▁ ▁▁▁ ▁▁▁▁ 7 | │ ╱ ▁ |╱ ▁ `╱╱ ▁▁╱╱ '▁╱╱ ▁▁╱╱ ▁▁╱╱ ▁ `╱╱ ▁▁╱╱ '▁╱╱ -▁)╱ ▁▁╱ 8 | │╱▁▁▁▁╱ ╲▁,▁╱ ╲▁▁╱╱▁╱╲▁╲ ╲▁▁╱╱▁╱ ╲▁,▁╱ ╲▁▁╱╱▁╱╲▁╲ ╲▁▁╱╱▁╱ 9 | 10 | 11 | (scroll with <↓>/<↑>, toggle info ) 12 | 13 | This is the classic maze building algorithm. It works by randomly carving out valid walls into passages until it can no longer progress down its current branch. Then it takes one step back and repeats the process for as long as possible until it arrives back at the origin. 14 | 15 | There are some special ways to optimize this algorithm, including the ability to only use O(1) auxiliaray space. The bits of the maze itself are sufficient to keep track of our backtracking. In fact, if you watch the animated version of this algorithm, you will see the illustrations of those backtracking bits as color coded arrows; the arrows signify which direction we need to go to return the way we came. 16 | 17 | Here is generalized pseudocode for the recursive backtracking algorithm. 18 | 19 | │mark a random starting point as the origin of all paths 20 | │ 21 | │while there are valid maze paths to carve 22 | │ 23 | │ mark the current square as visited 24 | │ 25 | │ for every neighboring square divided by a wall in random order 26 | │ 27 | │ if the neighbor is valid and not seen 28 | │ 29 | │ break the wall between current and neighbor 30 | │ 31 | │ mark neighbor with the direction you came from 32 | │ 33 | │ current becomes next 34 | │ 35 | │ continue the outer while loop 36 | │ 37 | │ if we are not at the origin 38 | │ 39 | │ backtrack to the previous square 40 | 41 | -------------------------------------------------------------------------------- /maze_tui/res/wilson_carver.txt: -------------------------------------------------------------------------------- 1 | │ ▁▁ ▁▁ .▁▁. ▁▁ 2 | │╱ ╲ ╱ ╲▕▁▁▏▕ ▕ ▁▁▁▁▁▁ ▁▁▁▁▁▁ ▁▁▁▁ 3 | │╲ ╲╱╲╱ ╱▕ ▏▕ ▕ ╱ ▁▁▁╱╱ ▁ ╲ ╱ ▁ ╲ 4 | │ ╲ ╱ ▕ ▏▕ ▕▁▁▁ ╲▁▁▁ ╲╲ ╱▁╱ ╱▕ ▏▏ ╲ 5 | │ ╲▁▁╱╲ ╱ ▕▁▁▏▕▁▁▁▁╱╱▁▁▁▁ ╱ ╲▁▁▁▁╱ ▕▁▁▁▏▏ ╱ 6 | │ ╲╱ ╲╱ ╲╱ 7 | │▁▁▁▁▁▁▁▁▁ 8 | │╲▁ ▁▁▁ ╲ ▁▁▁▁▁ ▁▁▁▁▁▁▁ ▁▁▁ ▁▁ ▁▁▁▁ ▁▁▁▁▁▁▁ 9 | │╱ ╲ ╲╱ ╲▁▁ ╲ ╲▁ ▁▁ ╲╲ ╲╱ ╱▁╱ ▁▁ ╲ ╲▁ ▁▁ ╲ 10 | │╲ ╲▁▁▁▁ ╱ ╱╲ ╲ ▕ ▏ ╲╱ ╲ ╱ ╲ ▁▁▁╱ ▕ ▏ ╲╱ 11 | │ ╲▁▁▁▁▁▁ ╱╱▁▁▁▁ ╱▕▁▁▏ ╲▁╱ ╲▁▁▁ ╲ ▕▁▁▏ 12 | │ ╲╱ ╲╱ ╲╱ 13 | 14 | (scroll with <↓>/<↑>, toggle info ) 15 | 16 | The Wilson's algorithms are my favorite. They are a much more visually interesting and fun to implement approach at producing perfect mazes. 17 | 18 | Watching this is one live is very fun and frustrating. Sometimes, you wish the flailing walk path would just find the square sitting out there in the grid but it won't. The starting maze point is a needle in a haystack and we eventually find it but it can just take some time. These are very well balanced mazes with interesting twists and turns and unexpected long paths that can sometimes weave through the maze. 19 | 20 | │pick a random square and make it a path that is part of a maze 21 | │ 22 | │pick a random WALK point for a random walk 23 | │ 24 | │creat a cell called PREVIOUS that starts as nil. 25 | │ 26 | │while we have selected a starting square for a random walk 27 | │ 28 | │ for each neighbor NEXT in random order 29 | │ 30 | │ if the NEXT != PREVIOUS and is in bounds 31 | │ 32 | │ select NEXT for consideration 33 | │ 34 | │ if NEXT is part of our own walk 35 | │ 36 | │ erase the loop we have formed using backtracking 37 | │ 38 | │ else if NEXT is part of the maze 39 | │ 40 | │ join our walk to the maze using backtracking to carve a path. 41 | │ 42 | │ else 43 | │ 44 | │ mark NEXT with the direction it needs for backtracking 45 | │ 46 | │ PREVIOUS = WALK 47 | │ 48 | │ WALK = NEXT 49 | │ 50 | │ continue outer while loop 51 | 52 | -------------------------------------------------------------------------------- /maze_tui/res/hunt_kill.txt: -------------------------------------------------------------------------------- 1 | │▁▁▁▁▁▁ ▁▁ ▁▁▁▁▁ ▁▁▁▁▁▁▁▁▁▁ ▁▁▁▁ ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ 2 | │▁▁▁ ╱ ╱ ╱▁▁▁ ▁▁▁▁▁▁▁▁▁▁ ╱▁ ▁▁▁▁▁▁ ▁▁▁▁▁▁▁▁▁▁▁▁▁ ╱ ▁▁ ╱╱▁╱ ▁▁(▁)▁▁ ╱▁▁ ╱ 3 | │▁▁ ╱▁╱ ╱▁ ╱ ╱ ╱▁ ▁▁ \ ▁▁╱ ▁ ▁▁ `╱▁ ▁▁ \ ▁▁ ╱ ▁▁ < ▁▁▁▁ ╱▁▁ ╱▁▁ ╱ 4 | │▁ ╱▁▁ ╱ ╱ ╱▁╱ ╱▁ ╱ ╱ ╱ ╱▁ ╱ ╱▁╱ ╱▁ ╱ ╱ ╱ ╱▁╱ ╱ ▁▁ ╱╲ ╲ ▁▁ ╱ ▁ ╱ ▁ ╱ 5 | │╱▁╱ ╱▁╱ ╲▁▁▁▁╱ ╱▁╱ ╱▁╱╲▁▁╱ ╲__,_╱ ╱_╱ ╱_╱╲__,_╱ ╱▁╱ ╲▁╲ ╱▁╱ ╱▁╱ ╱▁╱ 6 | 7 | The hunt and kill algorithm does not seem that interesting when first encountering it. It seems so much like a standard recursive backtracking algorithm that it is tempting to leave it out of a maze algorithm collection. However, there are some interesting details that make this algorithm worth inclusion in the set. First, it is efficient. We do not need any auxiliary storage to complete the building process. In fact, even though this algorithm behaves like a recursive backtracker in appearance, we actually don't need to remember any steps for backtracking. This is a straigtforward path carver that is also very fast. 8 | 9 | There are two phases: hunt and kill. While "killing," we are carving paths into the maze. While "hunting" we are searching for an un-built cell adjacent to a built cell so that we can form a path between the two. If we remember the last row with an un-built cell we can speed things up, instead of scanning the entire maze to search for a new start. 10 | 11 | │select a random starting point 12 | │ 13 | │while we can find an unvisited cell adjacent to a path cell 14 | │ 15 | │ for all neighbors in random order 16 | │ 17 | │ if a neighbor is valid and unvisited 18 | │ 19 | │ break the wall and join the squares 20 | │ 21 | │ continue outer while loop 22 | │ 23 | │ for each row starting at the last we found with unbuilt cells 24 | │ 25 | │ if an unbuilt cell is adjacent to a built cell 26 | │ 27 | │ join the squares 28 | │ 29 | │ start a new random walk from the previously unbuilt 30 | │ 31 | │ continue the outer while loop 32 | 33 | 34 | -------------------------------------------------------------------------------- /maze_tui/res/eller.txt: -------------------------------------------------------------------------------- 1 | │▄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄▄ 2 | │╲┄╱╲╲╲╲╲╲╲╲╲╲╲╲╲╲┄┄╱╲╲╲╲╲┄┄┄┄┄╱╲╲╲╲╲┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╲ 3 | │ ╲╲╱╲╲╱╱╱╱╱╱╱╱╱╱╱┄┄╲╱╱╱╱╲╲┄┄┄┄╲╱╱╱╱╲╲┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╲ 4 | │ ╲╲╱╲╲┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╲╱╲╲┄┄┄┄┄┄┄╲╱╲╲┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╲ 5 | │ ╲╲╱╲╲╲╲╲╲╲╲╲╲┄┄┄┄┄┄┄┄╲╱╲╲┄┄┄┄┄┄┄╲╱╲╲┄┄┄┄┄┄┄┄╱╲╲╲╲╲╲╲┄┄┄╱╲╲╱╲╲╲╲╲╲┄╲ 6 | │ ╲╲╱╲╲╲╱╱╱╱╱╱┄┄┄┄┄┄┄┄┄╲╱╲╲┄┄┄┄┄┄┄╲╱╲╲┄┄┄┄┄┄╱╲╲╲╱╱╱╱╱╲╲┄╲╱╲╲╱╱╱╱╱╱╲╲╲ 7 | │ ╲╲╱╲╲┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╲╱╲╲┄┄┄┄┄┄┄╲╱╲╲┄┄┄┄┄╱╲╲╲╲╲╲╲╲╲╲┄┄╲╱╲╲┄┄┄┄╲╱╱┄╲ 8 | │ ╲╲╱╲╲┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄╲╱╲╲┄┄┄┄┄┄┄╲╱╲╲┄┄┄┄╲╱╱╲╲╱╱╱╱╱╱┄┄┄╲╱╲╲┄┄┄┄┄┄┄┄╲ 9 | │ ╲╲╱╲╲╲╲╲╲╲╲╲╲╲╲╲╲┄┄╱╲╲╲╲╲╲╲╲┄┄╱╲╲╲╲╲╲╲╲┄┄╲╱╱╲╲╲╲╲╲╲╲╲┄╲╱╲╲┄┄┄┄┄┄┄┄╲ 10 | │ ╲╲╱╱╱╱╱╱╱╱╱╱╱╱╱╱┄┄╲╱╱╱╱╱╱╱╱┄┄╲╱╱╱╱╱╱╱╱┄┄┄┄╲╱╱╱╱╱╱╱╱╱┄┄╲╱╱┄┄┄┄┄┄┄┄┄╲ 11 | │ ▀┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄▀ 12 | 13 | (scroll with <↓>/<↑>, toggle info ) 14 | 15 | People interested in mazes love Eller's algorithm. This is because it can be implemented many ways, some of which allow for arbitrarily large generation of mazes with a memory requirement only equivalent to the width of the maze. This algorithm can generate row by row which makes it quite fast and efficient. For all of its benefits it is challenging to find good information on the implementation. So, I went with a somewhat original approach to the problem for now. I was able to uphold the main benefit of Eller, that being I only require a memory constant tied to the width of the maze. I think there are smarter ways to implement my approach and when I get a chance, I think I can cut down on the number of passes over a row that I require. 16 | 17 | │prepare a sliding window of the current and next row 18 | │ 19 | │give every cell in the first row of the window a unique set id 20 | │ 21 | │for every row in the maze except the last 22 | │ 23 | │ give every cell in the next sliding window row a unique set id 24 | │ 25 | │ for every column in the current row 26 | │ 27 | │ if chance merges a square with its right neighbor 28 | │ 29 | │ merge squares into the same set 30 | │ 31 | │ for every set merged with another or left isolated 32 | │ 33 | │ choose a random number of elements >= 1 in that set to drop to row below 34 | │ 35 | │ join that element with the set below, joining squares. 36 | │ 37 | │ adjust the sliding window, set current = next. 38 | │ 39 | │for every column in the final row 40 | │ 41 | │ if a square is not part of its neighbors set 42 | │ 43 | │ merge them into the same set, joining squares. 44 | 45 | -------------------------------------------------------------------------------- /maze_tui/res/wilson_adder.txt: -------------------------------------------------------------------------------- 1 | │ ▁▁▁ ▁▁ ▁▁▁ ▁▁ ▁▁▁ ▁▁▁▁▁▁▁▁ ▁▁▁▁▁▁ ▁▁▁▁▁ ▁▁▁ 2 | │▕" |╱ ╲| "▏ ▕" ╲ ▕" ▏ ╱" ▏ ╱ " ╲ ▕╲" ╲▕" ╲ 3 | │▕' ╱ ╲: ▏ ▕| ▏ ▕| ▏ ▏: ╲▁▁▁╱ ╱╱ ▁▁▁▁ ╲ ▕.╲╲ ╲ ▏ 4 | │▕: ╱' ▏ ▕: ▏ ▕: ▏ ╲▁▁▁ ╲ ╱ ╱ ╲ :▏▕: ╲. ╲╲ ▏ 5 | │ ╲╱╱ ╱╲' ▏ ▕. ▏ ▕╲ ▏▁▁▁ ▁▁╱ ╲╲ ▏: ╲▁▁▁▁╱╱╱ ▕. ╲ ╲. ▏ 6 | │ ╱ ╱ ╲╲ ▏ ╱╲ ▏╲▕ ╲▁▏: ╲ ╱" ╲ :▏╲ ╱ ▕ ╲ ╲ ▏ 7 | │▕▁▁▁╱ ╲▁▁▁▏╱▁▁╲▁▏▁╲╲▁▁▁▁▁▁▁▏ ▏▁▁▁▁▁▁╱ ╲"▁▁▁▁▁╱ ╲▁▁▁▏╲▁▁▁▁╲▏ 8 | │ ▁▁ ▁▁▁▁▁▁▁▁ ▁▁▁▁▁▁▁▁ ▁▁▁▁▁▁▁ ▁▁▁▁▁▁▁ 9 | │ ╱""╲ ▕" "╲ ▕" "╲ ╱" "▏ ╱" ╲ 10 | │ ╱ ╲ ▕. ▁▁▁▁ :▏▕. ▁▁▁▁ :▏ ▏:▁▁▁▁▁▁▏▕: ▏ 11 | │ ╱' ╱╲ ╲ ▕: ╲ ) |▏▕: ╲ ) |▏ ╲╱ ▁▁▏ ▕▁▁▁▁▁╱ ▏ 12 | │ ╱╱ ▁▁' ╲ ▕| (▁▁▁╲ |▏▕| (▁▁▁╲ |▏ ╱╱ ╱▁▁▁▁ ╱╱ ╱ 13 | │ ╱ ╱ ╲╲ ╲ ▕: :▏▕: :▏ ▏: "▏▕: ▁▁ ╲ 14 | │╱▁▁▁╱ ╲▁▁▁╲▕▁▁▁▁▁▁▁▁╱ ▕▁▁▁▁▁▁▁▁╱ ╲▁▁▁▁▁▁▁▏▕▁▁▕ ╲▁▁▁╲ 15 | 16 | (scroll with <↓>/<↑>, toggle info ) 17 | 18 | While the path carver version of Wilson's algorithm is like trying to find a needle in a haystack, we can flip this concept and be the needle surrounded by a haystack. Instead of starting the random walk by trying to find one path point in the maze and then carve a path out when we find it, we can become the walls of the maze. We then surround ourselves with the perimeter walls of the maze and it becomes trivial to find a maze wall. The algorithm is identical. While it is possible it could take a while for a random walk to find a wall at first, in practice this algorithm is extremely fast. I have not done time tests yet, but I am sure that it is much faster than the other version of Wilson's algorithm and can compete with any algorithm. 19 | 20 | │pick a random wall square somewhere within perimeter boundaries. 21 | │ 22 | │pick a random WALK point for a random walk 23 | │ 24 | │create a cell called PREVIOUS that starts as nil. 25 | │ 26 | │while we have selected a starting wall piece for a random walk 27 | │ 28 | │ for each neighbor NEXT in random order 29 | │ 30 | │ if the NEXT != PREVIOUS and is in bounds 31 | │ 32 | │ select NEXT for consideration 33 | │ 34 | │ if NEXT is part of our own walk 35 | │ 36 | │ erase the loop we have formed using backtracking 37 | │ 38 | │ else if NEXT is part of the maze 39 | │ 40 | │ join our walk to the maze walls using backtracking to build wall piece. 41 | │ 42 | │ else 43 | │ 44 | │ mark NEXT with the direction it needs for backtracking 45 | │ 46 | │ PREVIOUS = WALK 47 | │ 48 | │ WALK = NEXT 49 | │ 50 | │ continue outer while loop 51 | 52 | -------------------------------------------------------------------------------- /maze_tui/res/recursive_subdivision.txt: -------------------------------------------------------------------------------- 1 | │┏━━━━━━┓ ┏━━━━━━━┳━━━━━━━┳━━┓ ┏━━┳━━━━━━┓ ┏━━━━━━━┳━━━┳━━┓ ┏━━┳━━━━━━━┓ 2 | │┃ ┏━┓┃ ┃ ┃ ┃ ┃ ┃ ┃ ┏━┓┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ 3 | │┃ ┃ ┃┃ ┃ ┏━━━┫ ┃ ┗━┛ ┃ ┃ ┃┃ ┃ ┏━━━━━┫ ┃ ┗━┛ ┃ ┏━━━┛ 4 | │┃ ┗━┛┗━┫ ┗━━━┫ ┃ ┃ ┗━┛┗━┫ ┗━━━━━┫ ┃ ┃ ┗━━━┓ 5 | │┃ ┏━━┓ ┃ ┏━━━┫ ┏━┫ ┃ ┏━━┓ ┣━━━━━┓ ┃ ┣┓ ┏┫ ┏━━━┛ 6 | │┃ ┃ ┃ ┃ ┗━━━┫ ┗━┫ ┃ ┃ ┃ ┣━━━━━┛ ┃ ┃┗┓ ┏┛┃ ┗━━━┓ 7 | │┗━━━┛ ┗━┻━━━━━━━┻━━━━━━━┻━━━━━━━┻━━━┛ ┗━┻━━━━━━━┻━━━┛ ┗━━━┛ ┗━━━━━━━┛ 8 | │┏━━━━━━━┳━━┓ ┏━━┳━━━━━━━┳━━━━━━┓┏━━━┳━━┓ ┏━━┳━━━┳━━━━━━━┳━━━┳━━━━━━━┳━━┓ ┏━┓ 9 | │┃ ┃ ┃ ┃ ┃ ┏━┓ ┃ ┗┫ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┗┓ ┃ ┃ 10 | │┃ ┏━━━━━┫ ┗━┛ ┃ ┗━┛ ┃┏━┓ ┃ ┃ ┗━┛ ┃ ┃ ┏━━━━━┫ ┃ ┏━┓ ┃ ┗━┛ ┃ 11 | │┃ ┗━━━━━┫ ┃ ┃┃ ┃ ┃ ┃ ┃ ┃ ┗━━━━━┫ ┃ ┃▀┃ ┃ ┃ 12 | │┗━━━━━┓ ┃ ┃ ┏━┓ ┃┗━┛ ┃ ┣┓ ┏┫ ┣━━━━━┓ ┃ ┃ ┗━┛ ┃ ┏━┓ ┃ 13 | │┏━━━━━┛ ┃ ┃ ┗━┛ ┃ ┃ ┃┗┓ ┏┛┃ ┣━━━━━┛ ┃ ┃ ┃ ┃ ┃ ┃ 14 | │┗━━━━━━━┻━━━━━━━┻━━━━━━━┻━━━━━━━┻━━━┛ ┗━━━┛ ┗━━━┻━━━━━━━┻━━━┻━━━━━━━┻━┛ ┗━━━┛ 15 | 16 | (scroll with <↓>/<↑>, toggle info ) 17 | 18 | This algorithm technically produces fractals due to the recursive nature of the technique. It is a recursive algorithm, but my implementation uses an explicit stack rather than recursive function calls. Note that I describe pushing chambers or mazes onto a stack. However, in reality the implementation only needs to push the coordinates of the corner of a chamber, its height, and its width onto a stack, not all the cells. This starts with a maze of all paths and we draw walls. 19 | 20 | This is a great algorithm because the mazes it produces are completely different from anything else that you see. I appreciate the interesting flow patterns that breadth first searches produce. 21 | 22 | │push the entire maze onto a stack of chambers 23 | │ 24 | │while the stack of chambers is not empty 25 | │ 26 | │ if chamber height > chamber width and width meets min requirement 27 | │ 28 | │ choose a random height and divide the chamber by that height 29 | │ 30 | │ choose a random point in the divide for a path gap 31 | │ 32 | │ update the current chamber's height 33 | │ 34 | │ push the chamber after the divide onto the stack 35 | │ 36 | │ else if chamber width >= chamber height and height meets min requirement 37 | │ 38 | │ choose a random width and divide the chamber by that width 39 | │ 40 | │ choose a random point in the divide for a path gap 41 | │ 42 | │ update the current chamber's width 43 | │ 44 | │ push the chamber after the divide onto the stack 45 | │ 46 | │ else 47 | │ 48 | │ pop chamber from the stack. 49 | 50 | -------------------------------------------------------------------------------- /maze_tui/print/src/lib.rs: -------------------------------------------------------------------------------- 1 | use crossterm::{cursor, terminal, ExecutableCommand, QueueableCommand}; 2 | use std::io::{stdout, Write}; 3 | 4 | // The mazes look WAY better if the cursor square disapears while it builds. 5 | #[derive(Clone)] 6 | pub struct InvisibleCursor; 7 | 8 | impl InvisibleCursor { 9 | pub fn new() -> Self { 10 | Self 11 | } 12 | 13 | pub fn hide(&self) { 14 | stdout() 15 | .execute(cursor::Hide) 16 | .expect("Failed to hide cursor."); 17 | } 18 | } 19 | 20 | impl Default for InvisibleCursor { 21 | fn default() -> Self { 22 | Self::new() 23 | } 24 | } 25 | 26 | impl Drop for InvisibleCursor { 27 | fn drop(&mut self) { 28 | stdout() 29 | .execute(cursor::Show) 30 | .expect("Failed to unhide your cursor. Sorry! Restart your terminal."); 31 | } 32 | } 33 | 34 | // DO NOT use this in this unless you are exiting program early and Rust won't call drop. 35 | pub fn unhide_cursor_on_process_exit() { 36 | stdout() 37 | .execute(cursor::Show) 38 | .expect("Failed to unhide your cursor. Sorry! Restart your terminal."); 39 | } 40 | 41 | // Execute the command so clearing the screen forcefully flushes for the caller. 42 | pub fn clear_screen() { 43 | stdout() 44 | .execute(terminal::Clear(terminal::ClearType::All)) 45 | .expect("Could not clear screen, terminal may be incompatible."); 46 | } 47 | 48 | // Queue the command so setting the cursor position does NOT forcefully flush for caller. 49 | pub fn set_cursor_position(p: maze::Point, offset: maze::Offset) { 50 | stdout() 51 | .queue(cursor::MoveTo( 52 | (p.col + offset.add_cols) as u16, 53 | (p.row + offset.add_rows) as u16, 54 | )) 55 | .expect("Could not move cursor, terminal may be incompatible."); 56 | } 57 | 58 | pub fn flush() { 59 | stdout() 60 | .flush() 61 | .expect("Could not clear screen,terminal may be incompatible."); 62 | } 63 | 64 | // I think it would be really mean to panic and then the user loses their cursor, as 65 | // I have experienced. Because I hide the cursor to start, all of my code should panic 66 | // when appropriate but responsibly restore the cursor. This is made to use as you would 67 | // normal panic!(). This is used throughout all modules in the repo as default panic. 68 | #[macro_export] 69 | macro_rules! maze_panic { 70 | ($($arg:tt)*) => { 71 | { 72 | use std::fmt::Write; 73 | use std::io::stdout; 74 | use crossterm::{cursor::Show, ExecutableCommand}; 75 | stdout().execute(Show).expect( 76 | "Failed to unhide the cursor. Sorry! Restart your terminal." 77 | ); 78 | let mut buf = String::new(); 79 | write!(&mut buf, $($arg)*).expect("Couldn't write to buffer"); 80 | eprintln!("{}", buf); 81 | panic!(); 82 | } 83 | }; 84 | } 85 | -------------------------------------------------------------------------------- /maze_tui/builders/src/grid.rs: -------------------------------------------------------------------------------- 1 | use crate::build; 2 | use maze; 3 | 4 | use rand::{seq::SliceRandom, thread_rng, Rng}; 5 | 6 | const RUN_LIMIT: i32 = 4; 7 | 8 | struct RunStart { 9 | cur: maze::Point, 10 | dir: maze::Point, 11 | } 12 | 13 | /// 14 | /// Data only maze generator 15 | /// 16 | pub fn generate_maze(monitor: monitor::MazeMonitor) { 17 | let mut lk = match monitor.lock() { 18 | Ok(l) => l, 19 | Err(_) => print::maze_panic!("uncontested lock failure"), 20 | }; 21 | build::fill_maze_with_walls(&mut lk.maze); 22 | let mut rng = thread_rng(); 23 | let mut dfs: Vec = Vec::from([maze::Point { 24 | row: 2 * (rng.gen_range(1..lk.maze.rows() - 1) / 2) + 1, 25 | col: 2 * (rng.gen_range(1..lk.maze.cols() - 1) / 2) + 1, 26 | }]); 27 | let mut random_direction_indices: Vec = (0..build::NUM_DIRECTIONS).collect(); 28 | while let Some(run) = dfs.last().cloned() { 29 | random_direction_indices.shuffle(&mut rng); 30 | let mut branches = false; 31 | for &i in random_direction_indices.iter() { 32 | let p = build::GENERATE_DIRECTIONS[i]; 33 | let next = maze::Point { 34 | row: run.row + p.row, 35 | col: run.col + p.col, 36 | }; 37 | if build::can_build_new_square(&lk.maze, next) { 38 | complete_run(&mut lk.maze, &mut dfs, RunStart { cur: run, dir: p }); 39 | branches = true; 40 | break; 41 | } 42 | } 43 | if !branches { 44 | dfs.pop(); 45 | } 46 | } 47 | } 48 | 49 | fn complete_run(maze: &mut maze::Maze, dfs: &mut Vec, mut run: RunStart) { 50 | let mut next = maze::Point { 51 | row: run.cur.row + run.dir.row, 52 | col: run.cur.col + run.dir.col, 53 | }; 54 | let mut cur_run = 0; 55 | while build::is_square_within_perimeter_walls(maze, next) && cur_run < RUN_LIMIT { 56 | build::join_squares(maze, run.cur, next); 57 | dfs.push(next); 58 | run.cur = next; 59 | next.row += run.dir.row; 60 | next.col += run.dir.col; 61 | cur_run += 1; 62 | } 63 | } 64 | 65 | /// 66 | /// History based generator for animation and playback. 67 | /// 68 | pub fn generate_history(monitor: monitor::MazeMonitor) { 69 | let mut lk = match monitor.lock() { 70 | Ok(l) => l, 71 | Err(_) => print::maze_panic!("uncontested lock failure"), 72 | }; 73 | build::fill_maze_history_with_walls(&mut lk.maze); 74 | let mut rng = thread_rng(); 75 | let mut dfs: Vec = Vec::from([maze::Point { 76 | row: 2 * (rng.gen_range(1..lk.maze.rows() - 1) / 2) + 1, 77 | col: 2 * (rng.gen_range(1..lk.maze.cols() - 1) / 2) + 1, 78 | }]); 79 | let mut random_direction_indices: Vec = (0..build::NUM_DIRECTIONS).collect(); 80 | while let Some(run) = dfs.last().cloned() { 81 | random_direction_indices.shuffle(&mut rng); 82 | let mut branches = false; 83 | for &i in random_direction_indices.iter() { 84 | let p = build::GENERATE_DIRECTIONS[i]; 85 | let next = maze::Point { 86 | row: run.row + p.row, 87 | col: run.col + p.col, 88 | }; 89 | if build::can_build_new_square(&lk.maze, next) { 90 | complete_run_history(&mut lk.maze, &mut dfs, RunStart { cur: run, dir: p }); 91 | branches = true; 92 | break; 93 | } 94 | } 95 | if !branches { 96 | dfs.pop(); 97 | } 98 | } 99 | } 100 | 101 | fn complete_run_history(maze: &mut maze::Maze, dfs: &mut Vec, mut run: RunStart) { 102 | let mut next = maze::Point { 103 | row: run.cur.row + run.dir.row, 104 | col: run.cur.col + run.dir.col, 105 | }; 106 | let mut cur_run = 0; 107 | while build::is_square_within_perimeter_walls(maze, next) && cur_run < RUN_LIMIT { 108 | build::join_squares_history(maze, run.cur, next); 109 | dfs.push(next); 110 | run.cur = next; 111 | next.row += run.dir.row; 112 | next.col += run.dir.col; 113 | cur_run += 1; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /maze_tui/builders/src/kruskal.rs: -------------------------------------------------------------------------------- 1 | use crate::build; 2 | use crate::disjoint; 3 | use maze; 4 | use print; 5 | 6 | use rand::prelude::*; 7 | use std::collections::HashMap; 8 | 9 | /// 10 | /// Data only maze generator 11 | /// 12 | pub fn generate_maze(monitor: monitor::MazeMonitor) { 13 | let mut lk = match monitor.lock() { 14 | Ok(l) => l, 15 | Err(_) => print::maze_panic!("uncontested lock failure"), 16 | }; 17 | build::fill_maze_with_walls(&mut lk.maze); 18 | let walls = load_shuffled_walls(&lk.maze); 19 | let ids = tag_cells(&lk.maze); 20 | let mut sets = disjoint::DisjointSet::new(ids.len()); 21 | 22 | for w in &walls { 23 | if w.row % 2 == 0 { 24 | let above = maze::Point { 25 | row: w.row - 1, 26 | col: w.col, 27 | }; 28 | let below = maze::Point { 29 | row: w.row + 1, 30 | col: w.col, 31 | }; 32 | if let (Some(a_id), Some(b_id)) = (ids.get(&above), ids.get(&below)) { 33 | if sets.made_union(*a_id, *b_id) { 34 | build::join_squares(&mut lk.maze, above, below); 35 | } 36 | } else { 37 | print::maze_panic!("Kruskal couldn't find a cell id. Build broke."); 38 | } 39 | } else { 40 | let left = maze::Point { 41 | row: w.row, 42 | col: w.col - 1, 43 | }; 44 | let right = maze::Point { 45 | row: w.row, 46 | col: w.col + 1, 47 | }; 48 | if let (Some(l_id), Some(r_id)) = (ids.get(&left), ids.get(&right)) { 49 | if sets.made_union(*l_id, *r_id) { 50 | build::join_squares(&mut lk.maze, right, left); 51 | } 52 | } else { 53 | print::maze_panic!("Kruskal couldn't find a cell id. Build broke."); 54 | } 55 | } 56 | } 57 | } 58 | 59 | /// 60 | /// History based generator for animation and playback. 61 | /// 62 | pub fn generate_history(monitor: monitor::MazeMonitor) { 63 | let mut lk = match monitor.lock() { 64 | Ok(l) => l, 65 | Err(_) => print::maze_panic!("uncontested lock failure"), 66 | }; 67 | build::fill_maze_history_with_walls(&mut lk.maze); 68 | let walls = load_shuffled_walls(&lk.maze); 69 | let ids = tag_cells(&lk.maze); 70 | let mut sets = disjoint::DisjointSet::new(ids.len()); 71 | 72 | for w in &walls { 73 | if w.row % 2 == 0 { 74 | let above = maze::Point { 75 | row: w.row - 1, 76 | col: w.col, 77 | }; 78 | let below = maze::Point { 79 | row: w.row + 1, 80 | col: w.col, 81 | }; 82 | if let (Some(a_id), Some(b_id)) = (ids.get(&above), ids.get(&below)) { 83 | if sets.made_union(*a_id, *b_id) { 84 | build::join_squares_history(&mut lk.maze, above, below); 85 | } 86 | } else { 87 | print::maze_panic!("Kruskal couldn't find a cell id. Build broke."); 88 | } 89 | } else { 90 | let left = maze::Point { 91 | row: w.row, 92 | col: w.col - 1, 93 | }; 94 | let right = maze::Point { 95 | row: w.row, 96 | col: w.col + 1, 97 | }; 98 | if let (Some(l_id), Some(r_id)) = (ids.get(&left), ids.get(&right)) { 99 | if sets.made_union(*l_id, *r_id) { 100 | build::join_squares_history(&mut lk.maze, right, left); 101 | } 102 | } else { 103 | print::maze_panic!("Kruskal couldn't find a cell id. Build broke."); 104 | } 105 | } 106 | } 107 | } 108 | 109 | /// 110 | /// Data only helpers available to all. 111 | /// 112 | fn load_shuffled_walls(maze: &maze::Maze) -> Vec { 113 | let mut walls = Vec::new(); 114 | for r in (1..maze.rows() - 1).step_by(2) { 115 | for c in (2..maze.cols() - 1).step_by(2) { 116 | walls.push(maze::Point { row: r, col: c }); 117 | } 118 | } 119 | for r in (2..maze.rows() - 1).step_by(2) { 120 | for c in (1..maze.cols() - 1).step_by(2) { 121 | walls.push(maze::Point { row: r, col: c }); 122 | } 123 | } 124 | walls.shuffle(&mut thread_rng()); 125 | walls 126 | } 127 | 128 | fn tag_cells(maze: &maze::Maze) -> HashMap { 129 | let mut set_ids = HashMap::new(); 130 | let mut id = 0; 131 | for r in (1..maze.rows() - 1).step_by(2) { 132 | for c in (1..maze.cols() - 1).step_by(2) { 133 | set_ids.insert(maze::Point { row: r, col: c }, id); 134 | id += 1; 135 | } 136 | } 137 | set_ids 138 | } 139 | -------------------------------------------------------------------------------- /maze_tui/builders/src/recursive_backtracker.rs: -------------------------------------------------------------------------------- 1 | use crate::build; 2 | use maze; 3 | use rand::{seq::SliceRandom, thread_rng, Rng}; 4 | 5 | /// 6 | /// Data only maze generator 7 | /// 8 | pub fn generate_maze(monitor: monitor::MazeMonitor) { 9 | let mut lk = match monitor.lock() { 10 | Ok(l) => l, 11 | Err(_) => print::maze_panic!("uncontested lock failure"), 12 | }; 13 | build::fill_maze_with_walls(&mut lk.maze); 14 | let mut gen = thread_rng(); 15 | let start: maze::Point = maze::Point { 16 | row: 2 * (gen.gen_range(1..lk.maze.rows() - 2) / 2) + 1, 17 | col: 2 * (gen.gen_range(1..lk.maze.cols() - 2) / 2) + 1, 18 | }; 19 | let mut random_direction_indices: [usize; build::NUM_DIRECTIONS] = [0, 1, 2, 3]; 20 | let mut cur: maze::Point = start; 21 | 'descending: loop { 22 | random_direction_indices.shuffle(&mut gen); 23 | for &i in random_direction_indices.iter() { 24 | let direction = &build::GENERATE_DIRECTIONS[i]; 25 | let branch = maze::Point { 26 | row: cur.row + direction.row, 27 | col: cur.col + direction.col, 28 | }; 29 | if build::can_build_new_square(&lk.maze, branch) { 30 | build::carve_path_markings(&mut lk.maze, cur, branch); 31 | cur = branch; 32 | continue 'descending; 33 | } 34 | } 35 | let dir: build::BacktrackMarker = lk.maze.get(cur.row, cur.col) & build::MARKERS_MASK; 36 | // The solvers will need these bits later so we need to clear bits. 37 | let half: &maze::Point = &build::BACKTRACKING_HALF_POINTS[dir as usize]; 38 | let backtracking: &maze::Point = &build::BACKTRACKING_POINTS[dir as usize]; 39 | let half_step = maze::Point { 40 | row: cur.row + half.row, 41 | col: cur.col + half.col, 42 | }; 43 | *lk.maze.get_mut(cur.row, cur.col) &= !build::MARKERS_MASK; 44 | *lk.maze.get_mut(half_step.row, half_step.col) &= !build::MARKERS_MASK; 45 | cur.row += backtracking.row; 46 | cur.col += backtracking.col; 47 | 48 | if cur == start { 49 | return; 50 | } 51 | } 52 | } 53 | 54 | /// 55 | /// History based generator for animation and playback. 56 | /// 57 | pub fn generate_history(monitor: monitor::MazeMonitor) { 58 | let mut lk = match monitor.lock() { 59 | Ok(l) => l, 60 | Err(_) => print::maze_panic!("builder could not take lock"), 61 | }; 62 | build::fill_maze_history_with_walls(&mut lk.maze); 63 | let mut gen = thread_rng(); 64 | let start: maze::Point = maze::Point { 65 | row: 2 * (gen.gen_range(1..lk.maze.rows() - 2) / 2) + 1, 66 | col: 2 * (gen.gen_range(1..lk.maze.cols() - 2) / 2) + 1, 67 | }; 68 | let mut random_direction_indices: [usize; build::NUM_DIRECTIONS] = [0, 1, 2, 3]; 69 | let mut cur: maze::Point = start; 70 | 'descending: loop { 71 | random_direction_indices.shuffle(&mut gen); 72 | for &i in random_direction_indices.iter() { 73 | let direction = &build::GENERATE_DIRECTIONS[i]; 74 | let branch = maze::Point { 75 | row: cur.row + direction.row, 76 | col: cur.col + direction.col, 77 | }; 78 | if build::can_build_new_square(&lk.maze, branch) { 79 | build::carve_path_history(&mut lk.maze, cur, branch); 80 | cur = branch; 81 | continue 'descending; 82 | } 83 | } 84 | let dir: build::BacktrackMarker = lk.maze.get(cur.row, cur.col) & build::MARKERS_MASK; 85 | // The solvers will need these bits later so we need to clear bits. 86 | let half: &maze::Point = &build::BACKTRACKING_HALF_POINTS[dir as usize]; 87 | let backtracking: &maze::Point = &build::BACKTRACKING_POINTS[dir as usize]; 88 | let half_step = maze::Point { 89 | row: cur.row + half.row, 90 | col: cur.col + half.col, 91 | }; 92 | let square = lk.maze.get(cur.row, cur.col); 93 | let half_step_square = lk.maze.get(half_step.row, half_step.col); 94 | lk.maze.build_history.push(maze::Delta { 95 | id: cur, 96 | before: square, 97 | after: square & !build::MARKERS_MASK, 98 | burst: 1, 99 | }); 100 | lk.maze.build_history.push(maze::Delta { 101 | id: half_step, 102 | before: half_step_square, 103 | after: half_step_square & !build::MARKERS_MASK, 104 | burst: 1, 105 | }); 106 | *lk.maze.get_mut(cur.row, cur.col) &= !build::MARKERS_MASK; 107 | *lk.maze.get_mut(half_step.row, half_step.col) &= !build::MARKERS_MASK; 108 | cur.row += backtracking.row; 109 | cur.col += backtracking.col; 110 | 111 | if cur == start { 112 | return; 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /maze_tui/builders/src/prim.rs: -------------------------------------------------------------------------------- 1 | use crate::build; 2 | use maze; 3 | 4 | use rand::{ 5 | distributions::{Distribution, Uniform}, 6 | thread_rng, Rng, 7 | }; 8 | use std::collections::{BinaryHeap, HashMap}; 9 | 10 | #[derive(Clone, Copy, Eq)] 11 | struct PriorityPoint { 12 | priority: u8, 13 | p: maze::Point, 14 | } 15 | 16 | impl PartialEq for PriorityPoint { 17 | fn eq(&self, other: &Self) -> bool { 18 | self.priority.eq(&other.priority) && self.p.eq(&other.p) 19 | } 20 | } 21 | 22 | impl PartialOrd for PriorityPoint { 23 | fn partial_cmp(&self, other: &Self) -> Option { 24 | Some(self.cmp(other)) 25 | } 26 | } 27 | 28 | impl Ord for PriorityPoint { 29 | fn cmp(&self, other: &Self) -> std::cmp::Ordering { 30 | self.priority.cmp(&other.priority) 31 | } 32 | } 33 | 34 | /// 35 | /// Data only maze generator 36 | /// 37 | pub fn generate_maze(monitor: monitor::MazeMonitor) { 38 | let mut lk = match monitor.lock() { 39 | Ok(l) => l, 40 | Err(_) => print::maze_panic!("uncontested lock failure"), 41 | }; 42 | build::fill_maze_with_walls(&mut lk.maze); 43 | let mut rng = thread_rng(); 44 | let weight_range = Uniform::from(1..=100); 45 | let start = PriorityPoint { 46 | priority: weight_range.sample(&mut rng), 47 | p: maze::Point { 48 | row: 2 * rng.gen_range(1..((lk.maze.rows() - 2) / 2)) + 1, 49 | col: 2 * rng.gen_range(1..((lk.maze.cols() - 2) / 2)) + 1, 50 | }, 51 | }; 52 | let mut lookup_weights: HashMap = HashMap::from([(start.p, start.priority)]); 53 | let mut pq = BinaryHeap::from([start]); 54 | while let Some(&cur) = pq.peek() { 55 | let mut max_neighbor: Option = None; 56 | let mut max_weight = 0; 57 | for dir in &build::GENERATE_DIRECTIONS { 58 | let next = maze::Point { 59 | row: cur.p.row + dir.row, 60 | col: cur.p.col + dir.col, 61 | }; 62 | if !build::can_build_new_square(&lk.maze, next) { 63 | continue; 64 | } 65 | // Weights would have been randomly pre-generated anyway. Generate as we go 66 | // instead. However, once we choose a weight it must always be the same so 67 | // we cache that weight and will find it if we choose to join that square later. 68 | let weight = *lookup_weights 69 | .entry(next) 70 | .or_insert(weight_range.sample(&mut rng)); 71 | if weight > max_weight { 72 | max_weight = weight; 73 | max_neighbor.replace(PriorityPoint { 74 | priority: weight, 75 | p: next, 76 | }); 77 | } 78 | } 79 | if let Some(neighbor) = max_neighbor { 80 | build::join_squares(&mut lk.maze, cur.p, neighbor.p); 81 | pq.push(neighbor); 82 | } else { 83 | pq.pop(); 84 | } 85 | } 86 | } 87 | 88 | /// 89 | /// History based generator for animation and playback. 90 | /// 91 | pub fn generate_history(monitor: monitor::MazeMonitor) { 92 | let mut lk = match monitor.lock() { 93 | Ok(l) => l, 94 | Err(_) => print::maze_panic!("uncontested lock failure"), 95 | }; 96 | build::fill_maze_history_with_walls(&mut lk.maze); 97 | let mut rng = thread_rng(); 98 | let weight_range = Uniform::from(1..=100); 99 | let start = PriorityPoint { 100 | priority: weight_range.sample(&mut rng), 101 | p: maze::Point { 102 | row: 2 * rng.gen_range(1..((lk.maze.rows() - 2) / 2)) + 1, 103 | col: 2 * rng.gen_range(1..((lk.maze.cols() - 2) / 2)) + 1, 104 | }, 105 | }; 106 | let mut lookup_weights: HashMap = HashMap::from([(start.p, start.priority)]); 107 | let mut pq = BinaryHeap::from([start]); 108 | while let Some(&cur) = pq.peek() { 109 | let mut max_neighbor: Option = None; 110 | let mut max_weight = 0; 111 | for dir in &build::GENERATE_DIRECTIONS { 112 | let next = maze::Point { 113 | row: cur.p.row + dir.row, 114 | col: cur.p.col + dir.col, 115 | }; 116 | if !build::can_build_new_square(&lk.maze, next) { 117 | continue; 118 | } 119 | // Weights would have been randomly pre-generated anyway. Generate as we go 120 | // instead. However, once we choose a weight it must always be the same so 121 | // we cache that weight and will find it if we choose to join that square later. 122 | let weight = *lookup_weights 123 | .entry(next) 124 | .or_insert(weight_range.sample(&mut rng)); 125 | if weight > max_weight { 126 | max_weight = weight; 127 | max_neighbor.replace(PriorityPoint { 128 | priority: weight, 129 | p: next, 130 | }); 131 | } 132 | } 133 | if let Some(neighbor) = max_neighbor { 134 | build::join_squares_history(&mut lk.maze, cur.p, neighbor.p); 135 | pq.push(neighbor); 136 | } else { 137 | pq.pop(); 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /maze_tui/builders/src/recursive_subdivision.rs: -------------------------------------------------------------------------------- 1 | use crate::build; 2 | use maze; 3 | 4 | use rand::{rngs::ThreadRng, thread_rng, Rng}; 5 | 6 | type Height = i32; 7 | type Width = i32; 8 | 9 | #[derive(Clone, Copy)] 10 | struct Chamber { 11 | offset: maze::Point, 12 | h: Height, 13 | w: Width, 14 | } 15 | 16 | const MIN_CHAMBER: i32 = 3; 17 | 18 | /// 19 | /// Data only maze generator 20 | /// 21 | pub fn generate_maze(monitor: monitor::MazeMonitor) { 22 | let mut lk = match monitor.lock() { 23 | Ok(l) => l, 24 | Err(_) => print::maze_panic!("uncontested lock failure"), 25 | }; 26 | build::build_wall_outline(&mut lk.maze); 27 | let mut rng = thread_rng(); 28 | let mut chamber_stack: Vec = Vec::from([Chamber { 29 | offset: maze::Point { row: 0, col: 0 }, 30 | h: lk.maze.rows(), 31 | w: lk.maze.cols(), 32 | }]); 33 | while let Some(chamber) = chamber_stack.pop() { 34 | if chamber.h >= chamber.w && chamber.w > MIN_CHAMBER { 35 | let divide = rand_even_div(&mut rng, chamber.h); 36 | let passage = rand_odd_pass(&mut rng, chamber.w); 37 | for c in 0..chamber.w { 38 | if c == passage { 39 | continue; 40 | } 41 | build::build_wall_line( 42 | &mut lk.maze, 43 | maze::Point { 44 | row: chamber.offset.row + divide, 45 | col: chamber.offset.col + c, 46 | }, 47 | ); 48 | } 49 | chamber_stack.push(Chamber { 50 | offset: chamber.offset, 51 | h: divide + 1, 52 | w: chamber.w, 53 | }); 54 | chamber_stack.push(Chamber { 55 | offset: maze::Point { 56 | row: chamber.offset.row + divide, 57 | col: chamber.offset.col, 58 | }, 59 | h: chamber.h - divide, 60 | w: chamber.w, 61 | }); 62 | } else if chamber.w > chamber.h && chamber.h > MIN_CHAMBER { 63 | let divide = rand_even_div(&mut rng, chamber.w); 64 | let passage = rand_odd_pass(&mut rng, chamber.h); 65 | for r in 0..chamber.h { 66 | if r == passage { 67 | continue; 68 | } 69 | build::build_wall_line( 70 | &mut lk.maze, 71 | maze::Point { 72 | row: chamber.offset.row + r, 73 | col: chamber.offset.col + divide, 74 | }, 75 | ); 76 | } 77 | chamber_stack.push(Chamber { 78 | offset: chamber.offset, 79 | h: chamber.h, 80 | w: divide + 1, 81 | }); 82 | chamber_stack.push(Chamber { 83 | offset: maze::Point { 84 | row: chamber.offset.row, 85 | col: chamber.offset.col + divide, 86 | }, 87 | h: chamber.h, 88 | w: chamber.w - divide, 89 | }); 90 | } 91 | } 92 | } 93 | 94 | /// 95 | /// History based generator for animation and playback. 96 | /// 97 | pub fn generate_history(monitor: monitor::MazeMonitor) { 98 | let mut lk = match monitor.lock() { 99 | Ok(l) => l, 100 | Err(_) => print::maze_panic!("uncontested lock failure"), 101 | }; 102 | build::build_wall_outline_history(&mut lk.maze); 103 | let mut rng = thread_rng(); 104 | let mut chamber_stack: Vec = Vec::from([Chamber { 105 | offset: maze::Point { row: 0, col: 0 }, 106 | h: lk.maze.rows(), 107 | w: lk.maze.cols(), 108 | }]); 109 | while let Some(chamber) = chamber_stack.pop() { 110 | if chamber.h >= chamber.w && chamber.w > MIN_CHAMBER { 111 | let divide = rand_even_div(&mut rng, chamber.h); 112 | let passage = rand_odd_pass(&mut rng, chamber.w); 113 | for c in 0..chamber.w { 114 | if c == passage { 115 | continue; 116 | } 117 | build::build_wall_line_history( 118 | &mut lk.maze, 119 | maze::Point { 120 | row: chamber.offset.row + divide, 121 | col: chamber.offset.col + c, 122 | }, 123 | ); 124 | } 125 | chamber_stack.push(Chamber { 126 | offset: chamber.offset, 127 | h: divide + 1, 128 | w: chamber.w, 129 | }); 130 | chamber_stack.push(Chamber { 131 | offset: maze::Point { 132 | row: chamber.offset.row + divide, 133 | col: chamber.offset.col, 134 | }, 135 | h: chamber.h - divide, 136 | w: chamber.w, 137 | }); 138 | } else if chamber.w > chamber.h && chamber.h > MIN_CHAMBER { 139 | let divide = rand_even_div(&mut rng, chamber.w); 140 | let passage = rand_odd_pass(&mut rng, chamber.h); 141 | for r in 0..chamber.h { 142 | if r == passage { 143 | continue; 144 | } 145 | build::build_wall_line_history( 146 | &mut lk.maze, 147 | maze::Point { 148 | row: chamber.offset.row + r, 149 | col: chamber.offset.col + divide, 150 | }, 151 | ); 152 | } 153 | chamber_stack.push(Chamber { 154 | offset: chamber.offset, 155 | h: chamber.h, 156 | w: divide + 1, 157 | }); 158 | chamber_stack.push(Chamber { 159 | offset: maze::Point { 160 | row: chamber.offset.row, 161 | col: chamber.offset.col + divide, 162 | }, 163 | h: chamber.h, 164 | w: chamber.w - divide, 165 | }); 166 | } 167 | } 168 | } 169 | 170 | /// 171 | /// Data only helpers. 172 | /// 173 | fn rand_even_div(rng: &mut ThreadRng, axis_limit: i32) -> i32 { 174 | 2 * rng.gen_range(1..=((axis_limit - 2) / 2)) 175 | } 176 | 177 | fn rand_odd_pass(rng: &mut ThreadRng, axis_limit: i32) -> i32 { 178 | 2 * rng.gen_range(1..=((axis_limit - 2) / 2)) + 1 179 | } 180 | -------------------------------------------------------------------------------- /maze_tui/res/instructions.txt: -------------------------------------------------------------------------------- 1 | ███████████████████████████████████ 2 | ███╗ ███╗ █████╗ ███████╗███████╗ ████████╗██╗ ██╗██╗ ███████████████████████████████████ 3 | ████╗ ████║██╔══██╗╚══███╔╝██╔════╝ ╚══██╔══╝██║ ██║██║ █████████████████████▓█████████████ 4 | ██╔████╔██║███████║ ███╔╝ █████╗ ██║ ██║ ██║██║ ██▓██████▓████▓████████████████████ 5 | ██║╚██╔╝██║██╔══██║ ███╔╝ ██╔══╝ ██║ ██║ ██║██║ ███████████████████▓███████████████ 6 | ██║ ╚═╝ ██║██║ ██║███████╗███████╗ ██║ ╚██████╔╝██║ ██████▓██▓███▓██▓████▓███▓█████████ 7 | ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚══════╝ ╚═╝ ╚═════╝ ╚═╝ █▓█▓██▓▓██▓████████████████████████ 8 | ████▓█▓▓▓█▓██▓████▓█████▓██████████ 9 | Use flags, followed by arguments, in any order. ██▓█▓▓▓▓██▓████▓████████▓██████████ 10 | Press [ENTER] to confirm your flag choices. ▓▓████▓▓██▓████▓████▓██▓▓███▓██████ 11 | ▓██▓▓█▓▓██▓█▓██▓▓██████▓▓███▓██████ 12 | (scroll with [↓/↑], exit with [ESCAPE]) ▓▓▓█▓█▓▓██▓████▓▓███▓██▓▓███▓██████ 13 | ▓▓█▓▓█▓▓██▓██▓█▓▓▓██▓██▓▓▓██▓██████ 14 | BUILDER FLAG[-b] Set maze building algorithm. ▓▓██▓█▓▓██▓████▓▓▓██▓██▓▓▓██▓██████ 15 | [rdfs] - Randomized depth first search. ▓▓█▓▓▓▓▓██▒█▓██▒▓▓██▓██▓▓▓██▓██████ 16 | [hunt-kill] - Randomized walks and scans. ▓▓█▓▓▓▓▓██▒█▓██▒▓▓██▓██▓▓▓██▓██████ 17 | [kruskal] - Randomized Kruskal's algorithm. ▓▓██▓▓▓▓██▒▓██▓▒▓▓██▓██▓▓▓██▓██████ 18 | [prim] - Randomized Prim's algorithm. ▓▓██▓▓▒▓█▓▒▓██▓▒▓▓██▓██▓▓▓██▓██████ 19 | [eller] - Randomized Eller's algorithm. ▓▓██▓▓▒▓█▓▒▓▓█▓▒▓▓██▓██▓▒▓██▓█████▓ 20 | [wilson] - Loop-erased random path carver. ▓▓██▓▓▒▒█▓▒▓▓█▓▒▓▓██▓██▓▒▓██▓█████▓ 21 | [wilson-walls] - Loop-erased random wall adder. ▒▓██▓▓▒▒█▓▒▓▓█▓▒▓▓██▓██▓▒▓██▓█████▓ 22 | [fractal] - Randomized recursive subdivision. ▒▓██▓▓▒▒▓▓▒▓▓█▓▒▓▓██▓██▓▒▓██▓█████▓ 23 | [grid] - A random grid pattern. ▒▒█▓▓▓▒▒▓▒▒▓▓█▓▒▓▓█▓▒██▓▒▓█▓▓▓████▓ 24 | [arena] - Open floor with no walls. ▒▒█▓▓▓▒▒▓▒▒▓▓█▓▒▓▓█▓▒▓█▓▒▓██▓▓████▓ 25 | ▒▒▓▓▓▓▒▒▓▒▒▓▓█▓▒▓▓█▓▒▓█▓▒▓██▓▓████▓ 26 | MODIFICATION FLAG[-m] Add shortcuts to the maze. ▒▒▓▓▓▓▒▒▓▒░▓▓█▓▒▒▓█▓▒▓█▓▒▓██▓▓████▓ 27 | [cross]- Add crossroads through the center. ▒▒▓▓▓▓▒▒▓▒░▓▓█▓▒▒▒█▓▒▓█▓▒▓██▓▓████▓ 28 | [x]- Add an x of crossing paths through center. ▒▒▓▓▓▒░▒▓▒░▓▒█▓░▒▒█▓▒▓█▓░▓██▓▓███▓▓ 29 | ▒▒▓▓▒▒░▒▓▒░▓▒█▓░▒▒█▓▒▓█▓░▓██▓▓███▓▓ 30 | SOLVER FLAG[-s] Set maze solving algorithm. ▒▒▓▓▒▒░▒▓▒░▓▒█▓░▒▒█▓▒▓█▓░▓██▓▓███▓▓ 31 | [dfs-hunt] - Depth First Search ▒▒▓▓░▒░▒▓▒░▓▒▓▓░░▒█▓░▓█▓░▓█▓▒▒███▓▒ 32 | [dfs-gather] - Depth First Search ▒▒▓▓░▒░▒▓▒░▓▒▓▓░░▒█▓░▓█▓░▓█▓▒▒███▓▒ 33 | [dfs-corner] - Depth First Search ▒▒▓▓░▒░▒▓▒░▒▒▓▓░░▒█▓░▓█▓░▓▓▓▒▒███▓▒ 34 | [floodfs-hunt] - Depth First Search ▒▒▓▓░▒░▒▓▒░▒▒▓▓░░▒█▓░▓█▓░▓▓▓▒▒███▓▒ 35 | [floodfs-gather] - Depth First Search ▒▒▓▓░▒░▒▓▒░▒▒▓▓░░▒█▓░▓█▓░▓▓▓▒▒███▓▒ 36 | [floodfs-corner] - Depth First Search ▒▒▓▒░░░▒▓▒░▒▒▓▒░░░█▓░▓█▓░▓▓▓░▒███▓▒ 37 | [rdfs-hunt] - Randomized Depth First Search ░▒▓▒░░░▒▓▒░▒░▓▒░░░█▓░▓█▓░▓▓▓░▒███▓▒ 38 | [rdfs-gather] - Randomized Depth First Search ░▒▓▒░░░▒▒▒░▒░▓▒░░░▓▓░▓█▒░▓▓▓░▒███▒▒ 39 | [rdfs-corner] - Randomized Depth First Search ░▒▓▒░░░▒▒▒░▒░▓▒░░░▓▓░▓█▒░▓▓▓░▒███▒▒ 40 | [bfs-hunt] - Breadth First Search ░░▒▒░░░▒▒▒░▒░▓▒░░░▓▓░▓█▒░▓▓▓░▒███▒▒ 41 | [bfs-gather] - Breadth First Search ░░▒▒░░░░▒▒░▒░▒▒░░░▓▓░▓█▒░▓▓▓░▒███▒▒ 42 | [bfs-corner] - Breadth First Search ░░▒▒░░░░▒▒░▒░▒▒░░░▓▓░▓█▒░▓▓▓░▒███▒▒ 43 | [distance] - Path Distances from Center ░░▒▒░░░░▒▒░▒░▒▒░░░▓▓░▓█▒░▓▓▓░▒███▒▒ 44 | [runs] - Path Run Lengths ░░▒▒░░░░▒▒░▒░▒▒░░░▓▓░▓█▒░▓▓▓░▒███▒▒ 45 | ░░▒▒░░░░▒▒░▒░▒▒░░░▓▓░▓█▒░▓▓▓░▒██▓▒▒ 46 | WALL FLAG[-w] Set the wall style for the maze. ░▒▒ ░░░▒▒░▒░▒▒░░░▓▓░▓█▒░▓▓▓░▒██▓▒▒ 47 | [mini] - Half size walls and paths. ░ ▒▒░ ░░▒▒░▒░▒▒░░░▓▓░▓█▒░▓▓▓░▒██▓▒▒ 48 | [sharp] - The default straight lines. ░ ▒▒░ ░░▒▒░▒░▒▒░░░▓▓░▓█▒░▓▓▓░▒██▓▒▒ 49 | [round] - Rounded corners. ░░▒▒ ░ ░▒▒░▒░▒▒░░░▓▓░▓█▒░▓▓▓░▒▓█▓▒▒ 50 | [doubles] - Sharp double lines. ░░▒▒ ░▒░▒░▒▒░░░▓▓░▓█▒░▓▓▓░▒▓█▓▒▒ 51 | [bold] - Thicker straight lines. ░ ▒▒░ ░ ░▒░▒░▒▒░░░▓▓░▓▓▒░▓▓▓░▒▓█▓▒▒ 52 | [contrast] - Full block width and height walls. ░▒░ ░░░░▒░▒░▒▒░░░▓▓░▓▓▒░▓▓▓ ▒▓█▓▒▒ 53 | [half] - Half block walls full size paths. ░▒░ ░░░░▒░▒░▒▒░░░▓▓░▓▓▒░▓▓▓ ▒▓█▓▒▒ 54 | [spikes] - Connected lines with spikes. ░░░░ ░░░▒░▒░▒▒░░░▓▓░▒▓▒ ▓▓▓ ▒▓█▒▒▒ 55 | ░ ░░ ░ ░░▒░░░▒▒░░░▓▓░▒▓▒ ▓▓▓ ▒▓▓▒▒▒ 56 | Animations can play forward or reversed. ░ ░░ ░ ░░▒░░░▒▒░░░▓▓░▒▓▒ ▓▓▓ ▒▓▓▒▒▒ 57 | Cancel any animation by pressing [ESCAPE]. ░ ░░ ░ ░░░ ░▒ ░▒▓ ▒▒▒ ▒▒▓ ▒▓▓▒▒▒ 58 | Pause/Play an animation with [SPACE]. ░ ░░ ░ ░░░ ░▒ ░▒▓ ▒▒▒ ▒▒▓ ▒▓▓▒▒▒ 59 | Slower or faster []. Try it and watch the background! ░ ░░ ░ ░░░ ░▒ ░▒▒ ▒▒▒ ▒▒▓ ▒▓▓▒▒▒ 60 | Step next/previous or change play direction with [←/→]. ░ ░░ ░ ░░░ ░▒ ░▒▒ ▒▒▒ ▒▒▓ ▒▓▓▒▒▒ 61 | Zoom out/in with [CTRL-[-]]/[CTRL-[+]]. ░ ░ ░ ░ ░░ ░▒ ▒▒ ▒▒░ ▒▒▒ ▒▓▒░░░ 62 | If any flags are omitted, defaults are used. ░ ░░░ ░ ░ ░░ ▒▒ ▒▒░ ▒▒▒ ▒▓▒░░░ 63 | An empty command line will create a random maze. ░░ ░ ░ ░ ░░ ▒░ ▒▒ ▒▒▒ ▒▓▒░░░ 64 | ░ ░░ ░░ ░░░ ▒░ ░▒ ▒▒▒ ▒▓▒░░░ 65 | EXAMPLES: ░ ░ ░░ ░ ░ ▒ ░▒ ▒▒▒ ░▓▒░░░ 66 | ░ ░ ░ ░░░ ░ ░ ░ ▒ ░▒ ▒▒▒ ░▒▒░░░ 67 | -b rdfs -s bfs-hunt ░ ░░░ ░ ░ ░▒ ░░ ▒▒▒ ░▒░░░ 68 | -s bfs-gather -b prim ░ ░ ░ ░░ ░ ░ ░ ░░ ░▒▒ ░▒░░░ 69 | -s bfs-corners -w mini -b fractal ░░ ░ ░░ ░ ░░░ ░ ░░ ░▒▒ ░▒░ ░ 70 | ░░ ░ ░ ░ ░ ░ ░░▒ ░▒░ ░ 71 | ASCII lettering for this title and algorithm ░ ░ ░ ░ ░ ░ ░ ░░▒ ░░░ ░ 72 | descriptions are templates I used from ░ ░ ░ ░ ░ ░░ ░ ░░▒ ░░░ ░░ 73 | patorjk.com and modified to use box-drawing ░ ░ ░ ░░ ░ ░░░░ ░░░ ░ 74 | characters. ░ ░ ░ ░░ ░ ░░░ ░░░ 75 | ░ ░ ░ ░ ░ ░░ ░ 76 | Enjoy! ░ ░ ░ ░ ░ ░ 77 | 78 | ░ ░ 79 | ░ 80 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Maze TUI 2 | 3 | ![demo](/images/demo.gif) 4 | 5 | > **For a complete breakdown of all maze generation algorithms, pseudocode, and many other important details please read the [wiki](https://github.com/agl-alexglopez/maze-tui/wiki) This is just the quick start guide to refer to for usage instructions.** 6 | 7 | ## Requirements 8 | 9 | - Rustc >= 1.73.0 or as new as possible `rustup update`. 10 | - A terminal with full font support for special Unicode characters. This project leverages Unicode Box-Drawing characters. 11 | - A terminal with support for RGB colors as defined by Crossterm's compatibility specifications. ANSI color codes are also used. 12 | - Cargo to build and run the project. 13 | 14 | ## Quick Start Guide 15 | 16 | This project is a terminal user interface application powered by [crossterm](https://github.com/crossterm-rs/crossterm), [ratatui.rs](https://github.com/ratatui-org/ratatui), and [tui-textarea](https://github.com/rhysd/tui-textarea) that can be run with various combinations of commands. The goal of the program is to explore various maze building, solving, and measurement algorithms while visualizing them in interesting ways. The basic principle behind the program is that you can ask for any combination of settings, include any settings, exclude any settings, and the program will just work. There are sensible defaults for every flag so experiment with different combinations and tweaks until you get what you are looking for. In addition, you can learn about each maze algorithm you observe with informational popups (they are a work in progress right now!). 17 | 18 | If you just want to get started follow these steps. 19 | 20 | ```zsh 21 | $ git clone https://github.com/agl-alexglopez/maze-tui.git 22 | $ cd maze-tui/maze_tui 23 | $ cargo run --release --bin run_tui 24 | ``` 25 | 26 | You will be greeted by the home page. Read the directions or if you just want to see some cool maze animations right away press `[ENTER]`. Check out the pausing, arrow commands, and stepping capabilities for how to control the animation you are watching. See the introduction below. 27 | 28 | ## Run TUI Program 29 | 30 | Here are the instructions that greet you upon running the application. 31 | 32 | ```txt 33 | ███╗ ███╗ █████╗ ███████╗███████╗ ████████╗██╗ ██╗██╗ 34 | ████╗ ████║██╔══██╗╚══███╔╝██╔════╝ ╚══██╔══╝██║ ██║██║ 35 | ██╔████╔██║███████║ ███╔╝ █████╗ ██║ ██║ ██║██║ 36 | ██║╚██╔╝██║██╔══██║ ███╔╝ ██╔══╝ ██║ ██║ ██║██║ 37 | ██║ ╚═╝ ██║██║ ██║███████╗███████╗ ██║ ╚██████╔╝██║ 38 | ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚══════╝ ╚═╝ ╚═════╝ ╚═╝ 39 | 40 | Use flags, followed by arguments, in any order. 41 | Press [ENTER] to confirm your flag choices. 42 | 43 | (scroll with [↓/↑], exit with [ESCAPE]) 44 | 45 | BUILDER FLAG[-b] Set maze building algorithm. 46 | [rdfs] - Randomized depth first search. 47 | [hunt-kill] - Randomized walks and scans. 48 | [kruskal] - Randomized Kruskal's algorithm. 49 | [prim] - Randomized Prim's algorithm. 50 | [eller] - Randomized Eller's algorithm. 51 | [wilson] - Loop-erased random path carver. 52 | [wilson-walls] - Loop-erased random wall adder. 53 | [fractal] - Randomized recursive subdivision. 54 | [grid] - A random grid pattern. 55 | [arena] - Open floor with no walls. 56 | 57 | MODIFICATION FLAG[-m] Add shortcuts to the maze. 58 | [cross]- Add crossroads through the center. 59 | [x]- Add an x of crossing paths through center. 60 | 61 | SOLVER FLAG[-s] Set maze solving algorithm. 62 | [dfs-hunt] - Depth First Search 63 | [dfs-gather] - Depth First Search 64 | [dfs-corner] - Depth First Search 65 | [floodfs-hunt] - Depth First Search 66 | [floodfs-gather] - Depth First Search 67 | [floodfs-corner] - Depth First Search 68 | [rdfs-hunt] - Randomized Depth First Search 69 | [rdfs-gather] - Randomized Depth First Search 70 | [rdfs-corner] - Randomized Depth First Search 71 | [bfs-hunt] - Breadth First Search 72 | [bfs-gather] - Breadth First Search 73 | [bfs-corner] - Breadth First Search 74 | [distance] - Path Distances from Center 75 | [runs] - Path Run Lengths 76 | 77 | WALL FLAG[-w] Set the wall style for the maze. 78 | [mini] - Half size walls and paths. 79 | [sharp] - The default straight lines. 80 | [round] - Rounded corners. 81 | [doubles] - Sharp double lines. 82 | [bold] - Thicker straight lines. 83 | [contrast] - Full block width and height walls. 84 | [half] - Half wall height and full size paths. 85 | [spikes] - Connected lines with spikes. 86 | 87 | Animations can play forward or reversed. 88 | Cancel any animation by pressing [ESCAPE]. 89 | Pause/Play an animation with [SPACE] 90 | Slower or faster []. Try it and watch the background! 91 | Step next/previous or change the play direction with [←/→] 92 | Zoom out/in with [CTRL-[-]]/[CTRL-[+]] 93 | If any flags are omitted, defaults are used. 94 | An empty command line will create a random maze. 95 | 96 | EXAMPLES: 97 | 98 | -b rdfs -s bfs-hunt 99 | -s bfs-gather -b prim 100 | -s bfs-corner -w mini -b fractal 101 | 102 | ASCII lettering for this title and algorithm 103 | descriptions are templates I used from 104 | patorjk.com and modified to use box-drawing 105 | characters. 106 | 107 | Enjoy! 108 | ``` 109 | 110 | ## Details 111 | 112 | The underlying principles for this program are as follows. 113 | 114 | 1. Build a maze by representing paths and walls in a `u32` integer. 115 | 2. Use logical and visually pleasing unicode characters to represent the walls and features of the maze. 116 | 3. Solve the maze with various algorithms, both single and multi-threaded, so that we can observe important features of the mazes. 117 | 118 | I included as many **interesting** maze building algorithms as I could. The solving algorithms are multi-threaded in many cases. This is not particularly practical or special on its own. However, multithreading allows for more fun and interesting visualizations and can speed up what can sometimes be a slow solving process to watch for a single thread. 119 | 120 | While I have not yet put together a testing suite for performance testing of building and solving the mazes, I will be interested to see the performance implications of the solvers. The [wiki](https://github.com/agl-alexglopez/maze-tui/wiki) is likely where you will find documentation of new features or other testing. 121 | 122 | ## Wiki and Why 123 | 124 | Please read the [wiki](https://github.com/agl-alexglopez/maze-tui/wiki) for more detailed explanation of settings, write-ups for each maze generation algorithm, and much more. 125 | 126 | Recently, I have come to believe that a maze generation and solving program is a great "Rosetta Stone" program. If you want to know what a Rosetta Stone program is, and get a deeper insight into how you can make something like this yourself, please visit my [blog](https://agl-alexglopez.github.io/) and read my [Rosetta Stones and Mazes](https://agl-alexglopez.github.io/2025/08/01/rosetta-stones-and-mazes-the-premise.html) article series. Thank you! 127 | 128 | ## Tips 129 | 130 | - Windows is slow, sorry. For some reason the animations are much slower in Windows Terminal in my experience. It's not horrible but try smaller mazes at first. 131 | - Windows Terminal does not render fonts correctly. Depending on your font the Box-Drawing characters may be slightly disconnected or at times completely incorrect. I develop this project on WSL2 sometimes and notice the problems. Try the `-w contrast` or `-w half` option for smoother animations. 132 | - Mac and many Linux distributions render everything beautifully in my experience (as long as fully featured fonts are installed)! 133 | - Experiment with pausing with space `[SPACE]`, slowing the program down or speeding it up with `[]`, or stepping through it one delta at a time with `[←/→]` (the left and right arrows also change the direction of the animation based on the last direction you pressed). This is a great way to understand the algorithms at play! 134 | -------------------------------------------------------------------------------- /maze_tui/painters/src/distance.rs: -------------------------------------------------------------------------------- 1 | use crate::rgb; 2 | use maze; 3 | use std::collections::VecDeque; 4 | 5 | use std::thread; 6 | 7 | use rand::{thread_rng, Rng}; 8 | 9 | /// 10 | /// Data only modifiers 11 | /// 12 | pub fn paint_distance_from_center(monitor: monitor::MazeMonitor) { 13 | let mut lk = match monitor.lock() { 14 | Ok(l) => l, 15 | Err(_) => print::maze_panic!("Lock panic."), 16 | }; 17 | 18 | let row_mid = lk.maze.rows() / 2; 19 | let col_mid = lk.maze.cols() / 2; 20 | let start = maze::Point { 21 | row: row_mid + 1 - (row_mid % 2), 22 | col: col_mid + 1 - (col_mid % 2), 23 | }; 24 | let mut map = monitor::MaxMap::new(start, 0); 25 | let mut bfs = VecDeque::from([(start, 0u64)]); 26 | *lk.maze.get_mut(start.row, start.col) |= rgb::MEASURED; 27 | while let Some(cur) = bfs.pop_front() { 28 | if cur.1 > map.max { 29 | map.max = cur.1; 30 | } 31 | for &p in maze::CARDINAL_DIRECTIONS.iter() { 32 | let next = maze::Point { 33 | row: cur.0.row + p.row, 34 | col: cur.0.col + p.col, 35 | }; 36 | if (lk.maze.get(next.row, next.col) & maze::PATH_BIT) == 0 37 | || (lk.maze.get(next.row, next.col) & rgb::MEASURED) != 0 38 | { 39 | continue; 40 | } 41 | *lk.maze.get_mut(next.row, next.col) |= rgb::MEASURED; 42 | map.distances.insert(next, cur.1 + 1); 43 | bfs.push_back((next, cur.1 + 1)); 44 | } 45 | } 46 | painter(&mut lk.maze, &map); 47 | } 48 | 49 | fn painter(maze: &mut maze::Maze, map: &monitor::MaxMap) { 50 | let mut rng = thread_rng(); 51 | let rand_color_choice: usize = rng.gen_range(0..3); 52 | for r in 0..maze.rows() { 53 | for c in 0..maze.cols() { 54 | let cur = maze::Point { row: r, col: c }; 55 | if let Some(dist) = map.distances.get(&cur) { 56 | let intensity = (map.max - dist) as f64 / map.max as f64; 57 | let dark = (255f64 * intensity) as u8; 58 | let bright = 128 + (127f64 * intensity) as u8; 59 | let mut c: rgb::Rgb = [dark, dark, dark]; 60 | c[rand_color_choice] = bright; 61 | *maze.get_mut(cur.row, cur.col) |= ((c[0] as u32) << rgb::RED_SHIFT) 62 | | ((c[1] as u32) << rgb::GREEN_SHIFT) 63 | | (c[2] as u32); 64 | } 65 | } 66 | } 67 | } 68 | 69 | /// 70 | /// History based solvers. 71 | /// 72 | pub fn paint_distance_from_center_history(monitor: monitor::MazeMonitor) { 73 | let start = if let Ok(mut lk) = monitor.lock() { 74 | let row_mid = lk.maze.rows() / 2; 75 | let col_mid = lk.maze.cols() / 2; 76 | let start = maze::Point { 77 | row: row_mid + 1 - (row_mid % 2), 78 | col: col_mid + 1 - (col_mid % 2), 79 | }; 80 | lk.map.distances.insert(start, 0); 81 | let mut bfs = VecDeque::from([(start, 0u64)]); 82 | *lk.maze.get_mut(start.row, start.col) |= rgb::MEASURED; 83 | while let Some(cur) = bfs.pop_front() { 84 | if cur.1 > lk.map.max { 85 | lk.map.max = cur.1; 86 | } 87 | for &p in maze::CARDINAL_DIRECTIONS.iter() { 88 | let next = maze::Point { 89 | row: cur.0.row + p.row, 90 | col: cur.0.col + p.col, 91 | }; 92 | if (lk.maze.get(next.row, next.col) & maze::PATH_BIT) == 0 93 | || (lk.maze.get(next.row, next.col) & rgb::MEASURED) != 0 94 | { 95 | continue; 96 | } 97 | *lk.maze.get_mut(next.row, next.col) |= rgb::MEASURED; 98 | lk.map.distances.insert(next, cur.1 + 1); 99 | bfs.push_back((next, cur.1 + 1)); 100 | } 101 | } 102 | start 103 | } else { 104 | print::maze_panic!("Thread panic."); 105 | }; 106 | 107 | let mut rng = thread_rng(); 108 | let rand_color_choice: usize = rng.gen_range(0..3); 109 | let mut handles = Vec::with_capacity(rgb::NUM_PAINTERS - 1); 110 | for painter in 1..rgb::NUM_PAINTERS { 111 | let monitor_clone = monitor.clone(); 112 | handles.push(thread::spawn(move || { 113 | painter_history( 114 | monitor_clone, 115 | rgb::ThreadGuide { 116 | bias: painter, 117 | color_i: rand_color_choice, 118 | cache: rgb::MEASURED_MASKS[painter], 119 | p: start, 120 | }, 121 | ); 122 | })); 123 | } 124 | painter_history( 125 | monitor.clone(), 126 | rgb::ThreadGuide { 127 | bias: 0, 128 | color_i: rand_color_choice, 129 | cache: rgb::MEASURED_MASKS[0], 130 | p: start, 131 | }, 132 | ); 133 | for h in handles { 134 | h.join().expect("Error joining a thread."); 135 | } 136 | } 137 | 138 | fn painter_history(monitor: monitor::MazeMonitor, guide: rgb::ThreadGuide) { 139 | let mut bfs = VecDeque::from([guide.p]); 140 | while let Some(cur) = bfs.pop_front() { 141 | match monitor.lock() { 142 | Ok(mut lk) => { 143 | if lk.count == lk.map.distances.len() { 144 | return; 145 | } 146 | let dist = lk 147 | .map 148 | .distances 149 | .get(&cur) 150 | .expect("Could not find map entry?"); 151 | let before = lk.maze.get(cur.row, cur.col); 152 | if !rgb::has_paint_vals(before) { 153 | let intensity = (lk.map.max - dist) as f64 / lk.map.max as f64; 154 | let dark = (255f64 * intensity) as u8; 155 | let bright = 128 + (127f64 * intensity) as u8; 156 | let mut c: rgb::Rgb = [dark, dark, dark]; 157 | c[guide.color_i] = bright; 158 | lk.maze.solve_history.push(maze::Delta { 159 | id: cur, 160 | before, 161 | after: before 162 | | ((c[0] as u32) << rgb::RED_SHIFT) 163 | | ((c[1] as u32) << rgb::GREEN_SHIFT) 164 | | (c[2] as u32), 165 | burst: 1, 166 | }); 167 | *lk.maze.get_mut(cur.row, cur.col) |= ((c[0] as u32) << rgb::RED_SHIFT) 168 | | ((c[1] as u32) << rgb::GREEN_SHIFT) 169 | | (c[2] as u32); 170 | lk.count += 1; 171 | } 172 | } 173 | Err(p) => print::maze_panic!("Thread panicked with lock: {}", p), 174 | }; 175 | let mut i = guide.bias; 176 | for _ in 0..rgb::NUM_DIRECTIONS { 177 | let p = &maze::CARDINAL_DIRECTIONS[i]; 178 | let next = maze::Point { 179 | row: cur.row + p.row, 180 | col: cur.col + p.col, 181 | }; 182 | if match monitor.lock() { 183 | Err(p) => print::maze_panic!("Panic with lock: {}", p), 184 | Ok(mut lk) => { 185 | let nxt = lk.maze.get(next.row, next.col); 186 | let seen = (nxt & guide.cache) != 0; 187 | let is_path = maze::is_path(nxt); 188 | if !seen && is_path { 189 | *lk.maze.get_mut(next.row, next.col) |= guide.cache; 190 | } 191 | !seen && is_path 192 | } 193 | } { 194 | bfs.push_back(next); 195 | } 196 | i = (i + 1) % rgb::NUM_PAINTERS; 197 | } 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /maze_tui/painters/src/runs.rs: -------------------------------------------------------------------------------- 1 | use crate::rgb; 2 | use maze; 3 | 4 | use std::collections::VecDeque; 5 | use std::thread; 6 | 7 | use rand::{thread_rng, Rng}; 8 | 9 | struct RunPoint { 10 | len: u64, 11 | prev: maze::Point, 12 | cur: maze::Point, 13 | } 14 | 15 | /// 16 | /// Data only measurements. 17 | /// 18 | pub fn paint_run_lengths(monitor: monitor::MazeMonitor) { 19 | let mut lk = match monitor.lock() { 20 | Ok(l) => l, 21 | Err(_) => print::maze_panic!("Lock panic."), 22 | }; 23 | let row_mid = lk.maze.rows() / 2; 24 | let col_mid = lk.maze.cols() / 2; 25 | let start = maze::Point { 26 | row: row_mid + 1 - (row_mid % 2), 27 | col: col_mid + 1 - (col_mid % 2), 28 | }; 29 | let mut map = monitor::MaxMap::new(start, 0); 30 | let mut bfs = VecDeque::from([RunPoint { 31 | len: 0, 32 | prev: start, 33 | cur: start, 34 | }]); 35 | *lk.maze.get_mut(start.row, start.col) |= rgb::MEASURED; 36 | while let Some(cur) = bfs.pop_front() { 37 | if cur.len > map.max { 38 | map.max = cur.len; 39 | } 40 | for &p in maze::CARDINAL_DIRECTIONS.iter() { 41 | let next = maze::Point { 42 | row: cur.cur.row + p.row, 43 | col: cur.cur.col + p.col, 44 | }; 45 | if lk.maze.wall_at(next.row, next.col) 46 | || (lk.maze.get(next.row, next.col) & rgb::MEASURED) != 0 47 | { 48 | continue; 49 | } 50 | let next_run_len = 51 | if (next.row).abs_diff(cur.prev.row) == (next.col).abs_diff(cur.prev.col) { 52 | 1 53 | } else { 54 | cur.len + 1 55 | }; 56 | *lk.maze.get_mut(next.row, next.col) |= rgb::MEASURED; 57 | map.distances.insert(next, next_run_len); 58 | bfs.push_back(RunPoint { 59 | len: next_run_len, 60 | prev: cur.cur, 61 | cur: next, 62 | }); 63 | } 64 | } 65 | painter(&mut lk.maze, &map); 66 | } 67 | 68 | fn painter(maze: &mut maze::Maze, map: &monitor::MaxMap) { 69 | let mut rng = thread_rng(); 70 | let rand_color_choice: usize = rng.gen_range(0..3); 71 | for r in 0..maze.rows() { 72 | for c in 0..maze.cols() { 73 | let cur = maze::Point { row: r, col: c }; 74 | if let Some(dist) = map.distances.get(&cur) { 75 | let intensity = (map.max - dist) as f64 / map.max as f64; 76 | let dark = (255f64 * intensity) as u8; 77 | let bright = 128 + (127f64 * intensity) as u8; 78 | let mut c: rgb::Rgb = [dark, dark, dark]; 79 | c[rand_color_choice] = bright; 80 | *maze.get_mut(cur.row, cur.col) |= ((c[0] as u32) << rgb::RED_SHIFT) 81 | | ((c[1] as u32) << rgb::GREEN_SHIFT) 82 | | (c[2] as u32); 83 | } 84 | } 85 | } 86 | } 87 | 88 | /// 89 | /// History based solver. 90 | /// 91 | pub fn paint_run_lengths_history(monitor: monitor::MazeMonitor) { 92 | let start: maze::Point = if let Ok(mut lk) = monitor.lock() { 93 | let row_mid = lk.maze.rows() / 2; 94 | let col_mid = lk.maze.cols() / 2; 95 | let start = maze::Point { 96 | row: row_mid + 1 - (row_mid % 2), 97 | col: col_mid + 1 - (col_mid % 2), 98 | }; 99 | lk.map.distances.insert(start, 0); 100 | let mut bfs = VecDeque::from([RunPoint { 101 | len: 0, 102 | prev: start, 103 | cur: start, 104 | }]); 105 | *lk.maze.get_mut(start.row, start.col) |= rgb::MEASURED; 106 | while let Some(cur) = bfs.pop_front() { 107 | if cur.len > lk.map.max { 108 | lk.map.max = cur.len; 109 | } 110 | for &p in maze::CARDINAL_DIRECTIONS.iter() { 111 | let next = maze::Point { 112 | row: cur.cur.row + p.row, 113 | col: cur.cur.col + p.col, 114 | }; 115 | if (lk.maze.get(next.row, next.col) & maze::PATH_BIT) == 0 116 | || (lk.maze.get(next.row, next.col) & rgb::MEASURED) != 0 117 | { 118 | continue; 119 | } 120 | let next_run_len = 121 | if (next.row).abs_diff(cur.prev.row) == (next.col).abs_diff(cur.prev.col) { 122 | 1 123 | } else { 124 | cur.len + 1 125 | }; 126 | *lk.maze.get_mut(next.row, next.col) |= rgb::MEASURED; 127 | lk.map.distances.insert(next, next_run_len); 128 | bfs.push_back(RunPoint { 129 | len: next_run_len, 130 | prev: cur.cur, 131 | cur: next, 132 | }); 133 | } 134 | } 135 | start 136 | } else { 137 | print::maze_panic!("Thread panic."); 138 | }; 139 | 140 | let mut rng = thread_rng(); 141 | let rand_color_choice: usize = rng.gen_range(0..3); 142 | let mut handles = Vec::with_capacity(rgb::NUM_PAINTERS); 143 | for painter in 0..rgb::NUM_PAINTERS - 1 { 144 | let monitor_clone = monitor.clone(); 145 | handles.push(thread::spawn(move || { 146 | painter_history( 147 | monitor_clone, 148 | rgb::ThreadGuide { 149 | bias: painter, 150 | color_i: rand_color_choice, 151 | cache: rgb::MEASURED_MASKS[painter], 152 | p: start, 153 | }, 154 | ); 155 | })); 156 | } 157 | painter_history( 158 | monitor.clone(), 159 | rgb::ThreadGuide { 160 | bias: rgb::NUM_PAINTERS - 1, 161 | color_i: rand_color_choice, 162 | cache: rgb::MEASURED_MASKS[rgb::NUM_PAINTERS - 1], 163 | p: start, 164 | }, 165 | ); 166 | for h in handles { 167 | h.join().expect("Error joining a thread."); 168 | } 169 | } 170 | 171 | fn painter_history(monitor: monitor::MazeMonitor, guide: rgb::ThreadGuide) { 172 | let mut bfs = VecDeque::from([guide.p]); 173 | while let Some(cur) = bfs.pop_front() { 174 | match monitor.lock() { 175 | Ok(mut lk) => { 176 | if lk.count == lk.map.distances.len() { 177 | return; 178 | } 179 | let run = lk 180 | .map 181 | .distances 182 | .get(&cur) 183 | .expect("Could not find map entry?"); 184 | let before = lk.maze.get(cur.row, cur.col); 185 | if !rgb::has_paint_vals(before) { 186 | let intensity = (lk.map.max - run) as f64 / lk.map.max as f64; 187 | let dark = (255f64 * intensity) as u8; 188 | let bright = 128 + (127f64 * intensity) as u8; 189 | let mut c: rgb::Rgb = [dark, dark, dark]; 190 | c[guide.color_i] = bright; 191 | lk.maze.solve_history.push(maze::Delta { 192 | id: cur, 193 | before, 194 | after: before 195 | | ((c[0] as u32) << rgb::RED_SHIFT) 196 | | ((c[1] as u32) << rgb::GREEN_SHIFT) 197 | | (c[2] as u32), 198 | burst: 1, 199 | }); 200 | *lk.maze.get_mut(cur.row, cur.col) |= ((c[0] as u32) << rgb::RED_SHIFT) 201 | | ((c[1] as u32) << rgb::GREEN_SHIFT) 202 | | (c[2] as u32); 203 | lk.count += 1; 204 | } 205 | } 206 | Err(p) => print::maze_panic!("Thread panicked with lock: {}", p), 207 | }; 208 | let mut i = guide.bias; 209 | for _ in 0..rgb::NUM_DIRECTIONS { 210 | let p = &maze::CARDINAL_DIRECTIONS[i]; 211 | let next = maze::Point { 212 | row: cur.row + p.row, 213 | col: cur.col + p.col, 214 | }; 215 | if match monitor.lock() { 216 | Err(p) => print::maze_panic!("Panic with lock: {}", p), 217 | Ok(mut lk) => { 218 | let nxt = lk.maze.get(next.row, next.col); 219 | let seen = (nxt & guide.cache) == 0; 220 | let is_path = maze::is_path(nxt); 221 | if seen && is_path { 222 | *lk.maze.get_mut(next.row, next.col) |= guide.cache; 223 | } 224 | seen && is_path 225 | } 226 | } { 227 | bfs.push_back(next); 228 | } 229 | i = (i + 1) % rgb::NUM_PAINTERS; 230 | } 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /maze_tui/builders/src/modify.rs: -------------------------------------------------------------------------------- 1 | use crate::build; 2 | use maze; 3 | 4 | /// 5 | /// Data only maze generator 6 | /// 7 | pub fn add_cross(monitor: monitor::MazeMonitor) { 8 | let mut lk = match monitor.lock() { 9 | Ok(l) => l, 10 | Err(_) => print::maze_panic!("uncontested lock failure"), 11 | }; 12 | for r in 0..lk.maze.rows() { 13 | for c in 0..lk.maze.cols() { 14 | if (r == lk.maze.rows() / 2 && c > 1 && c < lk.maze.cols() - 2) 15 | || (c == lk.maze.cols() / 2 && r > 1 && r < lk.maze.rows() - 2) 16 | { 17 | build::build_path(&mut lk.maze, maze::Point { row: r, col: c }); 18 | if c + 1 < lk.maze.cols() - 2 { 19 | build::build_path(&mut lk.maze, maze::Point { row: r, col: c + 1 }); 20 | } 21 | } 22 | } 23 | } 24 | } 25 | 26 | pub fn add_x(monitor: monitor::MazeMonitor) { 27 | let mut lk = match monitor.lock() { 28 | Ok(l) => l, 29 | Err(_) => print::maze_panic!("uncontested lock failure"), 30 | }; 31 | for r in 1..lk.maze.rows() - 1 { 32 | for c in 1..lk.maze.cols() - 1 { 33 | add_positive_slope(&mut lk.maze, maze::Point { row: r, col: c }); 34 | add_negative_slope(&mut lk.maze, maze::Point { row: r, col: c }); 35 | } 36 | } 37 | } 38 | 39 | fn add_positive_slope(maze: &mut maze::Maze, p: maze::Point) { 40 | let row_size = maze.rows() as f32 - 2.0f32; 41 | let col_size = maze.cols() as f32 - 2.0f32; 42 | let cur_row = p.row as f32; 43 | let slope = (2.0f32 - row_size) / (2.0f32 - col_size); 44 | let b = 2.0f32 - (2.0f32 * slope); 45 | let on_slope = ((cur_row - b) / slope) as i32; 46 | if p.col == on_slope && p.col < maze.cols() - 2 && p.col > 1 { 47 | build::build_path(maze, p); 48 | if p.col + 1 < maze.cols() - 2 { 49 | build::build_path( 50 | maze, 51 | maze::Point { 52 | row: p.row, 53 | col: p.col + 1, 54 | }, 55 | ); 56 | } 57 | if p.col - 1 > 1 { 58 | build::build_path( 59 | maze, 60 | maze::Point { 61 | row: p.row, 62 | col: p.col - 1, 63 | }, 64 | ); 65 | } 66 | if p.col + 2 < maze.cols() - 2 { 67 | build::build_path( 68 | maze, 69 | maze::Point { 70 | row: p.row, 71 | col: p.col + 2, 72 | }, 73 | ); 74 | } 75 | if p.col - 2 > 1 { 76 | build::build_path( 77 | maze, 78 | maze::Point { 79 | row: p.row, 80 | col: p.col - 2, 81 | }, 82 | ); 83 | } 84 | } 85 | } 86 | 87 | fn add_negative_slope(maze: &mut maze::Maze, p: maze::Point) { 88 | let row_size = maze.rows() as f32 - 2.0f32; 89 | let col_size = maze.cols() as f32 - 2.0f32; 90 | let cur_row = p.row as f32; 91 | let slope = (2.0f32 - row_size) / (col_size - 2.0f32); 92 | let b = row_size - (2.0f32 * slope); 93 | let on_line = ((cur_row - b) / slope) as i32; 94 | if p.col == on_line && p.col > 1 && p.col < maze.cols() - 2 && p.row < maze.rows() - 2 { 95 | build::build_path(maze, p); 96 | if p.col + 1 < maze.cols() - 2 { 97 | build::build_path( 98 | maze, 99 | maze::Point { 100 | row: p.row, 101 | col: p.col + 1, 102 | }, 103 | ); 104 | } 105 | if p.col - 1 > 1 { 106 | build::build_path( 107 | maze, 108 | maze::Point { 109 | row: p.row, 110 | col: p.col - 1, 111 | }, 112 | ); 113 | } 114 | if p.col + 2 < maze.cols() - 2 { 115 | build::build_path( 116 | maze, 117 | maze::Point { 118 | row: p.row, 119 | col: p.col + 2, 120 | }, 121 | ); 122 | } 123 | if p.col - 2 > 1 { 124 | build::build_path( 125 | maze, 126 | maze::Point { 127 | row: p.row, 128 | col: p.col - 2, 129 | }, 130 | ); 131 | } 132 | } 133 | } 134 | 135 | /// 136 | /// History based generator for animation and playback. 137 | /// 138 | pub fn add_cross_history(monitor: monitor::MazeMonitor) { 139 | let mut lk = match monitor.lock() { 140 | Ok(l) => l, 141 | Err(_) => print::maze_panic!("uncontested lock failure"), 142 | }; 143 | for r in 0..lk.maze.rows() { 144 | for c in 0..lk.maze.cols() { 145 | if (r == lk.maze.rows() / 2 && c > 1 && c < lk.maze.cols() - 2) 146 | || (c == lk.maze.cols() / 2 && r > 1 && r < lk.maze.rows() - 2) 147 | { 148 | build::build_path_history(&mut lk.maze, maze::Point { row: r, col: c }); 149 | if c + 1 < lk.maze.cols() - 2 { 150 | build::build_path_history(&mut lk.maze, maze::Point { row: r, col: c + 1 }); 151 | } 152 | } 153 | } 154 | } 155 | } 156 | 157 | pub fn add_x_history(monitor: monitor::MazeMonitor) { 158 | let mut lk = match monitor.lock() { 159 | Ok(l) => l, 160 | Err(_) => print::maze_panic!("uncontested lock failure"), 161 | }; 162 | for r in 1..lk.maze.rows() - 1 { 163 | for c in 1..lk.maze.cols() - 1 { 164 | add_positive_slope_history(&mut lk.maze, maze::Point { row: r, col: c }); 165 | add_negative_slope_history(&mut lk.maze, maze::Point { row: r, col: c }); 166 | } 167 | } 168 | } 169 | 170 | fn add_positive_slope_history(maze: &mut maze::Maze, p: maze::Point) { 171 | let row_size = maze.rows() as f32 - 2.0f32; 172 | let col_size = maze.cols() as f32 - 2.0f32; 173 | let cur_row = p.row as f32; 174 | let slope = (2.0f32 - row_size) / (2.0f32 - col_size); 175 | let b = 2.0f32 - (2.0f32 * slope); 176 | let on_slope = ((cur_row - b) / slope) as i32; 177 | if p.col == on_slope && p.col < maze.cols() - 2 && p.col > 1 { 178 | build::build_path_history(maze, p); 179 | if p.col + 1 < maze.cols() - 2 { 180 | build::build_path_history( 181 | maze, 182 | maze::Point { 183 | row: p.row, 184 | col: p.col + 1, 185 | }, 186 | ); 187 | } 188 | if p.col - 1 > 1 { 189 | build::build_path_history( 190 | maze, 191 | maze::Point { 192 | row: p.row, 193 | col: p.col - 1, 194 | }, 195 | ); 196 | } 197 | if p.col + 2 < maze.cols() - 2 { 198 | build::build_path_history( 199 | maze, 200 | maze::Point { 201 | row: p.row, 202 | col: p.col + 2, 203 | }, 204 | ); 205 | } 206 | if p.col - 2 > 1 { 207 | build::build_path_history( 208 | maze, 209 | maze::Point { 210 | row: p.row, 211 | col: p.col - 2, 212 | }, 213 | ); 214 | } 215 | } 216 | } 217 | 218 | fn add_negative_slope_history(maze: &mut maze::Maze, p: maze::Point) { 219 | let row_size = maze.rows() as f32 - 2.0f32; 220 | let col_size = maze.cols() as f32 - 2.0f32; 221 | let cur_row = p.row as f32; 222 | let slope = (2.0f32 - row_size) / (col_size - 2.0f32); 223 | let b = row_size - (2.0f32 * slope); 224 | let on_line = ((cur_row - b) / slope) as i32; 225 | if p.col == on_line && p.col > 1 && p.col < maze.cols() - 2 && p.row < maze.rows() - 2 { 226 | build::build_path_history(maze, p); 227 | if p.col + 1 < maze.cols() - 2 { 228 | build::build_path_history( 229 | maze, 230 | maze::Point { 231 | row: p.row, 232 | col: p.col + 1, 233 | }, 234 | ); 235 | } 236 | if p.col - 1 > 1 { 237 | build::build_path_history( 238 | maze, 239 | maze::Point { 240 | row: p.row, 241 | col: p.col - 1, 242 | }, 243 | ); 244 | } 245 | if p.col + 2 < maze.cols() - 2 { 246 | build::build_path_history( 247 | maze, 248 | maze::Point { 249 | row: p.row, 250 | col: p.col + 2, 251 | }, 252 | ); 253 | } 254 | if p.col - 2 > 1 { 255 | build::build_path_history( 256 | maze, 257 | maze::Point { 258 | row: p.row, 259 | col: p.col - 2, 260 | }, 261 | ); 262 | } 263 | } 264 | } 265 | -------------------------------------------------------------------------------- /maze_tui/tables/src/lib.rs: -------------------------------------------------------------------------------- 1 | use rand::seq::SliceRandom; 2 | 3 | pub use builders::arena; 4 | pub use builders::eller; 5 | pub use builders::grid; 6 | pub use builders::hunt_kill; 7 | pub use builders::kruskal; 8 | pub use builders::modify; 9 | pub use builders::prim; 10 | pub use builders::recursive_backtracker; 11 | pub use builders::recursive_subdivision; 12 | pub use builders::wilson_adder; 13 | pub use builders::wilson_carver; 14 | pub use monitor; 15 | pub use painters::distance; 16 | pub use painters::rgb; 17 | pub use painters::runs; 18 | pub use solvers::bfs; 19 | pub use solvers::dfs; 20 | pub use solvers::floodfs; 21 | pub use solvers::rdfs; 22 | pub use solvers::solve; 23 | 24 | pub type BuildHistoryFunction = fn(monitor::MazeMonitor); 25 | pub type SolveHistoryFunction = fn(monitor::MazeMonitor); 26 | 27 | pub struct FlagArg<'a, 'b> { 28 | pub flag: &'a str, 29 | pub arg: &'b str, 30 | } 31 | 32 | #[derive(Clone, Copy)] 33 | pub enum ViewingMode { 34 | StaticImage = 0, 35 | AnimatedPlayback, 36 | } 37 | 38 | #[derive(Clone, Copy)] 39 | pub struct HistoryRunner { 40 | pub args: maze::MazeArgs, 41 | pub build: BuildHistoryType, 42 | pub modify: Option, 43 | pub solve: SolveHistoryType, 44 | } 45 | 46 | #[derive(Clone, Copy, PartialEq, Eq)] 47 | pub enum BuildHistoryType { 48 | Arena = 0, 49 | RecursiveBacktracker, 50 | HuntKill, 51 | RecursiveSubdivision, 52 | Prim, 53 | Kruskal, 54 | Eller, 55 | WilsonCarver, 56 | WilsonAdder, 57 | Grid, 58 | } 59 | 60 | #[derive(Clone, Copy, PartialEq, Eq)] 61 | pub enum SolveHistoryType { 62 | DfsHunt = 0, 63 | DfsGather, 64 | DfsCorner, 65 | RdfsHunt, 66 | RdfsGather, 67 | RdfsCorner, 68 | BfsHunt, 69 | BfsGather, 70 | BfsCorner, 71 | FdfsHunt, 72 | FdfsGather, 73 | FdfsCorner, 74 | Distance, 75 | Runs, 76 | } 77 | 78 | #[derive(Clone, Copy, PartialEq, Eq)] 79 | pub enum ModificationHistoryType { 80 | Cross = 0, 81 | X, 82 | } 83 | 84 | impl HistoryRunner { 85 | pub fn new() -> Self { 86 | Self { 87 | args: maze::MazeArgs { 88 | odd_rows: 33, 89 | odd_cols: 111, 90 | offset: maze::Offset::default(), 91 | style: maze::MazeStyle::Sharp, 92 | }, 93 | build: BuildHistoryType::RecursiveBacktracker, 94 | modify: None, 95 | solve: SolveHistoryType::DfsHunt, 96 | } 97 | } 98 | } 99 | 100 | impl Default for HistoryRunner { 101 | fn default() -> Self { 102 | Self::new() 103 | } 104 | } 105 | 106 | fn search_table(arg: &str, table: &[(&str, T)]) -> Option 107 | where 108 | T: Clone, 109 | { 110 | table 111 | .iter() 112 | .find(|(s, _)| *s == arg) 113 | .map(|(_, t)| t.clone()) 114 | } 115 | 116 | pub fn match_flag(arg: &str) -> Option<&str> { 117 | search_table(arg, &FLAGS) 118 | } 119 | 120 | pub fn match_builder(arg: &str) -> Option { 121 | search_table(arg, &HISTORY_BUILDERS) 122 | } 123 | 124 | pub fn match_modifier(arg: &str) -> Option { 125 | search_table(arg, &HISTORY_MODIFICATIONS) 126 | } 127 | 128 | pub fn match_solver(arg: &str) -> Option { 129 | search_table(arg, &HISTORY_SOLVERS) 130 | } 131 | 132 | pub fn match_walls(arg: &str) -> Option { 133 | search_table(arg, &WALL_STYLES) 134 | } 135 | 136 | impl BuildHistoryType { 137 | pub fn get_fn(&self) -> BuildHistoryFunction { 138 | BUILD_FN_TABLE[*self as usize] 139 | } 140 | 141 | pub fn get_description(&self) -> &str { 142 | BUILD_DESCRIPTIONS_TABLE[*self as usize] 143 | } 144 | 145 | pub fn get_random(rng: &mut rand::rngs::ThreadRng) -> BuildHistoryType { 146 | *ALL_BUILDER_TYPES 147 | .choose(rng) 148 | .expect("cannot choose random builder") 149 | } 150 | } 151 | 152 | impl SolveHistoryType { 153 | pub fn get_fn(&self) -> SolveHistoryFunction { 154 | SOLVE_FN_TABLE[*self as usize] 155 | } 156 | 157 | pub fn get_random(rng: &mut rand::rngs::ThreadRng) -> SolveHistoryType { 158 | *ALL_SOLVER_TYPES 159 | .choose(rng) 160 | .expect("cannot choose random solver") 161 | } 162 | } 163 | 164 | impl ModificationHistoryType { 165 | pub fn get_fn(&self) -> BuildHistoryFunction { 166 | MODIFICATION_FN_TABLE[*self as usize] 167 | } 168 | 169 | pub fn get_random(rng: &mut rand::rngs::ThreadRng) -> ModificationHistoryType { 170 | *ALL_MODIFICATION_TYPES 171 | .choose(rng) 172 | .expect("cannot modify the maze") 173 | } 174 | } 175 | 176 | static FLAGS: [(&str, &str); 6] = [ 177 | ("-b", "-b"), 178 | ("-m", "-m"), 179 | ("-s", "-s"), 180 | ("-w", "-w"), 181 | ("-sa", "-sa"), 182 | ("-ba", "-ba"), 183 | ]; 184 | 185 | static WALL_STYLES: [(&str, maze::MazeStyle); 8] = [ 186 | ("mini", maze::MazeStyle::Mini), 187 | ("sharp", maze::MazeStyle::Sharp), 188 | ("round", maze::MazeStyle::Round), 189 | ("doubles", maze::MazeStyle::Doubles), 190 | ("bold", maze::MazeStyle::Bold), 191 | ("contrast", maze::MazeStyle::Contrast), 192 | ("half", maze::MazeStyle::Half), 193 | ("spikes", maze::MazeStyle::Spikes), 194 | ]; 195 | 196 | static HISTORY_BUILDERS: [(&str, BuildHistoryType); 10] = [ 197 | ("arena", BuildHistoryType::Arena), 198 | ("rdfs", BuildHistoryType::RecursiveBacktracker), 199 | ("hunt-kill", BuildHistoryType::HuntKill), 200 | ("fractal", BuildHistoryType::RecursiveSubdivision), 201 | ("prim", BuildHistoryType::Prim), 202 | ("kruskal", BuildHistoryType::Kruskal), 203 | ("eller", BuildHistoryType::Eller), 204 | ("wilson", BuildHistoryType::WilsonCarver), 205 | ("wilson-walls", BuildHistoryType::WilsonAdder), 206 | ("grid", BuildHistoryType::Grid), 207 | ]; 208 | 209 | static BUILD_FN_TABLE: [BuildHistoryFunction; 10] = [ 210 | arena::generate_history, 211 | recursive_backtracker::generate_history, 212 | hunt_kill::generate_history, 213 | recursive_subdivision::generate_history, 214 | prim::generate_history, 215 | kruskal::generate_history, 216 | eller::generate_history, 217 | wilson_carver::generate_history, 218 | wilson_adder::generate_history, 219 | grid::generate_history, 220 | ]; 221 | 222 | static ALL_BUILDER_TYPES: [BuildHistoryType; 10] = [ 223 | BuildHistoryType::Arena, 224 | BuildHistoryType::RecursiveBacktracker, 225 | BuildHistoryType::HuntKill, 226 | BuildHistoryType::RecursiveSubdivision, 227 | BuildHistoryType::Prim, 228 | BuildHistoryType::Kruskal, 229 | BuildHistoryType::Eller, 230 | BuildHistoryType::WilsonCarver, 231 | BuildHistoryType::WilsonAdder, 232 | BuildHistoryType::Grid, 233 | ]; 234 | 235 | static BUILD_DESCRIPTIONS_TABLE: [&str; 10] = [ 236 | include_str!("../../res/arena.txt"), 237 | include_str!("../../res/recursive_backtracker.txt"), 238 | include_str!("../../res/hunt_kill.txt"), 239 | include_str!("../../res/recursive_subdivision.txt"), 240 | include_str!("../../res/prim.txt"), 241 | include_str!("../../res/kruskal.txt"), 242 | include_str!("../../res/eller.txt"), 243 | include_str!("../../res/wilson_carver.txt"), 244 | include_str!("../../res/wilson_adder.txt"), 245 | include_str!("../../res/grid.txt"), 246 | ]; 247 | 248 | static HISTORY_MODIFICATIONS: [(&str, ModificationHistoryType); 2] = [ 249 | ("cross", ModificationHistoryType::Cross), 250 | ("x", ModificationHistoryType::X), 251 | ]; 252 | 253 | static MODIFICATION_FN_TABLE: [BuildHistoryFunction; 2] = 254 | [modify::add_cross_history, modify::add_x_history]; 255 | 256 | static ALL_MODIFICATION_TYPES: [ModificationHistoryType; 2] = 257 | [ModificationHistoryType::Cross, ModificationHistoryType::X]; 258 | 259 | static HISTORY_SOLVERS: [(&str, SolveHistoryType); 14] = [ 260 | ("dfs-hunt", SolveHistoryType::DfsHunt), 261 | ("dfs-gather", SolveHistoryType::DfsGather), 262 | ("dfs-corner", SolveHistoryType::DfsCorner), 263 | ("rdfs-hunt", SolveHistoryType::RdfsHunt), 264 | ("rdfs-gather", SolveHistoryType::RdfsGather), 265 | ("rdfs-corner", SolveHistoryType::RdfsCorner), 266 | ("bfs-hunt", SolveHistoryType::BfsHunt), 267 | ("bfs-gather", SolveHistoryType::BfsGather), 268 | ("bfs-corner", SolveHistoryType::BfsCorner), 269 | ("floodfs-hunt", SolveHistoryType::FdfsHunt), 270 | ("floodfs-gather", SolveHistoryType::FdfsGather), 271 | ("floodfs-corner", SolveHistoryType::FdfsCorner), 272 | ("distance", SolveHistoryType::Distance), 273 | ("runs", SolveHistoryType::Runs), 274 | ]; 275 | 276 | static SOLVE_FN_TABLE: [SolveHistoryFunction; 14] = [ 277 | dfs::hunt_history, 278 | dfs::gather_history, 279 | dfs::corner_history, 280 | rdfs::hunt_history, 281 | rdfs::gather_history, 282 | rdfs::corner_history, 283 | bfs::hunt_history, 284 | bfs::gather_history, 285 | bfs::corner_history, 286 | floodfs::hunt_history, 287 | floodfs::gather_history, 288 | floodfs::corner_history, 289 | distance::paint_distance_from_center_history, 290 | runs::paint_run_lengths_history, 291 | ]; 292 | 293 | static ALL_SOLVER_TYPES: [SolveHistoryType; 14] = [ 294 | SolveHistoryType::DfsHunt, 295 | SolveHistoryType::DfsGather, 296 | SolveHistoryType::DfsCorner, 297 | SolveHistoryType::RdfsHunt, 298 | SolveHistoryType::RdfsGather, 299 | SolveHistoryType::RdfsCorner, 300 | SolveHistoryType::BfsHunt, 301 | SolveHistoryType::BfsGather, 302 | SolveHistoryType::BfsCorner, 303 | SolveHistoryType::FdfsHunt, 304 | SolveHistoryType::FdfsGather, 305 | SolveHistoryType::FdfsCorner, 306 | SolveHistoryType::Distance, 307 | SolveHistoryType::Runs, 308 | ]; 309 | -------------------------------------------------------------------------------- /maze_tui/builders/src/eller.rs: -------------------------------------------------------------------------------- 1 | use crate::build; 2 | use maze; 3 | use print; 4 | 5 | use rand::{ 6 | distributions::{Bernoulli, Distribution}, 7 | thread_rng, Rng, 8 | }; 9 | use std::collections::HashMap; 10 | 11 | const WINDOW_SIZE: usize = 2; 12 | const DROP_DIST: i32 = 2; 13 | const NEIGHBOR_DIST: i32 = 2; 14 | 15 | type SetId = usize; 16 | 17 | struct SlidingSetWindow { 18 | cur_row: usize, 19 | width: usize, 20 | next_available_id: SetId, 21 | sets: Vec, 22 | } 23 | 24 | struct IdMergeRequest { 25 | winner: SetId, 26 | loser: SetId, 27 | } 28 | 29 | /// 30 | /// Data only maze generator 31 | /// 32 | pub fn generate_maze(monitor: monitor::MazeMonitor) { 33 | let mut lk = match monitor.lock() { 34 | Ok(l) => l, 35 | Err(_) => print::maze_panic!("uncontested lock failure"), 36 | }; 37 | build::fill_maze_with_walls(&mut lk.maze); 38 | let mut rng = thread_rng(); 39 | let coin = Bernoulli::new(0.66); 40 | let mut window = SlidingSetWindow::new(&lk.maze); 41 | let mut sets_in_this_row: HashMap> = HashMap::new(); 42 | for r in (1..lk.maze.rows() - 2).step_by(2) { 43 | window.generate_sets(window.next_row_i()); 44 | for c in (1..lk.maze.cols() - 1).step_by(2) { 45 | let cur_id = window.get(window.cur_row, c as usize); 46 | let next = maze::Point { 47 | row: r, 48 | col: c + NEIGHBOR_DIST, 49 | }; 50 | if !build::is_square_within_perimeter_walls(&lk.maze, next) 51 | || cur_id == window.get(window.cur_row, next.col as usize) 52 | || !coin.expect("Bernoulli coin flip broke").sample(&mut rng) 53 | { 54 | continue; 55 | } 56 | let neighbor_id = window.get(window.cur_row, next.col as usize); 57 | build::join_squares(&mut lk.maze, maze::Point { row: r, col: c }, next); 58 | merge_cur_row_sets( 59 | &mut window, 60 | IdMergeRequest { 61 | winner: cur_id, 62 | loser: neighbor_id, 63 | }, 64 | ); 65 | } 66 | 67 | for c in (1..lk.maze.cols() - 1).step_by(2) { 68 | sets_in_this_row 69 | .entry(window.get(window.cur_row, c as usize)) 70 | .or_default() 71 | .push(maze::Point { row: r, col: c }); 72 | } 73 | 74 | for set in sets_in_this_row.iter() { 75 | for _drop in 0..rng.gen_range(1..=set.1.len()) { 76 | let chose: &maze::Point = &set.1[rng.gen_range(0..set.1.len())]; 77 | if (lk.maze.get(chose.row + DROP_DIST, chose.col) & build::BUILDER_BIT) != 0 { 78 | continue; 79 | } 80 | let next_row = window.next_row_i(); 81 | *window.get_mut(next_row, chose.col as usize) = *set.0; 82 | build::join_squares( 83 | &mut lk.maze, 84 | *chose, 85 | maze::Point { 86 | row: chose.row + DROP_DIST, 87 | col: chose.col, 88 | }, 89 | ); 90 | } 91 | } 92 | window.slide_window(); 93 | sets_in_this_row.clear(); 94 | } 95 | complete_final_row(&mut lk.maze, &mut window); 96 | } 97 | 98 | fn merge_cur_row_sets(window: &mut SlidingSetWindow, request: IdMergeRequest) { 99 | let row = window.cur_row; 100 | for id in window.row(row) { 101 | if *id == request.loser { 102 | *id = request.winner; 103 | } 104 | } 105 | } 106 | 107 | fn complete_final_row(maze: &mut maze::Maze, window: &mut SlidingSetWindow) { 108 | let r = maze.rows() - 2; 109 | let set_r = window.cur_row; 110 | for c in (1..maze.cols() - 2).step_by(2) { 111 | let this_id = window.get(set_r, c as usize); 112 | let next = maze::Point { 113 | row: r, 114 | col: c + NEIGHBOR_DIST, 115 | }; 116 | let neighbor_id = window.get(set_r, (c + NEIGHBOR_DIST) as usize); 117 | if this_id == neighbor_id { 118 | continue; 119 | } 120 | build::join_squares(maze, maze::Point { row: r, col: c }, next); 121 | for set_elem in (next.col..maze.cols() - 1).step_by(2) { 122 | if window.get(set_r, set_elem as usize) == neighbor_id { 123 | *window.get_mut(set_r, set_elem as usize) = this_id; 124 | } 125 | } 126 | } 127 | } 128 | 129 | /// 130 | /// History based generator for animation and playback. 131 | /// 132 | pub fn generate_history(monitor: monitor::MazeMonitor) { 133 | let mut lk = match monitor.lock() { 134 | Ok(l) => l, 135 | Err(_) => print::maze_panic!("uncontested lock failure"), 136 | }; 137 | build::fill_maze_history_with_walls(&mut lk.maze); 138 | let mut rng = thread_rng(); 139 | let coin = Bernoulli::new(0.66); 140 | let mut window = SlidingSetWindow::new(&lk.maze); 141 | let mut sets_in_this_row: HashMap> = HashMap::new(); 142 | for r in (1..lk.maze.rows() - 2).step_by(2) { 143 | window.generate_sets(window.next_row_i()); 144 | for c in (1..lk.maze.cols() - 1).step_by(2) { 145 | let cur_id = window.get(window.cur_row, c as usize); 146 | let next = maze::Point { 147 | row: r, 148 | col: c + NEIGHBOR_DIST, 149 | }; 150 | if !build::is_square_within_perimeter_walls(&lk.maze, next) 151 | || cur_id == window.get(window.cur_row, next.col as usize) 152 | || !coin.expect("Bernoulli coin flip broke").sample(&mut rng) 153 | { 154 | continue; 155 | } 156 | let neighbor_id = window.get(window.cur_row, next.col as usize); 157 | build::join_squares_history(&mut lk.maze, maze::Point { row: r, col: c }, next); 158 | merge_cur_row_sets( 159 | &mut window, 160 | IdMergeRequest { 161 | winner: cur_id, 162 | loser: neighbor_id, 163 | }, 164 | ); 165 | } 166 | 167 | for c in (1..lk.maze.cols() - 1).step_by(2) { 168 | sets_in_this_row 169 | .entry(window.get(window.cur_row, c as usize)) 170 | .or_default() 171 | .push(maze::Point { row: r, col: c }); 172 | } 173 | 174 | for set in sets_in_this_row.iter() { 175 | for _drop in 0..rng.gen_range(1..=set.1.len()) { 176 | let chose: &maze::Point = &set.1[rng.gen_range(0..set.1.len())]; 177 | if build::is_built(lk.maze.get(chose.row + DROP_DIST, chose.col)) { 178 | continue; 179 | } 180 | let next_row = window.next_row_i(); 181 | *window.get_mut(next_row, chose.col as usize) = *set.0; 182 | build::join_squares_history( 183 | &mut lk.maze, 184 | *chose, 185 | maze::Point { 186 | row: chose.row + DROP_DIST, 187 | col: chose.col, 188 | }, 189 | ); 190 | } 191 | } 192 | window.slide_window(); 193 | sets_in_this_row.clear(); 194 | } 195 | complete_final_row_history(&mut lk.maze, &mut window); 196 | } 197 | 198 | fn complete_final_row_history(maze: &mut maze::Maze, window: &mut SlidingSetWindow) { 199 | let r = maze.rows() - 2; 200 | let set_r = window.cur_row; 201 | for c in (1..maze.cols() - 2).step_by(2) { 202 | let this_id = window.get(set_r, c as usize); 203 | let next = maze::Point { 204 | row: r, 205 | col: c + NEIGHBOR_DIST, 206 | }; 207 | let neighbor_id = window.get(set_r, (c + NEIGHBOR_DIST) as usize); 208 | if this_id == neighbor_id { 209 | continue; 210 | } 211 | build::join_squares_history(maze, maze::Point { row: r, col: c }, next); 212 | for set_elem in (next.col..maze.cols() - 1).step_by(2) { 213 | if window.get(set_r, set_elem as usize) == neighbor_id { 214 | *window.get_mut(set_r, set_elem as usize) = this_id; 215 | } 216 | } 217 | } 218 | } 219 | 220 | /// 221 | /// Helper data structure implementation for managing sets available to all. 222 | /// 223 | impl SlidingSetWindow { 224 | fn new(maze: &maze::Maze) -> Self { 225 | let mut ids = vec![0; WINDOW_SIZE * maze.cols() as usize]; 226 | for (i, v) in ids.iter_mut().enumerate() { 227 | *v = i; 228 | } 229 | Self { 230 | cur_row: 0, 231 | width: maze.cols() as usize, 232 | next_available_id: maze.cols() as usize, 233 | sets: ids, 234 | } 235 | } 236 | 237 | fn slide_window(&mut self) { 238 | self.cur_row = (self.cur_row + 1) % 2; 239 | } 240 | 241 | fn next_row_i(&self) -> usize { 242 | (self.cur_row + 1) % WINDOW_SIZE 243 | } 244 | 245 | fn get(&self, row: usize, col: usize) -> SetId { 246 | self.sets[row * self.width + col] 247 | } 248 | 249 | fn get_mut(&mut self, row: usize, col: usize) -> &mut SetId { 250 | &mut self.sets[row * self.width + col] 251 | } 252 | 253 | fn row(&mut self, row: usize) -> &mut [SetId] { 254 | &mut self.sets[(row * self.width)..(row * self.width + self.width)] 255 | } 256 | 257 | fn generate_sets(&mut self, row: usize) { 258 | if row >= WINDOW_SIZE { 259 | print::maze_panic!( 260 | "Cannot generate sets for a row that does not exist, row: {}", 261 | row 262 | ); 263 | } 264 | for e in &mut self.sets[(row * self.width)..(row * self.width + self.width)].iter_mut() { 265 | *e = self.next_available_id; 266 | self.next_available_id += 1; 267 | } 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /maze_tui/builders/src/hunt_kill.rs: -------------------------------------------------------------------------------- 1 | use crate::build; 2 | use maze; 3 | use rand::{seq::SliceRandom, thread_rng, Rng}; 4 | 5 | type DirectionMarker = build::BacktrackMarker; 6 | 7 | const GOING_NORTH: DirectionMarker = build::FROM_NORTH; 8 | const GOING_EAST: DirectionMarker = build::FROM_EAST; 9 | const GOING_SOUTH: DirectionMarker = build::FROM_SOUTH; 10 | const GOING_WEST: DirectionMarker = build::FROM_WEST; 11 | 12 | /// 13 | /// Data only maze generator 14 | /// 15 | pub fn generate_maze(monitor: monitor::MazeMonitor) { 16 | let mut lk = match monitor.lock() { 17 | Ok(l) => l, 18 | Err(_) => print::maze_panic!("uncontested lock failure"), 19 | }; 20 | build::fill_maze_with_walls(&mut lk.maze); 21 | let mut gen = thread_rng(); 22 | let start: maze::Point = maze::Point { 23 | row: 2 * (gen.gen_range(1..lk.maze.rows() - 2) / 2) + 1, 24 | col: 2 * (gen.gen_range(1..lk.maze.cols() - 2) / 2) + 1, 25 | }; 26 | let mut random_direction_indices: [usize; 4] = [0, 1, 2, 3]; 27 | let mut cur: maze::Point = start; 28 | let mut highest_completed_row = 1; 29 | 'carving: loop { 30 | random_direction_indices.shuffle(&mut gen); 31 | for &i in random_direction_indices.iter() { 32 | let direction = &build::GENERATE_DIRECTIONS[i]; 33 | let branch = maze::Point { 34 | row: cur.row + direction.row, 35 | col: cur.col + direction.col, 36 | }; 37 | if build::can_build_new_square(&lk.maze, branch) { 38 | build::join_squares(&mut lk.maze, cur, branch); 39 | cur = branch; 40 | continue 'carving; 41 | } 42 | } 43 | 44 | let mut set_highest_completed_row = false; 45 | for r in (highest_completed_row..lk.maze.rows() - 1).step_by(2) { 46 | for c in (1..lk.maze.cols() - 1).step_by(2) { 47 | let start_candidate = maze::Point { row: r, col: c }; 48 | if !build::is_built(lk.maze.get(r, c)) { 49 | if !set_highest_completed_row { 50 | highest_completed_row = r; 51 | set_highest_completed_row = true; 52 | } 53 | for dir in &build::GENERATE_DIRECTIONS { 54 | let next = maze::Point { 55 | row: r + dir.row, 56 | col: c + dir.col, 57 | }; 58 | if build::is_square_within_perimeter_walls(&lk.maze, next) 59 | && build::is_built(lk.maze.get(next.row, next.col)) 60 | { 61 | build::join_squares(&mut lk.maze, start_candidate, next); 62 | cur = start_candidate; 63 | continue 'carving; 64 | } 65 | } 66 | } 67 | } 68 | } 69 | return; 70 | } 71 | } 72 | 73 | /// 74 | /// History based generator for animation and playback. 75 | /// 76 | pub fn generate_history(monitor: monitor::MazeMonitor) { 77 | let mut lk = match monitor.lock() { 78 | Ok(l) => l, 79 | Err(_) => print::maze_panic!("uncontested lock failure"), 80 | }; 81 | build::fill_maze_history_with_walls(&mut lk.maze); 82 | let mut gen = thread_rng(); 83 | let start: maze::Point = maze::Point { 84 | row: 2 * (gen.gen_range(1..lk.maze.rows() - 2) / 2) + 1, 85 | col: 2 * (gen.gen_range(1..lk.maze.cols() - 2) / 2) + 1, 86 | }; 87 | let mut random_direction_indices: [usize; 4] = [0, 1, 2, 3]; 88 | let mut cur: maze::Point = start; 89 | let mut highest_completed_row = 1; 90 | hunter_laser_history(&mut lk.maze, highest_completed_row); 91 | 'carving: loop { 92 | random_direction_indices.shuffle(&mut gen); 93 | for &i in random_direction_indices.iter() { 94 | let direction = &build::GENERATE_DIRECTIONS[i]; 95 | let branch = maze::Point { 96 | row: cur.row + direction.row, 97 | col: cur.col + direction.col, 98 | }; 99 | if build::can_build_new_square(&lk.maze, branch) { 100 | carve_forward_history(&mut lk.maze, cur, branch, highest_completed_row + 1); 101 | cur = branch; 102 | continue 'carving; 103 | } 104 | } 105 | let mut set_highest_completed_row = false; 106 | for r in (highest_completed_row..lk.maze.rows() - 1).step_by(2) { 107 | for c in (1..lk.maze.cols() - 1).step_by(2) { 108 | let start_candidate = maze::Point { row: r, col: c }; 109 | if !build::is_built(lk.maze.get(r, c)) { 110 | if !set_highest_completed_row { 111 | if r != highest_completed_row { 112 | reset_hunter_laser_history(&mut lk.maze, highest_completed_row); 113 | hunter_laser_history(&mut lk.maze, r); 114 | } 115 | highest_completed_row = r; 116 | set_highest_completed_row = true; 117 | } 118 | for dir in &build::GENERATE_DIRECTIONS { 119 | let next = maze::Point { 120 | row: r + dir.row, 121 | col: c + dir.col, 122 | }; 123 | if build::is_square_within_perimeter_walls(&lk.maze, next) 124 | && build::is_built(lk.maze.get(next.row, next.col)) 125 | { 126 | carve_forward_history( 127 | &mut lk.maze, 128 | start_candidate, 129 | next, 130 | highest_completed_row + 1, 131 | ); 132 | cur = start_candidate; 133 | continue 'carving; 134 | } 135 | } 136 | } 137 | } 138 | } 139 | reset_hunter_laser_history(&mut lk.maze, highest_completed_row); 140 | return; 141 | } 142 | } 143 | 144 | fn carve_forward_history(maze: &mut maze::Maze, cur: maze::Point, next: maze::Point, min_row: i32) { 145 | let mut wall: maze::Point = cur; 146 | let direction = if next.row < cur.row { 147 | wall.row -= 1; 148 | GOING_NORTH 149 | } else if next.row > cur.row { 150 | wall.row += 1; 151 | GOING_SOUTH 152 | } else if next.col < cur.col { 153 | wall.col -= 1; 154 | GOING_WEST 155 | } else if next.col > cur.col { 156 | wall.col += 1; 157 | GOING_EAST 158 | } else { 159 | print::maze_panic!("Wall break error. Cur: {:?} Next: {:?}", cur, next); 160 | }; 161 | carve_forward_wall_history(maze, cur, direction, min_row); 162 | carve_forward_wall_history(maze, wall, direction, min_row); 163 | carve_forward_wall_history(maze, next, direction, min_row); 164 | } 165 | 166 | fn carve_forward_wall_history( 167 | maze: &mut maze::Maze, 168 | p: maze::Point, 169 | direction: DirectionMarker, 170 | min_row: i32, 171 | ) { 172 | let mut wall_changes = [maze::Delta::default(); 5]; 173 | let mut burst = 1; 174 | let before = maze.get(p.row, p.col); 175 | let mut after = 176 | ((before & !build::MARKERS_MASK) & !maze::WALL_MASK) | maze::PATH_BIT | build::BUILDER_BIT; 177 | *maze.get_mut(p.row, p.col) = 178 | ((before & !build::MARKERS_MASK) & !maze::WALL_MASK) | maze::PATH_BIT | build::BUILDER_BIT; 179 | if p.row > min_row { 180 | after |= direction; 181 | *maze.get_mut(p.row, p.col) |= direction; 182 | } 183 | wall_changes[0] = maze::Delta { 184 | id: p, 185 | before, 186 | after, 187 | burst, 188 | }; 189 | if p.row > 0 { 190 | let square = maze.get(p.row - 1, p.col); 191 | wall_changes[burst] = maze::Delta { 192 | id: maze::Point { 193 | row: p.row - 1, 194 | col: p.col, 195 | }, 196 | before: square, 197 | after: square & !maze::SOUTH_WALL, 198 | burst: 1, 199 | }; 200 | burst += 1; 201 | *maze.get_mut(p.row - 1, p.col) &= !maze::SOUTH_WALL; 202 | } 203 | if p.row + 1 < maze.rows() { 204 | let square = maze.get(p.row + 1, p.col); 205 | wall_changes[burst] = maze::Delta { 206 | id: maze::Point { 207 | row: p.row + 1, 208 | col: p.col, 209 | }, 210 | before: square, 211 | after: square & !maze::NORTH_WALL, 212 | burst: 1, 213 | }; 214 | burst += 1; 215 | *maze.get_mut(p.row + 1, p.col) &= !maze::NORTH_WALL; 216 | } 217 | if p.col > 0 { 218 | let square = maze.get(p.row, p.col - 1); 219 | wall_changes[burst] = maze::Delta { 220 | id: maze::Point { 221 | row: p.row, 222 | col: p.col - 1, 223 | }, 224 | before: square, 225 | after: square & !maze::EAST_WALL, 226 | burst: 1, 227 | }; 228 | burst += 1; 229 | *maze.get_mut(p.row, p.col - 1) &= !maze::EAST_WALL; 230 | } 231 | if p.col + 1 < maze.cols() { 232 | let square = maze.get(p.row, p.col + 1); 233 | wall_changes[burst] = maze::Delta { 234 | id: maze::Point { 235 | row: p.row, 236 | col: p.col + 1, 237 | }, 238 | before: square, 239 | after: square & !maze::WEST_WALL, 240 | burst: 1, 241 | }; 242 | burst += 1; 243 | *maze.get_mut(p.row, p.col + 1) &= !maze::WEST_WALL; 244 | } 245 | wall_changes[0].burst = burst; 246 | wall_changes[burst - 1].burst = burst; 247 | maze.build_history.push_burst(&wall_changes[0..burst]); 248 | } 249 | 250 | fn hunter_laser_history(maze: &mut maze::Maze, current_row: i32) { 251 | let mut delta_vec = Vec::new(); 252 | for c in 0..maze.cols() { 253 | let square = maze.get(current_row, c); 254 | delta_vec.push(maze::Delta { 255 | id: maze::Point { 256 | row: current_row, 257 | col: c, 258 | }, 259 | before: square, 260 | after: (square & !build::MARKERS_MASK) | build::FROM_SOUTH, 261 | burst: 1, 262 | }); 263 | *maze.get_mut(current_row, c) = (square & !build::MARKERS_MASK) | build::FROM_SOUTH; 264 | } 265 | delta_vec[0].burst = maze.cols() as usize; 266 | delta_vec[(maze.cols() - 1) as usize].burst = maze.cols() as usize; 267 | maze.build_history.push_burst(delta_vec.as_slice()); 268 | } 269 | 270 | fn reset_hunter_laser_history(maze: &mut maze::Maze, current_row: i32) { 271 | if current_row != maze.rows() - 1 { 272 | let mut delta_vec = Vec::with_capacity((maze.cols() * 2) as usize); 273 | for c in 0..maze.cols() { 274 | let square = maze.get(current_row, c); 275 | delta_vec.push(maze::Delta { 276 | id: maze::Point { 277 | row: current_row, 278 | col: c, 279 | }, 280 | before: square, 281 | after: square & !build::MARKERS_MASK, 282 | burst: 1, 283 | }); 284 | *maze.get_mut(current_row, c) &= !build::MARKERS_MASK; 285 | let next_square = maze.get(current_row + 1, c); 286 | delta_vec.push(maze::Delta { 287 | id: maze::Point { 288 | row: current_row + 1, 289 | col: c, 290 | }, 291 | before: next_square, 292 | after: next_square & !build::MARKERS_MASK, 293 | burst: 1, 294 | }); 295 | *maze.get_mut(current_row + 1, c) &= !build::MARKERS_MASK; 296 | } 297 | delta_vec[0].burst = (maze.cols() * 2) as usize; 298 | delta_vec[(maze.cols() * 2 - 1) as usize].burst = (maze.cols() * 2) as usize; 299 | maze.build_history.push_burst(delta_vec.as_slice()); 300 | return; 301 | } 302 | let mut delta_vec = Vec::with_capacity(maze.cols() as usize); 303 | for c in 0..maze.cols() { 304 | let square = maze.get(current_row, c); 305 | delta_vec.push(maze::Delta { 306 | id: maze::Point { 307 | row: current_row, 308 | col: c, 309 | }, 310 | before: square, 311 | after: square & !build::MARKERS_MASK, 312 | burst: 1, 313 | }); 314 | *maze.get_mut(current_row, c) &= !build::MARKERS_MASK; 315 | } 316 | delta_vec[0].burst = maze.cols() as usize; 317 | delta_vec[(maze.cols() - 1) as usize].burst = maze.cols() as usize; 318 | maze.build_history.push_burst(delta_vec.as_slice()); 319 | } 320 | -------------------------------------------------------------------------------- /maze_tui/solvers/src/solve.rs: -------------------------------------------------------------------------------- 1 | use maze; 2 | use print::maze_panic; 3 | use rand::prelude::*; 4 | use ratatui::{ 5 | buffer::Cell, 6 | style::{Color as RatColor, Modifier}, 7 | }; 8 | 9 | // Types available to all solvers. 10 | pub type ThreadPaint = u32; 11 | pub type ThreadCache = u32; 12 | pub type SolveSpeedUnit = u64; 13 | pub struct ThreadGuide { 14 | pub index: usize, 15 | pub paint: ThreadPaint, 16 | pub cache: ThreadCache, 17 | pub start: maze::Point, 18 | pub speed: SolveSpeedUnit, 19 | } 20 | 21 | // Read Only Data Available to All Solvers 22 | pub const START_BIT: ThreadPaint = 0x40000000; 23 | pub const FINISH_BIT: ThreadPaint = 0x80000000; 24 | pub const NUM_THREADS: usize = 4; 25 | pub const NUM_DIRECTIONS: usize = 4; 26 | pub const THREAD_TAG_OFFSET: usize = 4; 27 | pub const NUM_GATHER_FINISHES: usize = 4; 28 | pub const INITIAL_PATH_LEN: usize = 1024; 29 | pub const THREAD_MASK: ThreadPaint = 0xFFFFFF; 30 | pub const RED_MASK: ThreadPaint = 0xFF0000; 31 | pub const RED_SHIFT: ThreadPaint = 16; 32 | pub const GREEN_MASK: ThreadPaint = 0xFF00; 33 | pub const GREEN_SHIFT: ThreadPaint = 8; 34 | pub const BLUE_MASK: ThreadPaint = 0xFF; 35 | pub const ANSI_CYN: u8 = 14; 36 | // Credit to Caesar on StackOverflow for writing the program to find this tetrad of colors. 37 | pub const THREAD_MASKS: [ThreadPaint; 4] = [0x880044, 0x766002, 0x009531, 0x010a88]; 38 | pub const CACHE_MASK: ThreadCache = 0xF000000; 39 | pub const ZERO_SEEN: ThreadCache = 0x1000000; 40 | pub const ONE_SEEN: ThreadCache = 0x2000000; 41 | pub const TWO_SEEN: ThreadCache = 0x4000000; 42 | pub const THREE_SEEN: ThreadCache = 0x8000000; 43 | pub const THREAD_CACHES: [ThreadCache; 4] = [ZERO_SEEN, ONE_SEEN, TWO_SEEN, THREE_SEEN]; 44 | pub const SOLVER_SPEEDS: [SolveSpeedUnit; 8] = [0, 20000, 10000, 5000, 2000, 1000, 500, 250]; 45 | 46 | /// 47 | /// Logical helpers for bitwise operations. 48 | /// 49 | #[inline] 50 | pub fn is_start(square: maze::Square) -> bool { 51 | (square & START_BIT) != 0 52 | } 53 | 54 | #[inline] 55 | pub fn is_finish(square: maze::Square) -> bool { 56 | (square & FINISH_BIT) != 0 57 | } 58 | 59 | #[inline] 60 | pub fn is_color(square: maze::Square) -> bool { 61 | (square & THREAD_MASK) != 0 62 | } 63 | 64 | #[inline] 65 | pub fn is_first(square: maze::Square) -> bool { 66 | (square & CACHE_MASK) == 0 67 | } 68 | 69 | #[inline] 70 | fn thread_rgb(square: maze::Square) -> RatColor { 71 | RatColor::Rgb( 72 | ((square & RED_MASK) >> RED_SHIFT) as u8, 73 | ((square & GREEN_MASK) >> GREEN_SHIFT) as u8, 74 | (square & BLUE_MASK) as u8, 75 | ) 76 | } 77 | 78 | #[inline] 79 | fn is_start_or_finish(square: maze::Square) -> bool { 80 | (square & (START_BIT | FINISH_BIT)) != 0 81 | } 82 | 83 | #[inline] 84 | fn is_valid_start_or_finish(maze: &maze::Maze, choice: maze::Point) -> bool { 85 | choice.row > 0 86 | && choice.row < maze.rows() - 1 87 | && choice.col > 0 88 | && choice.col < maze.cols() - 1 89 | && maze.path_at(choice.row, choice.col) 90 | && !is_finish(maze.get(choice.row, choice.col)) 91 | && !is_start(maze.get(choice.row, choice.col)) 92 | } 93 | 94 | /// 95 | /// Setup functions for starting and finishing a solver section. 96 | /// 97 | pub fn reset_solve(maze: &mut maze::Maze) { 98 | for square in maze.as_slice_mut().iter_mut() { 99 | if (*square & maze::PATH_BIT) != 0 { 100 | *square = maze::PATH_BIT; 101 | } 102 | } 103 | } 104 | 105 | pub fn set_corner_starts(maze: &maze::Maze) -> [maze::Point; 4] { 106 | let mut point1: maze::Point = maze::Point { row: 1, col: 1 }; 107 | if maze.wall_at(point1.row, point1.col) { 108 | point1 = find_nearest_square(maze, point1); 109 | } 110 | let mut point2: maze::Point = maze::Point { 111 | row: 1, 112 | col: maze.cols() - 2, 113 | }; 114 | if maze.wall_at(point2.row, point2.col) { 115 | point2 = find_nearest_square(maze, point2); 116 | } 117 | let mut point3: maze::Point = maze::Point { 118 | row: maze.rows() - 2, 119 | col: 1, 120 | }; 121 | if maze.wall_at(point3.row, point3.col) { 122 | point3 = find_nearest_square(maze, point3); 123 | } 124 | let mut point4: maze::Point = maze::Point { 125 | row: maze.rows() - 2, 126 | col: maze.cols() - 2, 127 | }; 128 | if maze.wall_at(point4.row, point4.col) { 129 | point4 = find_nearest_square(maze, point4); 130 | } 131 | [point1, point2, point3, point4] 132 | } 133 | 134 | pub fn pick_random_point(maze: &maze::Maze) -> maze::Point { 135 | let mut gen = thread_rng(); 136 | let choice = maze::Point { 137 | row: gen.gen_range(1..maze.rows() - 2), 138 | col: gen.gen_range(1..maze.cols() - 2), 139 | }; 140 | if is_valid_start_or_finish(maze, choice) { 141 | return choice; 142 | } 143 | find_nearest_square(maze, choice) 144 | } 145 | 146 | pub fn find_nearest_square(maze: &maze::Maze, choice: maze::Point) -> maze::Point { 147 | for p in &maze::ALL_DIRECTIONS { 148 | let next = maze::Point { 149 | row: choice.row + p.row, 150 | col: choice.col + p.col, 151 | }; 152 | if is_valid_start_or_finish(maze, next) { 153 | return next; 154 | } 155 | } 156 | for r in 1..maze.rows() - 1 { 157 | for c in 1..maze.cols() - 1 { 158 | let cur = maze::Point { row: r, col: c }; 159 | if is_valid_start_or_finish(maze, cur) { 160 | return cur; 161 | } 162 | } 163 | } 164 | print::maze_panic!("Could not place a point in this maze. Was it built correctly?"); 165 | } 166 | 167 | /// 168 | /// Playback and animation based logic for interacting with TUI buffer. 169 | /// 170 | pub fn decode_square(wall_row: &[char], square: maze::Square) -> Cell { 171 | // We have some special printing for the finish square. Not here. 172 | if is_finish(square) { 173 | Cell { 174 | symbol: 'F'.to_string(), 175 | fg: RatColor::Indexed(ANSI_CYN), 176 | bg: match thread_rgb(square) { 177 | RatColor::Rgb(0, 0, 0) => RatColor::Reset, 178 | any => any, 179 | }, 180 | underline_color: RatColor::Reset, 181 | modifier: Modifier::BOLD | Modifier::SLOW_BLINK, 182 | skip: false, 183 | } 184 | } else if is_start(square) { 185 | Cell { 186 | symbol: 'S'.to_string(), 187 | fg: RatColor::Indexed(ANSI_CYN), 188 | bg: match thread_rgb(square) { 189 | RatColor::Rgb(0, 0, 0) => RatColor::Reset, 190 | any => any, 191 | }, 192 | underline_color: RatColor::Reset, 193 | modifier: Modifier::BOLD, 194 | skip: false, 195 | } 196 | } else if is_color(square) { 197 | Cell { 198 | symbol: "█".to_string(), 199 | fg: thread_rgb(square), 200 | bg: RatColor::Reset, 201 | underline_color: RatColor::Reset, 202 | modifier: Modifier::empty(), 203 | skip: false, 204 | } 205 | } else if maze::is_wall(square) { 206 | Cell { 207 | symbol: wall_row[((square & maze::WALL_MASK) >> maze::WALL_SHIFT) as usize].to_string(), 208 | fg: RatColor::Reset, 209 | bg: RatColor::Reset, 210 | underline_color: RatColor::Reset, 211 | modifier: Modifier::empty(), 212 | skip: false, 213 | } 214 | } else if maze::is_path(square) { 215 | Cell { 216 | symbol: ' '.to_string(), 217 | fg: RatColor::Reset, 218 | bg: RatColor::Reset, 219 | underline_color: RatColor::Reset, 220 | modifier: Modifier::empty(), 221 | skip: false, 222 | } 223 | } else { 224 | maze_panic!("Uncategorized maze square! Check the bits."); 225 | } 226 | } 227 | 228 | // This is really bad, there must be a better way. Coloring halves correctly is a challenge. 229 | pub fn decode_mini_path(maze: &maze::Blueprint, p: maze::Point) -> Cell { 230 | let square = maze.get(p.row, p.col); 231 | let this_color = thread_rgb(square); 232 | if is_start_or_finish(square) { 233 | return Cell { 234 | symbol: '▀'.to_string(), 235 | fg: RatColor::Indexed(ANSI_CYN), 236 | bg: this_color, 237 | underline_color: RatColor::Reset, 238 | modifier: Modifier::SLOW_BLINK, 239 | skip: false, 240 | }; 241 | } 242 | // An odd square will always have something above but we could be a path or wall. 243 | if p.row % 2 != 0 { 244 | if maze.path_at(p.row, p.col) { 245 | if maze.path_at(p.row - 1, p.col) { 246 | let neighbor_square = maze.get(p.row - 1, p.col); 247 | // A special square is our neighbor. 248 | if is_start_or_finish(neighbor_square) { 249 | return Cell { 250 | symbol: '▀'.to_string(), 251 | fg: RatColor::Indexed(ANSI_CYN), 252 | bg: this_color, 253 | underline_color: RatColor::Reset, 254 | modifier: Modifier::SLOW_BLINK, 255 | skip: false, 256 | }; 257 | } 258 | // Another thread may be above us so grab the color invariantly just in case. 259 | return Cell { 260 | symbol: '▀'.to_string(), 261 | fg: thread_rgb(neighbor_square), 262 | bg: this_color, 263 | underline_color: RatColor::Reset, 264 | modifier: Modifier::empty(), 265 | skip: false, 266 | }; 267 | } 268 | // A wall is above a path so no extra color logic needed. 269 | return Cell { 270 | symbol: '▀'.to_string(), 271 | fg: RatColor::Reset, 272 | bg: this_color, 273 | underline_color: RatColor::Reset, 274 | modifier: Modifier::empty(), 275 | skip: false, 276 | }; 277 | } 278 | // The only odd wall sqares are those connecting two even rows above and below. 279 | return Cell { 280 | symbol: '█'.to_string(), 281 | fg: RatColor::Reset, 282 | bg: RatColor::Reset, 283 | underline_color: RatColor::Reset, 284 | modifier: Modifier::empty(), 285 | skip: false, 286 | }; 287 | } 288 | // p.row % 2 == 0. This is an even row. Run the logic for both paths and walls being here. 289 | if maze.path_at(p.row, p.col) { 290 | if maze.path_at(p.row + 1, p.col) { 291 | let neighbor_square = maze.get(p.row + 1, p.col); 292 | // A special neighbor is below us so we must split the square colors. 293 | if is_start_or_finish(neighbor_square) { 294 | return Cell { 295 | symbol: '▀'.to_string(), 296 | fg: RatColor::Indexed(ANSI_CYN), 297 | bg: this_color, 298 | underline_color: RatColor::Reset, 299 | modifier: Modifier::SLOW_BLINK, 300 | skip: false, 301 | }; 302 | } 303 | // Another thread may be below us so grab the color invariantly just in case. 304 | return Cell { 305 | symbol: '▀'.to_string(), 306 | fg: thread_rgb(neighbor_square), 307 | bg: this_color, 308 | underline_color: RatColor::Reset, 309 | modifier: Modifier::empty(), 310 | skip: false, 311 | }; 312 | } 313 | // A wall is below a path so not coloring of the block this time. 314 | return Cell { 315 | symbol: '▄'.to_string(), 316 | fg: RatColor::Reset, 317 | bg: this_color, 318 | underline_color: RatColor::Reset, 319 | modifier: Modifier::empty(), 320 | skip: false, 321 | }; 322 | } 323 | // This is a wall square in an even row. A path or other wall can be below. 324 | if p.row + 1 < maze.rows && maze.path_at(p.row + 1, p.col) { 325 | let neighbor_square = maze.get(p.row + 1, p.col); 326 | // The wall has a special neighbor so color halves appropriately. 327 | if is_start_or_finish(neighbor_square) { 328 | return Cell { 329 | symbol: '▀'.to_string(), 330 | fg: RatColor::Reset, 331 | bg: RatColor::Indexed(ANSI_CYN), 332 | underline_color: RatColor::Reset, 333 | modifier: Modifier::SLOW_BLINK, 334 | skip: false, 335 | }; 336 | } 337 | // The wall may have a thread below so grab the color just in case. 338 | return Cell { 339 | symbol: '▀'.to_string(), 340 | fg: RatColor::Reset, 341 | bg: thread_rgb(neighbor_square), 342 | underline_color: RatColor::Reset, 343 | modifier: Modifier::empty(), 344 | skip: false, 345 | }; 346 | } 347 | // Edge case. If a wall is below us in an even row it will print the full block for us when we 348 | // get to it. If not we are at the end of the maze and this is the correct mini to print. 349 | Cell { 350 | symbol: '▀'.to_string(), 351 | fg: RatColor::Reset, 352 | bg: RatColor::Reset, 353 | underline_color: RatColor::Reset, 354 | modifier: Modifier::empty(), 355 | skip: false, 356 | } 357 | } 358 | -------------------------------------------------------------------------------- /maze_tui/builders/src/wilson_carver.rs: -------------------------------------------------------------------------------- 1 | use crate::build; 2 | use maze; 3 | 4 | use rand::{seq::SliceRandom, thread_rng, Rng}; 5 | 6 | const WALK_BIT: maze::Square = 0b0100_0000_0000_0000; 7 | 8 | #[derive(Clone, Copy)] 9 | struct Loop { 10 | walk: maze::Point, 11 | root: maze::Point, 12 | } 13 | 14 | #[derive(Clone, Copy)] 15 | struct RandomWalk { 16 | // I scan for a random walk starts row by row. This way we don't check built rows. 17 | prev_row_start: i32, 18 | prev: maze::Point, 19 | walk: maze::Point, 20 | next: maze::Point, 21 | } 22 | 23 | /// 24 | /// Data only maze generator 25 | /// 26 | pub fn generate_maze(monitor: monitor::MazeMonitor) { 27 | let mut lk = match monitor.lock() { 28 | Ok(l) => l, 29 | Err(_) => print::maze_panic!("uncontested lock failure"), 30 | }; 31 | build::fill_maze_with_walls(&mut lk.maze); 32 | let mut rng = thread_rng(); 33 | let start = maze::Point { 34 | row: 2 * (rng.gen_range(2..lk.maze.rows() - 1) / 2) + 1, 35 | col: 2 * (rng.gen_range(2..lk.maze.cols() - 1) / 2) + 1, 36 | }; 37 | build::build_path(&mut lk.maze, start); 38 | *lk.maze.get_mut(start.row, start.col) |= build::BUILDER_BIT; 39 | let mut cur = RandomWalk { 40 | prev_row_start: 1, 41 | prev: maze::Point { row: 0, col: 0 }, 42 | walk: maze::Point { row: 1, col: 1 }, 43 | next: maze::Point { row: 0, col: 0 }, 44 | }; 45 | *lk.maze.get_mut(cur.walk.row, cur.walk.col) &= !build::MARKERS_MASK; 46 | let mut indices: [usize; 4] = [0, 1, 2, 3]; 47 | 'walking: loop { 48 | *lk.maze.get_mut(cur.walk.row, cur.walk.col) |= WALK_BIT; 49 | indices.shuffle(&mut rng); 50 | 'choosing_step: for &i in indices.iter() { 51 | let p = &build::GENERATE_DIRECTIONS[i]; 52 | cur.next = maze::Point { 53 | row: cur.walk.row + p.row, 54 | col: cur.walk.col + p.col, 55 | }; 56 | if !is_valid_step(&lk.maze, cur.next, cur.prev) { 57 | continue 'choosing_step; 58 | } 59 | cur = match complete_walk(&mut lk.maze, cur) { 60 | None => return, 61 | Some(new_walk) => new_walk, 62 | }; 63 | continue 'walking; 64 | } 65 | } 66 | } 67 | 68 | fn complete_walk(maze: &mut maze::Maze, mut walk: RandomWalk) -> Option { 69 | if build::has_builder_bit(maze, walk.next) { 70 | build_with_marks(maze, walk.walk, walk.next); 71 | connect_walk(maze, walk.walk); 72 | if let Some(point) = 73 | build::choose_point_from_row_start(maze, walk.prev_row_start, build::ParityPoint::Odd) 74 | { 75 | walk.prev_row_start = point.row; 76 | walk.walk = point; 77 | *maze.get_mut(walk.walk.row, walk.walk.col) &= !build::MARKERS_MASK; 78 | walk.prev = maze::Point { row: 0, col: 0 }; 79 | return Some(walk); 80 | } 81 | return None; 82 | } 83 | if found_loop(maze, walk.next) { 84 | erase_loop( 85 | maze, 86 | Loop { 87 | walk: walk.walk, 88 | root: walk.next, 89 | }, 90 | ); 91 | walk.walk = walk.next; 92 | let dir: &'static maze::Point = backtrack_point(maze, &walk.walk); 93 | walk.prev = maze::Point { 94 | row: walk.walk.row + dir.row, 95 | col: walk.walk.col + dir.col, 96 | }; 97 | return Some(walk); 98 | } 99 | build::mark_origin(maze, walk.walk, walk.next); 100 | walk.prev = walk.walk; 101 | walk.walk = walk.next; 102 | Some(walk) 103 | } 104 | 105 | fn erase_loop(maze: &mut maze::Maze, mut walk: Loop) { 106 | while walk.walk != walk.root { 107 | *maze.get_mut(walk.walk.row, walk.walk.col) &= !WALK_BIT; 108 | let dir: &'static maze::Point = backtrack_point(maze, &walk.walk); 109 | let next = maze::Point { 110 | row: walk.walk.row + dir.row, 111 | col: walk.walk.col + dir.col, 112 | }; 113 | *maze.get_mut(walk.walk.row, walk.walk.col) &= !build::MARKERS_MASK; 114 | walk.walk = next; 115 | } 116 | } 117 | 118 | fn connect_walk(maze: &mut maze::Maze, mut walk: maze::Point) { 119 | while (maze.get(walk.row, walk.col) & build::MARKERS_MASK) != 0 { 120 | let dir: &'static maze::Point = backtrack_point(maze, &walk); 121 | let next = maze::Point { 122 | row: walk.row + dir.row, 123 | col: walk.col + dir.col, 124 | }; 125 | build_with_marks(maze, walk, next); 126 | *maze.get_mut(walk.row, walk.col) &= !build::MARKERS_MASK; 127 | walk = next; 128 | } 129 | *maze.get_mut(walk.row, walk.col) &= !build::MARKERS_MASK; 130 | *maze.get_mut(walk.row, walk.col) &= !WALK_BIT; 131 | build::carve_path_walls(maze, walk); 132 | } 133 | 134 | fn build_with_marks(maze: &mut maze::Maze, cur: maze::Point, next: maze::Point) { 135 | let mut wall = cur; 136 | if next.row < cur.row { 137 | wall.row -= 1; 138 | } else if next.row > cur.row { 139 | wall.row += 1; 140 | } else if next.col < cur.col { 141 | wall.col -= 1; 142 | } else if next.col > cur.col { 143 | wall.col += 1; 144 | } else { 145 | print::maze_panic!("Wall break error. Step through wall didn't work"); 146 | } 147 | *maze.get_mut(cur.row, cur.col) &= !WALK_BIT; 148 | *maze.get_mut(next.row, next.col) &= !WALK_BIT; 149 | build::carve_path_walls(maze, cur); 150 | build::carve_path_walls(maze, wall); 151 | build::carve_path_walls(maze, next); 152 | } 153 | 154 | /// 155 | /// History based generator for animation and playback. 156 | /// 157 | pub fn generate_history(monitor: monitor::MazeMonitor) { 158 | let mut lk = match monitor.lock() { 159 | Ok(l) => l, 160 | Err(_) => print::maze_panic!("uncontested lock failure"), 161 | }; 162 | build::fill_maze_history_with_walls(&mut lk.maze); 163 | let mut rng = thread_rng(); 164 | let start = maze::Point { 165 | row: 2 * (rng.gen_range(2..lk.maze.rows() - 1) / 2) + 1, 166 | col: 2 * (rng.gen_range(2..lk.maze.cols() - 1) / 2) + 1, 167 | }; 168 | build::build_path_history(&mut lk.maze, start); 169 | *lk.maze.get_mut(start.row, start.col) |= build::BUILDER_BIT; 170 | let mut cur = RandomWalk { 171 | prev_row_start: 1, 172 | prev: maze::Point { row: 0, col: 0 }, 173 | walk: maze::Point { row: 1, col: 1 }, 174 | next: maze::Point { row: 0, col: 0 }, 175 | }; 176 | *lk.maze.get_mut(cur.walk.row, cur.walk.col) &= !build::MARKERS_MASK; 177 | let mut indices: [usize; 4] = [0, 1, 2, 3]; 178 | 'walking: loop { 179 | *lk.maze.get_mut(cur.walk.row, cur.walk.col) |= WALK_BIT; 180 | indices.shuffle(&mut rng); 181 | 'choosing_step: for &i in indices.iter() { 182 | let p = &build::GENERATE_DIRECTIONS[i]; 183 | cur.next = maze::Point { 184 | row: cur.walk.row + p.row, 185 | col: cur.walk.col + p.col, 186 | }; 187 | if !is_valid_step(&lk.maze, cur.next, cur.prev) { 188 | continue 'choosing_step; 189 | } 190 | cur = match complete_walk_history(&mut lk.maze, cur) { 191 | None => return, 192 | Some(new_walk) => new_walk, 193 | }; 194 | continue 'walking; 195 | } 196 | } 197 | } 198 | 199 | fn complete_walk_history(maze: &mut maze::Maze, mut walk: RandomWalk) -> Option { 200 | if build::has_builder_bit(maze, walk.next) { 201 | break_wall_history(maze, walk.walk, walk.next); 202 | connect_walk_history(maze, walk.walk); 203 | if let Some(point) = 204 | build::choose_point_from_row_start(maze, walk.prev_row_start, build::ParityPoint::Odd) 205 | { 206 | walk.prev_row_start = point.row; 207 | walk.walk = point; 208 | *maze.get_mut(walk.walk.row, walk.walk.col) &= !build::MARKERS_MASK; 209 | walk.prev = maze::Point { row: 0, col: 0 }; 210 | return Some(walk); 211 | } 212 | return None; 213 | } 214 | if found_loop(maze, walk.next) { 215 | erase_loop_history( 216 | maze, 217 | Loop { 218 | walk: walk.walk, 219 | root: walk.next, 220 | }, 221 | ); 222 | walk.walk = walk.next; 223 | let dir: &'static maze::Point = backtrack_point(maze, &walk.walk); 224 | walk.prev = maze::Point { 225 | row: walk.walk.row + dir.row, 226 | col: walk.walk.col + dir.col, 227 | }; 228 | return Some(walk); 229 | } 230 | build::mark_origin_history(maze, walk.walk, walk.next); 231 | walk.prev = walk.walk; 232 | walk.walk = walk.next; 233 | Some(walk) 234 | } 235 | 236 | fn erase_loop_history(maze: &mut maze::Maze, mut walk: Loop) { 237 | while walk.walk != walk.root { 238 | let walk_square = maze.get(walk.walk.row, walk.walk.col); 239 | let dir: &'static maze::Point = backtrack_point(maze, &walk.walk); 240 | let half: &'static maze::Point = backtrack_half_step(maze, &walk.walk); 241 | let half_step = maze::Point { 242 | row: walk.walk.row + half.row, 243 | col: walk.walk.col + half.col, 244 | }; 245 | let half_square = maze.get(half_step.row, half_step.col); 246 | let next = maze::Point { 247 | row: walk.walk.row + dir.row, 248 | col: walk.walk.col + dir.col, 249 | }; 250 | maze.build_history.push(maze::Delta { 251 | id: walk.walk, 252 | before: walk_square, 253 | after: (walk_square & !WALK_BIT) & !build::MARKERS_MASK, 254 | burst: 1, 255 | }); 256 | maze.build_history.push(maze::Delta { 257 | id: half_step, 258 | before: half_square, 259 | after: half_square & !build::MARKERS_MASK, 260 | burst: 1, 261 | }); 262 | *maze.get_mut(half_step.row, half_step.col) &= !build::MARKERS_MASK; 263 | *maze.get_mut(walk.walk.row, walk.walk.col) = 264 | (walk_square & !WALK_BIT) & !build::MARKERS_MASK; 265 | walk.walk = next; 266 | } 267 | } 268 | 269 | fn connect_walk_history(maze: &mut maze::Maze, mut walk: maze::Point) { 270 | while build::is_marked(maze.get(walk.row, walk.col)) { 271 | let dir: &'static maze::Point = backtrack_point(maze, &walk); 272 | let half: &'static maze::Point = backtrack_half_step(maze, &walk); 273 | let half_step = maze::Point { 274 | row: walk.row + half.row, 275 | col: walk.col + half.col, 276 | }; 277 | let next = maze::Point { 278 | row: walk.row + dir.row, 279 | col: walk.col + dir.col, 280 | }; 281 | build_walk_square(maze, walk); 282 | build_walk_square(maze, half_step); 283 | walk = next; 284 | } 285 | build_walk_square(maze, walk); 286 | } 287 | 288 | fn build_walk_square(maze: &mut maze::Maze, p: maze::Point) { 289 | let mut wall_changes = [maze::Delta::default(); 5]; 290 | let mut burst = 1; 291 | let before = maze.get(p.row, p.col); 292 | wall_changes[0] = maze::Delta { 293 | id: p, 294 | before, 295 | after: (((before & !WALK_BIT) & !maze::WALL_MASK) & !build::MARKERS_MASK) 296 | | maze::PATH_BIT 297 | | build::BUILDER_BIT, 298 | burst, 299 | }; 300 | *maze.get_mut(p.row, p.col) = (((before & !WALK_BIT) & !maze::WALL_MASK) 301 | & !build::MARKERS_MASK) 302 | | maze::PATH_BIT 303 | | build::BUILDER_BIT; 304 | if p.row > 0 { 305 | let square = maze.get(p.row - 1, p.col); 306 | wall_changes[burst] = maze::Delta { 307 | id: maze::Point { 308 | row: p.row - 1, 309 | col: p.col, 310 | }, 311 | before: square, 312 | after: square & !maze::SOUTH_WALL, 313 | burst: burst + 1, 314 | }; 315 | burst += 1; 316 | *maze.get_mut(p.row - 1, p.col) &= !maze::SOUTH_WALL; 317 | } 318 | if p.row + 1 < maze.rows() { 319 | let square = maze.get(p.row + 1, p.col); 320 | wall_changes[burst] = maze::Delta { 321 | id: maze::Point { 322 | row: p.row + 1, 323 | col: p.col, 324 | }, 325 | before: square, 326 | after: square & !maze::NORTH_WALL, 327 | burst: burst + 1, 328 | }; 329 | burst += 1; 330 | *maze.get_mut(p.row + 1, p.col) &= !maze::NORTH_WALL; 331 | } 332 | if p.col > 0 { 333 | let square = maze.get(p.row, p.col - 1); 334 | wall_changes[burst] = maze::Delta { 335 | id: maze::Point { 336 | row: p.row, 337 | col: p.col - 1, 338 | }, 339 | before: square, 340 | after: square & !maze::EAST_WALL, 341 | burst: burst + 1, 342 | }; 343 | burst += 1; 344 | *maze.get_mut(p.row, p.col - 1) &= !maze::EAST_WALL; 345 | } 346 | if p.col + 1 < maze.cols() { 347 | let square = maze.get(p.row, p.col + 1); 348 | wall_changes[burst] = maze::Delta { 349 | id: maze::Point { 350 | row: p.row, 351 | col: p.col + 1, 352 | }, 353 | before: square, 354 | after: square & !maze::WEST_WALL, 355 | burst: burst + 1, 356 | }; 357 | burst += 1; 358 | *maze.get_mut(p.row, p.col + 1) &= !maze::WEST_WALL; 359 | } 360 | wall_changes[0].burst = burst; 361 | maze.build_history.push_burst(&wall_changes[0..burst]); 362 | } 363 | 364 | fn break_wall_history(maze: &mut maze::Maze, cur: maze::Point, next: maze::Point) { 365 | let mut wall = cur; 366 | if next.row < cur.row { 367 | wall.row -= 1; 368 | } else if next.row > cur.row { 369 | wall.row += 1; 370 | } else if next.col < cur.col { 371 | wall.col -= 1; 372 | } else if next.col > cur.col { 373 | wall.col += 1; 374 | } else { 375 | print::maze_panic!( 376 | "Wall break error. Step through wall didn't work: cur {:?}, next {:?}", 377 | cur, 378 | next 379 | ); 380 | } 381 | build_walk_square(maze, wall); 382 | } 383 | 384 | /// 385 | /// Data only helpers for all. 386 | /// 387 | fn is_valid_step(maze: &maze::Maze, next: maze::Point, prev: maze::Point) -> bool { 388 | next.row > 0 389 | && next.row < maze.rows() - 1 390 | && next.col > 0 391 | && next.col < maze.cols() - 1 392 | && next != prev 393 | } 394 | 395 | fn backtrack_point(maze: &maze::Maze, walk: &maze::Point) -> &'static maze::Point { 396 | &build::BACKTRACKING_POINTS[(maze.get(walk.row, walk.col) & build::MARKERS_MASK) as usize] 397 | } 398 | 399 | fn backtrack_half_step(maze: &maze::Maze, walk: &maze::Point) -> &'static maze::Point { 400 | &build::BACKTRACKING_HALF_POINTS[(maze.get(walk.row, walk.col) & build::MARKERS_MASK) as usize] 401 | } 402 | 403 | fn found_loop(maze: &maze::Maze, p: maze::Point) -> bool { 404 | (maze.get(p.row, p.col) & WALK_BIT) != 0 405 | } 406 | -------------------------------------------------------------------------------- /maze_tui/maze/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Here is the scheme we will use to store tons of data in a square. 2 | // 3 | // When building the maze here is how we will use the available bits. 4 | // 5 | // We may use some color mixing bits for backtracking info. 6 | // However, they must be cleared if used before algorithm finishes. 7 | // 24 bit thread color mixing-----|---------------------------| 8 | // ------------------------------ |||| |||| |||| |||| |||| |||| 9 | // walls / thread cache------|||| |||| |||| |||| |||| |||| |||| 10 | // --------------------------|||| |||| |||| |||| |||| |||| |||| 11 | // maze build bit----------| |||| |||| |||| |||| |||| |||| |||| 12 | // maze paths bit---------|| |||| |||| |||| |||| |||| |||| |||| 13 | // maze start bit--------||| |||| |||| |||| |||| |||| |||| |||| 14 | // maze goals bit-------|||| |||| |||| |||| |||| |||| |||| |||| 15 | // 0b0000 0000 0000 0000 0000 0000 0000 0000 16 | // 17 | // The maze builder is responsible for zeroing out the direction bits as part of the 18 | // building process. When solving the maze we adjust how we use the middle bits. 19 | // 20 | // 24 bit thread color mixing-----|---------------------------| 21 | // ------------------------------ |||| |||| |||| |||| |||| |||| 22 | // walls / thread cache------|||| |||| |||| |||| |||| |||| |||| 23 | // --------------------------|||| |||| |||| |||| |||| |||| |||| 24 | // maze build bit----------| |||| |||| |||| |||| |||| |||| |||| 25 | // maze paths bit---------|| |||| |||| |||| |||| |||| |||| |||| 26 | // maze start bit--------||| |||| |||| |||| |||| |||| |||| |||| 27 | // maze goals bit-------|||| |||| |||| |||| |||| |||| |||| |||| 28 | // 0b0000 0000 0000 0000 0000 0000 0000 0000 29 | use rand::seq::SliceRandom; 30 | use std::ops::{Index, IndexMut}; 31 | 32 | // Public Types 33 | 34 | pub type Square = u32; 35 | pub type WallLine = u32; 36 | 37 | #[derive(Default, Debug, PartialEq, Eq, Hash, Copy, Clone)] 38 | pub struct Point { 39 | pub row: i32, 40 | pub col: i32, 41 | } 42 | 43 | #[derive(Default, Debug, Clone, Copy)] 44 | pub struct Offset { 45 | pub add_rows: i32, 46 | pub add_cols: i32, 47 | } 48 | 49 | #[derive(Debug, Clone, Copy, Eq, PartialEq)] 50 | pub enum MazeStyle { 51 | Mini = 0, 52 | Sharp, 53 | Round, 54 | Doubles, 55 | Bold, 56 | Contrast, 57 | Half, 58 | Spikes, 59 | } 60 | 61 | #[derive(Clone, Copy)] 62 | pub struct MazeArgs { 63 | pub odd_rows: i32, 64 | pub odd_cols: i32, 65 | pub offset: Offset, 66 | pub style: MazeStyle, 67 | } 68 | 69 | // This is at the core of our maze. The fundamental information and structure we need. 70 | #[derive(Debug, Clone, Default)] 71 | pub struct Blueprint { 72 | pub buf: Vec, 73 | pub rows: i32, 74 | pub cols: i32, 75 | pub offset: Offset, 76 | pub wall_style_index: usize, 77 | } 78 | 79 | // We will also be tracking how our maze changes for the TUI animation playback. 80 | #[derive(Debug, Default, Clone, Copy)] 81 | pub struct Delta { 82 | pub id: Point, 83 | pub before: Square, 84 | pub after: Square, 85 | // We can enter deltas into our Tape but then specify how many squares to change in one frame. 86 | pub burst: usize, 87 | } 88 | 89 | #[derive(Debug, Default, Clone)] 90 | pub struct Tape { 91 | steps: Vec, 92 | i: usize, 93 | } 94 | 95 | // Our maze now comes with the ability to track the history of algorithms that made and solved it. 96 | #[derive(Debug, Clone, Default)] 97 | pub struct Maze { 98 | pub maze: Blueprint, 99 | pub build_history: Tape, 100 | pub solve_history: Tape, 101 | } 102 | // Read Only Data Available to Any Maze Users 103 | 104 | // Any modification made to these bits by a builder MUST be cleared before build process completes. 105 | pub const CLEAR_AVAILABLE_BITS: Square = 0x10FFFFFF; 106 | 107 | pub const DEFAULT_ROWS: i32 = 31; 108 | pub const DEFAULT_COLS: i32 = 111; 109 | pub const PATH_BIT: Square = 0x20000000; 110 | pub const WALL_MASK: WallLine = 0xF000000; 111 | pub const WALL_SHIFT: usize = 24; 112 | pub const FLOATING_WALL: WallLine = 0b0; 113 | pub const NORTH_WALL: WallLine = 0x1000000; 114 | pub const EAST_WALL: WallLine = 0x2000000; 115 | pub const SOUTH_WALL: WallLine = 0x4000000; 116 | pub const WEST_WALL: WallLine = 0x8000000; 117 | // Walls are constructed in terms of other walls they need to connect to. For example, read 118 | // 0b0011 as, "this is a wall square that must connect to other walls to the East and North." 119 | const WALL_ROW: usize = 16; 120 | pub static WALL_STYLES: [char; 128] = [ 121 | // 0bWestSouthEastNorth. Note: 0b0000 is a floating wall with no walls around. 122 | // Then, count from 0 (0b0000) to 15 (0b1111) in binary to form different wall shapes. 123 | // mini 124 | '▀', '▀', '▀', '▀', '█', '█', '█', '█', '▀', '▀', '▀', '▀', '█', '█', '█', '█', 125 | // sharp 126 | '■', '╵', '╶', '└', '╷', '│', '┌', '├', '╴', '┘', '─', '┴', '┐', '┤', '┬', '┼', 127 | // rounded 128 | '●', '╵', '╶', '╰', '╷', '│', '╭', '├', '╴', '╯', '─', '┴', '╮', '┤', '┬', '┼', 129 | // doubles 130 | '◫', '║', '═', '╚', '║', '║', '╔', '╠', '═', '╝', '═', '╩', '╗', '╣', '╦', '╬', 131 | // bold 132 | '■', '╹', '╺', '┗', '╻', '┃', '┏', '┣', '╸', '┛', '━', '┻', '┓', '┫', '┳', '╋', 133 | // contrast 134 | '█', '█', '█', '█', '█', '█', '█', '█', '█', '█', '█', '█', '█', '█', '█', '█', 135 | // half 136 | '▄', '█', '▄', '█', '▄', '█', '▄', '█', '▄', '█', '▄', '█', '▄', '█', '▄', '█', 137 | // spikes 138 | '✸', '╀', '┾', '╊', '╁', '╂', '╆', '╊', '┽', '╃', '┿', '╇', '╅', '╉', '╈', '╋', 139 | ]; 140 | 141 | // north, east, south, west provided to any users of a maze for convenience. 142 | pub const CARDINAL_DIRECTIONS: [Point; 4] = [ 143 | Point { row: -1, col: 0 }, 144 | Point { row: 0, col: 1 }, 145 | Point { row: 1, col: 0 }, 146 | Point { row: 0, col: -1 }, 147 | ]; 148 | 149 | // south, south-east, east, north-east, north, north-west, west, south-west 150 | pub const ALL_DIRECTIONS: [Point; 8] = [ 151 | Point { row: 1, col: 0 }, 152 | Point { row: 1, col: 1 }, 153 | Point { row: 0, col: 1 }, 154 | Point { row: -1, col: 1 }, 155 | Point { row: -1, col: 0 }, 156 | Point { row: -1, col: -1 }, 157 | Point { row: 0, col: -1 }, 158 | Point { row: 1, col: -1 }, 159 | ]; 160 | 161 | // Core Maze Object Implementation 162 | 163 | // A maze in this program is intended to be shared both mutably and immutably. 164 | // A maze only provides building blocks and some convenience read-only data. 165 | // Builders and solvers use the visitor pattern to operate on and extend 166 | // what they wish on the maze. 167 | impl Maze { 168 | pub fn new(args: MazeArgs) -> Self { 169 | let rows = args.odd_rows + 1 - (args.odd_rows % 2); 170 | let cols = args.odd_cols + 1 - (args.odd_cols % 2); 171 | Self { 172 | maze: Blueprint { 173 | buf: (vec![0; rows as usize * cols as usize]), 174 | rows, 175 | cols, 176 | offset: args.offset, 177 | wall_style_index: args.style as usize, 178 | }, 179 | build_history: Tape::default(), 180 | solve_history: Tape::default(), 181 | } 182 | } 183 | 184 | #[inline] 185 | pub fn rows(&self) -> i32 { 186 | self.maze.rows 187 | } 188 | 189 | #[inline] 190 | pub fn offset(&self) -> Offset { 191 | self.maze.offset 192 | } 193 | 194 | #[inline] 195 | pub fn cols(&self) -> i32 { 196 | self.maze.cols 197 | } 198 | 199 | #[inline] 200 | pub fn wall_char(&self, square: Square) -> char { 201 | WALL_STYLES[(self.maze.wall_style_index * WALL_ROW) 202 | + ((square & WALL_MASK) >> WALL_SHIFT) as usize] 203 | } 204 | 205 | #[inline] 206 | pub fn wall_row(&self) -> &[char] { 207 | &WALL_STYLES[self.maze.wall_style_index * WALL_ROW 208 | ..self.maze.wall_style_index * WALL_ROW + WALL_ROW] 209 | } 210 | 211 | #[inline] 212 | pub fn style_index(&self) -> usize { 213 | self.maze.wall_style_index 214 | } 215 | 216 | #[inline] 217 | pub fn is_mini(&self) -> bool { 218 | self.maze.is_mini() 219 | } 220 | 221 | #[inline] 222 | pub fn as_slice(&self) -> &[Square] { 223 | self.maze.buf.as_slice() 224 | } 225 | 226 | #[inline] 227 | pub fn as_slice_mut(&mut self) -> &mut [Square] { 228 | self.maze.buf.as_mut_slice() 229 | } 230 | 231 | #[inline] 232 | pub fn get_mut(&mut self, row: i32, col: i32) -> &mut Square { 233 | self.maze.get_mut(row, col) 234 | } 235 | 236 | #[inline] 237 | pub fn get(&self, row: i32, col: i32) -> Square { 238 | self.maze.get(row, col) 239 | } 240 | 241 | #[inline] 242 | pub fn wall_at(&self, row: i32, col: i32) -> bool { 243 | self.maze.wall_at(row, col) 244 | } 245 | 246 | #[inline] 247 | pub fn path_at(&self, row: i32, col: i32) -> bool { 248 | self.maze.path_at(row, col) 249 | } 250 | } 251 | 252 | impl Blueprint { 253 | #[inline] 254 | pub fn get(&self, row: i32, col: i32) -> Square { 255 | self.buf[(row * self.cols + col) as usize] 256 | } 257 | 258 | #[inline] 259 | pub fn get_mut(&mut self, row: i32, col: i32) -> &mut Square { 260 | &mut self.buf[(row * self.cols + col) as usize] 261 | } 262 | 263 | #[inline] 264 | pub fn wall_char(&self, square: Square) -> char { 265 | WALL_STYLES 266 | [(self.wall_style_index * WALL_ROW) + ((square & WALL_MASK) >> WALL_SHIFT) as usize] 267 | } 268 | 269 | #[inline] 270 | pub fn wall_row(&self) -> &[char] { 271 | &WALL_STYLES[self.wall_style_index * WALL_ROW..self.wall_style_index * WALL_ROW + WALL_ROW] 272 | } 273 | 274 | #[inline] 275 | pub fn wall_at(&self, row: i32, col: i32) -> bool { 276 | (self.buf[(row * self.cols + col) as usize] & PATH_BIT) == 0 277 | } 278 | 279 | #[inline] 280 | pub fn path_at(&self, row: i32, col: i32) -> bool { 281 | (self.buf[(row * self.cols + col) as usize] & PATH_BIT) != 0 282 | } 283 | 284 | #[inline] 285 | pub fn is_mini(&self) -> bool { 286 | self.wall_style_index == (MazeStyle::Mini as usize) 287 | } 288 | } 289 | 290 | impl Default for MazeArgs { 291 | fn default() -> Self { 292 | Self { 293 | odd_rows: DEFAULT_ROWS, 294 | odd_cols: DEFAULT_COLS, 295 | style: MazeStyle::Sharp, 296 | offset: Offset::default(), 297 | } 298 | } 299 | } 300 | 301 | /// 302 | /// The Tape data structure implementation is concerned with sensible ways to step through the 303 | /// history of deltas as a maze build and solve operation completes. We only need an index and 304 | /// we track deltas as simple before and after u32's and what square changed. 305 | /// 306 | impl Index for Tape { 307 | type Output = Delta; 308 | fn index(&self, index: usize) -> &Self::Output { 309 | &self.steps[index] 310 | } 311 | } 312 | 313 | impl IndexMut for Tape { 314 | fn index_mut(&mut self, index: usize) -> &mut Self::Output { 315 | &mut self.steps[index] 316 | } 317 | } 318 | 319 | impl Tape { 320 | pub fn slice(&self, start: usize, end: usize) -> &[Delta] { 321 | &self.steps[start..end] 322 | } 323 | 324 | pub fn slice_mut(&mut self, start: usize, end: usize) -> &mut [Delta] { 325 | &mut self.steps[start..end] 326 | } 327 | 328 | pub fn is_empty(&self) -> bool { 329 | self.steps.is_empty() 330 | } 331 | 332 | pub fn end(&mut self) { 333 | if self.steps.is_empty() { 334 | panic!("no tape to end because no deltas provided"); 335 | } 336 | self.i = self.steps.len() - 1; 337 | } 338 | 339 | pub fn start(&mut self) { 340 | if self.steps.is_empty() { 341 | panic!("no tape to start because no deltas provided"); 342 | } 343 | self.i = 0; 344 | } 345 | 346 | pub fn len(&self) -> usize { 347 | self.steps.len() 348 | } 349 | 350 | pub fn cur_step(&self) -> Option<&[Delta]> { 351 | if self.steps.is_empty() { 352 | return None; 353 | } 354 | Some(&self.steps[self.i..self.i + self.steps[self.i].burst]) 355 | } 356 | 357 | fn peek_next_index(&self) -> usize { 358 | if self.steps.is_empty() || self.i + self.steps[self.i].burst >= self.steps.len() { 359 | return self.i; 360 | } 361 | self.i + self.steps[self.i].burst 362 | } 363 | 364 | fn peek_prev_index(&self) -> usize { 365 | if self.steps.is_empty() 366 | || self.i == 0 367 | || self.i.overflowing_sub(self.steps[self.i - 1].burst).1 368 | { 369 | return self.i; 370 | } 371 | self.i - self.steps[self.i - 1].burst 372 | } 373 | 374 | pub fn peek_next_delta(&self) -> Option<&[Delta]> { 375 | if self.i + self.steps[self.i].burst >= self.steps.len() { 376 | return None; 377 | } 378 | Some(&self.steps[self.i..self.i + self.steps[self.i].burst]) 379 | } 380 | 381 | pub fn peek_prev_delta(&self) -> Option<&[Delta]> { 382 | if self.i == 0 || self.i.overflowing_sub(self.steps[self.i - 1].burst).1 { 383 | return None; 384 | } 385 | Some(&self.steps[self.i - self.steps[self.i - 1].burst..self.i]) 386 | } 387 | 388 | pub fn next_delta(&mut self) -> Option<&[Delta]> { 389 | if self.steps.is_empty() || self.i + self.steps[self.i].burst >= self.steps.len() { 390 | return None; 391 | } 392 | self.i += self.steps[self.i].burst; 393 | Some(&self.steps[self.i..self.i + self.steps[self.i].burst]) 394 | } 395 | 396 | pub fn prev_delta(&mut self) -> Option<&[Delta]> { 397 | if self.i == 0 || self.i.overflowing_sub(self.steps[self.i - 1].burst).1 { 398 | return None; 399 | } 400 | self.i -= self.steps[self.i - 1].burst; 401 | Some(&self.steps[self.i..self.i + self.steps[self.i].burst]) 402 | } 403 | 404 | pub fn push_burst(&mut self, steps: &[Delta]) { 405 | if !steps.is_empty() 406 | && (steps[0].burst != steps.len() || steps[steps.len() - 1].burst != steps.len()) 407 | { 408 | panic!( 409 | "ill formed burst input burst burst[0]={},burst[burst-1]={}, len {}", 410 | steps[0].burst, 411 | steps[steps.len() - 1].burst, 412 | steps.len() 413 | ); 414 | } 415 | for s in steps.iter() { 416 | self.steps.push(*s); 417 | } 418 | } 419 | 420 | pub fn push(&mut self, s: Delta) { 421 | self.steps.push(s); 422 | } 423 | 424 | pub fn at_end(&self) -> bool { 425 | self.i == self.peek_next_index() 426 | } 427 | 428 | pub fn at_start(&self) -> bool { 429 | self.i == self.peek_prev_index() 430 | } 431 | 432 | pub fn set_prev(&mut self) -> bool { 433 | let prev = self.i; 434 | self.i = self.peek_prev_index(); 435 | self.i != prev 436 | } 437 | 438 | pub fn set_next(&mut self) -> bool { 439 | let prev = self.i; 440 | self.i = self.peek_next_index(); 441 | self.i != prev 442 | } 443 | } 444 | 445 | /// 446 | /// Free functions for when the maze object is not present but we still want static info. 447 | /// 448 | #[inline] 449 | pub fn wall_row(row_index: usize) -> &'static [char] { 450 | &WALL_STYLES[row_index * WALL_ROW..row_index * WALL_ROW + WALL_ROW] 451 | } 452 | 453 | #[inline] 454 | pub fn wall_char(style_index: usize, square: Square) -> char { 455 | WALL_STYLES[style_index * WALL_ROW + ((square & WALL_MASK) >> WALL_SHIFT) as usize] 456 | } 457 | 458 | #[inline] 459 | pub fn is_wall(square: Square) -> bool { 460 | (square & PATH_BIT) == 0 461 | } 462 | 463 | #[inline] 464 | pub fn is_path(square: Square) -> bool { 465 | (square & PATH_BIT) != 0 466 | } 467 | 468 | impl MazeStyle { 469 | pub fn get_random(rng: &mut rand::rngs::ThreadRng) -> MazeStyle { 470 | *ALL_WALL_STYLES 471 | .choose(rng) 472 | .expect("could not choose random wall style") 473 | } 474 | } 475 | 476 | const ALL_WALL_STYLES: [MazeStyle; 8] = [ 477 | MazeStyle::Mini, 478 | MazeStyle::Sharp, 479 | MazeStyle::Round, 480 | MazeStyle::Doubles, 481 | MazeStyle::Bold, 482 | MazeStyle::Contrast, 483 | MazeStyle::Half, 484 | MazeStyle::Spikes, 485 | ]; 486 | -------------------------------------------------------------------------------- /maze_tui/builders/src/wilson_adder.rs: -------------------------------------------------------------------------------- 1 | use crate::build; 2 | use maze; 3 | use print; 4 | 5 | use rand::{seq::SliceRandom, thread_rng, Rng}; 6 | 7 | const WALK_BIT: maze::Square = 0b0100_0000_0000_0000; 8 | 9 | #[derive(Clone, Copy)] 10 | struct Loop { 11 | walk: maze::Point, 12 | root: maze::Point, 13 | } 14 | 15 | #[derive(Clone, Copy)] 16 | struct RandomWalk { 17 | // I scan for a random walk starts row by row. This way we don't check built rows. 18 | prev_row_start: i32, 19 | prev: maze::Point, 20 | walk: maze::Point, 21 | next: maze::Point, 22 | } 23 | 24 | /// 25 | /// Data only maze generator 26 | /// 27 | pub fn generate_maze(monitor: monitor::MazeMonitor) { 28 | let mut lk = match monitor.lock() { 29 | Ok(l) => l, 30 | Err(_) => print::maze_panic!("uncontested lock failure"), 31 | }; 32 | build::build_wall_outline(&mut lk.maze); 33 | let mut rng = thread_rng(); 34 | let mut cur = RandomWalk { 35 | prev_row_start: 2, 36 | prev: maze::Point { row: 0, col: 0 }, 37 | walk: maze::Point { 38 | row: 2 * (rng.gen_range(2..lk.maze.rows() - 1) / 2), 39 | col: 2 * (rng.gen_range(2..lk.maze.cols() - 1) / 2), 40 | }, 41 | next: maze::Point { row: 0, col: 0 }, 42 | }; 43 | let mut indices: [usize; 4] = [0, 1, 2, 3]; 44 | 'walking: loop { 45 | *lk.maze.get_mut(cur.walk.row, cur.walk.col) |= WALK_BIT; 46 | indices.shuffle(&mut rng); 47 | 'choosing_step: for &i in indices.iter() { 48 | let p = &build::GENERATE_DIRECTIONS[i]; 49 | cur.next = maze::Point { 50 | row: cur.walk.row + p.row, 51 | col: cur.walk.col + p.col, 52 | }; 53 | if !is_valid_step(&lk.maze, cur.next, cur.prev) { 54 | continue 'choosing_step; 55 | } 56 | cur = match complete_walk(&mut lk.maze, cur) { 57 | None => return, 58 | Some(new_walk) => new_walk, 59 | }; 60 | continue 'walking; 61 | } 62 | } 63 | } 64 | 65 | fn complete_walk(maze: &mut maze::Maze, mut walk: RandomWalk) -> Option { 66 | if build::has_builder_bit(maze, walk.next) { 67 | build_with_marks(maze, walk.walk, walk.next); 68 | connect_walk(maze, walk.walk); 69 | if let Some(point) = 70 | build::choose_point_from_row_start(maze, walk.prev_row_start, build::ParityPoint::Even) 71 | { 72 | walk.prev_row_start = point.row; 73 | walk.walk = point; 74 | *maze.get_mut(walk.walk.row, walk.walk.col) &= !build::MARKERS_MASK; 75 | walk.prev = maze::Point { row: 0, col: 0 }; 76 | return Some(walk); 77 | } 78 | return None; 79 | } 80 | if found_loop(maze, walk.next) { 81 | erase_loop( 82 | maze, 83 | Loop { 84 | walk: walk.walk, 85 | root: walk.next, 86 | }, 87 | ); 88 | walk.walk = walk.next; 89 | let dir: &'static maze::Point = backtrack_point(maze, &walk.walk); 90 | walk.prev = maze::Point { 91 | row: walk.walk.row + dir.row, 92 | col: walk.walk.col + dir.col, 93 | }; 94 | return Some(walk); 95 | } 96 | build::mark_origin(maze, walk.walk, walk.next); 97 | walk.prev = walk.walk; 98 | walk.walk = walk.next; 99 | Some(walk) 100 | } 101 | 102 | fn erase_loop(maze: &mut maze::Maze, mut walk: Loop) { 103 | while walk.walk != walk.root { 104 | *maze.get_mut(walk.walk.row, walk.walk.col) &= !WALK_BIT; 105 | let dir: &'static maze::Point = backtrack_point(maze, &walk.walk); 106 | let next = maze::Point { 107 | row: walk.walk.row + dir.row, 108 | col: walk.walk.col + dir.col, 109 | }; 110 | *maze.get_mut(walk.walk.row, walk.walk.col) &= !build::MARKERS_MASK; 111 | walk.walk = next; 112 | } 113 | } 114 | 115 | fn connect_walk(maze: &mut maze::Maze, mut walk: maze::Point) { 116 | while (maze.get(walk.row, walk.col) & build::MARKERS_MASK) != 0 { 117 | let dir: &'static maze::Point = backtrack_point(maze, &walk); 118 | let next = maze::Point { 119 | row: walk.row + dir.row, 120 | col: walk.col + dir.col, 121 | }; 122 | build_with_marks(maze, walk, next); 123 | *maze.get_mut(walk.row, walk.col) &= !build::MARKERS_MASK; 124 | walk = next; 125 | } 126 | *maze.get_mut(walk.row, walk.col) &= !build::MARKERS_MASK; 127 | *maze.get_mut(walk.row, walk.col) &= !WALK_BIT; 128 | build::build_wall_line(maze, walk); 129 | } 130 | 131 | fn build_with_marks(maze: &mut maze::Maze, cur: maze::Point, next: maze::Point) { 132 | let mut wall = cur; 133 | if next.row < cur.row { 134 | wall.row -= 1; 135 | } else if next.row > cur.row { 136 | wall.row += 1; 137 | } else if next.col < cur.col { 138 | wall.col -= 1; 139 | } else if next.col > cur.col { 140 | wall.col += 1; 141 | } else { 142 | print::maze_panic!("Wall break error. Step through wall didn't work"); 143 | } 144 | *maze.get_mut(cur.row, cur.col) &= !WALK_BIT; 145 | *maze.get_mut(next.row, next.col) &= !WALK_BIT; 146 | build::build_wall_line(maze, cur); 147 | build::build_wall_line(maze, wall); 148 | build::build_wall_line(maze, next); 149 | } 150 | 151 | /// 152 | /// History based generator for animation and playback. 153 | /// 154 | pub fn generate_history(monitor: monitor::MazeMonitor) { 155 | let mut lk = match monitor.lock() { 156 | Ok(l) => l, 157 | Err(_) => print::maze_panic!("uncontested lock failure"), 158 | }; 159 | build::build_wall_outline_history(&mut lk.maze); 160 | let mut rng = thread_rng(); 161 | let mut cur = RandomWalk { 162 | prev_row_start: 2, 163 | prev: maze::Point { row: 0, col: 0 }, 164 | walk: maze::Point { 165 | row: 2 * (rng.gen_range(2..lk.maze.rows() - 1) / 2), 166 | col: 2 * (rng.gen_range(2..lk.maze.cols() - 1) / 2), 167 | }, 168 | next: maze::Point { row: 0, col: 0 }, 169 | }; 170 | let mut indices: [usize; 4] = [0, 1, 2, 3]; 171 | 'walking: loop { 172 | *lk.maze.get_mut(cur.walk.row, cur.walk.col) |= WALK_BIT; 173 | indices.shuffle(&mut rng); 174 | 'choosing_step: for &i in indices.iter() { 175 | let p = &build::GENERATE_DIRECTIONS[i]; 176 | cur.next = maze::Point { 177 | row: cur.walk.row + p.row, 178 | col: cur.walk.col + p.col, 179 | }; 180 | if !is_valid_step(&lk.maze, cur.next, cur.prev) { 181 | continue 'choosing_step; 182 | } 183 | cur = match complete_walk_history(&mut lk.maze, cur) { 184 | None => return, 185 | Some(new_walk) => new_walk, 186 | }; 187 | continue 'walking; 188 | } 189 | } 190 | } 191 | 192 | fn complete_walk_history(maze: &mut maze::Maze, mut walk: RandomWalk) -> Option { 193 | if build::has_builder_bit(maze, walk.next) { 194 | bridge_gap_history(maze, walk.walk, walk.next); 195 | connect_walk_history(maze, walk.walk); 196 | if let Some(point) = 197 | build::choose_point_from_row_start(maze, walk.prev_row_start, build::ParityPoint::Even) 198 | { 199 | walk.prev_row_start = point.row; 200 | walk.walk = point; 201 | *maze.get_mut(walk.walk.row, walk.walk.col) &= !build::MARKERS_MASK; 202 | walk.prev = maze::Point { row: 0, col: 0 }; 203 | return Some(walk); 204 | } 205 | return None; 206 | } 207 | if found_loop(maze, walk.next) { 208 | erase_loop_history( 209 | maze, 210 | Loop { 211 | walk: walk.walk, 212 | root: walk.next, 213 | }, 214 | ); 215 | walk.walk = walk.next; 216 | let dir: &'static maze::Point = backtrack_point(maze, &walk.walk); 217 | walk.prev = maze::Point { 218 | row: walk.walk.row + dir.row, 219 | col: walk.walk.col + dir.col, 220 | }; 221 | return Some(walk); 222 | } 223 | mark_line_history(maze, walk.walk, walk.next); 224 | walk.prev = walk.walk; 225 | walk.walk = walk.next; 226 | Some(walk) 227 | } 228 | 229 | fn erase_loop_history(maze: &mut maze::Maze, mut walk: Loop) { 230 | while walk.walk != walk.root { 231 | let walk_square = maze.get(walk.walk.row, walk.walk.col); 232 | let dir: &'static maze::Point = backtrack_point(maze, &walk.walk); 233 | let half: &'static maze::Point = backtrack_half_step(maze, &walk.walk); 234 | let half_step = maze::Point { 235 | row: walk.walk.row + half.row, 236 | col: walk.walk.col + half.col, 237 | }; 238 | let half_square = maze.get(half_step.row, half_step.col); 239 | let next = maze::Point { 240 | row: walk.walk.row + dir.row, 241 | col: walk.walk.col + dir.col, 242 | }; 243 | maze.build_history.push(maze::Delta { 244 | id: walk.walk, 245 | before: walk_square, 246 | after: (((walk_square & !WALK_BIT) & !maze::WALL_MASK) & !build::MARKERS_MASK) 247 | | maze::PATH_BIT, 248 | burst: 1, 249 | }); 250 | maze.build_history.push(maze::Delta { 251 | id: half_step, 252 | before: half_square, 253 | after: ((half_square & !build::MARKERS_MASK) & !maze::WALL_MASK) | maze::PATH_BIT, 254 | burst: 1, 255 | }); 256 | *maze.get_mut(half_step.row, half_step.col) = 257 | ((half_square & !build::MARKERS_MASK) & !maze::WALL_MASK) | maze::PATH_BIT; 258 | *maze.get_mut(walk.walk.row, walk.walk.col) = 259 | (((walk_square & !WALK_BIT) & !maze::WALL_MASK) & !build::MARKERS_MASK) 260 | | maze::PATH_BIT; 261 | walk.walk = next; 262 | } 263 | } 264 | 265 | fn connect_walk_history(maze: &mut maze::Maze, mut walk: maze::Point) { 266 | while (maze.get(walk.row, walk.col) & build::MARKERS_MASK) != 0 { 267 | let dir: &'static maze::Point = backtrack_point(maze, &walk); 268 | let half: &'static maze::Point = backtrack_half_step(maze, &walk); 269 | let half_step = maze::Point { 270 | row: walk.row + half.row, 271 | col: walk.col + half.col, 272 | }; 273 | let next = maze::Point { 274 | row: walk.row + dir.row, 275 | col: walk.col + dir.col, 276 | }; 277 | build_walk_line_history(maze, walk); 278 | build_walk_line_history(maze, half_step); 279 | walk = next; 280 | } 281 | build_walk_line_history(maze, walk); 282 | } 283 | 284 | pub fn mark_line_history(maze: &mut maze::Maze, walk: maze::Point, next: maze::Point) { 285 | let mut wall = walk; 286 | let next_before = maze.get(next.row, next.col); 287 | let wall_before = if next.row > walk.row { 288 | wall.row += 1; 289 | let before = maze.get(wall.row, wall.col); 290 | *maze.get_mut(wall.row, wall.col) = (before | build::FROM_NORTH) & !maze::PATH_BIT; 291 | *maze.get_mut(next.row, next.col) = (next_before | build::FROM_NORTH) & !maze::PATH_BIT; 292 | before 293 | } else if next.row < walk.row { 294 | wall.row -= 1; 295 | let before = maze.get(wall.row, wall.col); 296 | *maze.get_mut(wall.row, wall.col) = (before | build::FROM_SOUTH) & !maze::PATH_BIT; 297 | *maze.get_mut(next.row, next.col) = (next_before | build::FROM_SOUTH) & !maze::PATH_BIT; 298 | before 299 | } else if next.col < walk.col { 300 | wall.col -= 1; 301 | let before = maze.get(wall.row, wall.col); 302 | *maze.get_mut(wall.row, wall.col) = (before | build::FROM_EAST) & !maze::PATH_BIT; 303 | *maze.get_mut(next.row, next.col) = (next_before | build::FROM_EAST) & !maze::PATH_BIT; 304 | before 305 | } else if next.col > walk.col { 306 | wall.col += 1; 307 | let before = maze.get(wall.row, wall.col); 308 | *maze.get_mut(wall.row, wall.col) = (before | build::FROM_WEST) & !maze::PATH_BIT; 309 | *maze.get_mut(next.row, next.col) = (next_before | build::FROM_WEST) & !maze::PATH_BIT; 310 | before 311 | } else { 312 | print::maze_panic!("next cannot be equal to walk"); 313 | }; 314 | maze.build_history.push(maze::Delta { 315 | id: wall, 316 | before: wall_before, 317 | after: maze.get(wall.row, wall.col), 318 | burst: 1, 319 | }); 320 | maze.build_history.push(maze::Delta { 321 | id: next, 322 | before: next_before, 323 | after: maze.get(next.row, next.col), 324 | burst: 1, 325 | }); 326 | } 327 | 328 | fn build_walk_line_history(maze: &mut maze::Maze, p: maze::Point) { 329 | let mut wall_changes = [maze::Delta::default(); 5]; 330 | let mut burst = 1; 331 | let mut wall: maze::WallLine = 0b0; 332 | let square = maze.get(p.row, p.col); 333 | if p.row > 0 && maze.wall_at(p.row - 1, p.col) { 334 | let neighbor = maze.get(p.row - 1, p.col); 335 | wall_changes[burst] = maze::Delta { 336 | id: maze::Point { 337 | row: p.row - 1, 338 | col: p.col, 339 | }, 340 | before: neighbor, 341 | after: (neighbor | maze::SOUTH_WALL), 342 | burst: burst + 1, 343 | }; 344 | burst += 1; 345 | *maze.get_mut(p.row - 1, p.col) |= maze::SOUTH_WALL; 346 | wall |= maze::NORTH_WALL; 347 | } 348 | if p.row + 1 < maze.rows() && maze.wall_at(p.row + 1, p.col) { 349 | let neighbor = maze.get(p.row + 1, p.col); 350 | wall_changes[burst] = maze::Delta { 351 | id: maze::Point { 352 | row: p.row + 1, 353 | col: p.col, 354 | }, 355 | before: neighbor, 356 | after: (neighbor | maze::NORTH_WALL), 357 | burst: burst + 1, 358 | }; 359 | burst += 1; 360 | *maze.get_mut(p.row + 1, p.col) |= maze::NORTH_WALL; 361 | wall |= maze::SOUTH_WALL; 362 | } 363 | if p.col > 0 && maze.wall_at(p.row, p.col - 1) { 364 | let neighbor = maze.get(p.row, p.col - 1); 365 | wall_changes[burst] = maze::Delta { 366 | id: maze::Point { 367 | row: p.row, 368 | col: p.col - 1, 369 | }, 370 | before: neighbor, 371 | after: (neighbor | maze::EAST_WALL), 372 | burst: burst + 1, 373 | }; 374 | burst += 1; 375 | *maze.get_mut(p.row, p.col - 1) |= maze::EAST_WALL; 376 | wall |= maze::WEST_WALL; 377 | } 378 | if p.col + 1 < maze.cols() && maze.wall_at(p.row, p.col + 1) { 379 | let neighbor = maze.get(p.row, p.col + 1); 380 | wall_changes[burst] = maze::Delta { 381 | id: maze::Point { 382 | row: p.row, 383 | col: p.col + 1, 384 | }, 385 | before: neighbor, 386 | after: (neighbor | maze::WEST_WALL), 387 | burst: burst + 1, 388 | }; 389 | burst += 1; 390 | *maze.get_mut(p.row, p.col + 1) |= maze::WEST_WALL; 391 | wall |= maze::EAST_WALL; 392 | } 393 | wall_changes[0] = maze::Delta { 394 | id: p, 395 | before: square, 396 | after: ((square | wall | build::BUILDER_BIT) & !maze::PATH_BIT) 397 | & !WALK_BIT 398 | & !build::MARKERS_MASK, 399 | burst, 400 | }; 401 | *maze.get_mut(p.row, p.col) = 402 | ((square | wall | build::BUILDER_BIT) & !maze::PATH_BIT) & !WALK_BIT & !build::MARKERS_MASK; 403 | maze.build_history.push_burst(&wall_changes[0..burst]); 404 | } 405 | 406 | fn bridge_gap_history(maze: &mut maze::Maze, cur: maze::Point, next: maze::Point) { 407 | let mut wall = cur; 408 | if next.row < cur.row { 409 | wall.row -= 1; 410 | } else if next.row > cur.row { 411 | wall.row += 1; 412 | } else if next.col < cur.col { 413 | wall.col -= 1; 414 | } else if next.col > cur.col { 415 | wall.col += 1; 416 | } else { 417 | print::maze_panic!( 418 | "Wall break error. Step through wall didn't work: cur {:?}, next {:?}", 419 | cur, 420 | next 421 | ); 422 | } 423 | build_walk_line_history(maze, wall); 424 | } 425 | 426 | /// 427 | /// Logic utilities available to all. 428 | /// 429 | fn is_valid_step(maze: &maze::Maze, next: maze::Point, prev: maze::Point) -> bool { 430 | next.row >= 0 431 | && next.row < maze.rows() 432 | && next.col >= 0 433 | && next.col < maze.cols() 434 | && next != prev 435 | } 436 | 437 | fn backtrack_point(maze: &maze::Maze, walk: &maze::Point) -> &'static maze::Point { 438 | &build::BACKTRACKING_POINTS[(maze.get(walk.row, walk.col) & build::MARKERS_MASK) as usize] 439 | } 440 | 441 | fn backtrack_half_step(maze: &maze::Maze, walk: &maze::Point) -> &'static maze::Point { 442 | &build::BACKTRACKING_HALF_POINTS[(maze.get(walk.row, walk.col) & build::MARKERS_MASK) as usize] 443 | } 444 | 445 | fn found_loop(maze: &maze::Maze, p: maze::Point) -> bool { 446 | (maze.get(p.row, p.col) & WALK_BIT) != 0 447 | } 448 | -------------------------------------------------------------------------------- /maze_tui/run_tui/src/run.rs: -------------------------------------------------------------------------------- 1 | use crate::tui; 2 | use builders::build; 3 | use crossterm::event::KeyCode; 4 | use rand::{distributions::Bernoulli, distributions::Distribution, thread_rng}; 5 | use ratatui::{ 6 | prelude::{CrosstermBackend, Rect, Terminal}, 7 | widgets::ScrollDirection, 8 | }; 9 | use std::{error, fmt, rc::Rc, sync::Arc, sync::Mutex}; 10 | use tui_textarea::{Input, Key}; 11 | 12 | static VALID_FLAGS: &str = "VALID FLAGS:[-b][-ba][-s][-sa][-w][-m]"; 13 | static VALID_ARGS: [(&str, &str); 6] = [ 14 | ("-b", "see BUILDER FLAG section"), 15 | ("-m", "see MODIFICATION FLAG section"), 16 | ("-w", "see WALL FLAG section"), 17 | ("-s", "see SOLVER FLAG section"), 18 | ("-sa", "see SOLVER ANIMATION section"), 19 | ("-ba", "see BUILDER ANIMATION section"), 20 | ]; 21 | 22 | #[derive(Debug)] 23 | pub struct Quit { 24 | pub q: bool, 25 | } 26 | 27 | impl Quit { 28 | pub fn new() -> Self { 29 | Quit { q: false } 30 | } 31 | } 32 | 33 | impl fmt::Display for Quit { 34 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 35 | write!(f, "Quit: {}", self.q) 36 | } 37 | } 38 | 39 | impl error::Error for Quit {} 40 | 41 | // A Playback just extracts the internal histories of a maze::Maze for ease of use with animations. 42 | #[derive(Debug, Clone)] 43 | struct Playback { 44 | maze: maze::Blueprint, 45 | build_tape: maze::Tape, 46 | solve_tape: maze::Tape, 47 | forward: bool, 48 | pause: bool, 49 | } 50 | 51 | /// 52 | /// Main TUI program running and logic. 53 | /// 54 | /// The main render loop from the home page. This loop is relatively simple. When the more 55 | /// complex functionality of a maze animation is requested we will hand that off to another fn. 56 | pub fn run() -> tui::Result<()> { 57 | let backend = CrosstermBackend::new(std::io::stdout()); 58 | let terminal = Terminal::new(backend)?; 59 | let events = tui::EventHandler::new(4.0); 60 | let mut tui = tui::Tui::new(terminal, events); 61 | tui.enter()?; 62 | let mut play = new_home_tape(tui.padded_frame()); 63 | 'render: while let Some(ev) = tui.events.next() { 64 | match ev { 65 | tui::Pack::Resize(_, _) => { 66 | play = new_home_tape(tui.padded_frame()); 67 | } 68 | tui::Pack::Press(ev) => match ev.into() { 69 | Input { key: Key::Esc, .. } => break 'render, 70 | Input { key: Key::Down, .. } => tui.scroll(ScrollDirection::Forward), 71 | Input { key: Key::Up, .. } => tui.scroll(ScrollDirection::Backward), 72 | Input { 73 | key: Key::Enter, .. 74 | } => match set_command_args(tui.cmd.lines()[0].to_string(), &mut tui) { 75 | Ok(run) => { 76 | render_maze(run, &mut tui)?; 77 | } 78 | Err(msg) => 'reading_message: loop { 79 | if let Some(ev) = tui.events.next() { 80 | match ev { 81 | tui::Pack::Render => { 82 | tui.error_popup(&msg, tui::SolveFrame { maze: &play.maze })?; 83 | } 84 | _ => break 'reading_message, 85 | } 86 | } 87 | }, 88 | }, 89 | input => { 90 | tui.cmd_input(input); 91 | } 92 | }, 93 | tui::Pack::Render => { 94 | if play.pause { 95 | play.forward = !play.forward; 96 | play.pause = !play.pause; 97 | } else if !play.solve_delta() { 98 | play.forward = true; 99 | } 100 | tui.home(tui::SolveFrame { maze: &play.maze })?; 101 | } 102 | } 103 | } 104 | tui.exit()?; 105 | Ok(()) 106 | } 107 | 108 | // Keeping the three loops visible in one function like this makes it easier to reason about 109 | // playing the animation forward or in reverse. The handle_press function can mutate the 110 | // play direction but needed to extract repetitive logic that made this function harder to read. 111 | fn render_maze(this_run: tables::HistoryRunner, tui: &mut tui::Tui) -> tui::Result<()> { 112 | let render_space = tui.inner_maze_rect(); 113 | let mut play = new_tape(&this_run); 114 | 'rendering: loop { 115 | 'building: while let Some(ev) = tui.events.next() { 116 | match ev { 117 | tui::Pack::Press(ev) => { 118 | if !handle_press( 119 | tui, 120 | ev.code, 121 | tui::Process::Building, 122 | &this_run, 123 | &mut play, 124 | &render_space, 125 | ) { 126 | break 'rendering; 127 | } 128 | } 129 | tui::Pack::Render => { 130 | if !play.build_delta() { 131 | break 'building; 132 | } 133 | tui.render_maze_frame( 134 | tui::BuildFrame { 135 | maze: &mut play.maze, 136 | }, 137 | &render_space, 138 | play.forward, 139 | play.pause, 140 | )?; 141 | } 142 | tui::Pack::Resize(_, _) => break 'rendering, 143 | } 144 | } 145 | 'solving: while let Some(ev) = tui.events.next() { 146 | match ev { 147 | tui::Pack::Press(ev) => { 148 | if !handle_press( 149 | tui, 150 | ev.code, 151 | tui::Process::Solving, 152 | &this_run, 153 | &mut play, 154 | &render_space, 155 | ) { 156 | break 'rendering; 157 | } 158 | } 159 | tui::Pack::Render => { 160 | if !play.solve_delta() { 161 | break 'solving; 162 | } 163 | tui.render_maze_frame( 164 | tui::SolveFrame { maze: &play.maze }, 165 | &render_space, 166 | play.forward, 167 | play.pause, 168 | )?; 169 | } 170 | tui::Pack::Resize(_, _) => break 'rendering, 171 | } 172 | } 173 | } 174 | Ok(()) 175 | } 176 | 177 | fn handle_press( 178 | tui: &mut tui::Tui, 179 | ev: crossterm::event::KeyCode, 180 | process: tui::Process, 181 | args: &tables::HistoryRunner, 182 | play: &mut Playback, 183 | render_space: &Rc<[Rect]>, 184 | ) -> bool { 185 | match ev { 186 | KeyCode::Char('i') => { 187 | if handle_reader( 188 | tui, 189 | process, 190 | args.build.get_description(), 191 | &play.maze, 192 | render_space, 193 | ) 194 | .is_err() 195 | { 196 | return false; 197 | } 198 | } 199 | KeyCode::Char(' ') => play.pause = !play.pause, 200 | KeyCode::Right => { 201 | play.forward = true; 202 | play.pause = true; 203 | match process { 204 | tui::Process::Building => play.build_step(), 205 | tui::Process::Solving => play.solve_step(), 206 | }; 207 | } 208 | KeyCode::Left => { 209 | play.forward = false; 210 | play.pause = true; 211 | match process { 212 | tui::Process::Building => play.build_step(), 213 | tui::Process::Solving => play.solve_step(), 214 | }; 215 | } 216 | KeyCode::Esc => return false, 217 | _ => return true, 218 | } 219 | true 220 | } 221 | 222 | fn handle_reader( 223 | tui: &mut tui::Tui, 224 | process: tui::Process, 225 | description: &str, 226 | maze: &maze::Blueprint, 227 | render_space: &Rc<[Rect]>, 228 | ) -> tui::Result<()> { 229 | let mut scroll = tui::Scroller::default(); 230 | 'reading: loop { 231 | if let Some(k) = tui.events.next() { 232 | match k { 233 | tui::Pack::Press(k) => match k.code { 234 | KeyCode::Char('i') => break 'reading, 235 | KeyCode::Down => scroll.scroll(ScrollDirection::Forward), 236 | KeyCode::Up => scroll.scroll(ScrollDirection::Backward), 237 | KeyCode::Esc => return Err(Box::new(Quit::new())), 238 | _ => {} 239 | }, 240 | tui::Pack::Render => { 241 | tui.info_popup(process, render_space, maze, &mut scroll, description)?; 242 | } 243 | tui::Pack::Resize(_, _) => return Err(Box::new(Quit::new())), 244 | } 245 | } 246 | } 247 | Ok(()) 248 | } 249 | 250 | /// 251 | /// Maze generation and solving. It is simple because we don't have to worry about animations 252 | /// until the maze generation and solving histories have been recorded. Then we decide how 253 | /// we want to play all of that back with the help of builder and solver decoding functions. 254 | /// 255 | // A new tape runs to completion then resets the maze buffer to its starting state. 256 | fn new_tape(run: &tables::HistoryRunner) -> Playback { 257 | let monitor = monitor::Monitor::new(maze::Maze::new(run.args)); 258 | (run.build.get_fn())(monitor.clone()); 259 | if let Some(m) = run.modify { 260 | m.get_fn()(monitor.clone()); 261 | } 262 | (run.solve.get_fn())(monitor.clone()); 263 | match Arc::into_inner(monitor) { 264 | Some(a) => match Mutex::into_inner(a) { 265 | Ok(mut solver) => { 266 | build::reset_build(&mut solver.maze); 267 | Playback { 268 | maze: solver.maze.maze, 269 | build_tape: solver.maze.build_history, 270 | solve_tape: solver.maze.solve_history, 271 | forward: true, 272 | pause: false, 273 | } 274 | } 275 | Err(_) => print::maze_panic!("rendering cannot progress without lock"), 276 | }, 277 | None => print::maze_panic!("rendering cannot progress without lock"), 278 | } 279 | } 280 | 281 | // A new home tape solves everything but then only resets the solver for less distracting home. 282 | fn new_home_tape(rect: Rect) -> Playback { 283 | let run_bg = set_random_args(&rect); 284 | let bg_maze = monitor::Monitor::new(maze::Maze::new(run_bg.args)); 285 | (run_bg.build.get_fn())(bg_maze.clone()); 286 | if let Some(m) = run_bg.modify { 287 | (m.get_fn())(bg_maze.clone()); 288 | } 289 | (run_bg.solve.get_fn())(bg_maze.clone()); 290 | match Arc::into_inner(bg_maze) { 291 | Some(a) => match Mutex::into_inner(a) { 292 | Ok(mut solver) => { 293 | solvers::solve::reset_solve(&mut solver.maze); 294 | Playback { 295 | maze: solver.maze.maze, 296 | build_tape: solver.maze.build_history, 297 | solve_tape: solver.maze.solve_history, 298 | forward: true, 299 | pause: false, 300 | } 301 | } 302 | Err(_) => print::maze_panic!("rendering cannot progress without lock"), 303 | }, 304 | None => print::maze_panic!("rendering cannot progress without lock"), 305 | } 306 | } 307 | 308 | /// 309 | /// Argument parsing from the tui-textarea or random generation if empty 310 | /// 311 | pub fn set_command_args(cmd: String, tui: &mut tui::Tui) -> Result { 312 | if cmd.is_empty() { 313 | return Ok(set_random_args(&tui.inner_maze_rect()[0])); 314 | } 315 | let dimensions = tui.inner_dimensions(); 316 | let mut run = tables::HistoryRunner::new(); 317 | run.args.odd_rows = dimensions.rows; 318 | run.args.odd_cols = dimensions.cols; 319 | run.args.offset = dimensions.offset; 320 | let mut prev_flag: &str = ""; 321 | let mut process_current = false; 322 | for a in cmd.split_whitespace() { 323 | if process_current { 324 | if let Err(msg) = set_arg( 325 | &mut run, 326 | &tables::FlagArg { 327 | flag: prev_flag, 328 | arg: a, 329 | }, 330 | ) { 331 | return Err(msg.to_string()); 332 | } 333 | process_current = false; 334 | continue; 335 | } 336 | match tables::match_flag(a) { 337 | Some(flag) => { 338 | process_current = true; 339 | prev_flag = flag; 340 | } 341 | None => { 342 | return Err(format!( 343 | "unknown flag[{}].\n{}\npress any key to continue", 344 | a, VALID_FLAGS 345 | )); 346 | } 347 | } 348 | } 349 | if process_current { 350 | return Err(format!( 351 | "flag[{}] with missing arg[?]\n{}\npress any key to continue", 352 | prev_flag, 353 | get_arg_section(prev_flag) 354 | )); 355 | } 356 | if run.args.style == maze::MazeStyle::Mini { 357 | run.args.odd_rows *= 2; 358 | } 359 | Ok(run) 360 | } 361 | 362 | fn set_arg(run: &mut tables::HistoryRunner, args: &tables::FlagArg) -> Result<(), String> { 363 | match args.flag { 364 | "-b" => tables::match_builder(args.arg) 365 | .map(|func_pair| run.build = func_pair) 366 | .ok_or(err_string(args)), 367 | "-m" => tables::match_modifier(args.arg) 368 | .map(|mod_tuple| run.modify = Some(mod_tuple)) 369 | .ok_or(err_string(args)), 370 | "-s" => tables::match_solver(args.arg) 371 | .map(|solve_tuple| run.solve = solve_tuple) 372 | .ok_or(err_string(args)), 373 | "-w" => tables::match_walls(args.arg) 374 | .map(|wall_style| run.args.style = wall_style) 375 | .ok_or(err_string(args)), 376 | _ => Err(err_string(args)), 377 | } 378 | } 379 | 380 | fn set_random_args(rect: &Rect) -> tables::HistoryRunner { 381 | let mut rng = thread_rng(); 382 | let mut this_run = tables::HistoryRunner::new(); 383 | this_run.args.odd_rows = (rect.height - 2) as i32; 384 | this_run.args.odd_cols = (rect.width - 2) as i32; 385 | this_run.args.offset = maze::Offset { 386 | add_rows: rect.y as i32, 387 | add_cols: rect.x as i32, 388 | }; 389 | let modification_probability = Bernoulli::new(0.2); 390 | this_run.args.style = maze::MazeStyle::get_random(&mut rng); 391 | this_run.build = tables::BuildHistoryType::get_random(&mut rng); 392 | this_run.solve = tables::SolveHistoryType::get_random(&mut rng); 393 | this_run.modify = None; 394 | if modification_probability 395 | .expect("Bernoulli innefective") 396 | .sample(&mut rng) 397 | { 398 | this_run.modify = Some(tables::ModificationHistoryType::get_random(&mut rng)); 399 | } 400 | if this_run.args.style == maze::MazeStyle::Mini { 401 | this_run.args.odd_rows *= 2; 402 | } 403 | this_run 404 | } 405 | 406 | fn err_string(args: &tables::FlagArg) -> String { 407 | format!("invalid flag[{}] arg[{}] combo", args.flag, args.arg) 408 | } 409 | 410 | fn get_arg_section(flag: &str) -> &'static str { 411 | VALID_ARGS 412 | .iter() 413 | .find(|(f, _)| *f == flag) 414 | .expect("check VALID_ARGS table.") 415 | .1 416 | } 417 | 418 | /// 419 | /// History function wrappers to help simplify what the runner is responsible for with playback. 420 | /// 421 | // A step just progresses the Tape based on whatever the current direction state is. 422 | impl Playback { 423 | fn build_step(&mut self) -> bool { 424 | if let Some(history) = self.build_tape.cur_step() { 425 | if self.forward { 426 | for delta in history { 427 | self.maze.buf[(delta.id.row * self.maze.cols + delta.id.col) as usize] = 428 | delta.after; 429 | } 430 | return self.build_tape.set_next(); 431 | } 432 | for delta in history.iter().rev() { 433 | self.maze.buf[(delta.id.row * self.maze.cols + delta.id.col) as usize] = 434 | delta.before; 435 | } 436 | return self.build_tape.set_prev(); 437 | } 438 | false 439 | } 440 | 441 | fn solve_step(&mut self) -> bool { 442 | if let Some(history) = self.solve_tape.cur_step() { 443 | if self.forward { 444 | for delta in history { 445 | self.maze.buf[(delta.id.row * self.maze.cols + delta.id.col) as usize] = 446 | delta.after; 447 | } 448 | return self.solve_tape.set_next(); 449 | } 450 | for delta in history.iter().rev() { 451 | self.maze.buf[(delta.id.row * self.maze.cols + delta.id.col) as usize] = 452 | delta.before; 453 | } 454 | return self.solve_tape.set_prev(); 455 | } 456 | false 457 | } 458 | 459 | fn build_delta(&mut self) -> bool { 460 | if self.pause { 461 | return true; 462 | } 463 | if self.forward { 464 | if !self.build_step() { 465 | return false; 466 | } 467 | } else { 468 | self.forward = !self.build_step(); 469 | } 470 | true 471 | } 472 | 473 | fn solve_delta(&mut self) -> bool { 474 | if self.pause { 475 | return true; 476 | } 477 | if self.forward { 478 | if !self.solve_step() { 479 | self.pause = true; 480 | } 481 | } else if !self.solve_step() { 482 | return false; 483 | } 484 | true 485 | } 486 | } 487 | --------------------------------------------------------------------------------