forked from UNN/2026-rff_mp
180 lines
5.7 KiB
Python
180 lines
5.7 KiB
Python
|
|
"""
|
|||
|
|
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,
|
|||
|
|
}
|