Repository: jostbr/MazeGenerator
Branch: master
Commit: ac41b42df45b
Files: 25
Total size: 75.7 KB
Directory structure:
gitextract_q7eta_e0/
├── .gitignore
├── .travis.yml
├── LICENCE
├── README.md
├── examples/
│ ├── generate_binary_tree_algorithm.py
│ ├── quick_start.py
│ ├── solve_bi_directional.py
│ ├── solve_breadth_first_recursive.py
│ └── solve_depth_first_recursive.py
├── install_linter
├── requirements.txt
├── src/
│ ├── __init__.py
│ ├── algorithm.py
│ ├── cell.py
│ ├── maze.py
│ ├── maze_manager.py
│ ├── maze_viz.py
│ └── solver.py
└── tests/
├── __init__.py
├── algorithm_tests.py
├── cell_tests.py
├── maze_manager_tests.py
├── maze_tests.py
├── maze_viz_tests.py
└── solver_tests.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
*.png
*.jpg
*.mp4
.idea
*.pyc
__pycache__
================================================
FILE: .travis.yml
================================================
language: python
python:
- "3.5"
- "3.6"
before_install:
- "chmod +x tests/cell_tests.py"
- "chmod +x tests/maze_tests.py"
- "chmod +x tests/maze_manager_tests.py"
- "chmod +x tests/maze_viz_tests.py"
- "chmod +x tests/solver_tests.py"
install:
- "pip install -r requirements.txt"
script:
- "python -m unittest tests/cell_tests.py"
- "python -m unittest tests/maze_tests.py"
- "python -m unittest tests/maze_manager_tests.py"
- "python -m unittest tests/maze_viz_tests.py"
- "python -m unittest tests/solver_tests.py"
================================================
FILE: LICENCE
================================================
MIT License
Copyright (c) 2021 Jostein Brændshøi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# Maze generator and solver
Python scripts for generating random solvable mazes using the depth-first search and recursive backtracking algorithms. The code also implements a recursive backtracking pathfinding algorithm for solving the generated mazes. Here is an example of a generated maze and its computed solution.
Both the generator and solver algorithm uses recursive backtracking and here an example of the latter can be seen. Cells indicated in light orange are part of the backtracking. The algorithms works by moving randomly from a cell to one of its unvisited neighbours. If the search reaches cell which have no unvisited neighbours, the search backtracks until it moves to a cell with unvisited neighbours. The generator algorithm is heavily inspired by the psuedo code provided by [Wikipedia](https://en.wikipedia.org/wiki/Maze_generation_algorithm). The main difference between the generator and solver algorithms are in the fact that, when solving the maze, one has to take into account not being able to move through walls. And thus proper pathfinding needs to be implemented. There's also impmeneted an ehnaced version of the solver algorithm which moves not to a random neighbour, but moves to the neighbour that minimizes the distance sqrt(x^2 + y^2) to the exit cell (final destination).
## Quick Use Guide
The first step is to install the dependancies by opening the terminal, navigating to
the MazeGenerator directory, and running
`pip install -r requirements.txt`
Next, run the `quick_start` python example under the examples directory. If this ran without any errors,
you should be fine to create your own program. Use the format outlined in quick_start, or use
another example as a template.
The process for creating and solving a maze follows.
1. Create a maze manager
2. Add a maze to the manager
3. Solve the maze
4. Optionally visualize the results
An example of using the library with different options is shown below.
```python
from __future__ import absolute_import
from src.maze_manager import MazeManager
from src.maze import Maze
if __name__ == "__main__":
# The easiest way to use the library is through the Manager class. It acts as the glue between
# The visualization, solver, and maze classes. Mazes inside the manager have unique ids that we use
# to specify particular mazes.
manager = MazeManager()
# We can add mazes to the manager two different ways.
# The first way, we specify the maze dimensions. The maze that is created gets returned back to you.
maze = manager.add_maze(10, 10)
# The second way is by creating a maze, and then adding it to the manager. Doing this will require you to add
# from src.maze import Maze
# to your imports. Because the ids need to be unique, the manager will ensure this happens. It may change the
# id of the maze that was passed in, so we assign it to the return value to make sure we're using the updated maze.
maze2 = Maze(10, 10)
maze2 = manager.add_existing_maze(maze2)
# Once we have a maze in the manager, we can tell the manager to solve it with a particular algorithm.
#manager.solve_maze(maze.id, "BreadthFirst")
#manager.solve_maze(maze.id, "BiDirectional")
manager.solve_maze(maze.id, "DepthFirstBacktracker")
# If we want to save the maze & maze solution images along with their animations, we need to let the manager know.
manager.set_filename("myFileName")
# To see the unsolved maze, call
manager.show_maze(maze.id)
# You can also set the size of the cell by passing show_maze's second argument. The default is 1.
# manager.show_maze(maze.id, 2)
# To show an animation of how the maze was generated, use the following line
manager.show_generation_animation(maze.id)
# You can also see an animation of how the solver went about finding the end
manager.show_solution_animation(maze.id)
# Finally, you can show an image of the maze with the solution path overlaid. All of these display
# functions will save the figure if MazeManager::set_filename has been set.
manager.show_solution(maze.id)
```
## Developer's Guide
### Source Layout
* /src/ Holds the source code (modules) needed to run MazeGenerator.
* /tests/ Holds the unit tests that test the code in /src/
* /examples/ Example files that demonstrate how to use the library.
### Class Overview
* The`Maze` class. This class provides helper functions to easily manipulate the cells. It can be thought of as being a grid of Cells
* The `Cell` class is used to keep track of walls, and is what makes up the list.
* The `Visualizer` class is responsible for handling the generation, display, and saving of animations and grid images. It can be interacted with directly, or controlled thought the `MazeManager` class.
* The `Solve` class. All solution methods are derived from this class.
* The `MazeManager` class acts as the glue, bridging the `Visualizer`, `Maze`, and `Solve` classes together.
### Adding a new Solution Algorithm
#### Additional Overhead
Be sure to add it to add the method to `quick_start.py`. Also create a new example file using the method.
### Adding a new Maze Generation Algorithm
#### Additional Overhead
Be sure to create a new example using the new generation algorithm.
#### Using the linter
The style guide employed is pycodestyle. To install pycodestyle, navigate to the main directory and run
`pip install -r requirements.txt`
To check your file run
`pycodestyle src/my_file.py`.
================================================
FILE: examples/generate_binary_tree_algorithm.py
================================================
from __future__ import absolute_import
from src.maze_manager import MazeManager
from src.maze import Maze
if __name__ == "__main__":
# create a maze manager to handle all operations
manager = MazeManager()
# now create a maze using the binary tree method
maze_using_btree = Maze(10, 10, algorithm="bin_tree")
# add this maze to the maze manager
maze_using_btree = manager.add_existing_maze(maze_using_btree)
# show the maze
manager.show_maze(maze_using_btree.id)
# show how the maze was generated
manager.show_generation_animation(maze_using_btree.id)
================================================
FILE: examples/quick_start.py
================================================
from __future__ import absolute_import
from src.maze_manager import MazeManager
from src.maze import Maze
if __name__ == "__main__":
# The easiest way to use the library is through the Manager class. It acts as the glue between
# The visualization, solver, and maze classes. Mazes inside the manager have unique ids that we use
# to specify particular mazes.
manager = MazeManager()
# We can add mazes to the manager two different ways.
# The first way, we specify the maze dimensions. The maze that is created gets returned back to you.
maze = manager.add_maze(10, 10)
# The second way is by creating a maze, and then adding it to the manager. Doing this will require you to add
# from src.maze import Maze
# to your imports. Because the ids need to be unique, the manager will ensure this happens. It may change the
# id of the maze that was passed in, so we assign it to the return value to make sure we're using the updated maze.
maze2 = Maze(10, 10)
maze2 = manager.add_existing_maze(maze2)
# by default when creating a maze, depth first search is used.
# to generate maze using binary tree method,
maze_binTree = Maze(10, 10, algorithm = "bin_tree")
maze_binTree = manager.add_existing_maze(maze_binTree)
# We can disable showing any output from the solver by entering quiet mode
# manager.set_quiet_mode(True)
# Once we have a maze in the manager, we can tell the manager to solve it with a particular algorithm.
#manager.solve_maze(maze.id, "BreadthFirst")
#manager.solve_maze(maze.id, "BiDirectional")
manager.solve_maze(maze.id, "DepthFirstBacktracker")
# If we want to save the maze & maze solution images along with their animations, we need to let the manager know.
manager.set_filename("myFileName")
# To see the unsolved maze, call
manager.show_maze(maze.id)
# You can also set the size of the cell by passing show_maze's second argument. The default is 1.
# manager.show_maze(maze.id, 2)
# To show an animation of how the maze was generated, use the following line
manager.show_generation_animation(maze.id)
# You can also see an animation of how the solver went about finding the end
manager.show_solution_animation(maze.id)
# Finally, you can show an image of the maze with the solution path overlaid. All of these display
# functions will save the figure if MazeManager::set_filename has been set.
manager.show_solution(maze.id)
================================================
FILE: examples/solve_bi_directional.py
================================================
from __future__ import absolute_import
from src.maze_manager import MazeManager
if __name__ == "__main__":
# Create the manager
manager = MazeManager()
# Add a 10x10 maze to the manager
maze = manager.add_maze(10, 10)
# Solve the maze using the Bi Directional algorithm
manager.solve_maze(maze.id, "BiDirectional", "fancy")
# Display the maze
manager.show_maze(maze.id)
# Show how the maze was generated
manager.show_generation_animation(maze.id)
# Show how the maze was solved
manager.show_solution_animation(maze.id)
# Display the maze with the solution overlaid
manager.show_solution(maze.id)
================================================
FILE: examples/solve_breadth_first_recursive.py
================================================
from __future__ import absolute_import
from src.maze_manager import MazeManager
if __name__ == "__main__":
# Create the manager
manager = MazeManager()
# Add a 10x10 maze to the manager
maze = manager.add_maze(10, 10)
# Solve the maze using the Depth First Backtracker algorithm
manager.solve_maze(maze.id, "BreadthFirst")
# Display the maze
manager.show_maze(maze.id)
# Show how the maze was generated
manager.show_generation_animation(maze.id)
# Show how the maze was solved
manager.show_solution_animation(maze.id)
# Display the maze with the solution overlaid
manager.show_solution(maze.id)
================================================
FILE: examples/solve_depth_first_recursive.py
================================================
from __future__ import absolute_import
from src.maze_manager import MazeManager
if __name__ == "__main__":
# Create the manager
manager = MazeManager()
# Add a 10x10 maze to the manager
maze = manager.add_maze(10, 10)
# Solve the maze using the Breadth First algorithm
manager.solve_maze(maze.id, "DepthFirstBacktracker")
# Display the maze
manager.show_maze(maze.id)
# Show how the maze was generated
manager.show_generation_animation(maze.id)
# Show how the maze was solved
manager.show_solution_animation(maze.id)
# Display the maze with the solution overlaid
manager.show_solution(maze.id)
================================================
FILE: install_linter
================================================
pycodestyle
================================================
FILE: requirements.txt
================================================
matplotlib==2.0.0
================================================
FILE: src/__init__.py
================================================
================================================
FILE: src/algorithm.py
================================================
import time
import random
import math
# global variable to store list of all available algorithms
algorithm_list = ["dfs_backtrack", "bin_tree"]
def depth_first_recursive_backtracker( maze, start_coor ):
k_curr, l_curr = start_coor # Where to start generating
path = [(k_curr, l_curr)] # To track path of solution
maze.grid[k_curr][l_curr].visited = True # Set initial cell to visited
visit_counter = 1 # To count number of visited cells
visited_cells = list() # Stack of visited cells for backtracking
print("\nGenerating the maze with depth-first search...")
time_start = time.time()
while visit_counter < maze.grid_size: # While there are unvisited cells
neighbour_indices = maze.find_neighbours(k_curr, l_curr) # Find neighbour indicies
neighbour_indices = maze._validate_neighbours_generate(neighbour_indices)
if neighbour_indices is not None: # If there are unvisited neighbour cells
visited_cells.append((k_curr, l_curr)) # Add current cell to stack
k_next, l_next = random.choice(neighbour_indices) # Choose random neighbour
maze.grid[k_curr][l_curr].remove_walls(k_next, l_next) # Remove walls between neighbours
maze.grid[k_next][l_next].remove_walls(k_curr, l_curr) # Remove walls between neighbours
maze.grid[k_next][l_next].visited = True # Move to that neighbour
k_curr = k_next
l_curr = l_next
path.append((k_curr, l_curr)) # Add coordinates to part of generation path
visit_counter += 1
elif len(visited_cells) > 0: # If there are no unvisited neighbour cells
k_curr, l_curr = visited_cells.pop() # Pop previous visited cell (backtracking)
path.append((k_curr, l_curr)) # Add coordinates to part of generation path
print("Number of moves performed: {}".format(len(path)))
print("Execution time for algorithm: {:.4f}".format(time.time() - time_start))
maze.grid[maze.entry_coor[0]][maze.entry_coor[1]].set_as_entry_exit("entry",
maze.num_rows-1, maze.num_cols-1)
maze.grid[maze.exit_coor[0]][maze.exit_coor[1]].set_as_entry_exit("exit",
maze.num_rows-1, maze.num_cols-1)
for i in range(maze.num_rows):
for j in range(maze.num_cols):
maze.grid[i][j].visited = False # Set all cells to unvisited before returning grid
maze.generation_path = path
def binary_tree( maze, start_coor ):
# store the current time
time_start = time.time()
# repeat the following for all rows
for i in range(0, maze.num_rows):
# check if we are in top row
if( i == maze.num_rows - 1 ):
# remove the right wall for this, because we cant remove top wall
for j in range(0, maze.num_cols-1):
maze.grid[i][j].remove_walls(i, j+1)
maze.grid[i][j+1].remove_walls(i, j)
# go to the next row
break
# repeat the following for all cells in rows
for j in range(0, maze.num_cols):
# check if we are in the last column
if( j == maze.num_cols-1 ):
# remove only the top wall for this cell
maze.grid[i][j].remove_walls(i+1, j)
maze.grid[i+1][j].remove_walls(i, j)
continue
# for all other cells
# randomly choose between 0 and 1.
# if we get 0, remove top wall; otherwise remove right wall
remove_top = random.choice([True,False])
# if we chose to remove top wall
if remove_top:
maze.grid[i][j].remove_walls(i+1, j)
maze.grid[i+1][j].remove_walls(i, j)
# if we chose top remove right wall
else:
maze.grid[i][j].remove_walls(i, j+1)
maze.grid[i][j+1].remove_walls(i, j)
print("Number of moves performed: {}".format(maze.num_cols * maze.num_rows))
print("Execution time for algorithm: {:.4f}".format(time.time() - time_start))
# choose the entry and exit coordinates
maze.grid[maze.entry_coor[0]][maze.entry_coor[1]].set_as_entry_exit("entry",
maze.num_rows-1, maze.num_cols-1)
maze.grid[maze.exit_coor[0]][maze.exit_coor[1]].set_as_entry_exit("exit",
maze.num_rows-1, maze.num_cols-1)
# create a path for animating the maze creation using a binary tree
path = list()
# variable for holding number of cells visited until now
visit_counter = 0
# created list of cell visited uptil now to for backtracking
visited = list()
# create variables to hold the coords of current cell
# no matter what the user gives as start coords, we choose the
k_curr, l_curr = (maze.num_rows-1, maze.num_cols-1)
# add first cell to the path
path.append( (k_curr,l_curr) )
# mark first cell as visited
begin_time = time.time()
# repeat until all the cells have been visited
while visit_counter < maze.grid_size: # While there are unvisited cells
# for each cell, we only visit top and right cells.
possible_neighbours = list()
try:
# take only those cells that are unvisited and accessible
if not maze.grid[k_curr-1][l_curr].visited and k_curr != 0:
if not maze.grid[k_curr][l_curr].is_walls_between(maze.grid[k_curr-1][l_curr]):
possible_neighbours.append( (k_curr-1,l_curr))
except:
print()
try:
# take only those cells that are unvisited and accessible
if not maze.grid[k_curr][l_curr-1].visited and l_curr != 0:
if not maze.grid[k_curr][l_curr].is_walls_between(maze.grid[k_curr][l_curr-1]):
possible_neighbours.append( (k_curr,l_curr-1))
except:
print()
# if there are still traversible cell from current cell
if len( possible_neighbours ) != 0:
# select to first element to traverse
k_next, l_next = possible_neighbours[0]
# add this cell to the path
path.append( possible_neighbours[0] )
# add this cell to the visited
visited.append( (k_curr,l_curr) )
# mark this cell as visited
maze.grid[k_next][l_next].visited = True
visit_counter+= 1
# update the current cell coords
k_curr, l_curr = k_next, l_next
else:
# check if no more cells can be visited
if len( visited ) != 0:
k_curr, l_curr = visited.pop()
path.append( (k_curr,l_curr) )
else:
break
for row in maze.grid:
for cell in row:
cell.visited = False
print(f"Generating path for maze took {time.time() - begin_time}s.")
maze.generation_path = path
================================================
FILE: src/cell.py
================================================
class Cell(object):
"""Class for representing a cell in a 2D grid.
Attributes:
row (int): The row that this cell belongs to
col (int): The column that this cell belongs to
visited (bool): True if this cell has been visited by an algorithm
active (bool):
is_entry_exit (bool): True when the cell is the beginning or end of the maze
walls (list):
neighbours (list):
"""
def __init__(self, row, col):
self.row = row
self.col = col
self.visited = False
self.active = False
self.is_entry_exit = None
self.walls = {"top": True, "right": True, "bottom": True, "left": True}
self.neighbours = list()
def is_walls_between(self, neighbour):
"""Function that checks if there are walls between self and a neighbour cell.
Returns true if there are walls between. Otherwise returns False.
Args:
neighbour The cell to check between
Return:
True: If there are walls in between self and neighbor
False: If there are no walls in between the neighbors and self
"""
if self.row - neighbour.row == 1 and self.walls["top"] and neighbour.walls["bottom"]:
return True
elif self.row - neighbour.row == -1 and self.walls["bottom"] and neighbour.walls["top"]:
return True
elif self.col - neighbour.col == 1 and self.walls["left"] and neighbour.walls["right"]:
return True
elif self.col - neighbour.col == -1 and self.walls["right"] and neighbour.walls["left"]:
return True
return False
def remove_walls(self, neighbour_row, neighbour_col):
"""Function that removes walls between neighbour cell given by indices in grid.
Args:
neighbour_row (int):
neighbour_col (int):
Return:
True: If the operation was a success
False: If the operation failed
"""
if self.row - neighbour_row == 1:
self.walls["top"] = False
return True, ""
elif self.row - neighbour_row == -1:
self.walls["bottom"] = False
return True, ""
elif self.col - neighbour_col == 1:
self.walls["left"] = False
return True, ""
elif self.col - neighbour_col == -1:
self.walls["right"] = False
return True, ""
return False
def set_as_entry_exit(self, entry_exit, row_limit, col_limit):
"""Function that sets the cell as an entry/exit cell by
disabling the outer boundary wall.
First, we check if the entrance/exit is on the top row. Next, we check if it should
be on the bottom row. Finally, we check if it is on the left wall or the bottom row.
Args:
entry_exit: True to set this cell as an exit/entry. False to remove it as one
row_limit:
col_limit:
"""
if self.row == 0:
self.walls["top"] = False
elif self.row == row_limit:
self.walls["bottom"] = False
elif self.col == 0:
self.walls["left"] = False
elif self.col == col_limit:
self.walls["right"] = False
self.is_entry_exit = entry_exit
================================================
FILE: src/maze.py
================================================
import random
import math
import time
from src.cell import Cell
from src.algorithm import depth_first_recursive_backtracker, binary_tree
class Maze(object):
"""Class representing a maze; a 2D grid of Cell objects. Contains functions
for generating randomly generating the maze as well as for solving the maze.
Attributes:
num_cols (int): The height of the maze, in Cells
num_rows (int): The width of the maze, in Cells
id (int): A unique identifier for the maze
grid_size (int): The area of the maze, also the total number of Cells in the maze
entry_coor Entry location cell of maze
exit_coor Exit location cell of maze
generation_path : The path that was taken when generating the maze
solution_path : The path that was taken by a solver when solving the maze
initial_grid (list):
grid (list): A copy of initial_grid (possible this is un-needed)
"""
def __init__(self, num_rows, num_cols, id=0, algorithm = "dfs_backtrack"):
"""Creates a gird of Cell objects that are neighbors to each other.
Args:
num_rows (int): The width of the maze, in cells
num_cols (int): The height of the maze in cells
id (id): An unique identifier
"""
self.num_cols = num_cols
self.num_rows = num_rows
self.id = id
self.grid_size = num_rows*num_cols
self.entry_coor = self._pick_random_entry_exit(None)
self.exit_coor = self._pick_random_entry_exit(self.entry_coor)
self.generation_path = []
self.solution_path = None
self.initial_grid = self.generate_grid()
self.grid = self.initial_grid
self.generate_maze(algorithm, (0, 0))
def generate_grid(self):
"""Function that creates a 2D grid of Cell objects. This can be thought of as a
maze without any paths carved out
Return:
A list with Cell objects at each position
"""
# Create an empty list
grid = list()
# Place a Cell object at each location in the grid
for i in range(self.num_rows):
grid.append(list())
for j in range(self.num_cols):
grid[i].append(Cell(i, j))
return grid
def find_neighbours(self, cell_row, cell_col):
"""Finds all existing and unvisited neighbours of a cell in the
grid. Return a list of tuples containing indices for the unvisited neighbours.
Args:
cell_row (int):
cell_col (int):
Return:
None: If there are no unvisited neighbors
list: A list of neighbors that have not been visited
"""
neighbours = list()
def check_neighbour(row, col):
# Check that a neighbour exists and that it's not visited before.
if row >= 0 and row < self.num_rows and col >= 0 and col < self.num_cols:
neighbours.append((row, col))
check_neighbour(cell_row-1, cell_col) # Top neighbour
check_neighbour(cell_row, cell_col+1) # Right neighbour
check_neighbour(cell_row+1, cell_col) # Bottom neighbour
check_neighbour(cell_row, cell_col-1) # Left neighbour
if len(neighbours) > 0:
return neighbours
else:
return None # None if no unvisited neighbours found
def _validate_neighbours_generate(self, neighbour_indices):
"""Function that validates whether a neighbour is unvisited or not. When generating
the maze, we only want to move to move to unvisited cells (unless we are backtracking).
Args:
neighbour_indices:
Return:
True: If the neighbor has been visited
False: If the neighbor has not been visited
"""
neigh_list = [n for n in neighbour_indices if not self.grid[n[0]][n[1]].visited]
if len(neigh_list) > 0:
return neigh_list
else:
return None
def validate_neighbours_solve(self, neighbour_indices, k, l, k_end, l_end, method = "fancy"):
"""Function that validates whether a neighbour is unvisited or not and discards the
neighbours that are inaccessible due to walls between them and the current cell. The
function implements two methods for choosing next cell; one is 'brute-force' where one
of the neighbours are chosen randomly. The other is 'fancy' where the next cell is chosen
based on which neighbour that gives the shortest distance to the final destination.
Args:
neighbour_indices
k
l
k_end
l_end
method
Return:
"""
if method == "fancy":
neigh_list = list()
min_dist_to_target = 100000
for k_n, l_n in neighbour_indices:
if (not self.grid[k_n][l_n].visited
and not self.grid[k][l].is_walls_between(self.grid[k_n][l_n])):
dist_to_target = math.sqrt((k_n - k_end) ** 2 + (l_n - l_end) ** 2)
if (dist_to_target < min_dist_to_target):
min_dist_to_target = dist_to_target
min_neigh = (k_n, l_n)
if "min_neigh" in locals():
neigh_list.append(min_neigh)
elif method == "brute-force":
neigh_list = [n for n in neighbour_indices if not self.grid[n[0]][n[1]].visited
and not self.grid[k][l].is_walls_between(self.grid[n[0]][n[1]])]
if len(neigh_list) > 0:
return neigh_list
else:
return None
def _pick_random_entry_exit(self, used_entry_exit=None):
"""Function that picks random coordinates along the maze boundary to represent either
the entry or exit point of the maze. Makes sure they are not at the same place.
Args:
used_entry_exit
Return:
"""
rng_entry_exit = used_entry_exit # Initialize with used value
# Try until unused location along boundary is found.
while rng_entry_exit == used_entry_exit:
rng_side = random.randint(0, 3)
if (rng_side == 0): # Top side
rng_entry_exit = (0, random.randint(0, self.num_cols-1))
elif (rng_side == 2): # Right side
rng_entry_exit = (self.num_rows-1, random.randint(0, self.num_cols-1))
elif (rng_side == 1): # Bottom side
rng_entry_exit = (random.randint(0, self.num_rows-1), self.num_cols-1)
elif (rng_side == 3): # Left side
rng_entry_exit = (random.randint(0, self.num_rows-1), 0)
return rng_entry_exit # Return entry/exit that is different from exit/entry
def generate_maze(self, algorithm, start_coor = (0, 0)):
"""This takes the internal grid object and removes walls between cells using the
depth-first recursive backtracker algorithm.
Args:
start_coor: The starting point for the algorithm
"""
if algorithm == "dfs_backtrack":
depth_first_recursive_backtracker(self, start_coor)
elif algorithm == "bin_tree":
binary_tree(self, start_coor)
================================================
FILE: src/maze_manager.py
================================================
from src.maze import Maze
from src.maze_viz import Visualizer
from src.solver import DepthFirstBacktracker
from src.solver import BiDirectional
from src.solver import BreadthFirst
class MazeManager(object):
"""A manager that abstracts the interaction with the library's components. The graphs, animations, maze creation,
and solutions are all handled through the manager.
Attributes:
mazes (list): It is possible to have more than one maze. They are stored inside this variable.
media_name (string): The filename for animations and images
quiet_mode (bool): When true, information is not shown on the console
"""
def __init__(self):
self.mazes = []
self.media_name = ""
self.quiet_mode = False
def add_maze(self, row, col, id=0):
"""Add a maze to the manager. We give the maze an index of
the total number of mazes in the manager. As long as we don't
add functionality to delete mazes from the manager, the ids will
always be unique. Note that the id will always be greater than 0 because
we add 1 to the length of self.mazes, which is set after the id assignment
Args:
row (int): The height of the maze
col (int): The width of the maze
id (int): The optional unique id of the maze.
Returns
Maze: The newly created maze
"""
if id is not 0:
self.mazes.append(Maze(row, col, id))
else:
if len(self.mazes) < 1:
self.mazes.append(Maze(row, col, 0))
else:
self.mazes.append(Maze(row, col, len(self.mazes) + 1))
return self.mazes[-1]
def add_existing_maze(self, maze, override=True):
"""Add an already existing maze to the manager.
Note that it is assumed that the maze already has an id. If the id
already exists, the function will fail. To assign a new, unique id to
the maze, set the overwrite flag to true.
Args:
maze: The maze that will be added to the manager
override (bool): A flag that you can set to bypass checking the id
Returns:
True: If the maze was added to the manager
False: If the maze could not be added to the manager
"""
# Check if there is a maze with the same id. If there is a conflict, return False
if self.check_matching_id(maze.id) is None:
if override:
if len(self.mazes) < 1:
maze.id = 0
else:
maze.id = self.mazes.__len__()+1
else:
return False
self.mazes.append(maze)
return maze
def get_maze(self, id):
"""Get a maze by its id.
Args:
id (int): The id of the desired maze
Return:
Maze: Returns the maze if it was found.
None: If no maze was found
"""
for maze in self.mazes:
if maze.id == id:
return maze
print("Unable to locate maze")
return None
def get_mazes(self):
"""Get all of the mazes that the manager is holding"""
return self.mazes
def get_maze_count(self):
"""Gets the number of mazes that the manager is holding"""
return self.mazes.__len__()
def solve_maze(self, maze_id, method, neighbor_method="fancy"):
""" Called to solve a maze by a particular method. The method
is specified by a string. The options are
1. DepthFirstBacktracker
2.
3.
Args:
maze_id (int): The id of the maze that will be solved
method (string): The name of the method (see above)
neighbor_method:
"""
maze = self.get_maze(maze_id)
if maze is None:
print("Unable to locate maze. Exiting solver.")
return None
"""DEVNOTE: When adding a new solution method, call it from here.
Also update the list of names in the documentation above"""
if method == "DepthFirstBacktracker":
solver = DepthFirstBacktracker(maze, neighbor_method, self.quiet_mode)
maze.solution_path = solver.solve()
elif method == "BiDirectional":
solver = BiDirectional(maze, neighbor_method, self.quiet_mode)
maze.solution_path = solver.solve()
elif method == "BreadthFirst":
solver = BreadthFirst(maze, neighbor_method, self.quiet_mode)
maze.solution_path = solver.solve()
def show_maze(self, id, cell_size=1):
"""Just show the generation animation and maze"""
vis = Visualizer(self.get_maze(id), cell_size, self.media_name)
vis.show_maze()
def show_generation_animation(self, id, cell_size=1):
vis = Visualizer(self.get_maze(id), cell_size, self.media_name)
vis.show_generation_animation()
def show_solution(self, id, cell_size=1):
vis = Visualizer(self.get_maze(id), cell_size, self.media_name)
vis.show_maze_solution()
def show_solution_animation(self, id, cell_size =1):
"""
Shows the animation of the path that the solver took.
Args:
id (int): The id of the maze whose solution will be shown
cell_size (int):
"""
vis = Visualizer(self.get_maze(id), cell_size, self.media_name)
vis.animate_maze_solution()
def check_matching_id(self, id):
"""Check if the id already belongs to an existing maze
Args:
id (int): The id to be checked
Returns:
"""
return next((maze for maze in self.mazes if maze .id == id), None)
def set_filename(self, filename):
"""
Sets the filename for saving animations and images
Args:
filename (string): The name of the file without an extension
"""
self.media_name = filename
def set_quiet_mode(self, enabled):
"""
Enables/Disables the quiet mode
Args:
enabled (bool): True when quiet mode is on, False when it is off
"""
self.quiet_mode=enabled
================================================
FILE: src/maze_viz.py
================================================
import matplotlib.pyplot as plt
from matplotlib import animation
import logging
logging.basicConfig(level=logging.DEBUG)
class Visualizer(object):
"""Class that handles all aspects of visualization.
Attributes:
maze: The maze that will be visualized
cell_size (int): How large the cells will be in the plots
height (int): The height of the maze
width (int): The width of the maze
ax: The axes for the plot
lines:
squares:
media_filename (string): The name of the animations and images
"""
def __init__(self, maze, cell_size, media_filename):
self.maze = maze
self.cell_size = cell_size
self.height = maze.num_rows * cell_size
self.width = maze.num_cols * cell_size
self.ax = None
self.lines = dict()
self.squares = dict()
self.media_filename = media_filename
def set_media_filename(self, filename):
"""Sets the filename of the media
Args:
filename (string): The name of the media
"""
self.media_filename = filename
def show_maze(self):
"""Displays a plot of the maze without the solution path"""
# Create the plot figure and style the axes
fig = self.configure_plot()
# Plot the walls on the figure
self.plot_walls()
# Display the plot to the user
plt.show()
# Handle any potential saving
if self.media_filename:
fig.savefig("{}{}.png".format(self.media_filename, "_generation"), frameon=None)
def plot_walls(self):
""" Plots the walls of a maze. This is used when generating the maze image"""
for i in range(self.maze.num_rows):
for j in range(self.maze.num_cols):
if self.maze.initial_grid[i][j].is_entry_exit == "entry":
self.ax.text(j*self.cell_size, i*self.cell_size, "START", fontsize=7, weight="bold")
elif self.maze.initial_grid[i][j].is_entry_exit == "exit":
self.ax.text(j*self.cell_size, i*self.cell_size, "END", fontsize=7, weight="bold")
if self.maze.initial_grid[i][j].walls["top"]:
self.ax.plot([j*self.cell_size, (j+1)*self.cell_size],
[i*self.cell_size, i*self.cell_size], color="k")
if self.maze.initial_grid[i][j].walls["right"]:
self.ax.plot([(j+1)*self.cell_size, (j+1)*self.cell_size],
[i*self.cell_size, (i+1)*self.cell_size], color="k")
if self.maze.initial_grid[i][j].walls["bottom"]:
self.ax.plot([(j+1)*self.cell_size, j*self.cell_size],
[(i+1)*self.cell_size, (i+1)*self.cell_size], color="k")
if self.maze.initial_grid[i][j].walls["left"]:
self.ax.plot([j*self.cell_size, j*self.cell_size],
[(i+1)*self.cell_size, i*self.cell_size], color="k")
def configure_plot(self):
"""Sets the initial properties of the maze plot. Also creates the plot and axes"""
# Create the plot figure
fig = plt.figure(figsize = (7, 7*self.maze.num_rows/self.maze.num_cols))
# Create the axes
self.ax = plt.axes()
# Set an equal aspect ratio
self.ax.set_aspect("equal")
# Remove the axes from the figure
self.ax.axes.get_xaxis().set_visible(False)
self.ax.axes.get_yaxis().set_visible(False)
title_box = self.ax.text(0, self.maze.num_rows + self.cell_size + 0.1,
r"{}$\times${}".format(self.maze.num_rows, self.maze.num_cols),
bbox={"facecolor": "gray", "alpha": 0.5, "pad": 4}, fontname="serif", fontsize=15)
return fig
def show_maze_solution(self):
"""Function that plots the solution to the maze. Also adds indication of entry and exit points."""
# Create the figure and style the axes
fig = self.configure_plot()
# Plot the walls onto the figure
self.plot_walls()
list_of_backtrackers = [path_element[0] for path_element in self.maze.solution_path if path_element[1]]
# Keeps track of how many circles have been drawn
circle_num = 0
self.ax.add_patch(plt.Circle(((self.maze.solution_path[0][0][1] + 0.5)*self.cell_size,
(self.maze.solution_path[0][0][0] + 0.5)*self.cell_size), 0.2*self.cell_size,
fc=(0, circle_num/(len(self.maze.solution_path) - 2*len(list_of_backtrackers)),
0), alpha=0.4))
for i in range(1, self.maze.solution_path.__len__()):
if self.maze.solution_path[i][0] not in list_of_backtrackers and\
self.maze.solution_path[i-1][0] not in list_of_backtrackers:
circle_num += 1
self.ax.add_patch(plt.Circle(((self.maze.solution_path[i][0][1] + 0.5)*self.cell_size,
(self.maze.solution_path[i][0][0] + 0.5)*self.cell_size), 0.2*self.cell_size,
fc = (0, circle_num/(len(self.maze.solution_path) - 2*len(list_of_backtrackers)), 0), alpha = 0.4))
# Display the plot to the user
plt.show()
# Handle any saving
if self.media_filename:
fig.savefig("{}{}.png".format(self.media_filename, "_solution"), frameon=None)
def show_generation_animation(self):
"""Function that animates the process of generating the a maze where path is a list
of coordinates indicating the path taken to carve out (break down walls) the maze."""
# Create the figure and style the axes
fig = self.configure_plot()
# The square that represents the head of the algorithm
indicator = plt.Rectangle((self.maze.generation_path[0][0]*self.cell_size, self.maze.generation_path[0][1]*self.cell_size),
self.cell_size, self.cell_size, fc = "purple", alpha = 0.6)
self.ax.add_patch(indicator)
# Only need to plot right and bottom wall for each cell since walls overlap.
# Also adding squares to animate the path taken to carve out the maze.
color_walls = "k"
for i in range(self.maze.num_rows):
for j in range(self.maze.num_cols):
self.lines["{},{}: right".format(i, j)] = self.ax.plot([(j+1)*self.cell_size, (j+1)*self.cell_size],
[i*self.cell_size, (i+1)*self.cell_size],
linewidth = 2, color = color_walls)[0]
self.lines["{},{}: bottom".format(i, j)] = self.ax.plot([(j+1)*self.cell_size, j*self.cell_size],
[(i+1)*self.cell_size, (i+1)*self.cell_size],
linewidth = 2, color = color_walls)[0]
self.squares["{},{}".format(i, j)] = plt.Rectangle((j*self.cell_size,
i*self.cell_size), self.cell_size, self.cell_size, fc = "red", alpha = 0.4)
self.ax.add_patch(self.squares["{},{}".format(i, j)])
# Plotting boundaries of maze.
color_boundary = "k"
self.ax.plot([0, self.width], [self.height,self.height], linewidth = 2, color = color_boundary)
self.ax.plot([self.width, self.width], [self.height, 0], linewidth = 2, color = color_boundary)
self.ax.plot([self.width, 0], [0, 0], linewidth = 2, color = color_boundary)
self.ax.plot([0, 0], [0, self.height], linewidth = 2, color = color_boundary)
def animate(frame):
"""Function to supervise animation of all objects."""
animate_walls(frame)
animate_squares(frame)
animate_indicator(frame)
self.ax.set_title("Step: {}".format(frame + 1), fontname="serif", fontsize=19)
return []
def animate_walls(frame):
"""Function that animates the visibility of the walls between cells."""
if frame > 0:
self.maze.grid[self.maze.generation_path[frame-1][0]][self.maze.generation_path[frame-1][1]].remove_walls(
self.maze.generation_path[frame][0],
self.maze.generation_path[frame][1]) # Wall between curr and neigh
self.maze.grid[self.maze.generation_path[frame][0]][self.maze.generation_path[frame][1]].remove_walls(
self.maze.generation_path[frame-1][0],
self.maze.generation_path[frame-1][1]) # Wall between neigh and curr
current_cell = self.maze.grid[self.maze.generation_path[frame-1][0]][self.maze.generation_path[frame-1][1]]
next_cell = self.maze.grid[self.maze.generation_path[frame][0]][self.maze.generation_path[frame][1]]
"""Function to animate walls between cells as the search goes on."""
for wall_key in ["right", "bottom"]: # Only need to draw two of the four walls (overlap)
if current_cell.walls[wall_key] is False:
self.lines["{},{}: {}".format(current_cell.row,
current_cell.col, wall_key)].set_visible(False)
if next_cell.walls[wall_key] is False:
self.lines["{},{}: {}".format(next_cell.row,
next_cell.col, wall_key)].set_visible(False)
def animate_squares(frame):
"""Function to animate the searched path of the algorithm."""
self.squares["{},{}".format(self.maze.generation_path[frame][0],
self.maze.generation_path[frame][1])].set_visible(False)
return []
def animate_indicator(frame):
"""Function to animate where the current search is happening."""
indicator.set_xy((self.maze.generation_path[frame][1]*self.cell_size,
self.maze.generation_path[frame][0]*self.cell_size))
return []
logging.debug("Creating generation animation")
anim = animation.FuncAnimation(fig, animate, frames=self.maze.generation_path.__len__(),
interval=100, blit=True, repeat=False)
logging.debug("Finished creating the generation animation")
# Display the plot to the user
plt.show()
# Handle any saving
if self.media_filename:
print("Saving generation animation. This may take a minute....")
mpeg_writer = animation.FFMpegWriter(fps=24, bitrate=1000,
codec="libx264", extra_args=["-pix_fmt", "yuv420p"])
anim.save("{}{}{}x{}.mp4".format(self.media_filename, "_generation_", self.maze.num_rows,
self.maze.num_cols), writer=mpeg_writer)
def add_path(self):
# Adding squares to animate the path taken to solve the maze. Also adding entry/exit text
color_walls = "k"
for i in range(self.maze.num_rows):
for j in range(self.maze.num_cols):
if self.maze.initial_grid[i][j].is_entry_exit == "entry":
self.ax.text(j*self.cell_size, i*self.cell_size, "START", fontsize = 7, weight = "bold")
elif self.maze.initial_grid[i][j].is_entry_exit == "exit":
self.ax.text(j*self.cell_size, i*self.cell_size, "END", fontsize = 7, weight = "bold")
if self.maze.initial_grid[i][j].walls["top"]:
self.lines["{},{}: top".format(i, j)] = self.ax.plot([j*self.cell_size, (j+1)*self.cell_size],
[i*self.cell_size, i*self.cell_size], linewidth = 2, color = color_walls)[0]
if self.maze.initial_grid[i][j].walls["right"]:
self.lines["{},{}: right".format(i, j)] = self.ax.plot([(j+1)*self.cell_size, (j+1)*self.cell_size],
[i*self.cell_size, (i+1)*self.cell_size], linewidth = 2, color = color_walls)[0]
if self.maze.initial_grid[i][j].walls["bottom"]:
self.lines["{},{}: bottom".format(i, j)] = self.ax.plot([(j+1)*self.cell_size, j*self.cell_size],
[(i+1)*self.cell_size, (i+1)*self.cell_size], linewidth = 2, color = color_walls)[0]
if self.maze.initial_grid[i][j].walls["left"]:
self.lines["{},{}: left".format(i, j)] = self.ax.plot([j*self.cell_size, j*self.cell_size],
[(i+1)*self.cell_size, i*self.cell_size], linewidth = 2, color = color_walls)[0]
self.squares["{},{}".format(i, j)] = plt.Rectangle((j*self.cell_size,
i*self.cell_size), self.cell_size, self.cell_size,
fc = "red", alpha = 0.4, visible = False)
self.ax.add_patch(self.squares["{},{}".format(i, j)])
def animate_maze_solution(self):
"""Function that animates the process of generating the a maze where path is a list
of coordinates indicating the path taken to carve out (break down walls) the maze."""
# Create the figure and style the axes
fig = self.configure_plot()
# Adding indicator to see shere current search is happening.
indicator = plt.Rectangle((self.maze.solution_path[0][0][0]*self.cell_size,
self.maze.solution_path[0][0][1]*self.cell_size), self.cell_size, self.cell_size,
fc="purple", alpha=0.6)
self.ax.add_patch(indicator)
self.add_path()
def animate_squares(frame):
"""Function to animate the solved path of the algorithm."""
if frame > 0:
if self.maze.solution_path[frame - 1][1]: # Color backtracking
self.squares["{},{}".format(self.maze.solution_path[frame - 1][0][0],
self.maze.solution_path[frame - 1][0][1])].set_facecolor("orange")
self.squares["{},{}".format(self.maze.solution_path[frame - 1][0][0],
self.maze.solution_path[frame - 1][0][1])].set_visible(True)
self.squares["{},{}".format(self.maze.solution_path[frame][0][0],
self.maze.solution_path[frame][0][1])].set_visible(False)
return []
def animate_indicator(frame):
"""Function to animate where the current search is happening."""
indicator.set_xy((self.maze.solution_path[frame][0][1] * self.cell_size,
self.maze.solution_path[frame][0][0] * self.cell_size))
return []
def animate(frame):
"""Function to supervise animation of all objects."""
animate_squares(frame)
animate_indicator(frame)
self.ax.set_title("Step: {}".format(frame + 1), fontname = "serif", fontsize = 19)
return []
logging.debug("Creating solution animation")
anim = animation.FuncAnimation(fig, animate, frames=self.maze.solution_path.__len__(),
interval=100, blit=True, repeat=False)
logging.debug("Finished creating solution animation")
# Display the animation to the user
plt.show()
# Handle any saving
if self.media_filename:
print("Saving solution animation. This may take a minute....")
mpeg_writer = animation.FFMpegWriter(fps=24, bitrate=1000,
codec="libx264", extra_args=["-pix_fmt", "yuv420p"])
anim.save("{}{}{}x{}.mp4".format(self.media_filename, "_solution_", self.maze.num_rows,
self.maze.num_cols), writer=mpeg_writer)
================================================
FILE: src/solver.py
================================================
import time
import random
import logging
from src.maze import Maze
logging.basicConfig(level=logging.DEBUG)
class Solver(object):
"""Base class for solution methods.
Every new solution method should override the solve method.
Attributes:
maze (list): The maze which is being solved.
neighbor_method:
quiet_mode: When enabled, information is not outputted to the console
"""
def __init__(self, maze, quiet_mode, neighbor_method):
logging.debug("Class Solver ctor called")
self.maze = maze
self.neighbor_method = neighbor_method
self.name = ""
self.quiet_mode = quiet_mode
def solve(self):
logging.debug('Class: Solver solve called')
raise NotImplementedError
def get_name(self):
logging.debug('Class Solver get_name called')
raise self.name
def get_path(self):
logging.debug('Class Solver get_path called')
return self.path
class BreadthFirst(Solver):
def __init__(self, maze, quiet_mode=False, neighbor_method="fancy"):
logging.debug('Class BreadthFirst ctor called')
self.name = "Breadth First Recursive"
super().__init__(maze, neighbor_method, quiet_mode)
def solve(self):
"""Function that implements the breadth-first algorithm for solving the maze. This means that
for each iteration in the outer loop, the search visits one cell in all possible branches. Then
moves on to the next level of cells in each branch to continue the search."""
logging.debug("Class BreadthFirst solve called")
current_level = [self.maze.entry_coor] # Stack of cells at current level of search
path = list() # To track path of solution cell coordinates
print("\nSolving the maze with breadth-first search...")
time_start = time.clock()
while True: # Loop until return statement is encountered
next_level = list()
while current_level: # While still cells left to search on current level
k_curr, l_curr = current_level.pop(0) # Search one cell on the current level
self.maze.grid[k_curr][l_curr].visited = True # Mark current cell as visited
path.append(((k_curr, l_curr), False)) # Append current cell to total search path
if (k_curr, l_curr) == self.maze.exit_coor: # Exit if current cell is exit cell
if not self.quiet_mode:
print("Number of moves performed: {}".format(len(path)))
print("Execution time for algorithm: {:.4f}".format(time.clock() - time_start))
return path
neighbour_coors = self.maze.find_neighbours(k_curr, l_curr) # Find neighbour indicies
neighbour_coors = self.maze.validate_neighbours_solve(neighbour_coors, k_curr,
l_curr, self.maze.exit_coor[0],
self.maze.exit_coor[1], self.neighbor_method)
if neighbour_coors is not None:
for coor in neighbour_coors:
next_level.append(coor) # Add all existing real neighbours to next search level
for cell in next_level:
current_level.append(cell) # Update current_level list with cells for nex search level
logging.debug("Class BreadthFirst leaving solve")
class BiDirectional(Solver):
def __init__(self, maze, quiet_mode=False, neighbor_method="fancy"):
logging.debug('Class BiDirectional ctor called')
super().__init__(maze, neighbor_method, quiet_mode)
self.name = "Bi Directional"
def solve(self):
"""Function that implements a bidirectional depth-first recursive backtracker algorithm for
solving the maze, i.e. starting at the entry point and exit points where each search searches
for the other search path. NOTE: THE FUNCTION ENDS IN AN INFINITE LOOP FOR SOME RARE CASES OF
THE INPUT MAZE. WILL BE FIXED IN FUTURE."""
logging.debug("Class BiDirectional solve called")
grid = self.maze.grid
k_curr, l_curr = self.maze.entry_coor # Where to start the first search
p_curr, q_curr = self.maze.exit_coor # Where to start the second search
grid[k_curr][l_curr].visited = True # Set initial cell to visited
grid[p_curr][q_curr].visited = True # Set final cell to visited
backtrack_kl = list() # Stack of visited cells for backtracking
backtrack_pq = list() # Stack of visited cells for backtracking
path_kl = list() # To track path of solution and backtracking cells
path_pq = list() # To track path of solution and backtracking cells
if not self.quiet_mode:
print("\nSolving the maze with bidirectional depth-first search...")
time_start = time.clock()
while True: # Loop until return statement is encountered
neighbours_kl = self.maze.find_neighbours(k_curr, l_curr) # Find neighbours for first search
real_neighbours_kl = [neigh for neigh in neighbours_kl if not grid[k_curr][l_curr].is_walls_between(grid[neigh[0]][neigh[1]])]
neighbours_kl = [neigh for neigh in real_neighbours_kl if not grid[neigh[0]][neigh[1]].visited]
neighbours_pq = self.maze.find_neighbours(p_curr, q_curr) # Find neighbours for second search
real_neighbours_pq = [neigh for neigh in neighbours_pq if not grid[p_curr][q_curr].is_walls_between(grid[neigh[0]][neigh[1]])]
neighbours_pq = [neigh for neigh in real_neighbours_pq if not grid[neigh[0]][neigh[1]].visited]
if len(neighbours_kl) > 0: # If there are unvisited neighbour cells
backtrack_kl.append((k_curr, l_curr)) # Add current cell to stack
path_kl.append(((k_curr, l_curr), False)) # Add coordinates to part of search path
k_next, l_next = random.choice(neighbours_kl) # Choose random neighbour
grid[k_next][l_next].visited = True # Move to that neighbour
k_curr = k_next
l_curr = l_next
elif len(backtrack_kl) > 0: # If there are no unvisited neighbour cells
path_kl.append(((k_curr, l_curr), True)) # Add coordinates to part of search path
k_curr, l_curr = backtrack_kl.pop() # Pop previous visited cell (backtracking)
if len(neighbours_pq) > 0: # If there are unvisited neighbour cells
backtrack_pq.append((p_curr, q_curr)) # Add current cell to stack
path_pq.append(((p_curr, q_curr), False)) # Add coordinates to part of search path
p_next, q_next = random.choice(neighbours_pq) # Choose random neighbour
grid[p_next][q_next].visited = True # Move to that neighbour
p_curr = p_next
q_curr = q_next
elif len(backtrack_pq) > 0: # If there are no unvisited neighbour cells
path_pq.append(((p_curr, q_curr), True)) # Add coordinates to part of search path
p_curr, q_curr = backtrack_pq.pop() # Pop previous visited cell (backtracking)
# Exit loop and return path if any opf the kl neighbours are in path_pq.
if any((True for n_kl in real_neighbours_kl if (n_kl, False) in path_pq)):
path_kl.append(((k_curr, l_curr), False))
path = [p_el for p_tuple in zip(path_kl, path_pq) for p_el in p_tuple] # Zip paths
if not self.quiet_mode:
print("Number of moves performed: {}".format(len(path)))
print("Execution time for algorithm: {:.4f}".format(time.clock() - time_start))
logging.debug("Class BiDirectional leaving solve")
return path
# Exit loop and return path if any opf the pq neighbours are in path_kl.
elif any((True for n_pq in real_neighbours_pq if (n_pq, False) in path_kl)):
path_pq.append(((p_curr, q_curr), False))
path = [p_el for p_tuple in zip(path_kl, path_pq) for p_el in p_tuple] # Zip paths
if not self.quiet_mode:
print("Number of moves performed: {}".format(len(path)))
print("Execution time for algorithm: {:.4f}".format(time.clock() - time_start))
logging.debug("Class BiDirectional leaving solve")
return path
class DepthFirstBacktracker(Solver):
"""A solver that implements the depth-first recursive backtracker algorithm.
"""
def __init__(self, maze, quiet_mode=False, neighbor_method="fancy"):
logging.debug('Class DepthFirstBacktracker ctor called')
super().__init__(maze, neighbor_method, quiet_mode)
self.name = "Depth First Backtracker"
def solve(self):
logging.debug("Class DepthFirstBacktracker solve called")
k_curr, l_curr = self.maze.entry_coor # Where to start searching
self.maze.grid[k_curr][l_curr].visited = True # Set initial cell to visited
visited_cells = list() # Stack of visited cells for backtracking
path = list() # To track path of solution and backtracking cells
if not self.quiet_mode:
print("\nSolving the maze with depth-first search...")
time_start = time.time()
while (k_curr, l_curr) != self.maze.exit_coor: # While the exit cell has not been encountered
neighbour_indices = self.maze.find_neighbours(k_curr, l_curr) # Find neighbour indices
neighbour_indices = self.maze.validate_neighbours_solve(neighbour_indices, k_curr,
l_curr, self.maze.exit_coor[0], self.maze.exit_coor[1], self.neighbor_method)
if neighbour_indices is not None: # If there are unvisited neighbour cells
visited_cells.append((k_curr, l_curr)) # Add current cell to stack
path.append(((k_curr, l_curr), False)) # Add coordinates to part of search path
k_next, l_next = random.choice(neighbour_indices) # Choose random neighbour
self.maze.grid[k_next][l_next].visited = True # Move to that neighbour
k_curr = k_next
l_curr = l_next
elif len(visited_cells) > 0: # If there are no unvisited neighbour cells
path.append(((k_curr, l_curr), True)) # Add coordinates to part of search path
k_curr, l_curr = visited_cells.pop() # Pop previous visited cell (backtracking)
path.append(((k_curr, l_curr), False)) # Append final location to path
if not self.quiet_mode:
print("Number of moves performed: {}".format(len(path)))
print("Execution time for algorithm: {:.4f}".format(time.time() - time_start))
logging.debug('Class DepthFirstBacktracker leaving solve')
return path
================================================
FILE: tests/__init__.py
================================================
================================================
FILE: tests/algorithm_tests.py
================================================
# test no cell has a wall on all four walls
import unittest
# import all algorithms present in algorithm.py
from src.algorithm import *
from src.maze import Maze
def create_maze(algorithm):
rows, cols = (5,5)
return Maze(rows, cols, algorithm = algorithm)
class TestAlgorithm(unittest.TestCase):
def test_NonEmptyPath(self):
"""Test to check that generation path for a maze is not an empty list"""
# repeat the following for all algorithms developed in algorithm.py
for algorithm in algorithm_list:
# generate a maze using this algorithm
maze = create_maze( algorithm )
# create message to display when test fails
err_msg = f'Algorithm {algorithm} generated empty path'
# assert path is non empty list
self.assertNotEqual( maze.generation_path, list(), msg = err_msg)
def test_MazeHasEntryExit(self):
"""Test to check that entry and exit cells have been properly marked"""
# repeat the following for all algorithms
for algorithm in algorithm_list:
# generate a maze using the algorithm
maze = create_maze( algorithm )
# create message to display when test fails
err_msg = f'Algorithm {algorithm} did not generate entry_exit cells'
# get the cell that has been set as entry point
entry_cell = maze.grid[maze.entry_coor[0]][maze.entry_coor[1]]
# check that the cell has been marked as an entry cell
self.assertIsNotNone( entry_cell.is_entry_exit, msg = err_msg )
# get the cell that has been set as exit point
exit_cell = maze.grid[maze.exit_coor[0]][maze.exit_coor[1]]
# check that the cell has been marked as an exit cell
self.assertIsNotNone( entry_cell.is_entry_exit ,msg = err_msg )
def test_AllCellsUnvisited(self):
"""Test to check that after maze generation all cells have been
marked as unvisited."""
# repeat the following for all algorithms
for algorithm in algorithm_list:
# generate a maze using the algorithm
maze = create_maze( algorithm )
# create message to display when test fails
err_msg = f'Algorithm {algorithm} did not unvisit all cells'
# repeat the following for all rows in maze
for row in maze.grid:
# repeat the following for all cells in the row
for cell in row:
# assert that no cell is marked as visited
self.assertFalse( cell.visited, msg = err_msg )
def test_NoCellUnvisited(self):
"""Test to check that all cells have been processed, thus no cell has
walls on all four sides"""
# repeat the following for all algorithms
for algorithm in algorithm_list:
# generate a maze using the algorithm
maze = create_maze( algorithm )
# create message to display when test fails
err_msg = f'Algorithm {algorithm} did not generate entry_exit cells'
# variable to store how a cell with walls on all sides is denoted
walls_4 = {"top": True, "right": True, "bottom": True, "left": True}
# repeat the following for all rows in maze
for row in maze.grid:
# repeat the following for all cells in the row
for cell in row:
# check that the cell does not have walls on all four sides
self.assertNotEqual( cell.walls, walls_4, msg = err_msg )
================================================
FILE: tests/cell_tests.py
================================================
from __future__ import absolute_import
import unittest
from src.cell import Cell
class TestCell(unittest.TestCase):
def test_ctor(self):
"""Make sure that the constructor values are getting properly set."""
cell = Cell(2, 2)
self.assertEqual(cell.row, 2)
self.assertEqual(cell.col, 2)
self.assertEqual(cell.visited, False)
self.assertEqual(cell.active, False)
self.assertEqual(cell.is_entry_exit, None)
self.assertEqual(cell.walls, {"top": True, "right": True, "bottom": True, "left": True})
self.assertEqual(cell.neighbours, list())
def test_entry_exit(self):
"""Test the Cell::entry_exit method"""
# Check if the entrance/exit is on the top row.
cell = Cell(0, 1)
cell.set_as_entry_exit(True, 3, 3)
self.assertEqual(cell.is_entry_exit, True)
self.assertEqual(cell.walls["top"], False)
cell.set_as_entry_exit(False, 1, 0)
self.assertEqual(cell.is_entry_exit, False)
self.assertEqual(cell.walls["top"], False)
# Check if the entrance/exit is on the bottom row.
cell = Cell(1, 0)
cell.set_as_entry_exit(True, 1, 0)
self.assertEqual(cell.walls["bottom"], False)
self.assertEqual(cell.is_entry_exit, True)
# Check if the entrance/exit is on the left wall.
cell = Cell(2, 0)
cell.set_as_entry_exit(True, 3, 1)
self.assertEqual(cell.walls["left"], False)
cell.set_as_entry_exit(True, 1, 1)
# Check if the entrance/exit is on the right side wall.
cell = Cell(3, 2)
cell.set_as_entry_exit(True, 2, 2)
self.assertEqual(cell.walls["right"], False)
# Check if we can make the exit on the right wall in a corner
cell = Cell(2, 2)
cell.set_as_entry_exit(True, 2, 2)
self.assertEqual(cell.walls["right"], True)
def test_remove_walls(self):
"""Test the Cell::remove_walls method"""
# Remove the cell to the right
cell = Cell(0, 0)
cell.remove_walls(0,1)
self.assertEqual(cell.walls["right"], False)
# Remove the cell to the left
cell = Cell(0, 1)
cell.remove_walls(0, 0)
self.assertEqual(cell.walls["left"], False)
# Remove the cell above
cell = Cell(1, 1)
cell.remove_walls(0, 1)
self.assertEqual(cell.walls["top"], False)
# Remove the cell below
cell = Cell(1, 1)
cell.remove_walls(2, 1)
self.assertEqual(cell.walls["bottom"], False)
def test_is_walls_between(self):
"""Test the Cell::is_walls_between method
Note that cells are constructed with neighbors on each side.
We'll need to remove some walls to get full coverage.
"""
# Create a base cell for which we will be testing whether walls exist
cell = Cell (1, 1)
# Create a cell appearing to the top of this cell
cell_top = Cell(0,1)
# Create a cell appearing to the right of this cell
cell_right = Cell(1,2)
# Create a cell appearing to the bottom of this cell
cell_bottom = Cell(2,1)
# Create a cell appearing to the left of this cell
cell_left = Cell(1,0)
# check for walls between all these cells
self.assertEqual(cell.is_walls_between(cell_top), True)
self.assertEqual(cell.is_walls_between(cell_right), True)
self.assertEqual(cell.is_walls_between(cell_bottom), True)
self.assertEqual(cell.is_walls_between(cell_left), True)
# remove top wall of 'cell' and bottom wall of 'cell_top'
cell.remove_walls(0,1)
cell_top.remove_walls(1,1)
# check that there are no walls between these cells
self.assertEqual(cell.is_walls_between(cell_top), False)
if __name__ == "__main__":
unittest.main()
================================================
FILE: tests/maze_manager_tests.py
================================================
from __future__ import absolute_import
import unittest
from src.maze_manager import MazeManager
from src.maze_viz import Visualizer
from src.maze import Maze
class TestMgr(unittest.TestCase):
def test_ctor(self):
"""Make sure that the constructor values are getting properly set."""
manager = MazeManager()
self.assertEqual(manager.get_maze_count(), 0)
self.assertEqual(manager.get_mazes(), [])
self.assertEqual(manager.quiet_mode, False)
def test_add_new(self):
"""Test adding mazes by passing maze specs into add_maze"""
manager = MazeManager()
maze1 = manager.add_maze(6, 6)
self.assertEqual(maze1.id, 0)
self.assertEqual(manager.get_mazes().__len__(), 1)
self.assertEqual(manager.get_maze_count(), 1)
maze2 = manager.add_maze(3, 3, 1)
self.assertEqual(maze2.id, 1)
self.assertEqual(manager.get_mazes().__len__(), 2)
self.assertEqual(manager.get_maze_count(), 2)
def test_add_existing(self):
"""Test adding mazes by passing already existing Maze objects in"""
manager = MazeManager()
maze1 = Maze(2, 2)
self.assertEqual(maze1.id, 0)
manager.add_existing_maze(maze1)
self.assertEqual(manager.get_mazes().__len__(), 1)
self.assertEqual(manager.get_maze_count(), 1)
self.assertIsNotNone(manager.get_maze(maze1.id))
self.assertEqual(manager.get_maze(maze1.id).id, maze1.id)
maze2 = Maze(3, 3, 1)
self.assertEqual(maze2.id, 1)
manager.add_existing_maze(maze2)
self.assertEqual(manager.get_mazes().__len__(), 2)
self.assertEqual(manager.get_maze_count(), 2)
self.assertIsNotNone(manager.get_maze(maze2.id))
self.assertEqual(manager.get_maze(maze2.id).id, maze2.id)
def test_get_maze(self):
"""Test the get_maze function"""
manager = MazeManager()
self.assertEqual(manager.get_maze(0), None)
self.assertEqual(manager.get_mazes(), [])
maze1 = manager.add_maze(6, 6)
self.assertEqual(maze1.id, 0)
def test_get_mazes(self):
"""Tests that get_mazes is returning all mazes"""
manager = MazeManager()
self.assertEqual(manager.get_maze(0), None)
self.assertEqual(manager.get_mazes(), [])
manager.add_maze(6, 6)
manager.add_maze(6, 6)
mazes = manager.get_mazes()
self.assertAlmostEqual(mazes.__len__(), 2)
def test_get_maze_count(self):
"""Tests the get_maze_number function"""
manager = MazeManager()
self.assertEqual(manager.get_maze_count(), 0)
maze1 = Maze(2, 2)
manager.add_existing_maze(maze1)
self.assertEqual(manager.get_maze_count(), 1)
def test_check_matching_id(self):
"""Check that check_matching_id is functioning properly"""
manager = MazeManager()
manager.add_maze(8, 8, 1)
manager.add_maze(8, 8, 1)
result = [manager.check_matching_id(1)]
self.assertEqual(len(result), 1)
def test_set_filename(self):
"""Tests that the filename is getting set"""
manager = MazeManager()
filename = "myFile"
manager.set_filename(filename)
self.assertEqual(filename, manager.media_name)
def test_set_quiet_mode(self):
manager = MazeManager()
self.assertEqual(manager.quiet_mode, False)
manager.set_quiet_mode(True)
self.assertEqual(manager.quiet_mode, True)
if __name__ == "__main__":
unittest.main()
================================================
FILE: tests/maze_tests.py
================================================
from __future__ import absolute_import
import unittest
from src.maze import Maze
from src.cell import Cell
def generate_maze():
# Used to generate a 5x5 maze for testing, Feel free to modify as needed
cols = 5
rows = 5
return Maze(rows, cols)
class TestMaze(unittest.TestCase):
def test_ctor(self):
"""Make sure that the constructor values are getting properly set."""
cols = 5
rows = 5
maze = Maze(rows, cols)
self.assertEqual(maze.num_cols, cols)
self.assertEqual(maze.num_rows, rows)
self.assertEqual(maze.id, 0)
self.assertEqual(maze.grid_size, rows*cols)
id=33
maze2 = Maze(rows, cols, id)
self.assertEqual(maze2.num_cols, cols)
self.assertEqual(maze2.num_rows, rows)
self.assertEqual(maze2.id, id)
self.assertEqual(maze2.grid_size, rows * cols)
def test_generate_grid(self):
maze = generate_maze()
grid = maze.generate_grid()
self.assertEqual(len(grid), maze.num_cols)
self.assertGreater(len(grid), 2)
self.assertEqual(len(grid[0]), maze.num_rows)
def test_find_neighbors(self):
maze = Maze(2, 2)
neighbors = maze.find_neighbours(0, 1)
self.assertIsNotNone(neighbors)
if __name__ == "__main__":
unittest.main()
================================================
FILE: tests/maze_viz_tests.py
================================================
================================================
FILE: tests/solver_tests.py
================================================
from __future__ import absolute_import
import unittest
from src.solver import Solver
class TestSolver(unittest.TestCase):
def test_ctor(self):
solver = Solver("", "", False)
self.assertEqual(solver.name, "")
self.assertEqual(solver.quiet_mode, False)
if __name__ == "__main__":
unittest.main()
gitextract_q7eta_e0/
├── .gitignore
├── .travis.yml
├── LICENCE
├── README.md
├── examples/
│ ├── generate_binary_tree_algorithm.py
│ ├── quick_start.py
│ ├── solve_bi_directional.py
│ ├── solve_breadth_first_recursive.py
│ └── solve_depth_first_recursive.py
├── install_linter
├── requirements.txt
├── src/
│ ├── __init__.py
│ ├── algorithm.py
│ ├── cell.py
│ ├── maze.py
│ ├── maze_manager.py
│ ├── maze_viz.py
│ └── solver.py
└── tests/
├── __init__.py
├── algorithm_tests.py
├── cell_tests.py
├── maze_manager_tests.py
├── maze_tests.py
├── maze_viz_tests.py
└── solver_tests.py
SYMBOL INDEX (82 symbols across 11 files)
FILE: src/algorithm.py
function depth_first_recursive_backtracker (line 8) | def depth_first_recursive_backtracker( maze, start_coor ):
function binary_tree (line 51) | def binary_tree( maze, start_coor ):
FILE: src/cell.py
class Cell (line 2) | class Cell(object):
method __init__ (line 14) | def __init__(self, row, col):
method is_walls_between (line 23) | def is_walls_between(self, neighbour):
method remove_walls (line 46) | def remove_walls(self, neighbour_row, neighbour_col):
method set_as_entry_exit (line 72) | def set_as_entry_exit(self, entry_exit, row_limit, col_limit):
FILE: src/maze.py
class Maze (line 9) | class Maze(object):
method __init__ (line 26) | def __init__(self, num_rows, num_cols, id=0, algorithm = "dfs_backtrac...
method generate_grid (line 47) | def generate_grid(self):
method find_neighbours (line 68) | def find_neighbours(self, cell_row, cell_col):
method _validate_neighbours_generate (line 98) | def _validate_neighbours_generate(self, neighbour_indices):
method validate_neighbours_solve (line 118) | def validate_neighbours_solve(self, neighbour_indices, k, l, k_end, l_...
method _pick_random_entry_exit (line 162) | def _pick_random_entry_exit(self, used_entry_exit=None):
method generate_maze (line 192) | def generate_maze(self, algorithm, start_coor = (0, 0)):
FILE: src/maze_manager.py
class MazeManager (line 8) | class MazeManager(object):
method __init__ (line 18) | def __init__(self):
method add_maze (line 23) | def add_maze(self, row, col, id=0):
method add_existing_maze (line 49) | def add_existing_maze(self, maze, override=True):
method get_maze (line 76) | def get_maze(self, id):
method get_mazes (line 93) | def get_mazes(self):
method get_maze_count (line 97) | def get_maze_count(self):
method solve_maze (line 101) | def solve_maze(self, maze_id, method, neighbor_method="fancy"):
method show_maze (line 130) | def show_maze(self, id, cell_size=1):
method show_generation_animation (line 135) | def show_generation_animation(self, id, cell_size=1):
method show_solution (line 139) | def show_solution(self, id, cell_size=1):
method show_solution_animation (line 143) | def show_solution_animation(self, id, cell_size =1):
method check_matching_id (line 154) | def check_matching_id(self, id):
method set_filename (line 165) | def set_filename(self, filename):
method set_quiet_mode (line 174) | def set_quiet_mode(self, enabled):
FILE: src/maze_viz.py
class Visualizer (line 8) | class Visualizer(object):
method __init__ (line 23) | def __init__(self, maze, cell_size, media_filename):
method set_media_filename (line 33) | def set_media_filename(self, filename):
method show_maze (line 40) | def show_maze(self):
method plot_walls (line 56) | def plot_walls(self):
method configure_plot (line 77) | def configure_plot(self):
method show_maze_solution (line 99) | def show_maze_solution(self):
method show_generation_animation (line 133) | def show_generation_animation(self):
method add_path (line 229) | def add_path(self):
method animate_maze_solution (line 256) | def animate_maze_solution(self):
FILE: src/solver.py
class Solver (line 9) | class Solver(object):
method __init__ (line 20) | def __init__(self, maze, quiet_mode, neighbor_method):
method solve (line 28) | def solve(self):
method get_name (line 32) | def get_name(self):
method get_path (line 36) | def get_path(self):
class BreadthFirst (line 41) | class BreadthFirst(Solver):
method __init__ (line 43) | def __init__(self, maze, quiet_mode=False, neighbor_method="fancy"):
method solve (line 49) | def solve(self):
class BiDirectional (line 90) | class BiDirectional(Solver):
method __init__ (line 92) | def __init__(self, maze, quiet_mode=False, neighbor_method="fancy"):
method solve (line 98) | def solve(self):
class DepthFirstBacktracker (line 174) | class DepthFirstBacktracker(Solver):
method __init__ (line 178) | def __init__(self, maze, quiet_mode=False, neighbor_method="fancy"):
method solve (line 184) | def solve(self):
FILE: tests/algorithm_tests.py
function create_maze (line 9) | def create_maze(algorithm):
class TestAlgorithm (line 13) | class TestAlgorithm(unittest.TestCase):
method test_NonEmptyPath (line 14) | def test_NonEmptyPath(self):
method test_MazeHasEntryExit (line 25) | def test_MazeHasEntryExit(self):
method test_AllCellsUnvisited (line 44) | def test_AllCellsUnvisited(self):
method test_NoCellUnvisited (line 61) | def test_NoCellUnvisited(self):
FILE: tests/cell_tests.py
class TestCell (line 6) | class TestCell(unittest.TestCase):
method test_ctor (line 7) | def test_ctor(self):
method test_entry_exit (line 19) | def test_entry_exit(self):
method test_remove_walls (line 54) | def test_remove_walls(self):
method test_is_walls_between (line 76) | def test_is_walls_between(self):
FILE: tests/maze_manager_tests.py
class TestMgr (line 9) | class TestMgr(unittest.TestCase):
method test_ctor (line 11) | def test_ctor(self):
method test_add_new (line 19) | def test_add_new(self):
method test_add_existing (line 33) | def test_add_existing(self):
method test_get_maze (line 55) | def test_get_maze(self):
method test_get_mazes (line 64) | def test_get_mazes(self):
method test_get_maze_count (line 75) | def test_get_maze_count(self):
method test_check_matching_id (line 84) | def test_check_matching_id(self):
method test_set_filename (line 93) | def test_set_filename(self):
method test_set_quiet_mode (line 100) | def test_set_quiet_mode(self):
FILE: tests/maze_tests.py
function generate_maze (line 8) | def generate_maze():
class TestMaze (line 16) | class TestMaze(unittest.TestCase):
method test_ctor (line 17) | def test_ctor(self):
method test_generate_grid (line 35) | def test_generate_grid(self):
method test_find_neighbors (line 43) | def test_find_neighbors(self):
FILE: tests/solver_tests.py
class TestSolver (line 8) | class TestSolver(unittest.TestCase):
method test_ctor (line 9) | def test_ctor(self):
Condensed preview — 25 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (82K chars).
[
{
"path": ".gitignore",
"chars": 45,
"preview": "\n*.png\n*.jpg\n*.mp4\n.idea\n*.pyc\n__pycache__\n\n\n"
},
{
"path": ".travis.yml",
"chars": 574,
"preview": "language: python\npython:\n - \"3.5\"\n - \"3.6\"\n\nbefore_install:\n - \"chmod +x tests/cell_tests.py\"\n - \"chmod +x t"
},
{
"path": "LICENCE",
"chars": 1074,
"preview": "MIT License\n\nCopyright (c) 2021 Jostein Brændshøi\n\nPermission is hereby granted, free of charge, to any person obtaining"
},
{
"path": "README.md",
"chars": 5549,
"preview": "# Maze generator and solver\nPython scripts for generating random solvable mazes using the depth-first search and recursi"
},
{
"path": "examples/generate_binary_tree_algorithm.py",
"chars": 599,
"preview": "from __future__ import absolute_import\nfrom src.maze_manager import MazeManager\nfrom src.maze import Maze\n\n\nif __name__ "
},
{
"path": "examples/quick_start.py",
"chars": 2506,
"preview": "from __future__ import absolute_import\nfrom src.maze_manager import MazeManager\nfrom src.maze import Maze\n\n\nif __name__ "
},
{
"path": "examples/solve_bi_directional.py",
"chars": 661,
"preview": "from __future__ import absolute_import\nfrom src.maze_manager import MazeManager\n\n\nif __name__ == \"__main__\":\n\n # Crea"
},
{
"path": "examples/solve_breadth_first_recursive.py",
"chars": 660,
"preview": "from __future__ import absolute_import\nfrom src.maze_manager import MazeManager\n\n\nif __name__ == \"__main__\":\n\n # Crea"
},
{
"path": "examples/solve_depth_first_recursive.py",
"chars": 659,
"preview": "from __future__ import absolute_import\nfrom src.maze_manager import MazeManager\n\n\nif __name__ == \"__main__\":\n\n # Crea"
},
{
"path": "install_linter",
"chars": 11,
"preview": "pycodestyle"
},
{
"path": "requirements.txt",
"chars": 18,
"preview": "matplotlib==2.0.0\n"
},
{
"path": "src/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "src/algorithm.py",
"chars": 7151,
"preview": "import time\nimport random\nimport math\n\n# global variable to store list of all available algorithms\nalgorithm_list = [\"df"
},
{
"path": "src/cell.py",
"chars": 3384,
"preview": "\nclass Cell(object):\n \"\"\"Class for representing a cell in a 2D grid.\n\n Attributes:\n row (int): The "
},
{
"path": "src/maze.py",
"chars": 7399,
"preview": "\nimport random\nimport math\nimport time\nfrom src.cell import Cell\nfrom src.algorithm import depth_first_recursive_backtra"
},
{
"path": "src/maze_manager.py",
"chars": 6452,
"preview": "from src.maze import Maze\r\nfrom src.maze_viz import Visualizer\r\nfrom src.solver import DepthFirstBacktracker\r\nfrom src.s"
},
{
"path": "src/maze_viz.py",
"chars": 16335,
"preview": "import matplotlib.pyplot as plt\r\nfrom matplotlib import animation\r\nimport logging\r\n\r\nlogging.basicConfig(level=logging.D"
},
{
"path": "src/solver.py",
"chars": 11425,
"preview": "import time\nimport random\nimport logging\nfrom src.maze import Maze\n\nlogging.basicConfig(level=logging.DEBUG)\n\n\nclass Sol"
},
{
"path": "tests/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/algorithm_tests.py",
"chars": 3626,
"preview": "\n# test no cell has a wall on all four walls\nimport unittest\n\n# import all algorithms present in algorithm.py\nfrom src.a"
},
{
"path": "tests/cell_tests.py",
"chars": 4012,
"preview": "from __future__ import absolute_import\r\nimport unittest\r\nfrom src.cell import Cell\r\n\r\n\r\nclass TestCell(unittest.TestCase"
},
{
"path": "tests/maze_manager_tests.py",
"chars": 3687,
"preview": "from __future__ import absolute_import\r\nimport unittest\r\n\r\nfrom src.maze_manager import MazeManager\r\nfrom src.maze_viz i"
},
{
"path": "tests/maze_tests.py",
"chars": 1386,
"preview": "from __future__ import absolute_import\r\nimport unittest\r\n\r\nfrom src.maze import Maze\r\nfrom src.cell import Cell\r\n\r\n\r\ndef"
},
{
"path": "tests/maze_viz_tests.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/solver_tests.py",
"chars": 332,
"preview": "from __future__ import absolute_import\nimport unittest\n\nfrom src.solver import Solver\n\n\n\nclass TestSolver(unittest.TestC"
}
]
About this extraction
This page contains the full source code of the jostbr/MazeGenerator GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 25 files (75.7 KB), approximately 18.2k tokens, and a symbol index with 82 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.