add the algorithm to solve the maze
This commit is contained in:
parent
5be7f5041a
commit
758bf00451
4 changed files with 90 additions and 26 deletions
28
cell.py
28
cell.py
|
@ -4,7 +4,7 @@ from graphics import Window, Point, Line
|
||||||
import errors
|
import errors
|
||||||
|
|
||||||
|
|
||||||
class CellWallLabel(Enum):
|
class CellWallLabels(Enum):
|
||||||
"""
|
"""
|
||||||
CellWallLabel is used to label a CellWall
|
CellWallLabel is used to label a CellWall
|
||||||
"""
|
"""
|
||||||
|
@ -61,11 +61,11 @@ class Cell:
|
||||||
left_wall = Line(Point(x1, y1), Point(x1, y2))
|
left_wall = Line(Point(x1, y1), Point(x1, y2))
|
||||||
right_wall = Line(Point(x2, y1), Point(x2, y2))
|
right_wall = Line(Point(x2, y1), Point(x2, y2))
|
||||||
|
|
||||||
self._walls: Dict[CellWallLabel, CellWall] = {
|
self._walls: Dict[CellWallLabels, CellWall] = {
|
||||||
CellWallLabel.TOP: CellWall(top_wall, window),
|
CellWallLabels.TOP: CellWall(top_wall, window),
|
||||||
CellWallLabel.BOTTOM: CellWall(bottom_wall, window),
|
CellWallLabels.BOTTOM: CellWall(bottom_wall, window),
|
||||||
CellWallLabel.LEFT: CellWall(left_wall, window),
|
CellWallLabels.LEFT: CellWall(left_wall, window),
|
||||||
CellWallLabel.RIGHT: CellWall(right_wall, window),
|
CellWallLabels.RIGHT: CellWall(right_wall, window),
|
||||||
}
|
}
|
||||||
|
|
||||||
# Calculate the cell's central point
|
# Calculate the cell's central point
|
||||||
|
@ -90,13 +90,13 @@ class Cell:
|
||||||
configure_walls configures the existence of the Cell's walls.
|
configure_walls configures the existence of the Cell's walls.
|
||||||
"""
|
"""
|
||||||
if top is not None:
|
if top is not None:
|
||||||
self._walls[CellWallLabel.TOP].exists = top
|
self._walls[CellWallLabels.TOP].exists = top
|
||||||
if bottom is not None:
|
if bottom is not None:
|
||||||
self._walls[CellWallLabel.BOTTOM].exists = bottom
|
self._walls[CellWallLabels.BOTTOM].exists = bottom
|
||||||
if left is not None:
|
if left is not None:
|
||||||
self._walls[CellWallLabel.LEFT].exists = left
|
self._walls[CellWallLabels.LEFT].exists = left
|
||||||
if right is not None:
|
if right is not None:
|
||||||
self._walls[CellWallLabel.RIGHT].exists = right
|
self._walls[CellWallLabels.RIGHT].exists = right
|
||||||
|
|
||||||
def centre(self) -> Point:
|
def centre(self) -> Point:
|
||||||
"""
|
"""
|
||||||
|
@ -104,10 +104,14 @@ class Cell:
|
||||||
"""
|
"""
|
||||||
return self._centre
|
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.
|
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
|
return self._walls[wall].exists
|
||||||
|
|
||||||
def draw(self) -> None:
|
def draw(self) -> None:
|
||||||
|
@ -117,7 +121,7 @@ class Cell:
|
||||||
if not self._window:
|
if not self._window:
|
||||||
return
|
return
|
||||||
|
|
||||||
for label in CellWallLabel:
|
for label in CellWallLabels:
|
||||||
self._walls[label].draw()
|
self._walls[label].draw()
|
||||||
|
|
||||||
def draw_move(self, to_cell: 'Cell', undo: bool = False) -> None:
|
def draw_move(self, to_cell: 'Cell', undo: bool = False) -> None:
|
||||||
|
|
8
main.py
8
main.py
|
@ -5,7 +5,7 @@ from maze import Maze
|
||||||
def main():
|
def main():
|
||||||
window = Window(800, 800)
|
window = Window(800, 800)
|
||||||
|
|
||||||
_ = Maze(
|
game = Maze(
|
||||||
x_position=10,
|
x_position=10,
|
||||||
y_position=10,
|
y_position=10,
|
||||||
num_cell_rows=30,
|
num_cell_rows=30,
|
||||||
|
@ -15,6 +15,12 @@ def main():
|
||||||
window=window
|
window=window
|
||||||
)
|
)
|
||||||
|
|
||||||
|
solved = game.solve()
|
||||||
|
if solved:
|
||||||
|
print("Maze solved successfully :)")
|
||||||
|
else:
|
||||||
|
print("I'm unable to solve the maze :(")
|
||||||
|
|
||||||
window.mainloop()
|
window.mainloop()
|
||||||
|
|
||||||
|
|
||||||
|
|
74
maze.py
74
maze.py
|
@ -1,9 +1,9 @@
|
||||||
from typing import List
|
from typing import List, Dict
|
||||||
from time import sleep
|
from time import sleep
|
||||||
import random
|
import random
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from graphics import Window
|
from graphics import Window
|
||||||
from cell import Cell
|
from cell import Cell, CellWallLabels
|
||||||
|
|
||||||
|
|
||||||
class MazeDirections(Enum):
|
class MazeDirections(Enum):
|
||||||
|
@ -39,7 +39,7 @@ class MazePosition:
|
||||||
) -> 'MazePosition':
|
) -> 'MazePosition':
|
||||||
if direction not in MazeDirections:
|
if direction not in MazeDirections:
|
||||||
raise TypeError(
|
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):
|
if direction is MazeDirections.ABOVE and (self.i-1 >= 0):
|
||||||
|
@ -105,6 +105,8 @@ class Maze:
|
||||||
self._cells: List[List[Cell]] = [
|
self._cells: List[List[Cell]] = [
|
||||||
None for i in range(self._num_cell_rows)]
|
None for i in range(self._num_cell_rows)]
|
||||||
self._create_cell_grid()
|
self._create_cell_grid()
|
||||||
|
|
||||||
|
# Open up the maze's entrance and exit.
|
||||||
self._open_entrance_and_exit()
|
self._open_entrance_and_exit()
|
||||||
|
|
||||||
start_position = MazePosition(
|
start_position = MazePosition(
|
||||||
|
@ -114,13 +116,7 @@ class Maze:
|
||||||
max_j=self._num_cells_per_row-1,
|
max_j=self._num_cells_per_row-1,
|
||||||
)
|
)
|
||||||
|
|
||||||
end_position = MazePosition(
|
# Generate the maze.
|
||||||
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,
|
|
||||||
)
|
|
||||||
|
|
||||||
self._break_walls_r(start_position)
|
self._break_walls_r(start_position)
|
||||||
|
|
||||||
def _create_cell_grid(self) -> None:
|
def _create_cell_grid(self) -> None:
|
||||||
|
@ -227,6 +223,59 @@ class Maze:
|
||||||
|
|
||||||
self._break_walls_r(next_position)
|
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:
|
def _draw_cell(self, i: int, j: int) -> None:
|
||||||
"""
|
"""
|
||||||
_draw_cell draws the cells in an animated way.
|
_draw_cell draws the cells in an animated way.
|
||||||
|
@ -234,3 +283,8 @@ class Maze:
|
||||||
self._cells[i][j].draw()
|
self._cells[i][j].draw()
|
||||||
self._window.redraw()
|
self._window.redraw()
|
||||||
sleep(0.05)
|
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)
|
||||||
|
|
6
tests.py
6
tests.py
|
@ -1,5 +1,5 @@
|
||||||
import unittest
|
import unittest
|
||||||
from cell import Cell, CellWallLabel
|
from cell import Cell, CellWallLabels
|
||||||
import maze
|
import maze
|
||||||
import errors
|
import errors
|
||||||
|
|
||||||
|
@ -65,10 +65,10 @@ class Tests(unittest.TestCase):
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
self.assertFalse(m._cells[0][0].wall_exists(CellWallLabel.TOP))
|
self.assertFalse(m._cells[0][0].wall_exists(CellWallLabels.TOP))
|
||||||
self.assertFalse(
|
self.assertFalse(
|
||||||
m._cells[number_of_cell_rows - 1]
|
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):
|
def test_invalid_cell_exception(self):
|
||||||
|
|
Loading…
Reference in a new issue