from abc import ABC, abstractmethod from collections import deque import heapq from maze_model import Cell, Maze class PathFindingStrategy(ABC): @abstractmethod def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> list[Cell]: ... @staticmethod def _reconstruct_path(came_from: dict, start: Cell, goal: Cell) -> list[Cell]: path = [] current = goal while current != start: path.append(current) current = came_from[current] path.append(start) path.reverse() return path class BFSStrategy(PathFindingStrategy): def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> list[Cell]: queue = deque([start]) came_from: dict[Cell, Cell | None] = {start: None} self.visited_count = 0 while queue: current = queue.popleft() self.visited_count += 1 if current == exit_cell: return self._reconstruct_path(came_from, start, exit_cell) for neighbor in maze.get_neighbors(current): if neighbor not in came_from: came_from[neighbor] = current queue.append(neighbor) return [] class DFSStrategy(PathFindingStrategy): def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> list[Cell]: stack = [start] came_from: dict[Cell, Cell | None] = {start: None} self.visited_count = 0 while stack: current = stack.pop() self.visited_count += 1 if current == exit_cell: return self._reconstruct_path(came_from, start, exit_cell) for neighbor in maze.get_neighbors(current): if neighbor not in came_from: came_from[neighbor] = current stack.append(neighbor) return [] # ── A* ─────────────────────────────────────────────────────────────────────── 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: Cell, exit_cell: Cell) -> list[Cell]: counter = 0 open_heap = [(0, counter, start)] came_from: dict[Cell, Cell | None] = {start: None} g_score: dict[Cell, int] = {start: 0} self.visited_count = 0 while open_heap: _, _, current = heapq.heappop(open_heap) self.visited_count += 1 if current == exit_cell: return self._reconstruct_path(came_from, start, exit_cell) for neighbor in maze.get_neighbors(current): tentative_g = g_score[current] + 1 if tentative_g < g_score.get(neighbor, float("inf")): g_score[neighbor] = tentative_g came_from[neighbor] = current f = tentative_g + self._heuristic(neighbor, exit_cell) counter += 1 heapq.heappush(open_heap, (f, counter, neighbor)) return [] class DijkstraStrategy(PathFindingStrategy): def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> list[Cell]: counter = 0 open_heap = [(0, counter, start)] came_from: dict[Cell, Cell | None] = {start: None} dist: dict[Cell, int] = {start: 0} self.visited_count = 0 while open_heap: cost, _, current = heapq.heappop(open_heap) self.visited_count += 1 if current == exit_cell: return self._reconstruct_path(came_from, start, exit_cell) if cost > dist.get(current, float("inf")): continue for neighbor in maze.get_neighbors(current): weight = getattr(neighbor, "weight", 1) new_cost = dist[current] + weight if new_cost < dist.get(neighbor, float("inf")): dist[neighbor] = new_cost came_from[neighbor] = current counter += 1 heapq.heappush(open_heap, (new_cost, counter, neighbor)) return []