Full Code of jostbr/MazeGenerator for AI

master ac41b42df45b cached
25 files
75.7 KB
18.2k tokens
82 symbols
1 requests
Download .txt
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()
Download .txt
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
Download .txt
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.

Copied to clipboard!