maze-solver/solver.py
Dan Anglin fa75e61acf
fix: add randomisation to original DFS algorithm
Update the DFS algorithm to allow the user to select if the solver
should randomly choose the directions when traversing through the maze.

The duplicate method has been removed.
2024-02-17 03:55:34 +00:00

172 lines
5.8 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,
}
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(),
)
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,
)
if enable_random_direction:
random.seed()
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,
)
if enable_random_direction:
random.seed()
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