# Отчёт по лабораторной работе №2 ## Поиск выхода из лабиринта (объектно-ориентированная реализация с паттернами) --- ## 1. Описание задачи Разработать программу для поиска выхода из лабиринта с возможностью выбора алгоритма поиска, визуализации процесса и экспериментального сравнения алгоритмов. Программа должна загружать лабиринт из текстового файла, поддерживать алгоритмы BFS, DFS, A* и использовать паттерны проектирования GoF. --- ## 2. Выбранные паттерны ### 2.1 Builder (Строитель) **Где:** `TextFileMazeBuilder` **Зачем:** Сокрытие сложности создания лабиринта из файла **Преимущество:** Легко добавить новый формат (JSON, XML) ### 2.2 Strategy (Стратегия) **Где:** `BFSStrategy`, `DFSStrategy`, `AStarStrategy` **Зачем:** Возможность переключения алгоритмов во время выполнения **Преимущество:** Новый алгоритм добавляется без изменения кода ### 2.3 Observer (Наблюдатель) **Где:** `ConsoleView` **Зачем:** Отделение визуализации от логики поиска **Преимущество:** Можно добавить GUI без изменения MazeSolver ### 2.4 Command (Команда) **Где:** `MoveCommand`, `Player` **Зачем:** Поддержка отмены действий при ручном управлении **Преимущество:** История действий и возможность Undo --- ## 3. Диаграмма классов (Mermaid) ```python classDiagram class Maze { -width, height -_cells[][] -start, exit +get_cell(x,y) +get_neighbors(cell) } class Cell { -x, y -is_wall -is_start -is_exit +is_passable() } class MazeBuilder { <> +build_from_file(filename) } class TextFileMazeBuilder { +build_from_file(filename) } class PathFindingStrategy { <> +find_path(maze, start, exit) } class BFSStrategy class DFSStrategy class AStarStrategy class MazeSolver { -maze -strategy +set_strategy() +solve() } class Observer { <> +update(event, data) } class ConsoleView { +render(maze, path) +update(event, data) } class Command { <> +execute() +undo() } class MoveCommand { -player -direction +execute() +undo() } MazeBuilder <|.. TextFileMazeBuilder PathFindingStrategy <|.. BFSStrategy PathFindingStrategy <|.. DFSStrategy PathFindingStrategy <|.. AStarStrategy MazeSolver --> PathFindingStrategy Observer <|.. ConsoleView Command <|.. MoveCommand class TextFileMazeBuilder(MazeBuilder): WALL_CHAR = '#' START_CHAR = 'S' EXIT_CHAR = 'E' 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()] height = len(lines) width = max(len(line) for line in lines) maze = Maze(width, height) for y, line in enumerate(lines): for x, ch in enumerate(line): if x >= width: continue cell = Cell(x, y) if ch == self.WALL_CHAR: cell.is_wall = True elif ch == self.START_CHAR: cell.is_start = True elif ch == self.EXIT_CHAR: cell.is_exit = True maze.set_cell(x, y, cell) if maze.start is None: raise ValueError("Нет стартовой клетки (S)") if maze.exit is None: raise ValueError("Нет выхода (E)") return maze class TextFileMazeBuilder(MazeBuilder): WALL_CHAR = '#' START_CHAR = 'S' EXIT_CHAR = 'E' 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()] height = len(lines) width = max(len(line) for line in lines) maze = Maze(width, height) for y, line in enumerate(lines): for x, ch in enumerate(line): if x >= width: continue cell = Cell(x, y) if ch == self.WALL_CHAR: cell.is_wall = True elif ch == self.START_CHAR: cell.is_start = True elif ch == self.EXIT_CHAR: cell.is_exit = True maze.set_cell(x, y, cell) if maze.start is None: raise ValueError("Нет стартовой клетки (S)") if maze.exit is None: raise ValueError("Нет выхода (E)") return maze class BFSStrategy(PathFindingStrategy): def find_path(self, maze, start, exit_cell): queue = deque([start]) visited = {start} parent = {start: None} while queue: current = queue.popleft() if current == exit_cell: return self._reconstruct_path(parent, current) for neighbor in maze.get_neighbors(current): if neighbor not in visited: visited.add(neighbor) parent[neighbor] = current queue.append(neighbor) return [] def _reconstruct_path(self, parent, current): path = [] while current: path.append(current) current = parent[current] return list(reversed(path)) class DFSStrategy(PathFindingStrategy): def find_path(self, maze, start, exit_cell): stack = [(start, [start])] visited = {start} while stack: current, path = stack.pop() if current == exit_cell: return path for neighbor in maze.get_neighbors(current): if neighbor not in visited: visited.add(neighbor) stack.append((neighbor, path + [neighbor])) return [] 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_cell): counter = 0 open_set = [(self._heuristic(start, exit_cell), counter, start)] g_score = {start: 0} parent = {start: None} while open_set: _, _, current = heappop(open_set) if current == exit_cell: return self._reconstruct_path(parent, current) 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]: parent[neighbor] = current g_score[neighbor] = tentative_g counter += 1 f = tentative_g + self._heuristic(neighbor, exit_cell) heappush(open_set, (f, counter, neighbor)) return [] class ConsoleView(Observer): def render(self, maze, path=None): path_set = set(path) if path else set() print("\n+" + "-" * maze.width + "+") for y in range(maze.height): row = [] for x in range(maze.width): cell = maze.get_cell(x, y) if cell.is_start: row.append('S') elif cell.is_exit: row.append('E') elif cell in path_set: row.append('*') elif cell.is_wall: row.append('#') else: row.append(' ') print("|" + ''.join(row) + "|") print("+" + "-" * maze.width + "+") def update(self, event, data): if event == "maze_loaded": self.render(data.get('maze')) elif event == "path_found": self.render(data.get('maze'), data.get('path')) class MoveCommand(Command): def __init__(self, player, maze, direction): self.player = player self.maze = maze self.direction = direction self.previous_cell = None def execute(self): self.previous_cell = self.player.current_cell dx, dy = self.direction new_cell = self.maze.get_cell( self.player.current_cell.x + dx, self.player.current_cell.y + dy ) if new_cell and new_cell.is_passable(): self.player.move_to(new_cell) return True return False def undo(self): if self.previous_cell: self.player.move_to(self.previous_cell) 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): if not self._strategy: raise ValueError("Стратегия не установлена") start_time = time.perf_counter() path = self._strategy.find_path(self.maze, self.maze.start, self.maze.exit) end_time = time.perf_counter() stats = SearchStats( time_ms=(end_time - start_time) * 1000, visited_cells=len(path) if path else 0, path_length=len(path) if path else 0, path_found=bool(path) ) return path, stats