import sys 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 @property def x(self): return self._x @property def y(self): return self._y @property def is_wall(self): return self._is_wall @is_wall.setter def is_wall(self, value): self._is_wall = value @property def is_start(self): return self._is_start @is_start.setter def is_start(self, value): self._is_start = value @property def is_exit(self): return self._is_exit @is_exit.setter def is_exit(self, value): self._is_exit = value def is_passable(self): return not self._is_wall 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 = None self._exit = None @property def width(self): return self._width @property def height(self): return self._height @property def start(self): return self._start @property def exit(self): return self._exit def get_cell(self, x, y): if 0 <= x < self._width and 0 <= y < self._height: return self._cells[y][x] return None def set_cell(self, x, y, cell_type): 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): 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): raise NotImplementedError("Need to realise in calss") class TextFileMazeBuilder(MazeBuilder): def build_from_file(self, filename): with open(filename, 'r') as f: lines = [line.rstrip('\n')for line in f.readlines()] height = len(lines) width = max(len(line) for line in lines) if height > 0 else 0 start_en = 0 exit_en = 0 maze = Maze(width, height) for y,line in enumerate(lines): for x, ch in enumerate(line): if ch == "#": maze.set_cell(x,y,"wall") elif ch == "S": maze.set_cell(x,y,"start") start_en+=1 elif ch == "E": maze.set_cell(x,y,"exit") exit_en+=1 else: maze.set_cell(x, y, 'path') if start_en > 1 or exit_en > 1 or start_en==0 or exit_en ==0: sys.exit("Error while reading file(you have too many or no match start and exits)") return maze class PathFindingStrategy: def find_path(self, maze, start, exit_cell): raise NotImplementedError def _reconstruct_path(self, came_from, start, exit_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, start, exit_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, start, exit_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, exit_cell): return abs(cell.x - exit_cell.x) + abs(cell.y - exit_cell.y) def find_path(self, maze, start, exit_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, visited_cells, path_length): self.time_ms = time_ms self.visited_cells = visited_cells self.path_length = path_length class Observer: def update(self, event_type, data): raise NotImplementedError class ConsoleView(Observer): def __init__(self, player=None): self._last_path = None self._player = player def update(self, event_type, data): if event_type == "maze_loaded": self.render_maze(data) elif event_type == "path_found": self._last_path = data self.render_path(data) elif event_type == "player_moved": self.render_maze_with_player(data) def render_maze(self, maze): os.system('cls' if os.name == 'nt' else 'clear') print("=" * (maze.width * 2 + 4)) print(" ЛАБИРИНТ") print("=" * (maze.width * 2 + 4)) for y in range(maze.height): print(" ", end='') for x in range(maze.width): cell = maze.get_cell(x, y) if cell == maze.start: print('S', end=' ') elif cell == maze.exit: print('E', end=' ') elif cell.is_wall: print('#', end=' ') else: print('.', end=' ') print() print("=" * (maze.width * 2 + 4)) print(" S - старт E - выход # - стена . - проход") def render_maze_with_player(self, maze): os.system('cls' if os.name == 'nt' else 'clear') print("=" * (maze.width * 2 + 4)) print(" ЛАБИРИНТ (P - вы)") print("=" * (maze.width * 2 + 4)) for y in range(maze.height): print(" ", end='') for x in range(maze.width): cell = maze.get_cell(x, y) if self._player and cell == self._player.current: print('P', end=' ') elif cell == maze.start: print('S', end=' ') elif cell == maze.exit: print('E', end=' ') elif cell.is_wall: print('#', end=' ') else: print('.', end=' ') print() print("=" * (maze.width * 2 + 4)) print(f" Позиция игрока: ({self._player.current.x}, {self._player.current.y})") print(" S - старт E - выход # - стена . - проход P - игрок") def render_path(self, path): if not path: print("\n Путь не найден!") return print(f"\n Путь найден! Длина: {len(path)}") def render_player(self, player_cell): if self._player: self.render_maze_with_player(self._player._maze) class Player: def __init__(self, start_cell, maze): self._current = start_cell self._previous = None self._maze = maze @property def current(self): return self._current def move_to(self, cell): if cell and cell.is_passable(): self._previous = self._current self._current = cell return True return False def undo_move(self): if self._previous: self._current, self._previous = self._previous, None return True return False class Command: def execute(self): raise NotImplementedError def undo(self): raise NotImplementedError class MoveCommand(Command): def __init__(self, player, direction, maze): self._player = player self._direction = direction self._maze = maze self._executed = False def execute(self): dx, dy = self._direction new_x = self._player.current.x + dx new_y = self._player.current.y + dy target_cell = self._maze.get_cell(new_x, new_y) if target_cell and target_cell.is_passable(): self._player.move_to(target_cell) self._executed = True return True return False def undo(self): if self._executed: self._player.undo_move() self._executed = False return True return False class MazeSolver: def __init__(self, maze): self._maze = maze self._strategy = None self._observers = [] def attach(self, observer): self._observers.append(observer) def notify(self, event_type, data): for observer in self._observers: observer.update(event_type, data) def set_strategy(self, strategy): self._strategy = strategy def solve(self): if self._strategy is None: 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 self.notify("path_found", path) return SearchStats(time_ms, 0, len(path)) if __name__ == "__main__": builder = TextFileMazeBuilder() maze = builder.build_from_file("maze1.txt") player = Player(maze.start, maze) view = ConsoleView(player) view.render_maze(maze) solver = MazeSolver(maze) solver.attach(view) print("\n УПРАВЛЕНИЕ:") print(" ┌─────────────────────────────────────┐") print(" │ H (влево) J (вниз) K (вверх) L (вправо) │") print(" │ U - отмена хода Q - выход │") print(" └─────────────────────────────────────┘") print("\n АВТОМАТИЧЕСКИЙ ПОИСК:") print(" ┌─────────────────────────────────────┐") print(" │ B - BFS (поиск в ширину) │") print(" │ D - DFS (поиск в глубину) │") print(" │ A - A* (A звездочка) │") print(" └─────────────────────────────────────┘") print("\n" + "=" * 50) command_stack = [] while True: key = input("\n Введите команду > ").lower() if key == 'q': print("\n До свидания!") break elif key == 'b': solver.set_strategy(BFSStrategy()) stats = solver.solve() print(f"\n BFS: время={stats.time_ms:.3f}мс, длина пути={stats.path_length}") elif key == 'd': solver.set_strategy(DFSStrategy()) stats = solver.solve() print(f"\n DFS: время={stats.time_ms:.3f}мс, длина пути={stats.path_length}") elif key == 'a': solver.set_strategy(AStarStrategy()) stats = solver.solve() print(f"\n A*: время={stats.time_ms:.3f}мс, длина пути={stats.path_length}") elif key in ['h', 'j', 'k', 'l']: dirs = {'h': (-1, 0), 'l': (1, 0), 'k': (0, -1), 'j': (0, 1)} cmd = MoveCommand(player, dirs[key], maze) if cmd.execute(): command_stack.append(cmd) view.render_maze_with_player(maze) if player.current == maze.exit: print("\n ПОЗДРАВЛЯЮ! ВЫ НАШЛИ ВЫХОД! ") print(f" Всего сделано ходов: {len(command_stack)}") break else: print("\n Нельзя туда идти! Там стена.") elif key == 'u': if command_stack: cmd = command_stack.pop() cmd.undo() view.render_maze_with_player(maze) print("\n Отмена последнего хода") else: print("\n Нечего отменять") else: print("\n Неизвестная команда. Используйте h,j,k,l для движения, u для отмены, q для выхода") print("\n Игра завершена. Спасибо за игру!")