from abc import ABC, abstractmethod from typing import List, Tuple from collections import deque import heapq from maze import Maze, Cell class PathFindingStrategy(ABC): """Интерфейс стратегии поиска пути (Strategy pattern).""" @abstractmethod def find_path(self, maze: Maze, start: Cell, exit: Cell) -> Tuple[List[Cell], int]: """ Возвращает: - путь (список клеток от старта до выхода) - количество посещённых клеток Если пути нет — возвращает ([], 0). """ pass @abstractmethod def get_name(self) -> str: """Название алгоритма для отчёта.""" pass class BFSStrategy(PathFindingStrategy): """BFS — поиск в ширину. Гарантирует кратчайший путь по числу шагов.""" def get_name(self) -> str: return "BFS" def find_path(self, maze: Maze, start: Cell, exit: Cell) -> Tuple[List[Cell], int]: if not start or not exit: return [], 0 # Очередь: (текущая_клетка, путь_до_неё) queue = deque([(start, [start])]) visited = {start} visited_count = 1 while queue: current, path = queue.popleft() if current == exit: return path, visited_count # Проверяем всех соседей for neighbor in maze.get_neighbors(current): if neighbor not in visited: visited.add(neighbor) visited_count += 1 queue.append((neighbor, path + [neighbor])) return [], visited_count class DFSStrategy(PathFindingStrategy): """DFS — поиск в глубину. Быстрый, но путь не обязательно кратчайший.""" def get_name(self) -> str: return "DFS" def find_path(self, maze: Maze, start: Cell, exit: Cell) -> Tuple[List[Cell], int]: if not start or not exit: return [], 0 # Стек: (текущая_клетка, путь_до_неё) stack = [(start, [start])] visited = {start} visited_count = 1 while stack: current, path = stack.pop() if current == exit: return path, visited_count for neighbor in maze.get_neighbors(current): if neighbor not in visited: visited.add(neighbor) visited_count += 1 stack.append((neighbor, path + [neighbor])) return [], visited_count class AStarStrategy(PathFindingStrategy): """ A* — поиск с эвристикой. Использует приоритетную очередь (кучу) и манхэттенское расстояние. """ def get_name(self) -> str: return "A*" def _heuristic(self, cell: Cell, exit: Cell) -> int: """Манхэттенское расстояние: |x1-x2| + |y1-y2|.""" if not cell or not exit: return 0 return abs(cell.x - exit.x) + abs(cell.y - exit.y) def find_path(self, maze: Maze, start: Cell, exit: Cell) -> Tuple[List[Cell], int]: if not start or not exit: return [], 0 # Куча: (f_score, счётчик, клетка, путь, g_score) # f = g + h, где g — пройденный путь, h — эвристика counter = 0 open_set = [(self._heuristic(start, exit), counter, start, [start], 0)] visited = set() visited_count = 0 g_scores = {start: 0} while open_set: _, _, current, path, g_score = heapq.heappop(open_set) if current in visited: continue visited.add(current) visited_count += 1 if current == exit: return path, visited_count for neighbor in maze.get_neighbors(current): if neighbor in visited: continue tentative_g = g_score + 1 if neighbor not in g_scores or tentative_g < g_scores[neighbor]: g_scores[neighbor] = tentative_g f_score = tentative_g + self._heuristic(neighbor, exit) counter += 1 heapq.heappush(open_set, (f_score, counter, neighbor, path + [neighbor], tentative_g)) return [], visited_count class DijkstraStrategy(PathFindingStrategy): """ Дейкстра — для взвешенных графов. В базовом варианте (вес=1) работает как BFS, но с приоритетной очередью. """ def get_name(self) -> str: return "Dijkstra" def find_path(self, maze: Maze, start: Cell, exit: Cell) -> Tuple[List[Cell], int]: if not start or not exit: return [], 0 # Куча: (расстояние, счётчик, клетка, путь) counter = 0 pq = [(0, counter, start, [start])] distances = {start: 0} visited = set() visited_count = 0 while pq: dist, _, current, path = heapq.heappop(pq) if current in visited: continue visited.add(current) visited_count += 1 if current == exit: return path, visited_count for neighbor in maze.get_neighbors(current): weight = neighbor.weight new_dist = dist + weight if neighbor not in distances or new_dist < distances[neighbor]: distances[neighbor] = new_dist counter += 1 heapq.heappush(pq, (new_dist, counter, neighbor, path + [neighbor])) return [], visited_count