forked from UNN/2026-rff_mp
145 lines
4.0 KiB
Python
145 lines
4.0 KiB
Python
from abc import ABC, abstractmethod
|
|
from typing import List
|
|
from collections import deque
|
|
import heapq
|
|
from maze_core import Maze, Cell
|
|
|
|
class SearchStrategy(ABC):
|
|
@abstractmethod
|
|
def find_path(self, maze: Maze, start: Cell, goal: Cell) -> List[Cell]:
|
|
pass
|
|
@property
|
|
@abstractmethod
|
|
def visited_count(self) -> int:
|
|
pass
|
|
|
|
class BFSSearch(SearchStrategy):
|
|
def __init__(self):
|
|
self._visited = 0
|
|
def find_path(self, maze: Maze, start: Cell, goal: Cell) -> List[Cell]:
|
|
self._visited = 0
|
|
if start == goal:
|
|
return [start]
|
|
|
|
visited = {start}
|
|
queue = deque([start])
|
|
start.prev = None
|
|
|
|
while queue:
|
|
curr = queue.popleft()
|
|
self._visited += 1
|
|
|
|
if curr == goal:
|
|
return self._build_path(curr)
|
|
|
|
for nb in maze.neighbors(curr):
|
|
if nb not in visited:
|
|
visited.add(nb)
|
|
nb.prev = curr
|
|
queue.append(nb)
|
|
return []
|
|
|
|
def _build_path(self, end: Cell) -> List[Cell]:
|
|
path = []
|
|
while end:
|
|
path.append(end)
|
|
end = end.prev
|
|
return path[::-1]
|
|
|
|
@property
|
|
def visited_count(self) -> int:
|
|
return self._visited
|
|
|
|
class DFSSearch(SearchStrategy):
|
|
def __init__(self):
|
|
self._visited = 0
|
|
|
|
def find_path(self, maze: Maze, start: Cell, goal: Cell) -> List[Cell]:
|
|
self._visited = 0
|
|
if start == goal:
|
|
return [start]
|
|
|
|
visited = set()
|
|
stack = [start]
|
|
start.prev = None
|
|
|
|
while stack:
|
|
curr = stack.pop()
|
|
if curr in visited:
|
|
continue
|
|
|
|
visited.add(curr)
|
|
self._visited += 1
|
|
|
|
if curr == goal:
|
|
return self._build_path(curr)
|
|
|
|
for nb in maze.neighbors(curr):
|
|
if nb not in visited:
|
|
nb.prev = curr
|
|
stack.append(nb)
|
|
|
|
return []
|
|
|
|
def _build_path(self, end: Cell) -> List[Cell]:
|
|
path = []
|
|
while end:
|
|
path.append(end)
|
|
end = end.prev
|
|
return path[::-1]
|
|
|
|
@property
|
|
def visited_count(self) -> int:
|
|
return self._visited
|
|
|
|
class AStarSearch(SearchStrategy):
|
|
def __init__(self):
|
|
self._visited = 0
|
|
|
|
def find_path(self, maze: Maze, start: Cell, goal: Cell) -> List[Cell]:
|
|
self._visited = 0
|
|
if start == goal:
|
|
return [start]
|
|
|
|
counter = 0
|
|
open_set = [(self._h(start, goal), counter, start)]
|
|
came_from = {}
|
|
g_score = {start: 0}
|
|
open_hash = {start}
|
|
start.prev = None
|
|
|
|
while open_set:
|
|
_, _, curr = heapq.heappop(open_set)
|
|
open_hash.discard(curr)
|
|
self._visited += 1
|
|
|
|
if curr == goal:
|
|
return self._build_path(curr, came_from)
|
|
|
|
for nb in maze.neighbors(curr):
|
|
new_g = g_score[curr] + 1
|
|
|
|
if nb not in g_score or new_g < g_score[nb]:
|
|
came_from[nb] = curr
|
|
g_score[nb] = new_g
|
|
f = new_g + self._h(nb, goal)
|
|
|
|
if nb not in open_hash:
|
|
counter += 1
|
|
heapq.heappush(open_set, (f, counter, nb))
|
|
open_hash.add(nb)
|
|
nb.prev = curr
|
|
return []
|
|
|
|
def _h(self, a: Cell, b: Cell) -> float:
|
|
return abs(a.x - b.x) + abs(a.y - b.y)
|
|
|
|
def _build_path(self, end: Cell, came_from: dict) -> List[Cell]:
|
|
path = [end]
|
|
while end in came_from:
|
|
end = came_from[end]
|
|
path.append(end)
|
|
return path[::-1]
|
|
@property
|
|
def visited_count(self) -> int:
|
|
return self._visited |