from abc import ABC, abstractmethod from collections import deque import heapq import time class Cell: def __init__(self, x, y): self.x = x self.y = y self.is_wall = False self.is_start = False self.is_exit = False def is_passable(self): return not self.is_wall def __repr__(self): return f"Cell({self.x},{self.y})" class Maze: def __init__(self, width, height): self.width = width self.height = height self.cells = [] self.start = None self.exit = None for y in range(height): row = [] for x in range(width): row.append(Cell(x, y)) self.cells.append(row) def get_cell(self, x, y): if 0 <= x < self.width and 0 <= y < self.height: return self.cells[y][x] return None def get_neighbors(self, cell): neighbors = [] for dx, dy in [(0, -1), (0, 1), (-1, 0), (1, 0)]: nx, ny = cell.x + dx, cell.y + dy neighbor = self.get_cell(nx, ny) if neighbor and neighbor.is_passable(): neighbors.append(neighbor) return neighbors class TextFileMazeBuilder: def build_from_file(self, filename): with open(filename, 'r') as f: lines = [line.rstrip() for line in f.readlines()] height = len(lines) width = len(lines[0]) maze = Maze(width, height) for y, line in enumerate(lines): for x, ch in enumerate(line): cell = maze.get_cell(x, y) if ch == '#': cell.is_wall = True elif ch == 'S': maze.start = cell cell.is_start = True elif ch == 'E': maze.exit = cell cell.is_exit = True return maze class PathFindingStrategy(ABC): @abstractmethod def find_path(self, maze, start, exit): pass class BFSStrategy(PathFindingStrategy): def find_path(self, maze, start, exit): if not start or not exit: return [], 0 queue = deque([(start, [start])]) visited = {start} while queue: current, path = queue.popleft() if current == exit: return path, len(visited) for neighbor in maze.get_neighbors(current): if neighbor not in visited: visited.add(neighbor) queue.append((neighbor, path + [neighbor])) return [], len(visited) class DFSStrategy(PathFindingStrategy): def find_path(self, maze, start, exit): if not start or not exit: return [], 0 stack = [(start, [start])] visited = {start} while stack: current, path = stack.pop() if current == exit: return path, len(visited) for neighbor in maze.get_neighbors(current): if neighbor not in visited: visited.add(neighbor) stack.append((neighbor, path + [neighbor])) return [], len(visited) class AStarStrategy(PathFindingStrategy): def _heuristic(self, a, b): return abs(a.x - b.x) + abs(a.y - b.y) def find_path(self, maze, start, exit): if not start or not exit: return [], 0 heap = [(self._heuristic(start, exit), 0, start, [start])] g_score = {start: 0} visited = set() counter = 1 while heap: _, _, current, path = heapq.heappop(heap) if current in visited: continue visited.add(current) if current == exit: return path, len(visited) for neighbor in maze.get_neighbors(current): tentative_g = g_score[current] + 1 if neighbor not in g_score or tentative_g < g_score[neighbor]: g_score[neighbor] = tentative_g f = tentative_g + self._heuristic(neighbor, exit) heapq.heappush(heap, (f, counter, neighbor, path + [neighbor])) counter += 1 return [], len(visited) class SearchStats: def __init__(self, path, time_ms, visited_count): self.path = path self.time_ms = time_ms self.visited_count = visited_count self.path_length = len(path) if path else 0 class MazeSolver: def __init__(self, maze, strategy=None): self.maze = maze self.strategy = strategy def set_strategy(self, strategy): self.strategy = strategy def solve(self): start_time = time.perf_counter() path, visited = self.strategy.find_path(self.maze, self.maze.start, self.maze.exit) end_time = time.perf_counter() return SearchStats(path, (end_time - start_time) * 1000, visited)