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 == "player_moved": print(f"Игрок переместился в: ({data.x}, {data.y})") elif event == "error": print(f"Ошибка: {data}") # --- 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