From bb1a35103e413454dd8768e3b9f083f84e504fab Mon Sep 17 00:00:00 2001 From: IvanBud123 Date: Fri, 15 May 2026 09:16:11 +0300 Subject: [PATCH] [2] add player mode --- BudakovIS/docs/data/2-nd-exercize/main.py | 354 ++++++++++++++++------ 1 file changed, 254 insertions(+), 100 deletions(-) diff --git a/BudakovIS/docs/data/2-nd-exercize/main.py b/BudakovIS/docs/data/2-nd-exercize/main.py index 08dac90..3c8e0a6 100644 --- a/BudakovIS/docs/data/2-nd-exercize/main.py +++ b/BudakovIS/docs/data/2-nd-exercize/main.py @@ -2,11 +2,11 @@ import sys from collections import deque import heapq import time -from typing import List, Optional +import os class Cell: - def __init__(self, x: int, y: int): + def __init__(self, x, y): self._x = x self._y = y self._is_wall = False @@ -14,46 +14,43 @@ class Cell: self._is_exit = False @property - def x(self) -> int: + def x(self): return self._x @property - def y(self) -> int: + def y(self): return self._y @property - def is_wall(self) -> bool: + def is_wall(self): return self._is_wall @is_wall.setter - def is_wall(self, value: bool) -> None: + def is_wall(self, value): self._is_wall = value @property - def is_start(self) -> bool: + def is_start(self): return self._is_start @is_start.setter - def is_start(self, value: bool) -> None: + def is_start(self, value): self._is_start = value @property - def is_exit(self) -> bool: + def is_exit(self): return self._is_exit @is_exit.setter - def is_exit(self, value: bool) -> None: + def is_exit(self, value): self._is_exit = value - def is_passable(self) -> bool: + def is_passable(self): return not self._is_wall - - def __repr__(self) -> str: - return f"Cell({self._x}, {self._y})" class Maze: - def __init__(self, width: int, height: int): + 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)] @@ -61,27 +58,27 @@ class Maze: self._exit = None @property - def width(self) -> int: + def width(self): return self._width @property - def height(self) -> int: + def height(self): return self._height @property - def start(self) -> Optional[Cell]: + def start(self): return self._start @property - def exit(self) -> Optional[Cell]: + def exit(self): return self._exit - def get_cell(self, x: int, y: int) -> Optional[Cell]: + 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: int, y: int, cell_type: str) -> None: + def set_cell(self, x, y, cell_type): cell = self.get_cell(x, y) if cell is None: return @@ -103,7 +100,7 @@ class Maze: elif cell_type == 'path': cell.is_wall = False - def get_neighbors(self, cell: Cell) -> List[Cell]: + def get_neighbors(self, cell): neighbors = [] directions = [(0, -1), (0, 1), (-1, 0), (1, 0)] for dx, dy in directions: @@ -115,54 +112,42 @@ class Maze: class MazeBuilder: - def build_from_file(self, filename: str) -> Maze: - raise NotImplementedError("Need to implement in subclass") + def build_from_file(self, filename): + raise NotImplementedError("Need to realise in calss") class TextFileMazeBuilder(MazeBuilder): - def build_from_file(self, filename: str) -> Maze: - with open(filename, 'r', encoding='utf-8') as f: - lines = [line.rstrip('\n') for line in f.readlines()] - - if not lines: - raise ValueError("Файл пуст") - + 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) - - start_count = 0 - exit_count = 0 + 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 y,line in enumerate(lines): for x, ch in enumerate(line): - if x >= width: - continue - if ch == '#': - maze.set_cell(x, y, "wall") - elif ch == 'S': - maze.set_cell(x, y, "start") - start_count += 1 - elif ch == 'E': - maze.set_cell(x, y, "exit") - exit_count += 1 + 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_count != 1 or exit_count != 1: - raise ValueError( - f"Лабиринт должен иметь ровно один S и один E. " - f"Найдено: S={start_count}, E={exit_count}" - ) - + 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: Maze, start: Cell, exit_cell: Cell) -> List[Cell]: - raise NotImplementedError("Subclasses must implement find_path") + def find_path(self, maze, start, exit_cell): + raise NotImplementedError - def _reconstruct_path(self, came_from: dict, start: Cell, exit_cell: Cell) -> List[Cell]: + def _reconstruct_path(self, came_from, start, exit_cell): path = [] current = exit_cell while current is not None: @@ -173,58 +158,49 @@ class PathFindingStrategy: class BFSStrategy(PathFindingStrategy): - def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]: + 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: Maze, start: Cell, exit_cell: Cell) -> List[Cell]: + 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: Cell, exit_cell: Cell) -> int: + def _heuristic(self, cell, exit_cell): return abs(cell.x - exit_cell.x) + abs(cell.y - exit_cell.y) - def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]: + 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 @@ -235,16 +211,12 @@ class AStarStrategy(PathFindingStrategy): 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 @@ -252,65 +224,247 @@ class AStarStrategy(PathFindingStrategy): f_score[neighbor] = new_f heapq.heappush(heap, (new_f, counter, neighbor)) counter += 1 - return [] class SearchStats: - def __init__(self, time_ms: float, visited_cells: int, path_length: int): + 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 __repr__(self) -> str: - return f"SearchStats(time={self.time_ms:.3f}ms, visited={self.visited_cells}, path_length={self.path_length})" + 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: Maze): + def __init__(self, maze): self._maze = maze self._strategy = None + self._observers = [] - def set_strategy(self, strategy: PathFindingStrategy) -> None: + 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) -> Optional[SearchStats]: + def solve(self): if self._strategy is None: - print("Ошибка: стратегия не установлена") 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 - visited_cells = 0 + self.notify("path_found", path) - return SearchStats(time_ms, visited_cells, len(path)) + return SearchStats(time_ms, 0, len(path)) if __name__ == "__main__": builder = TextFileMazeBuilder() maze = builder.build_from_file("maze1.txt") - print(f"Лабиринт {maze.width}x{maze.height}") - print(f"Старт: ({maze.start.x}, {maze.start.y})") - print(f"Выход: ({maze.exit.x}, {maze.exit.y})") - print("-" * 40) + player = Player(maze.start, maze) + view = ConsoleView(player) + view.render_maze(maze) solver = MazeSolver(maze) + solver.attach(view) - solver.set_strategy(BFSStrategy()) - stats = solver.solve() - print(f"BFS: {stats}") + 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) - solver.set_strategy(DFSStrategy()) - stats = solver.solve() - print(f"DFS: {stats}") + command_stack = [] - solver.set_strategy(AStarStrategy()) - stats = solver.solve() - print(f"A*: {stats}") + 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 Игра завершена. Спасибо за игру!")