Compare commits
No commits in common. "856391542b6e0b8b127757de08af8ba578b16c54" and "5c48f47a0a525ef2cb735da56ab477be4d6af6be" have entirely different histories.
856391542b
...
5c48f47a0a
5 changed files with 170 additions and 333 deletions
24
cell.py
24
cell.py
|
@ -76,10 +76,8 @@ class Cell:
|
||||||
# A reference to the root Window class for drawing purposes.
|
# A reference to the root Window class for drawing purposes.
|
||||||
self._window = window
|
self._window = window
|
||||||
|
|
||||||
self._visited: Dict[str, bool] = {
|
self.visited_by_maze_generator = False
|
||||||
"generator": False,
|
self.visited_by_maze_solver = False
|
||||||
"solver": False,
|
|
||||||
}
|
|
||||||
|
|
||||||
def configure_walls(
|
def configure_walls(
|
||||||
self,
|
self,
|
||||||
|
@ -139,21 +137,3 @@ class Cell:
|
||||||
fill_colour = "grey"
|
fill_colour = "grey"
|
||||||
line = Line(self.centre(), to_cell.centre())
|
line = Line(self.centre(), to_cell.centre())
|
||||||
self._window.draw_line(line, fill_colour)
|
self._window.draw_line(line, fill_colour)
|
||||||
|
|
||||||
def was_visited_by(self, visitor: str) -> bool:
|
|
||||||
"""
|
|
||||||
returns True if the cell was visited by the
|
|
||||||
specified visitor.
|
|
||||||
"""
|
|
||||||
if visitor not in ("solver", "generator"):
|
|
||||||
raise ValueError(f"This is an unknown visitor ({visitor})")
|
|
||||||
|
|
||||||
return self._visited[visitor]
|
|
||||||
|
|
||||||
def mark_as_visited_by(self, visitor: str) -> None:
|
|
||||||
"""
|
|
||||||
marks the cell as visited by the specified visitor.
|
|
||||||
"""
|
|
||||||
if visitor not in ("solver", "generator"):
|
|
||||||
raise ValueError(f"This is an unknown visitor ({visitor})")
|
|
||||||
self._visited[visitor] = True
|
|
||||||
|
|
14
main.py
14
main.py
|
@ -1,6 +1,5 @@
|
||||||
from graphics import Window
|
from graphics import Window
|
||||||
from maze import Maze
|
from maze import Maze
|
||||||
from solver import Solver
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
@ -9,16 +8,15 @@ def main():
|
||||||
game = Maze(
|
game = Maze(
|
||||||
x_position=10,
|
x_position=10,
|
||||||
y_position=10,
|
y_position=10,
|
||||||
height=16,
|
num_cell_rows=30,
|
||||||
width=16,
|
num_cells_per_row=30,
|
||||||
cell_height=40,
|
cell_size_x=20,
|
||||||
cell_width=40,
|
cell_size_y=20,
|
||||||
window=window
|
window=window
|
||||||
)
|
)
|
||||||
|
|
||||||
solver = Solver(game)
|
solved = game.solve()
|
||||||
|
if solved:
|
||||||
if solver.solve():
|
|
||||||
print("Maze solved successfully :)")
|
print("Maze solved successfully :)")
|
||||||
else:
|
else:
|
||||||
print("I'm unable to solve the maze :(")
|
print("I'm unable to solve the maze :(")
|
||||||
|
|
308
maze.py
308
maze.py
|
@ -1,4 +1,4 @@
|
||||||
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
|
||||||
|
@ -6,7 +6,7 @@ from graphics import Window
|
||||||
from cell import Cell, CellWallLabels
|
from cell import Cell, CellWallLabels
|
||||||
|
|
||||||
|
|
||||||
class MazeDirection(Enum):
|
class MazeDirections(Enum):
|
||||||
"""
|
"""
|
||||||
MazeDirection represents the directions you can
|
MazeDirection represents the directions you can
|
||||||
take in the maze.
|
take in the maze.
|
||||||
|
@ -22,59 +22,53 @@ class MazePosition:
|
||||||
MazePosition represents a position on the maze grid.
|
MazePosition represents a position on the maze grid.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, i: int, j: int, last_i: int, last_j: int):
|
def __init__(self, i: int, j: int, max_i: int, max_j: int):
|
||||||
self.i = i
|
self.i = i
|
||||||
self.j = j
|
self.j = j
|
||||||
self.last_i = last_i
|
self.max_i = max_i
|
||||||
self.last_j = last_j
|
self.max_j = max_j
|
||||||
|
|
||||||
def __eq__(self, other) -> bool:
|
def __eq__(self, other) -> bool:
|
||||||
if (self.i == other.i) and (self.j == other.j) and (self.last_i == other.last_i) and (self.last_j == other.last_j):
|
if (self.i == other.i) and (self.j == other.j) and (self.max_i == other.max_i) and (self.max_j == other.max_j):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def get_adjacent_position(
|
def get_adjacent_position(
|
||||||
self,
|
self,
|
||||||
direction: MazeDirection
|
direction: MazeDirections
|
||||||
) -> 'MazePosition':
|
) -> 'MazePosition':
|
||||||
"""
|
if direction not in MazeDirections:
|
||||||
calculate and return the position on the maze that is directly adjacent
|
|
||||||
to this maze position in the specified direction. If the adjacent
|
|
||||||
position is outside the boundaries of the maze then a value of None
|
|
||||||
is returned.
|
|
||||||
"""
|
|
||||||
if direction not in MazeDirection:
|
|
||||||
raise TypeError(
|
raise TypeError(
|
||||||
"The argument does not appear to be a valid maze direction."
|
"The argument does not appear to be a valid maze direction."
|
||||||
)
|
)
|
||||||
|
|
||||||
if direction is MazeDirection.ABOVE and (self.i-1 >= 0):
|
if direction is MazeDirections.ABOVE and (self.i-1 >= 0):
|
||||||
return MazePosition(
|
return MazePosition(
|
||||||
i=self.i-1,
|
i=self.i-1,
|
||||||
j=self.j,
|
j=self.j,
|
||||||
last_i=self.last_i,
|
max_i=self.max_i,
|
||||||
last_j=self.last_j,
|
max_j=self.max_j,
|
||||||
)
|
)
|
||||||
if direction is MazeDirection.BELOW and (self.i+1 <= self.last_i):
|
if direction is MazeDirections.BELOW and (self.i+1 <= self.max_i):
|
||||||
return MazePosition(
|
return MazePosition(
|
||||||
i=self.i+1,
|
i=self.i+1,
|
||||||
j=self.j,
|
j=self.j,
|
||||||
last_i=self.last_i,
|
max_i=self.max_i,
|
||||||
last_j=self.last_j,
|
max_j=self.max_j,
|
||||||
)
|
)
|
||||||
if direction is MazeDirection.LEFT and (self.j-1 >= 0):
|
if direction is MazeDirections.LEFT and (self.j-1 >= 0):
|
||||||
return MazePosition(
|
return MazePosition(
|
||||||
i=self.i,
|
i=self.i,
|
||||||
j=self.j-1,
|
j=self.j-1,
|
||||||
last_i=self.last_i,
|
max_i=self.max_i,
|
||||||
last_j=self.last_j,
|
max_j=self.max_j,
|
||||||
)
|
)
|
||||||
if direction is MazeDirection.RIGHT and (self.j+1 <= self.last_j):
|
if direction is MazeDirections.RIGHT and (self.j+1 <= self.max_j):
|
||||||
return MazePosition(
|
return MazePosition(
|
||||||
i=self.i,
|
i=self.i,
|
||||||
j=self.j+1,
|
j=self.j+1,
|
||||||
last_i=self.last_i,
|
max_i=self.max_i,
|
||||||
last_j=self.last_j,
|
max_j=self.max_j,
|
||||||
)
|
)
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
@ -89,27 +83,27 @@ class Maze:
|
||||||
self,
|
self,
|
||||||
x_position: int,
|
x_position: int,
|
||||||
y_position: int,
|
y_position: int,
|
||||||
height: int,
|
num_cell_rows: int,
|
||||||
width: int,
|
num_cells_per_row: int,
|
||||||
cell_height: int,
|
cell_size_x: int,
|
||||||
cell_width: int,
|
cell_size_y: int,
|
||||||
window: Window = None,
|
window: Window = None,
|
||||||
seed=None,
|
seed=None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self._x_position = x_position
|
self._x_position = x_position
|
||||||
self._y_position = y_position
|
self._y_position = y_position
|
||||||
self._height = height
|
self._num_cell_rows = num_cell_rows
|
||||||
self._width = width
|
self._num_cells_per_row = num_cells_per_row
|
||||||
self._cell_height = cell_height
|
self._cell_size_x = cell_size_x
|
||||||
self._cell_width = cell_width
|
self._cell_size_y = cell_size_y
|
||||||
self._window = window
|
self._window = window
|
||||||
self._generator = "generator"
|
|
||||||
|
|
||||||
# initialise the random number generator
|
# initialise the random number generator
|
||||||
random.seed(seed)
|
random.seed(seed)
|
||||||
|
|
||||||
# Create the Maze's cells
|
# Create the Maze's cells
|
||||||
self._cell_grid: List[List[Cell]] = []
|
self._cells: List[List[Cell]] = [
|
||||||
|
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.
|
# Open up the maze's entrance and exit.
|
||||||
|
@ -118,49 +112,37 @@ class Maze:
|
||||||
start_position = MazePosition(
|
start_position = MazePosition(
|
||||||
i=0,
|
i=0,
|
||||||
j=0,
|
j=0,
|
||||||
last_i=self._height-1,
|
max_i=self._num_cell_rows-1,
|
||||||
last_j=self._width-1,
|
max_j=self._num_cells_per_row-1,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Generate the maze.
|
# Generate the maze.
|
||||||
self._break_walls_r(start_position)
|
self._break_walls_r(start_position)
|
||||||
|
|
||||||
def get_last_i(self) -> int:
|
|
||||||
"returns the last position of the Maze's outer list."
|
|
||||||
|
|
||||||
return self._height-1
|
|
||||||
|
|
||||||
def get_last_j(self) -> int:
|
|
||||||
"returns the last position of the Maze's inner list."
|
|
||||||
|
|
||||||
return self._width-1
|
|
||||||
|
|
||||||
def _create_cell_grid(self) -> None:
|
def _create_cell_grid(self) -> None:
|
||||||
"""
|
"""
|
||||||
creates all the cells and draws them.
|
creates all the cells and draws them.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self._cell_grid: List[List[Cell]] = [None for i in range(self._height)]
|
|
||||||
cursor_x = self._x_position
|
cursor_x = self._x_position
|
||||||
cursor_y = self._y_position
|
cursor_y = self._y_position
|
||||||
|
|
||||||
for i in range(self._height):
|
for i in range(self._num_cell_rows):
|
||||||
cells: List[Cell] = [None for j in range(self._width)]
|
cells: List[Cell] = [None for j in range(self._num_cells_per_row)]
|
||||||
for j in range(self._width):
|
for j in range(self._num_cells_per_row):
|
||||||
cell = Cell(
|
cell = Cell(
|
||||||
cursor_x,
|
cursor_x,
|
||||||
cursor_y,
|
cursor_y,
|
||||||
(cursor_x + self._cell_width),
|
(cursor_x + self._cell_size_x),
|
||||||
(cursor_y + self._cell_height),
|
(cursor_y + self._cell_size_y),
|
||||||
self._window
|
self._window
|
||||||
)
|
)
|
||||||
cells[j] = cell
|
cells[j] = cell
|
||||||
if j == self._width - 1:
|
if j == self._num_cells_per_row - 1:
|
||||||
cursor_x = self._x_position
|
cursor_x = self._x_position
|
||||||
else:
|
else:
|
||||||
cursor_x += self._cell_width
|
cursor_x += self._cell_size_x
|
||||||
self._cell_grid[i] = cells
|
self._cells[i] = cells
|
||||||
cursor_y += self._cell_height
|
cursor_y += self._cell_size_y
|
||||||
|
|
||||||
if self._window:
|
if self._window:
|
||||||
self._draw_cell_grid()
|
self._draw_cell_grid()
|
||||||
|
@ -170,9 +152,8 @@ class Maze:
|
||||||
draws all the cells on the maze with a short pause between each cell
|
draws all the cells on the maze with a short pause between each cell
|
||||||
for animation purposes.
|
for animation purposes.
|
||||||
"""
|
"""
|
||||||
|
for i in range(self._num_cell_rows):
|
||||||
for i in range(self._height):
|
for j in range(self._num_cells_per_row):
|
||||||
for j in range(self._width):
|
|
||||||
self._draw_cell(i=i, j=j)
|
self._draw_cell(i=i, j=j)
|
||||||
|
|
||||||
def _open_entrance_and_exit(self) -> None:
|
def _open_entrance_and_exit(self) -> None:
|
||||||
|
@ -181,16 +162,15 @@ class Maze:
|
||||||
walls. The entrance is located at the top left and the exit is located
|
walls. The entrance is located at the top left and the exit is located
|
||||||
at the bottom right of the maze.
|
at the bottom right of the maze.
|
||||||
"""
|
"""
|
||||||
|
self._cells[0][0].configure_walls(top=False)
|
||||||
self._cell_grid[0][0].configure_walls(top=False)
|
self._cells[self._num_cell_rows -
|
||||||
self._cell_grid[self._height-1][self._width -
|
1][self._num_cells_per_row-1].configure_walls(bottom=False)
|
||||||
1].configure_walls(bottom=False)
|
|
||||||
|
|
||||||
if self._window:
|
if self._window:
|
||||||
self._draw_cell(0, 0)
|
self._draw_cell(0, 0)
|
||||||
self._draw_cell(
|
self._draw_cell(
|
||||||
i=self._height-1,
|
i=self._num_cell_rows-1,
|
||||||
j=self._width-1
|
j=self._num_cells_per_row-1
|
||||||
)
|
)
|
||||||
|
|
||||||
def _break_walls_r(self, current_position: MazePosition) -> None:
|
def _break_walls_r(self, current_position: MazePosition) -> None:
|
||||||
|
@ -199,25 +179,19 @@ class Maze:
|
||||||
cells and randomly knocking down the walls to create the maze's paths.
|
cells and randomly knocking down the walls to create the maze's paths.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.mark_cell_as_visited(
|
current_cell = self._cells[current_position.i][current_position.j]
|
||||||
i=current_position.i,
|
current_cell.visited_by_maze_generator = True
|
||||||
j=current_position.j,
|
|
||||||
visitor=self._generator,
|
|
||||||
)
|
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
possible_directions: List[MazeDirection] = []
|
possible_directions: List[MazeDirections] = []
|
||||||
|
|
||||||
for direction in MazeDirection:
|
for direction in MazeDirections:
|
||||||
adjacent_position = current_position.get_adjacent_position(
|
adjacent_position = current_position.get_adjacent_position(
|
||||||
direction)
|
direction)
|
||||||
if adjacent_position is None:
|
if adjacent_position is None:
|
||||||
continue
|
continue
|
||||||
if self.cell_was_visited_by(
|
adjacent_cell = self._cells[adjacent_position.i][adjacent_position.j]
|
||||||
i=adjacent_position.i,
|
if adjacent_cell.visited_by_maze_generator:
|
||||||
j=adjacent_position.j,
|
|
||||||
visitor=self._generator,
|
|
||||||
):
|
|
||||||
continue
|
continue
|
||||||
possible_directions.append(direction)
|
possible_directions.append(direction)
|
||||||
|
|
||||||
|
@ -227,124 +201,90 @@ class Maze:
|
||||||
break
|
break
|
||||||
|
|
||||||
chosen_direction = random.choice(possible_directions)
|
chosen_direction = random.choice(possible_directions)
|
||||||
next_position = current_position.get_adjacent_position(chosen_direction)
|
next_position = current_position.get_adjacent_position(
|
||||||
|
chosen_direction)
|
||||||
|
next_cell = self._cells[next_position.i][next_position.j]
|
||||||
|
|
||||||
if chosen_direction is MazeDirection.ABOVE:
|
if chosen_direction is MazeDirections.ABOVE:
|
||||||
self._configure_cell_walls(
|
current_cell.configure_walls(top=False)
|
||||||
i=current_position.i,
|
next_cell.configure_walls(bottom=False)
|
||||||
j=current_position.j,
|
elif chosen_direction is MazeDirections.BELOW:
|
||||||
top=False,
|
current_cell.configure_walls(bottom=False)
|
||||||
)
|
next_cell.configure_walls(top=False)
|
||||||
self._configure_cell_walls(
|
elif chosen_direction is MazeDirections.LEFT:
|
||||||
i=next_position.i,
|
current_cell.configure_walls(left=False)
|
||||||
j=next_position.j,
|
next_cell.configure_walls(right=False)
|
||||||
bottom=False,
|
elif chosen_direction is MazeDirections.RIGHT:
|
||||||
)
|
current_cell.configure_walls(right=False)
|
||||||
elif chosen_direction is MazeDirection.BELOW:
|
next_cell.configure_walls(left=False)
|
||||||
self._configure_cell_walls(
|
|
||||||
i=current_position.i,
|
|
||||||
j=current_position.j,
|
|
||||||
bottom=False,
|
|
||||||
)
|
|
||||||
self._configure_cell_walls(
|
|
||||||
i=next_position.i,
|
|
||||||
j=next_position.j,
|
|
||||||
top=False,
|
|
||||||
)
|
|
||||||
elif chosen_direction is MazeDirection.LEFT:
|
|
||||||
self._configure_cell_walls(
|
|
||||||
i=current_position.i,
|
|
||||||
j=current_position.j,
|
|
||||||
left=False,
|
|
||||||
)
|
|
||||||
self._configure_cell_walls(
|
|
||||||
i=next_position.i,
|
|
||||||
j=next_position.j,
|
|
||||||
right=False,
|
|
||||||
)
|
|
||||||
elif chosen_direction is MazeDirection.RIGHT:
|
|
||||||
self._configure_cell_walls(
|
|
||||||
i=current_position.i,
|
|
||||||
j=current_position.j,
|
|
||||||
right=False,
|
|
||||||
)
|
|
||||||
self._configure_cell_walls(
|
|
||||||
i=next_position.i,
|
|
||||||
j=next_position.j,
|
|
||||||
left=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
if self._window:
|
if self._window:
|
||||||
self._draw_cell(i=current_position.i, j=current_position.j)
|
self._draw_cell(i=current_position.i, j=current_position.j)
|
||||||
|
|
||||||
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:
|
||||||
"""
|
"""
|
||||||
draws the cells in an animated way.
|
_draw_cell draws the cells in an animated way.
|
||||||
"""
|
"""
|
||||||
|
self._cells[i][j].draw()
|
||||||
self._cell_grid[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:
|
def _draw_path(self, current_cell: Cell, next_cell: Cell, undo: bool = False) -> None:
|
||||||
"""
|
|
||||||
draws a path between two cells in an animated way.
|
|
||||||
"""
|
|
||||||
|
|
||||||
current_cell.draw_move(next_cell, undo)
|
current_cell.draw_move(next_cell, undo)
|
||||||
self._window.redraw()
|
self._window.redraw()
|
||||||
sleep(0.05)
|
sleep(0.05)
|
||||||
|
|
||||||
def mark_cell_as_visited(self, i: int, j: int, visitor: str) -> None:
|
|
||||||
"""
|
|
||||||
marks the cell at the specified position by the specified visitor.
|
|
||||||
"""
|
|
||||||
|
|
||||||
self._cell_grid[i][j].mark_as_visited_by(visitor)
|
|
||||||
|
|
||||||
def cell_was_visited_by(self, i: int, j: int, visitor: str) -> bool:
|
|
||||||
"""
|
|
||||||
returns True if the cell at the specified position was visited by
|
|
||||||
the specified visitor.
|
|
||||||
"""
|
|
||||||
|
|
||||||
return self._cell_grid[i][j].was_visited_by(visitor)
|
|
||||||
|
|
||||||
def cell_wall_exists(self, i: int, j: int, wall: CellWallLabels) -> bool:
|
|
||||||
"""
|
|
||||||
returns true if a specified cell's wall exists.
|
|
||||||
"""
|
|
||||||
|
|
||||||
return self._cell_grid[i][j].wall_exists(wall)
|
|
||||||
|
|
||||||
def draw_path_between(self, a: MazePosition, b: MazePosition, undo: bool = False) -> None:
|
|
||||||
"""
|
|
||||||
draws a path between position A and position B
|
|
||||||
"""
|
|
||||||
|
|
||||||
cell_a = self._cell_grid[a.i][a.j]
|
|
||||||
cell_b = self._cell_grid[b.i][b.j]
|
|
||||||
|
|
||||||
self._draw_path(cell_a, cell_b, undo)
|
|
||||||
|
|
||||||
def _configure_cell_walls(
|
|
||||||
self,
|
|
||||||
i: int,
|
|
||||||
j: int,
|
|
||||||
top: bool = None,
|
|
||||||
bottom: bool = None,
|
|
||||||
left: bool = None,
|
|
||||||
right: bool = None,
|
|
||||||
) -> None:
|
|
||||||
"""
|
|
||||||
(re)configures the walls of the specified cell.
|
|
||||||
"""
|
|
||||||
|
|
||||||
self._cell_grid[i][j].configure_walls(
|
|
||||||
top=top,
|
|
||||||
bottom=bottom,
|
|
||||||
left=left,
|
|
||||||
right=right,
|
|
||||||
)
|
|
||||||
|
|
81
solver.py
81
solver.py
|
@ -1,81 +0,0 @@
|
||||||
from typing import Dict
|
|
||||||
from maze import Maze, MazeDirection, MazePosition
|
|
||||||
from cell import CellWallLabels
|
|
||||||
|
|
||||||
|
|
||||||
class Solver:
|
|
||||||
def __init__(self, game: Maze):
|
|
||||||
self._game = game
|
|
||||||
self._solver = "solver"
|
|
||||||
|
|
||||||
# This is a dictionary mapping the direction to the maze to the
|
|
||||||
# wall of the adjacent cell. It is used to identify the wall that could
|
|
||||||
# potentially block the solver's path.
|
|
||||||
# For example if the solver wants to move to the right, it's path
|
|
||||||
# could be blocked by the adjacent cell's left wall.
|
|
||||||
self._wall_map: Dict[MazeDirection, CellWallLabels] = {
|
|
||||||
MazeDirection.ABOVE: CellWallLabels.BOTTOM,
|
|
||||||
MazeDirection.BELOW: CellWallLabels.TOP,
|
|
||||||
MazeDirection.LEFT: CellWallLabels.RIGHT,
|
|
||||||
MazeDirection.RIGHT: CellWallLabels.LEFT,
|
|
||||||
}
|
|
||||||
|
|
||||||
def solve(self) -> bool:
|
|
||||||
"""
|
|
||||||
solve attempts to solve the generated maze.
|
|
||||||
"""
|
|
||||||
start_position = MazePosition(
|
|
||||||
i=0,
|
|
||||||
j=0,
|
|
||||||
last_i=self._game.get_last_i(),
|
|
||||||
last_j=self._game.get_last_j(),
|
|
||||||
)
|
|
||||||
|
|
||||||
end_position = MazePosition(
|
|
||||||
i=self._game.get_last_i(),
|
|
||||||
j=self._game.get_last_j(),
|
|
||||||
last_i=self._game.get_last_i(),
|
|
||||||
last_j=self._game.get_last_j(),
|
|
||||||
)
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
self._game.mark_cell_as_visited(
|
|
||||||
i=current_position.i,
|
|
||||||
j=current_position.j,
|
|
||||||
visitor=self._solver,
|
|
||||||
)
|
|
||||||
|
|
||||||
for direction in MazeDirection:
|
|
||||||
adjacent_position = current_position.get_adjacent_position(
|
|
||||||
direction
|
|
||||||
)
|
|
||||||
|
|
||||||
if adjacent_position is None:
|
|
||||||
continue
|
|
||||||
if self._game.cell_was_visited_by(
|
|
||||||
i=adjacent_position.i,
|
|
||||||
j=adjacent_position.j,
|
|
||||||
visitor=self._solver,
|
|
||||||
) or self._game.cell_wall_exists(
|
|
||||||
i=adjacent_position.i,
|
|
||||||
j=adjacent_position.j,
|
|
||||||
wall=self._wall_map[direction],
|
|
||||||
):
|
|
||||||
continue
|
|
||||||
|
|
||||||
self._game.draw_path_between(current_position, adjacent_position)
|
|
||||||
result = self._solve_r(adjacent_position, end_position)
|
|
||||||
if result is True:
|
|
||||||
return True
|
|
||||||
self._game.draw_path_between(current_position, adjacent_position, undo=True)
|
|
||||||
|
|
||||||
return False
|
|
76
tests.py
76
tests.py
|
@ -15,16 +15,16 @@ class Tests(unittest.TestCase):
|
||||||
"""
|
"""
|
||||||
cases = [
|
cases = [
|
||||||
{
|
{
|
||||||
"height": 6,
|
"number_of_cell_rows": 6,
|
||||||
"width": 9,
|
"number_of_cells_per_row": 9,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"height": 3,
|
"number_of_cell_rows": 3,
|
||||||
"width": 12,
|
"number_of_cells_per_row": 12,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"height": 4,
|
"number_of_cell_rows": 4,
|
||||||
"width": 4,
|
"number_of_cells_per_row": 4,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -32,20 +32,20 @@ class Tests(unittest.TestCase):
|
||||||
m = maze.Maze(
|
m = maze.Maze(
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
case["height"],
|
case["number_of_cell_rows"],
|
||||||
case["width"],
|
case["number_of_cells_per_row"],
|
||||||
2,
|
2,
|
||||||
2,
|
2,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
len(m._cell_grid),
|
len(m._cells),
|
||||||
case["height"],
|
case["number_of_cell_rows"],
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
len(m._cell_grid[0]),
|
len(m._cells[0]),
|
||||||
case["width"],
|
case["number_of_cells_per_row"],
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_break_entrance_and_exit(self):
|
def test_break_entrance_and_exit(self):
|
||||||
|
@ -65,9 +65,9 @@ class Tests(unittest.TestCase):
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
self.assertFalse(m._cell_grid[0][0].wall_exists(CellWallLabels.TOP))
|
self.assertFalse(m._cells[0][0].wall_exists(CellWallLabels.TOP))
|
||||||
self.assertFalse(
|
self.assertFalse(
|
||||||
m._cell_grid[number_of_cell_rows - 1]
|
m._cells[number_of_cell_rows - 1]
|
||||||
[number_of_cells_per_row - 1].wall_exists(CellWallLabels.BOTTOM)
|
[number_of_cells_per_row - 1].wall_exists(CellWallLabels.BOTTOM)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -112,13 +112,13 @@ class Tests(unittest.TestCase):
|
||||||
def test_maze_position_equality(self):
|
def test_maze_position_equality(self):
|
||||||
cases = [
|
cases = [
|
||||||
{
|
{
|
||||||
"m1": maze.MazePosition(i=1, j=3, last_i=10, last_j=100),
|
"m1": maze.MazePosition(i=1, j=3, max_i=10, max_j=100),
|
||||||
"m2": maze.MazePosition(i=1, j=3, last_i=10, last_j=100),
|
"m2": maze.MazePosition(i=1, j=3, max_i=10, max_j=100),
|
||||||
"expected": True,
|
"expected": True,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"m1": maze.MazePosition(i=1, j=3, last_i=10, last_j=100),
|
"m1": maze.MazePosition(i=1, j=3, max_i=10, max_j=100),
|
||||||
"m2": maze.MazePosition(i=100, j=30, last_i=200, last_j=100),
|
"m2": maze.MazePosition(i=100, j=30, max_i=200, max_j=100),
|
||||||
"expected": False,
|
"expected": False,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -130,43 +130,43 @@ class Tests(unittest.TestCase):
|
||||||
def test_maze_position_adjacent_positition(self):
|
def test_maze_position_adjacent_positition(self):
|
||||||
cases = [
|
cases = [
|
||||||
{
|
{
|
||||||
"position": maze.MazePosition(i=3, j=4, last_i=10, last_j=10),
|
"position": maze.MazePosition(i=3, j=4, max_i=10, max_j=10),
|
||||||
"direction": maze.MazeDirection.ABOVE,
|
"direction": maze.MazeDirections.ABOVE,
|
||||||
"expected": maze.MazePosition(i=2, j=4, last_i=10, last_j=10),
|
"expected": maze.MazePosition(i=2, j=4, max_i=10, max_j=10),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"position": maze.MazePosition(i=9, j=4, last_i=10, last_j=10),
|
"position": maze.MazePosition(i=9, j=4, max_i=10, max_j=10),
|
||||||
"direction": maze.MazeDirection.BELOW,
|
"direction": maze.MazeDirections.BELOW,
|
||||||
"expected": maze.MazePosition(i=10, j=4, last_i=10, last_j=10),
|
"expected": maze.MazePosition(i=10, j=4, max_i=10, max_j=10),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"position": maze.MazePosition(i=1, j=1, last_i=10, last_j=10),
|
"position": maze.MazePosition(i=1, j=1, max_i=10, max_j=10),
|
||||||
"direction": maze.MazeDirection.LEFT,
|
"direction": maze.MazeDirections.LEFT,
|
||||||
"expected": maze.MazePosition(i=1, j=0, last_i=10, last_j=10),
|
"expected": maze.MazePosition(i=1, j=0, max_i=10, max_j=10),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"position": maze.MazePosition(i=3, j=9, last_i=10, last_j=10),
|
"position": maze.MazePosition(i=3, j=9, max_i=10, max_j=10),
|
||||||
"direction": maze.MazeDirection.RIGHT,
|
"direction": maze.MazeDirections.RIGHT,
|
||||||
"expected": maze.MazePosition(i=3, j=10, last_i=10, last_j=10),
|
"expected": maze.MazePosition(i=3, j=10, max_i=10, max_j=10),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"position": maze.MazePosition(i=0, j=4, last_i=10, last_j=10),
|
"position": maze.MazePosition(i=0, j=4, max_i=10, max_j=10),
|
||||||
"direction": maze.MazeDirection.ABOVE,
|
"direction": maze.MazeDirections.ABOVE,
|
||||||
"expected": None,
|
"expected": None,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"position": maze.MazePosition(i=10, j=4, last_i=10, last_j=10),
|
"position": maze.MazePosition(i=10, j=4, max_i=10, max_j=10),
|
||||||
"direction": maze.MazeDirection.BELOW,
|
"direction": maze.MazeDirections.BELOW,
|
||||||
"expected": None,
|
"expected": None,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"position": maze.MazePosition(i=1, j=0, last_i=10, last_j=10),
|
"position": maze.MazePosition(i=1, j=0, max_i=10, max_j=10),
|
||||||
"direction": maze.MazeDirection.LEFT,
|
"direction": maze.MazeDirections.LEFT,
|
||||||
"expected": None,
|
"expected": None,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"position": maze.MazePosition(i=3, j=10, last_i=10, last_j=10),
|
"position": maze.MazePosition(i=3, j=10, max_i=10, max_j=10),
|
||||||
"direction": maze.MazeDirection.RIGHT,
|
"direction": maze.MazeDirections.RIGHT,
|
||||||
"expected": None,
|
"expected": None,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
Loading…
Reference in a new issue