An algorithmic maze generator and pathfinder with Pygame visualization. The generation algorithm builds a maze as a 2D NumPy array with values indicating walls, paths, and entry/exit; then, another algorithm solves the generated maze; finally, a Pygame visualizer displays generation and pathfinding, and statistic results are shown in-terminal.
This project was selected as an introduction to algorithmic simulations. As a learning project, the project introduced me to Pygame and heapq while expanding my existing complex-data skillset (NumPy, especially). Originally, the project was based exclusively around pathfinding; however, the generation aspect quickly became an equal focus.
generation/— Contains multiple generation algorithms to build and pass a completed maze. Results are returned as a normalized GenerationResult dataclass.pathfinding/— Variety of pathfinding algorithms that solve a completed maze using a unique strategy. Results are returned as a normalized PathfindingResult dataclass.outputs/— Result handling, including statistic calculations and generation/pathfinding visualization.visualizer.py— GenerationVisualizer + PathfindingVisualizer, both Pygame-based. Carve order and exploration order are animated in sequence, with a "search head" highlight showing the active frontier cell.stats.py— Terminal-printed mean/median/stdev/min/max per stat over full run
main.py— SingleRun / MultiRun orchestration, episode-count-driven routingcli.py— Command argparse for maze dimensions and cell-size
Additional generation and pathfinding algorithms will be added over time. Currently, two generation algorithms and two pathfinding algorithms are fully implemented.
recursive_backtracking.py— Iterative stack-based carving using DFS-similar approach; avoided fixed Python recursion-depth limits by moving the call stack into an explicit, heap-allocated list.prims.py— Heap-based frontier carving with lazy deletion; frontier cells pushed with a random priority and only validated against the maze on pop, avoiding the O(n) cost (list-removal and membership checking) of live, deduplicated frontier.
bfs.py— Queue-based algorithm (dependent on path-copying deque system) that explores breadth-first; duplicate queueing combated via at-discovery "visited" tagging (rather than at-processing)dfs.py— Stack-based algorithm (dependent on path-copying list system) that explores depth-first along a single path; like BFS, combats duplicate data via at-discovery tagging.
- Initially, the system depended on a dictionary of (x, y) tuples for storing the maze itself. While time complexity was optimized (O(1)), the look-up demand at scale was too large: tuple hashing and per-entry Python object overhead for the key, value, and hash table slot. To combat this, a NumPy-based 2D array of [x][y] cells was implemented, simplifying to a single contiguous, fixed-type memory block. This system directly improved the memory footprint and real-time lookup speed.
- The registry-based design, with
generation/andpathfinding/folders, enables simplified addition of new algorithms. The modularity of the design means that any new algorithm is one self-contained file addition. - AlgorithmResult dataclasses were chosen as an extension to the simplified registry design, adding normalization across algorithms for efficient data processing.
- Entry/Exit are fixed at (1, 1) and (width - 2, height - 2) as a strategy of deliberate simplicity. While passing an entry and exit upon generation is plausible, the guaranteed entry/exit coordinates allow for cross-run comparison between multiple algorithms without the possibility of entry/exit-skewed results.
- Maze dimensions must be odd-value (automatically enforced). Since any cell can be a wall or path (rather than path cells containing border-wall data), the carve algorithms check across a neighboring cell to determine if the cell can be carved. This preserves the outer border of the maze and ensures that paths remain 1-wide.
- No weighted-cell support. Currently, algorithms that manage weighted cells, such as Dijkstra's, are unintegrated. The existing maze storage architecture marks 0/1 values as paths and walls—the complexity of cell weighting, especially given the wide variety of non-weighted algorithms available, seemed an unnecessary feature.
- Perfect mazes only. Currently, no loops occur within generated mazes. Future generators, such as a loop-allowed iteration of Prim's algorithm, could use randomization to (rarely) allow loops to form rather than guaranteeing clean path-branch structures.
- Stats are terminal only. Initially, full chart-based comparison architecture was designed. However, the system appeared entirely unnecessary—only a handful of stats, such as efficiency, actually offered comparative value. As such, I elected to remove the matplotlib-based chart architecture and return to a fully terminal-based system. Comparison can be achieved by manually saving statistic results and comparing pertinent datapoints, such as efficiency or generation time, across runs.
uv venv
uv syncuv run python main.py --width int --height int --cell-size intuv run python main.py --width 25 --height 25 --cell-size 5--width and --height default to 55 and dictate the dimensions of the generated maze. These dimensions remain the same for all generated mazes across a multiepisodic run.
--cell-size defaults to 10 and dictates the pixel size per maze cell, used in Pygame visualization.
generation/ # Storage folder for generation algorithms
recursive_backtracking.py
prims.py
result.py # GenerationResult dataclass
pathfinding/ # Storage folder for pathfinding algorithms
bfs.py
dfs.py
result.py # PathfindingResult dataclass
outputs/ # Folder for output systems, including Pygame visualization and statistics display
visualizer.py
stats.py
result.py # RunResult dataclass (AlgorithmResult dataclasses + run-specific configs)
cli.py # argparse for cross-file dimension and pixel configurations
main.py # Main orchestration architecture