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