from abc import ABC, abstractmethod from collections import deque import heapq import time import os 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) class Observer(ABC): @abstractmethod def update(self, event, data=None): pass class ConsoleView(Observer): def __init__(self): self.events = [] def update(self, event, data=None): self.events.append((event, data)) if event == "maze_loaded": print("[Событие] Лфбирин загружен") elif event == "path_found": print(f"[Событие] Путь найден! Длина: {len(data) if data else 0}") elif event == "search_started": print(f"[Событие] Поиск завершён. Время: {data:.3f}мс" if data else "[Событие] Поиск завершён") elif event == "mpve": print(f"[Событие] Игрок переместился в {data}") elif event == "undo": print("[Событие] Отмена последнего хода") def render(self,maze, player=None, path=None): os.system('cls' if os.name == 'nt' else 'clear') print("Лабиринт") for y in range(maze.height): row = "" for x in range(maze.width): cell = maze.get_cell(x,y) if player and cell == player.current_cell: row += "p " #игрок elif path and cell in path: row += "* " #путь elif cell.is_wall: row += "# " #стена elif cell.is_start: row += "S " #старт elif cell.is_exit: row += "E " #выход else: row += ". " #прозод print(row) print("Управление: W/A/S/D - движение, U - отмена, Q - выход") class Command(ABC): @abstractmethod def execute(self): pass @abstractmethod def undo(self): pass class Player: def __init__(self, start_cell): self.current_cell = start_cell self.start_cell = start_cell def move_to(self, cell): self.current_cell = cell def resent(self): self.current_cell = self.start_cell def __repr__(self): return f"Player at ({self.current_cell.x}, {self.current_cell.y})" class MoveCommand(Command): def __init__(self, player, new_cell, view): self.player = player self.new_cell = new_cell self.old_cell = player.current_cell self.view = view def execute(self): self.player.move_to(self.new_cell) self.view.update("undo", None) class GameController: def __init__(self, maze, view): self.maze = maze self.view = view self.player = Player(maze.start) self.command_history = [] def get_cell_in_direction(self, direction): x, y = self.player.current_cell.x, self.player.current_cell.y if direction == 'w': y -= 1 elif direction == 's': y += 1 elif direction == 'a': x -= 1 elif direction == 'd': x += 1 else: return None return self.maze.get_cell(x, y) def try_move(self, direction): new_cell = self.get_cell_in_direction(direction) if new_cell and new_cell.is_passable(): command = MoveCommand(self.player, new_cell, self.view) command.execute() self.command_history.append(command) if new_cell.is_exit: self.view.update("path_found", []) print("Вы нашли выход.") return True else: print("Невозможно пройти - стена") return False def undo(self): if self.command_history: command = self.command_history.pop() command.undo() else: print("Нечего отменять") def visualize_path(self, path): self.view.render(self.maze, self.player, path) def run_manual_mode(self): while True: self.view.render(self.maze, self.player) command = input("Введите команду: ").lower().strip()