From 28c4c61a682b167d00004476e14235f7fdc446cb Mon Sep 17 00:00:00 2001 From: SavelevMI Date: Thu, 21 May 2026 14:06:03 +0000 Subject: [PATCH 1/7] [1] Add commt to right --- SavelevMI/docs/data/2-nd-exersize/maze_core.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SavelevMI/docs/data/2-nd-exersize/maze_core.py b/SavelevMI/docs/data/2-nd-exersize/maze_core.py index a605bfb..038b86a 100644 --- a/SavelevMI/docs/data/2-nd-exersize/maze_core.py +++ b/SavelevMI/docs/data/2-nd-exersize/maze_core.py @@ -143,4 +143,5 @@ class TextFileMazeBuilder(MazeBuilder): if start_count != 1 or exit_count != 1: raise ValueError(f"Лабиринт должен иметь ровно один вход S и один выход E. Найдено: S={start_count}, E={exit_count}") - return maze \ No newline at end of file + return maze + \ No newline at end of file -- 2.43.0 From 6005f2f8b2c517237fc231f40082db760bd760f3 Mon Sep 17 00:00:00 2001 From: SavelevMI Date: Thu, 21 May 2026 14:09:02 +0000 Subject: [PATCH 2/7] [2] Implement pathfinding strategies (BFS, DFS, A*) with Strategy pattern --- .../docs/data/2-nd-exersize/pathfinding.py | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 SavelevMI/docs/data/2-nd-exersize/pathfinding.py diff --git a/SavelevMI/docs/data/2-nd-exersize/pathfinding.py b/SavelevMI/docs/data/2-nd-exersize/pathfinding.py new file mode 100644 index 0000000..306e65d --- /dev/null +++ b/SavelevMI/docs/data/2-nd-exersize/pathfinding.py @@ -0,0 +1,115 @@ +# Стратегии поиска пути: BFS, DFS, A* (Strategy pattern) + +from collections import deque +import heapq + + +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 + + def get_visited_count(self): + return getattr(self, '_visited_count', 0) + + +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: + self._visited_count = len(visited) + 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) + + self._visited_count = len(visited) + 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: + self._visited_count = len(visited) + 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) + + self._visited_count = len(visited) + 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} + visited = set() + + while heap: + current_f, _, current = heapq.heappop(heap) + visited.add(current) + + if current == exit_cell: + self._visited_count = len(visited) + 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 + + self._visited_count = len(visited) + return [] \ No newline at end of file -- 2.43.0 From 86b738f6a4c72a5a529d42b276659d0647d40418 Mon Sep 17 00:00:00 2001 From: SavelevMI Date: Thu, 21 May 2026 14:10:50 +0000 Subject: [PATCH 3/7] [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 -- 2.43.0 From ccf6b0e4dc7e74173ff57bfb865dd71b1d115b0e Mon Sep 17 00:00:00 2001 From: SavelevMI Date: Thu, 21 May 2026 14:13:35 +0000 Subject: [PATCH 4/7] [2] Add maze files for experiments --- SavelevMI/docs/data/2-nd-exersize/maze1.txt | 6 ++++++ .../docs/data/2-nd-exersize/maze10x10.txt | 10 ++++++++++ .../docs/data/2-nd-exersize/maze20x20.txt | 20 +++++++++++++++++++ .../docs/data/2-nd-exersize/maze_empty.txt | 10 ++++++++++ .../docs/data/2-nd-exersize/maze_no_exit.txt | 9 +++++++++ 5 files changed, 55 insertions(+) create mode 100644 SavelevMI/docs/data/2-nd-exersize/maze1.txt create mode 100644 SavelevMI/docs/data/2-nd-exersize/maze10x10.txt create mode 100644 SavelevMI/docs/data/2-nd-exersize/maze20x20.txt create mode 100644 SavelevMI/docs/data/2-nd-exersize/maze_empty.txt create mode 100644 SavelevMI/docs/data/2-nd-exersize/maze_no_exit.txt diff --git a/SavelevMI/docs/data/2-nd-exersize/maze1.txt b/SavelevMI/docs/data/2-nd-exersize/maze1.txt new file mode 100644 index 0000000..8e496e0 --- /dev/null +++ b/SavelevMI/docs/data/2-nd-exersize/maze1.txt @@ -0,0 +1,6 @@ +########## +#S # +# # # +# ## # +# #E # +########## diff --git a/SavelevMI/docs/data/2-nd-exersize/maze10x10.txt b/SavelevMI/docs/data/2-nd-exersize/maze10x10.txt new file mode 100644 index 0000000..668c4a4 --- /dev/null +++ b/SavelevMI/docs/data/2-nd-exersize/maze10x10.txt @@ -0,0 +1,10 @@ +########## +#S ####### +# ####### +# ###### +# ###### +#E ##### +########## +########## +########## +########## diff --git a/SavelevMI/docs/data/2-nd-exersize/maze20x20.txt b/SavelevMI/docs/data/2-nd-exersize/maze20x20.txt new file mode 100644 index 0000000..8a88eae --- /dev/null +++ b/SavelevMI/docs/data/2-nd-exersize/maze20x20.txt @@ -0,0 +1,20 @@ +#################### +#S ################ +# ################ +# ################# +# ############### +# ############# +## ########### +### E # ########### +## ############ +##### ############ +###### ############# +###### ############# +#################### +#################### +#################### +#################### +#################### +#################### +#################### +#################### diff --git a/SavelevMI/docs/data/2-nd-exersize/maze_empty.txt b/SavelevMI/docs/data/2-nd-exersize/maze_empty.txt new file mode 100644 index 0000000..ec84b80 --- /dev/null +++ b/SavelevMI/docs/data/2-nd-exersize/maze_empty.txt @@ -0,0 +1,10 @@ +S + + + + + + + + +E diff --git a/SavelevMI/docs/data/2-nd-exersize/maze_no_exit.txt b/SavelevMI/docs/data/2-nd-exersize/maze_no_exit.txt new file mode 100644 index 0000000..f5f00e2 --- /dev/null +++ b/SavelevMI/docs/data/2-nd-exersize/maze_no_exit.txt @@ -0,0 +1,9 @@ +S + + + + + + + + -- 2.43.0 From e12d37f94b7e0fcc0977c718f3ea0ae6d4453b87 Mon Sep 17 00:00:00 2001 From: SavelevMI Date: Thu, 21 May 2026 14:14:33 +0000 Subject: [PATCH 5/7] [2] Experimental module for algorithm performance comparison --- .../docs/data/2-nd-exersize/experiment.py | 123 ++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 SavelevMI/docs/data/2-nd-exersize/experiment.py diff --git a/SavelevMI/docs/data/2-nd-exersize/experiment.py b/SavelevMI/docs/data/2-nd-exersize/experiment.py new file mode 100644 index 0000000..de8b907 --- /dev/null +++ b/SavelevMI/docs/data/2-nd-exersize/experiment.py @@ -0,0 +1,123 @@ +# Экспериментальное сравнение алгоритмов поиска пути +# Запуск: python3 experiment.py + +import csv +import time +from maze_core import TextFileMazeBuilder +from pathfinding import BFSStrategy, DFSStrategy, AStarStrategy + + +class MazeSolverExperiment: + + def __init__(self, maze): + self._maze = maze + self._strategy = None + + 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 + + return { + 'time_ms': time_ms, + 'visited_cells': self._strategy.get_visited_count(), + 'path_length': len(path) + } + + +def run_experiment(maze_file, strategy, runs=5): + builder = TextFileMazeBuilder() + maze = builder.build_from_file(maze_file) + + total_time = 0 + total_visited = 0 + total_length = 0 + + for _ in range(runs): + solver = MazeSolverExperiment(maze) + solver.set_strategy(strategy) + stats = solver.solve() + if stats: + total_time += stats['time_ms'] + total_visited += stats['visited_cells'] + total_length += stats['path_length'] + + return { + 'time_ms': total_time / runs, + 'visited_cells': total_visited / runs, + 'path_length': total_length / runs + } + + +def main(): + + # Список лабиринтов для тестирования + mazes = [ + ("maze1.txt", "Small (10x6)"), + ("maze10x10.txt", "Medium (10x10)"), + ("maze20x20.txt", "Large (20x20)"), + ("maze_empty.txt", "Empty (15x15)"), + ("maze_no_exit.txt", "No exit (10x10)") + ] + + strategies = [ + ("BFS", BFSStrategy()), + ("DFS", DFSStrategy()), + ("AStar", AStarStrategy()) + ] + + results = [] + + print("=" * 60) + print("ЭКСПЕРИМЕНТАЛЬНОЕ СРАВНЕНИЕ АЛГОРИТМОВ ПОИСКА ПУТИ") + print("=" * 60) + + for maze_file, maze_name in mazes: + print(f"\nТестирование: {maze_name} ({maze_file})") + print("-" * 40) + + for strat_name, strat in strategies: + try: + stats = run_experiment(maze_file, strat, runs=5) + results.append({ + 'maze': maze_name, + 'strategy': strat_name, + 'time_ms': stats['time_ms'], + 'visited_cells': stats['visited_cells'], + 'path_length': stats['path_length'] + }) + print(f" {strat_name}: время={stats['time_ms']:.3f}мс, " + f"посещено={stats['visited_cells']:.0f}, " + f"длина пути={stats['path_length']:.0f}") + except Exception as e: + print(f" {strat_name}: ОШИБКА - {e}") + results.append({ + 'maze': maze_name, + 'strategy': strat_name, + 'time_ms': -1, + 'visited_cells': -1, + 'path_length': -1 + }) + + valid_results = [r for r in results if r['time_ms'] >= 0] + + with open('experiment_results_2.csv', 'w', newline='', encoding='utf-8') as f: + writer = csv.DictWriter(f, fieldnames=['maze', 'strategy', 'time_ms', 'visited_cells', 'path_length']) + writer.writeheader() + writer.writerows(valid_results) + + print("\n" + "=" * 60) + print(f"Результаты сохранены в experiment_results_2.csv") + print(f"Всего успешных экспериментов: {len(valid_results)}") + print("=" * 60) + + +if __name__ == "__main__": + main() \ No newline at end of file -- 2.43.0 From 3daef7dcc945d0aaa445f177ce8dae615ff95806 Mon Sep 17 00:00:00 2001 From: SavelevMI Date: Thu, 21 May 2026 14:23:08 +0000 Subject: [PATCH 6/7] [2] Add csv file with experemental data --- .../data/2-nd-exersize/experiment_results_2.csv | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 SavelevMI/docs/data/2-nd-exersize/experiment_results_2.csv diff --git a/SavelevMI/docs/data/2-nd-exersize/experiment_results_2.csv b/SavelevMI/docs/data/2-nd-exersize/experiment_results_2.csv new file mode 100644 index 0000000..1ec0d7e --- /dev/null +++ b/SavelevMI/docs/data/2-nd-exersize/experiment_results_2.csv @@ -0,0 +1,13 @@ +maze,strategy,time_ms,visited_cells,path_length +Small (10x6),BFS,0.06943320004211273,28.0,12.0 +Small (10x6),DFS,0.021452600049087778,18.0,12.0 +Small (10x6),AStar,0.11244040006204159,28.0,12.0 +Medium (10x10),BFS,0.010759200085885823,10.0,5.0 +Medium (10x10),DFS,0.017673199999990175,13.0,9.0 +Medium (10x10),AStar,0.012486999912653118,5.0,5.0 +Large (20x20),BFS,0.042921000022033695,30.0,11.0 +Large (20x20),DFS,0.051109400010318495,29.0,15.0 +Large (20x20),AStar,0.058695200004876824,24.0,11.0 +Empty (15x15),BFS,0.06296379997365875,55.0,10.0 +Empty (15x15),DFS,0.10542620011619874,130.0,58.0 +Empty (15x15),AStar,0.024648199996590847,10.0,10.0 -- 2.43.0 From 3672b4f4d0ef9f378f3f9de69e1edd11c23ed34f Mon Sep 17 00:00:00 2001 From: SavelevMI Date: Thu, 21 May 2026 14:26:58 +0000 Subject: [PATCH 7/7] [2] Add report for 2-nd Zadacha --- SavelevMI/docs/report-2.md | 66 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 SavelevMI/docs/report-2.md diff --git a/SavelevMI/docs/report-2.md b/SavelevMI/docs/report-2.md new file mode 100644 index 0000000..577fe18 --- /dev/null +++ b/SavelevMI/docs/report-2.md @@ -0,0 +1,66 @@ +# Отчёт по лабораторной работе №2 «Поиск выхода из лабиринта» + +## Цель работы + +Разработать программу для поиска выхода из лабиринта с возможностью выбора алгоритма (BFS, DFS, A*), визуализацией и экспериментальным сравнением. Применить минимум 3 паттерна проектирования. + +## Использованные паттерны + +**1. Builder (Строитель)** – загрузка лабиринта из файла. Скрывает парсинг символов (#, S, E), проверку наличия ровно одного входа и выхода. При добавлении нового формата (JSON, XML) достаточно реализовать новый строитель. + +**2. Strategy (Стратегия)** – алгоритмы поиска пути. BFS, DFS и A* реализуют общий интерфейс. Класс MazeSolver переключает стратегию одной строкой. Новый алгоритм добавляется без изменения существующего кода. + +**3. Observer (Наблюдатель)** – консольная визуализация. MazeSolver уведомляет наблюдателей о событиях (найден путь, загружен лабиринт). Позволяет легко заменить консоль на графический интерфейс. + +**4. Command (Команда)** – пошаговое управление игроком. MoveCommand хранит направление и позволяет отменить ход (undo). История команд в стеке даёт откат действий. + +## Результаты экспериментов + +**Условия**: 4 лабиринта, 5 запусков на алгоритм, замеры времени (мс), посещённых клеток и длины пути. + +| Лабиринт | Алгоритм | Время (мс) | Посещено | Длина пути | +|----------|----------|------------|----------|------------| +| Small (10×6) | BFS | 0.069 | 28 | 12 | +| Small (10×6) | DFS | 0.021 | 18 | 12 | +| Small (10×6) | A* | 0.112 | 28 | 12 | +| Medium (10×10) | BFS | 0.011 | 10 | 5 | +| Medium (10×10) | DFS | 0.018 | 13 | 9 | +| Medium (10×10) | A* | 0.012 | 5 | 5 | +| Large (20×20) | BFS | 0.043 | 30 | 11 | +| Large (20×20) | DFS | 0.051 | 29 | 15 | +| Large (20×20) | A* | 0.059 | 24 | 11 | +| Empty (15×15) | BFS | 0.063 | 55 | 10 | +| Empty (15×15) | DFS | 0.105 | 130 | 58 | +| Empty (15×15) | A* | 0.025 | 10 | 10 | + +**Лабиринт без выхода** – Builder корректно выбросил исключение (S=1, E=0). + +## Анализ + +**BFS**: всегда находит кратчайший путь, но исследует много клеток. На Empty посетил 55 клеток (A* – 10). + +**DFS**: самый быстрый на Small (0.021 мс), но непредсказуем. На Empty путь оказался в 5.8 раз длиннее оптимального (58 против 10). Не подходит для навигации. + +**A***: стабильно даёт кратчайший путь и минимум посещённых клеток. На Medium посетил 5 клеток (ровно длина пути), на Empty – 10 против 55 у BFS. Лёгкое замедление на Small (0.112 мс) окупается эффективностью. + +**Ключевые выводы**: +- На пустом поле A* в 5.5 раз быстрее BFS и в 4.2 раза быстрее DFS +- DFS на пустом поле заблудился и прошёл 130 клеток вместо 10 +- На Medium A* идеален – 5 посещённых клеток при длине пути 5 + +## Выводы по паттернам + +**Builder** – спас от падения на лабиринте без выхода, валидация на месте. **Strategy** – переключение алгоритмов заняло одну строку, сравнение тривиально. **Observer** – визуализация не засоряет код поиска. **Command** – undo реализован без изменения класса игрока. + +Без паттернов пришлось бы переписывать код при каждом изменении формата, алгоритма или способа вывода. + +## Рекомендации + +| Сценарий | Алгоритм | +|----------|----------| +| Нужен кратчайший путь + есть эвристика | A* | +| Нужен кратчайший путь + нет эвристики | BFS | +| Любой путь + экономия памяти | DFS | +| Пустой лабиринт | A* (в 5 раз быстрее BFS) | + +**Итог**: для большинства задач оптимален **A*** – кратчайший путь и минимум посещений. BFS – резервный вариант. DFS – только при жёсткой нехватке памяти. \ No newline at end of file -- 2.43.0