from abc import ABC, abstractmethod from collections import deque import heapq from typing import List, Dict, Optional from models import Cell, Maze class PathFindingStrategy(ABC): @abstractmethod def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]: pass @property @abstractmethod def name(self) -> str: pass class BFSStrategy(PathFindingStrategy): @property def name(self) -> str: return "BFS" def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]: if start == exit_cell: return [start] queue = deque([start]) visited = {start} parent: Dict[Cell, Optional[Cell]] = {start: None} while queue: current = queue.popleft() if current == exit_cell: return self._reconstruct_path(parent, start, exit_cell) for neighbor in maze.get_neighbors(current): if neighbor not in visited: visited.add(neighbor) parent[neighbor] = current queue.append(neighbor) return [] def _reconstruct_path(self, parent: Dict[Cell, Optional[Cell]], start: Cell, exit_cell: Cell) -> List[Cell]: path = [] current = exit_cell while current is not None: path.append(current) current = parent.get(current) path.reverse() return path class DFSStrategy(PathFindingStrategy): @property def name(self) -> str: return "DFS" def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]: if start == exit_cell: return [start] stack = [(start, [start])] visited = {start} while stack: current, path = stack.pop() if current == exit_cell: return path for neighbor in maze.get_neighbors(current): if neighbor not in visited: visited.add(neighbor) stack.append((neighbor, path + [neighbor])) return [] class AStarStrategy(PathFindingStrategy): @property def name(self) -> str: return "A*" def _heuristic(self, cell: Cell, target: Cell) -> int: return abs(cell.x - target.x) + abs(cell.y - target.y) def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]: if start == exit_cell: return [start] counter = 0 open_set = [(0, counter, start)] came_from: Dict[Cell, Optional[Cell]] = {start: None} g_score = {start: 0} f_score = {start: self._heuristic(start, exit_cell)} closed_set = set() while open_set: current_f, _, current = heapq.heappop(open_set) if current in closed_set: continue if current == exit_cell: return self._reconstruct_path(came_from, start, exit_cell) closed_set.add(current) for neighbor in maze.get_neighbors(current): if neighbor in closed_set: continue tentative_g = g_score[current] + 1 if neighbor not in g_score or tentative_g < g_score[neighbor]: came_from[neighbor] = current g_score[neighbor] = tentative_g f_score[neighbor] = tentative_g + self._heuristic(neighbor, exit_cell) counter += 1 heapq.heappush(open_set, (f_score[neighbor], counter, neighbor)) return [] def _reconstruct_path(self, came_from: Dict[Cell, Optional[Cell]], start: Cell, exit_cell: Cell) -> List[Cell]: path = [] current = exit_cell while current is not None: path.append(current) current = came_from.get(current) path.reverse() return path