feat: add the algorithm to solve the maze

This commit is contained in:
Dan Anglin 2024-02-15 19:13:31 +00:00
parent 5be7f5041a
commit 5c48f47a0a
Signed by: dananglin
GPG key ID: 0C1D44CFBEE68638
4 changed files with 90 additions and 26 deletions

28
cell.py
View file

@ -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:

View file

@ -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()

74
maze.py
View file

@ -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)

View file

@ -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):