from abc import ABC, abstractmethod from collections import deque from typing import List, Tuple, Dict, Optional import heapq from maze import Maze, Cell class PathFindingStrategy(ABC): @abstractmethod def find_path(self, maze: Maze, start: Optional[Cell], exit: Optional[Cell]) -> Tuple[List[Cell], int]: pass class BFSStrategy(PathFindingStrategy): def find_path(self, maze: Maze, start: Optional[Cell], exit: Optional[Cell]) -> Tuple[List[Cell], int]: if start is None or exit is None: return [], 0 queue = deque([start]) visited = {start} parent = {start: None} while queue: current = queue.popleft() if current is exit: return self._reconstruct_path(parent, exit), len(visited) for nb in maze.get_neighbors(current): if nb not in visited: visited.add(nb) parent[nb] = current queue.append(nb) return [], len(visited) def _reconstruct_path(self, parent: Dict[Cell, Optional[Cell]], end: Cell) -> List[Cell]: path = [] cur = end while cur is not None: path.append(cur) cur = parent[cur] path.reverse() return path class DFSStrategy(PathFindingStrategy): def find_path(self, maze: Maze, start: Optional[Cell], exit: Optional[Cell]) -> Tuple[List[Cell], int]: if start is None or exit is None: return [], 0 stack = [(start, [start])] visited = {start} while stack: current, path = stack.pop() if current is exit: return path, len(visited) for nb in maze.get_neighbors(current): if nb not in visited: visited.add(nb) stack.append((nb, path + [nb])) return [], len(visited) class AStarStrategy(PathFindingStrategy): @staticmethod def heuristic(a: Cell, b: Cell) -> int: return abs(a.x - b.x) + abs(a.y - b.y) def find_path(self, maze: Maze, start: Optional[Cell], exit: Optional[Cell]) -> Tuple[List[Cell], int]: if start is None or exit is None: return [], 0 open_set = [] heapq.heappush(open_set, (0, start)) came_from = {} g_score = {start: 0} f_score = {start: self.heuristic(start, exit)} visited_count = 0 while open_set: _, current = heapq.heappop(open_set) visited_count += 1 if current is exit: path = self._reconstruct_path(came_from, exit) return path, visited_count for nb in maze.get_neighbors(current): tentative_g = g_score[current] + 1 if nb not in g_score or tentative_g < g_score[nb]: came_from[nb] = current g_score[nb] = tentative_g f_score[nb] = tentative_g + self.heuristic(nb, exit) heapq.heappush(open_set, (f_score[nb], nb)) return [], visited_count def _reconstruct_path(self, came_from: Dict[Cell, Cell], current: Cell) -> List[Cell]: path = [current] while current in came_from: current = came_from[current] path.append(current) path.reverse() return path