""" maze_solver/strategies.py - паттерн Strategy. Каждая стратегия реализует один и тот же интерфейс PathFindingStrategy с методом find_path(maze, start, exit_), возвращающим: {'path': [Cell, ...], 'visited': int} Стратегии не модифицируют сам лабиринт. """ from abc import ABC, abstractmethod from collections import deque import heapq # ---------- интерфейс стратегии ---------- class PathFindingStrategy(ABC): name = "Strategy" @abstractmethod def find_path(self, maze, start, exit_): """Возвращает dict с ключами 'path' (list[Cell]) и 'visited' (int). Если пути нет - path = [].""" # ---------- общая утилита: восстановление пути ---------- def _reconstruct(parents, start, end): """Восстанавливает путь по словарю предшественников {(x,y): Cell|None}.""" path = [] cur = end while cur is not None: path.append(cur) cur = parents.get((cur.x, cur.y)) path.reverse() if path and path[0] is start: return path return [] # ---------- BFS ---------- class BFSStrategy(PathFindingStrategy): """Поиск в ширину. Гарантирует кратчайший путь по числу шагов (когда веса всех клеток равны).""" name = "BFS" def find_path(self, maze, start, exit_): queue = deque([start]) parents = {(start.x, start.y): None} visited = 1 while queue: cell = queue.popleft() if cell is exit_: return {"path": _reconstruct(parents, start, exit_), "visited": visited} for nb in maze.get_neighbors(cell): key = (nb.x, nb.y) if key not in parents: parents[key] = cell visited += 1 queue.append(nb) return {"path": [], "visited": visited} # ---------- DFS ---------- class DFSStrategy(PathFindingStrategy): """Поиск в глубину. Не гарантирует кратчайший путь, но прост и быстр.""" name = "DFS" def find_path(self, maze, start, exit_): stack = [start] parents = {(start.x, start.y): None} visited = 1 while stack: cell = stack.pop() if cell is exit_: return {"path": _reconstruct(parents, start, exit_), "visited": visited} for nb in maze.get_neighbors(cell): key = (nb.x, nb.y) if key not in parents: parents[key] = cell visited += 1 stack.append(nb) return {"path": [], "visited": visited} # ---------- A* ---------- def _manhattan(a, b): return abs(a.x - b.x) + abs(a.y - b.y) class AStarStrategy(PathFindingStrategy): """A*-поиск с манхэттенской эвристикой. Учитывает вес клеток (weight).""" name = "A*" def find_path(self, maze, start, exit_): # f = g + h; в куче храним (f, tie, cell) g_score = {(start.x, start.y): 0} parents = {(start.x, start.y): None} tie = 0 heap = [(_manhattan(start, exit_), tie, start)] visited = 0 closed = set() while heap: f, _, cell = heapq.heappop(heap) key = (cell.x, cell.y) if key in closed: continue closed.add(key) visited += 1 if cell is exit_: return {"path": _reconstruct(parents, start, exit_), "visited": visited} for nb in maze.get_neighbors(cell): nb_key = (nb.x, nb.y) tentative_g = g_score[key] + nb.weight if tentative_g < g_score.get(nb_key, float("inf")): g_score[nb_key] = tentative_g parents[nb_key] = cell tie += 1 heapq.heappush(heap, (tentative_g + _manhattan(nb, exit_), tie, nb)) return {"path": [], "visited": visited} # ---------- Дейкстра ---------- class DijkstraStrategy(PathFindingStrategy): """Дейкстра - оптимальный путь с учётом веса клеток. На немодифицированном лабиринте (все веса = 1) совпадает с BFS.""" name = "Dijkstra" def find_path(self, maze, start, exit_): dist = {(start.x, start.y): 0} parents = {(start.x, start.y): None} tie = 0 heap = [(0, tie, start)] visited = 0 closed = set() while heap: d, _, cell = heapq.heappop(heap) key = (cell.x, cell.y) if key in closed: continue closed.add(key) visited += 1 if cell is exit_: return {"path": _reconstruct(parents, start, exit_), "visited": visited} for nb in maze.get_neighbors(cell): nb_key = (nb.x, nb.y) nd = d + nb.weight if nd < dist.get(nb_key, float("inf")): dist[nb_key] = nd parents[nb_key] = cell tie += 1 heapq.heappush(heap, (nd, tie, nb)) return {"path": [], "visited": visited} STRATEGIES = { "BFS": BFSStrategy, "DFS": DFSStrategy, "A*": AStarStrategy, "Dijkstra": DijkstraStrategy, }