Dan Anglin
1b0f044834
Some checks failed
test / test (pull_request) Failing after 6s
This commit adds a simple side panel to allow users to interact with the application. The side panel allows the user to: - Generate and regenerate mazes - Choose between the searching algorithms - Choose whether or not to add randomness to the algorithm - Run and re-run the simulation
183 lines
6.1 KiB
Python
183 lines
6.1 KiB
Python
from typing import Dict, Callable, List
|
|
import random
|
|
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,
|
|
}
|
|
|
|
random.seed()
|
|
|
|
def _reset(self):
|
|
self._game.reset_solution(self._solver)
|
|
|
|
def solve(
|
|
self,
|
|
solve_method: Callable[[MazePosition, MazePosition, bool], bool],
|
|
enable_random_direction: bool = False,
|
|
) -> 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(),
|
|
)
|
|
|
|
# Clear the maze if there was a previous run
|
|
if self._game.cell_was_visited_by(
|
|
i=start_position.i,
|
|
j=start_position.j,
|
|
visitor=self._solver,
|
|
):
|
|
self._game.reset_solution(self._solver)
|
|
|
|
return solve_method(
|
|
start_position,
|
|
end_position,
|
|
enable_random_direction,
|
|
)
|
|
|
|
def solve_with_dfs_r(
|
|
self,
|
|
current_position: MazePosition,
|
|
end_position: MazePosition,
|
|
enable_random_direction: bool = False,
|
|
) -> 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,
|
|
)
|
|
|
|
while True:
|
|
possible_directions: List[MazeDirection] = []
|
|
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
|
|
possible_directions.append(direction)
|
|
if len(possible_directions) == 0:
|
|
break
|
|
next_direction = None
|
|
if len(possible_directions) == 1 or not enable_random_direction:
|
|
next_direction = possible_directions[0]
|
|
else:
|
|
next_direction = random.choice(possible_directions)
|
|
next_position = current_position.get_adjacent_position(
|
|
next_direction,
|
|
)
|
|
self._game.draw_path_between(current_position, next_position)
|
|
solved = self.solve_with_dfs_r(
|
|
next_position, end_position)
|
|
if solved:
|
|
return True
|
|
self._game.draw_path_between(
|
|
current_position,
|
|
next_position,
|
|
undo=True
|
|
)
|
|
|
|
return False
|
|
|
|
def solve_with_bfs_r(
|
|
self,
|
|
current_position: MazePosition,
|
|
end_position: MazePosition,
|
|
enable_random_direction: bool = False,
|
|
) -> bool:
|
|
self._game.mark_cell_as_visited(
|
|
i=current_position.i,
|
|
j=current_position.j,
|
|
visitor=self._solver,
|
|
)
|
|
|
|
while True:
|
|
possible_directions = []
|
|
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
|
|
if adjacent_position == end_position:
|
|
self._game.draw_path_between(
|
|
current_position, adjacent_position)
|
|
return True
|
|
|
|
possible_directions.append(direction)
|
|
if len(possible_directions) == 0:
|
|
break
|
|
next_direction = None
|
|
if len(possible_directions) == 1 or not enable_random_direction:
|
|
next_direction = possible_directions[0]
|
|
else:
|
|
next_direction = random.choice(possible_directions)
|
|
next_position = current_position.get_adjacent_position(
|
|
next_direction,
|
|
)
|
|
self._game.draw_path_between(current_position, next_position)
|
|
solved = self.solve_with_bfs_r(
|
|
next_position,
|
|
end_position,
|
|
enable_random_direction
|
|
)
|
|
if solved:
|
|
return True
|
|
self._game.draw_path_between(
|
|
current_position,
|
|
next_position,
|
|
undo=True
|
|
)
|
|
|
|
return False
|