import sys from collections import deque import heapq import time from typing import List, Optional class Cell: def __init__(self, x: int, y: int): self._x = x self._y = y self._is_wall = False self._is_start = False self._is_exit = False @property def x(self) -> int: return self._x @property def y(self) -> int: return self._y @property def is_wall(self) -> bool: return self._is_wall @is_wall.setter def is_wall(self, value: bool) -> None: self._is_wall = value @property def is_start(self) -> bool: return self._is_start @is_start.setter def is_start(self, value: bool) -> None: self._is_start = value @property def is_exit(self) -> bool: return self._is_exit @is_exit.setter def is_exit(self, value: bool) -> None: self._is_exit = value def is_passable(self) -> bool: return not self._is_wall def __repr__(self) -> str: return f"Cell({self._x}, {self._y})" class Maze: def __init__(self, width: int, height: int): self._width = width self._height = height self._cells = [[Cell(x, y) for x in range(width)] for y in range(height)] self._start = None self._exit = None @property def width(self) -> int: return self._width @property def height(self) -> int: return self._height @property def start(self) -> Optional[Cell]: return self._start @property def exit(self) -> Optional[Cell]: return self._exit def get_cell(self, x: int, y: int) -> Optional[Cell]: if 0 <= x < self._width and 0 <= y < self._height: return self._cells[y][x] return None def set_cell(self, x: int, y: int, cell_type: str) -> None: cell = self.get_cell(x, y) if cell is None: return if cell_type == 'wall': cell.is_wall = True elif cell_type == 'start': if self._start: self._start.is_start = False cell.is_start = True cell.is_wall = False self._start = cell elif cell_type == 'exit': if self._exit: self._exit.is_exit = False cell.is_exit = True cell.is_wall = False self._exit = cell elif cell_type == 'path': cell.is_wall = False def get_neighbors(self, cell: Cell) -> List[Cell]: neighbors = [] directions = [(0, -1), (0, 1), (-1, 0), (1, 0)] for dx, dy in directions: 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 MazeBuilder: def build_from_file(self, filename: str) -> Maze: raise NotImplementedError("Need to implement in subclass") class TextFileMazeBuilder(MazeBuilder): def build_from_file(self, filename: str) -> Maze: with open(filename, 'r', encoding='utf-8') as f: lines = [line.rstrip('\n') for line in f.readlines()] if not lines: raise ValueError("Файл пуст") height = len(lines) width = max(len(line) for line in lines) start_count = 0 exit_count = 0 maze = Maze(width, height) for y, line in enumerate(lines): for x, ch in enumerate(line): if x >= width: continue if ch == '#': maze.set_cell(x, y, "wall") elif ch == 'S': maze.set_cell(x, y, "start") start_count += 1 elif ch == 'E': maze.set_cell(x, y, "exit") exit_count += 1 else: maze.set_cell(x, y, 'path') if start_count != 1 or exit_count != 1: raise ValueError( f"Лабиринт должен иметь ровно один S и один E. " f"Найдено: S={start_count}, E={exit_count}" ) return maze class PathFindingStrategy: def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]: raise NotImplementedError("Subclasses must implement find_path") def _reconstruct_path(self, came_from: dict, start: Cell, exit_cell: Cell) -> List[Cell]: path = [] current = exit_cell while current is not None: path.append(current) current = came_from.get(current) path.reverse() return path class BFSStrategy(PathFindingStrategy): def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]: queue = deque() queue.append(start) came_from = {start: None} visited = {start} while queue: current = queue.popleft() if current == exit_cell: return self._reconstruct_path(came_from, start, exit_cell) for neighbor in maze.get_neighbors(current): if neighbor not in visited: visited.add(neighbor) came_from[neighbor] = current queue.append(neighbor) return [] class DFSStrategy(PathFindingStrategy): def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]: stack = [start] came_from = {start: None} visited = {start} while stack: current = stack.pop() if current == exit_cell: return self._reconstruct_path(came_from, start, exit_cell) for neighbor in maze.get_neighbors(current): if neighbor not in visited: visited.add(neighbor) came_from[neighbor] = current stack.append(neighbor) return [] class AStarStrategy(PathFindingStrategy): def _heuristic(self, cell: Cell, exit_cell: Cell) -> int: return abs(cell.x - exit_cell.x) + abs(cell.y - exit_cell.y) def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]: heap = [] counter = 0 start_f = self._heuristic(start, exit_cell) heapq.heappush(heap, (start_f, counter, start)) counter += 1 came_from = {} g_score = {start: 0} f_score = {start: start_f} while heap: current_f, _, current = heapq.heappop(heap) if current == exit_cell: return self._reconstruct_path(came_from, start, exit_cell) if current_f > f_score.get(current, float('inf')): continue for neighbor in maze.get_neighbors(current): tentative_g = g_score[current] + 1 if tentative_g < g_score.get(neighbor, float('inf')): came_from[neighbor] = current g_score[neighbor] = tentative_g new_f = tentative_g + self._heuristic(neighbor, exit_cell) f_score[neighbor] = new_f heapq.heappush(heap, (new_f, counter, neighbor)) counter += 1 return [] class SearchStats: def __init__(self, time_ms: float, visited_cells: int, path_length: int): self.time_ms = time_ms self.visited_cells = visited_cells self.path_length = path_length def __repr__(self) -> str: return f"SearchStats(time={self.time_ms:.3f}ms, visited={self.visited_cells}, path_length={self.path_length})" class MazeSolver: def __init__(self, maze: Maze): self._maze = maze self._strategy = None def set_strategy(self, strategy: PathFindingStrategy) -> None: self._strategy = strategy def solve(self) -> Optional[SearchStats]: if self._strategy is None: print("Ошибка: стратегия не установлена") return None start_time = time.perf_counter() path = self._strategy.find_path(self._maze, self._maze.start, self._maze.exit) end_time = time.perf_counter() time_ms = (end_time - start_time) * 1000 visited_cells = 0 return SearchStats(time_ms, visited_cells, len(path)) if __name__ == "__main__": builder = TextFileMazeBuilder() maze = builder.build_from_file("maze1.txt") print(f"Лабиринт {maze.width}x{maze.height}") print(f"Старт: ({maze.start.x}, {maze.start.y})") print(f"Выход: ({maze.exit.x}, {maze.exit.y})") print("-" * 40) solver = MazeSolver(maze) solver.set_strategy(BFSStrategy()) stats = solver.solve() print(f"BFS: {stats}") solver.set_strategy(DFSStrategy()) stats = solver.solve() print(f"DFS: {stats}") solver.set_strategy(AStarStrategy()) stats = solver.solve() print(f"A*: {stats}")