From 86b738f6a4c72a5a529d42b276659d0647d40418 Mon Sep 17 00:00:00 2001 From: SavelevMI Date: Thu, 21 May 2026 14:10:50 +0000 Subject: [PATCH] [2] Add MazeSolver orchestrator and interactive console game --- SavelevMI/docs/data/2-nd-exersize/main.py | 269 ++++++++++++++++++++++ 1 file changed, 269 insertions(+) create mode 100644 SavelevMI/docs/data/2-nd-exersize/main.py diff --git a/SavelevMI/docs/data/2-nd-exersize/main.py b/SavelevMI/docs/data/2-nd-exersize/main.py new file mode 100644 index 0000000..fbe208e --- /dev/null +++ b/SavelevMI/docs/data/2-nd-exersize/main.py @@ -0,0 +1,269 @@ +# Основной модуль: оркестратор MazeSolver, Observer для визуализации, +# Player, Command для пошагового управления, интерактивная игра + +import sys +import time +import os +from maze_core import TextFileMazeBuilder, Maze +from pathfinding import BFSStrategy, DFSStrategy, AStarStrategy + + +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)}") + + +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, self._strategy.get_visited_count(), len(path)) + + +def main(): + + builder = TextFileMazeBuilder() + + # Загрузка лабиринта из файла + if len(sys.argv) > 1: + maze = builder.build_from_file(sys.argv[1]) + else: + 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(" H (влево) J (вниз) K (вверх) L (вправо)") + print(" U - отменить ход Q - выход") + print("\n АВТО-ПОИСК:") + print(" B - BFS (поиск в ширину)") + print(" D - DFS (поиск в глубину)") + print(" A - A* (звездочка)") + 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.visited_cells}, длина={stats.path_length}") + + elif key == 'd': + solver.set_strategy(DFSStrategy()) + stats = solver.solve() + print(f"\n DFS: время={stats.time_ms:.3f}мс, посещено={stats.visited_cells}, длина={stats.path_length}") + + elif key == 'a': + solver.set_strategy(AStarStrategy()) + stats = solver.solve() + print(f"\n A*: время={stats.time_ms:.3f}мс, посещено={stats.visited_cells}, длина={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 для выхода") + + +if __name__ == "__main__": + main() \ No newline at end of file