maze-solver/solver.py
Dan Anglin 1b0f044834
Some checks failed
test / test (pull_request) Failing after 6s
feat: add a side panel for user interaction
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
2024-02-18 08:53:36 +00:00

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