from collections import deque import heapq import time from abc import ABC, abstractmethod # Модель  class Cell: def __init__(self, x, y, is_wall=False, is_start=False, is_exit=False):         self.x = x         self.y = y         self.is_wall = is_wall         self.is_start = is_start         self.is_exit = is_exit         self.visited = False       def is_passable(self):         return not self.is_wall class Player: def __init__(self, start_cell): self.current_cell = start_cell class Maze:     def __init__(self, width, height):         self.width = width         self.height = height         self.cells = [[Cell(x, y) for x in range(width)] for y in range(height)]         self.start_cell = self.cells[0][0] self.exit_cell = self.cells[height-1][width-1]       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 = []         directions = [(0, 1), (0, -1), (1, 0), (-1, 0)]         for dx, dy in directions:             neighbor = self.get_cell(cell.x + dx, cell.y + dy)             if neighbor and neighbor.is_passable():                 neighbors.append(neighbor)         return neighbors # Строитель  class MazeBuilder:     def buildFromFile(self, filename):         with open(filename, 'r') as f:             lines = f.readlines()         height = len(lines)         width = len(lines[0].strip())         maze = Maze(width, height)         for y, line in enumerate(lines):             for x, char in enumerate(line.strip()):                 cell = maze.get_cell(x, y)                 if char == '#': cell.is_wall = True                 elif char == 'S':                     cell.is_start = True                     maze.start_cell = cell                 elif char == 'E':                     cell.is_exit = True                     maze.exit_cell = cell         if not maze.start_cell or not maze.exit_cell:             raise ValueError("Лабиринт сломан")         return maze #  Strategy  class PathFindingStrategy:     def findPath(self, maze, start, exit):         raise NotImplementedError("Этот метод должен быть реализован в стратегии!")     def _reconstruct_path(self, parents, current):         path = []         while current:             path.append(current)             current = parents.get(current)         return path[::-1] class Command(ABC): @abstractmethod def execute(self): pass @abstractmethod def undo(self): pass class MoveCommand(Command): def __init__(self, player, dx, dy, maze): self.player = player self.dx = dx self.dy = dy self.maze = maze self.prev_cell = None def execute(self): self.prev_cell = self.player.current_cell target = self.maze.get_cell(self.prev_cell.x + self.dx, self.prev_cell.y + self.dy) if target and target.is_passable(): self.player.current_cell = target return True return False def undo(self): self.player.current_cell = self.prev_cell class BFSStrategy(PathFindingStrategy):     def findPath(self, maze, start, exit):         queue = deque([start])         parents = {start: None}         start.visited = True         while queue:             current = queue.popleft()             if current == exit:                 return self._reconstruct_path(parents, exit)             for neighbor in maze.get_neighbors(current):                 if not neighbor.visited:                     neighbor.visited = True                     parents[neighbor] = current                     queue.append(neighbor)         return [] class AStarStrategy(PathFindingStrategy):     def _heuristic(self, a, b):         return abs(a.x - b.x) + abs(a.y - b.y)     def findPath(self, maze, start, exit):         heap = [(0, start)]         parents = {start: None}         g_score = {start: 0}         while heap:             _, current = heapq.heappop(heap)             if current == exit:                 return self._reconstruct_path(parents, exit)             for neighbor in maze.get_neighbors(current):                 new_g = g_score[current] + 1                 if new_g < g_score.get(neighbor, float('inf')):                     parents[neighbor] = current                     g_score[neighbor] = new_g                     f = new_g + self._heuristic(neighbor, exit)                     heapq.heappush(heap, (f, neighbor))         return [] # Статистика  class SearchStats:     def __init__(self, time_ms, visited, length):         self.time_ms = time_ms         self.visited = visited         self.length = length # Паттерн Observer  class Observer(ABC):     @abstractmethod     def update(self, event: str, data=None):         pass class ConsoleView(Observer):     def update(self, event: str, data=None):         if event == "path_found":             print(f"Событие '{event}': время={data.time_ms:.2f}мс, посещено={data.visited}, путь={data.length}")         elif event == "maze_loaded":             print(f"Событие '{event}': Лабиринт загружен.") # --- 6. Оркестратор (MazeSolver) --- class MazeSolver: def __init__(self, maze, player): self.maze = maze self.player = player self.strat = None self._observers = [] self._history = [] # Стек для undo def attach(self, observer): self._observers.append(observer) def notify(self, event, data=None): for obs in self._observers: obs.update(event, data) # Метод для ручного управления (Command) def move_player(self, dx, dy): cmd = MoveCommand(self.player, dx, dy, self.maze) if cmd.execute(): self._history.append(cmd) self.notify("player_moved", self.player.current_cell) else: self.notify("error", "Стена!") def undo_move(self): if self._history: cmd = self._history.pop() cmd.undo() self.notify("player_moved", self.player.current_cell)     def solve(self):         if not self.strat: return None                  t0 = time.perf_counter()         path = self.strat.findPath(self.maze, self.maze.start_cell, self.maze.exit_cell)         t1 = time.perf_counter()                  visited_count = sum(c.visited for row in self.maze.cells for c in row)         stats = SearchStats((t1 - t0) * 1000, visited_count, len(path))                  self.notify("path_found", stats)         return stats # --- Запуск --- if __name__ == "__main__": maze = Maze(10, 10) player = Player(maze.start_cell) solver = MazeSolver(maze, player) solver.attach(ConsoleView()) solver.move_player(1, 0) solver.undo_move() print("Используйте WASD для движения, Z для отмены хода, Q для выхода") while True: cmd = input("Введите команду: ").lower() if cmd == 'd': solver.move_player(1, 0) elif cmd == 'a': solver.move_player(-1, 0) elif cmd == 's': solver.move_player(0, 1) elif cmd == 'w': solver.move_player(0, -1) elif cmd == 'z': solver.undo_move() elif cmd == 'q': break