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