diff --git a/cell.py b/cell.py index 0b13fc0..467c423 100644 --- a/cell.py +++ b/cell.py @@ -4,7 +4,7 @@ from graphics import Window, Point, Line import errors -class CellWallLabel(Enum): +class CellWallLabels(Enum): """ CellWallLabel is used to label a CellWall """ @@ -61,11 +61,11 @@ class Cell: left_wall = Line(Point(x1, y1), Point(x1, y2)) right_wall = Line(Point(x2, y1), Point(x2, y2)) - self._walls: Dict[CellWallLabel, CellWall] = { - CellWallLabel.TOP: CellWall(top_wall, window), - CellWallLabel.BOTTOM: CellWall(bottom_wall, window), - CellWallLabel.LEFT: CellWall(left_wall, window), - CellWallLabel.RIGHT: CellWall(right_wall, window), + self._walls: Dict[CellWallLabels, CellWall] = { + CellWallLabels.TOP: CellWall(top_wall, window), + CellWallLabels.BOTTOM: CellWall(bottom_wall, window), + CellWallLabels.LEFT: CellWall(left_wall, window), + CellWallLabels.RIGHT: CellWall(right_wall, window), } # Calculate the cell's central point @@ -90,13 +90,13 @@ class Cell: configure_walls configures the existence of the Cell's walls. """ if top is not None: - self._walls[CellWallLabel.TOP].exists = top + self._walls[CellWallLabels.TOP].exists = top if bottom is not None: - self._walls[CellWallLabel.BOTTOM].exists = bottom + self._walls[CellWallLabels.BOTTOM].exists = bottom if left is not None: - self._walls[CellWallLabel.LEFT].exists = left + self._walls[CellWallLabels.LEFT].exists = left if right is not None: - self._walls[CellWallLabel.RIGHT].exists = right + self._walls[CellWallLabels.RIGHT].exists = right def centre(self) -> Point: """ @@ -104,10 +104,14 @@ class Cell: """ return self._centre - def wall_exists(self, wall: CellWallLabel) -> bool: + def wall_exists(self, wall: CellWallLabels) -> bool: """ returns True if a given cell wall exists, or false otherwise. """ + if wall not in CellWallLabels: + raise TypeError( + "The argument does not appear to be a valid cell wall." + ) return self._walls[wall].exists def draw(self) -> None: @@ -117,7 +121,7 @@ class Cell: if not self._window: return - for label in CellWallLabel: + for label in CellWallLabels: self._walls[label].draw() def draw_move(self, to_cell: 'Cell', undo: bool = False) -> None: diff --git a/main.py b/main.py index 7a9b18e..5b9f02b 100644 --- a/main.py +++ b/main.py @@ -5,7 +5,7 @@ from maze import Maze def main(): window = Window(800, 800) - _ = Maze( + game = Maze( x_position=10, y_position=10, num_cell_rows=30, @@ -15,6 +15,12 @@ def main(): window=window ) + solved = game.solve() + if solved: + print("Maze solved successfully :)") + else: + print("I'm unable to solve the maze :(") + window.mainloop() diff --git a/maze.py b/maze.py index 301defe..7ae97b3 100644 --- a/maze.py +++ b/maze.py @@ -1,9 +1,9 @@ -from typing import List +from typing import List, Dict from time import sleep import random from enum import Enum from graphics import Window -from cell import Cell +from cell import Cell, CellWallLabels class MazeDirections(Enum): @@ -39,7 +39,7 @@ class MazePosition: ) -> 'MazePosition': if direction not in MazeDirections: raise TypeError( - "The argument does not appear to be a valid MazeDirection" + "The argument does not appear to be a valid maze direction." ) if direction is MazeDirections.ABOVE and (self.i-1 >= 0): @@ -105,6 +105,8 @@ class Maze: self._cells: List[List[Cell]] = [ None for i in range(self._num_cell_rows)] self._create_cell_grid() + + # Open up the maze's entrance and exit. self._open_entrance_and_exit() start_position = MazePosition( @@ -114,13 +116,7 @@ class Maze: max_j=self._num_cells_per_row-1, ) - end_position = MazePosition( - i=self._num_cell_rows-1, - j=self._num_cells_per_row-1, - max_i=self._num_cell_rows-1, - max_j=self._num_cells_per_row-1, - ) - + # Generate the maze. self._break_walls_r(start_position) def _create_cell_grid(self) -> None: @@ -227,6 +223,59 @@ class Maze: self._break_walls_r(next_position) + def solve(self) -> bool: + """ + solve attempts to solve the generated maze. + """ + start_position = MazePosition( + i=0, + j=0, + max_i=self._num_cell_rows-1, + max_j=self._num_cells_per_row-1, + ) + + end_position = MazePosition( + i=self._num_cell_rows-1, + j=self._num_cells_per_row-1, + max_i=self._num_cell_rows-1, + max_j=self._num_cells_per_row-1, + ) + + return self._solve_r(start_position, end_position) + + def _solve_r( + self, + current_position: MazePosition, + end_position: MazePosition, + ) -> bool: + if current_position == end_position: + return True + current_cell = self._cells[current_position.i][current_position.j] + current_cell.visited_by_maze_solver = True + + wall_map: Dict[MazeDirections, CellWallLabels] = { + MazeDirections.ABOVE: CellWallLabels.BOTTOM, + MazeDirections.BELOW: CellWallLabels.TOP, + MazeDirections.LEFT: CellWallLabels.RIGHT, + MazeDirections.RIGHT: CellWallLabels.LEFT, + } + + for direction in MazeDirections: + adjacent_position = current_position.get_adjacent_position( + direction) + if adjacent_position is None: + continue + adjacent_cell = self._cells[adjacent_position.i][adjacent_position.j] + if adjacent_cell.visited_by_maze_solver or adjacent_cell.wall_exists(wall_map[direction]): + continue + self._draw_path(current_cell, adjacent_cell) + result = self._solve_r(adjacent_position, end_position) + if result is True: + return True + self._draw_path(current_cell, adjacent_cell, undo=True) + + return False + def _draw_cell(self, i: int, j: int) -> None: """ _draw_cell draws the cells in an animated way. @@ -234,3 +283,8 @@ class Maze: self._cells[i][j].draw() self._window.redraw() sleep(0.05) + + def _draw_path(self, current_cell: Cell, next_cell: Cell, undo: bool = False) -> None: + current_cell.draw_move(next_cell, undo) + self._window.redraw() + sleep(0.05) diff --git a/tests.py b/tests.py index 2a28035..2c08609 100644 --- a/tests.py +++ b/tests.py @@ -1,5 +1,5 @@ import unittest -from cell import Cell, CellWallLabel +from cell import Cell, CellWallLabels import maze import errors @@ -65,10 +65,10 @@ class Tests(unittest.TestCase): None, None, ) - self.assertFalse(m._cells[0][0].wall_exists(CellWallLabel.TOP)) + self.assertFalse(m._cells[0][0].wall_exists(CellWallLabels.TOP)) self.assertFalse( m._cells[number_of_cell_rows - 1] - [number_of_cells_per_row - 1].wall_exists(CellWallLabel.BOTTOM) + [number_of_cells_per_row - 1].wall_exists(CellWallLabels.BOTTOM) ) def test_invalid_cell_exception(self):