[2] папка с лабой2 и отчетом

This commit is contained in:
ivanchenkoam 2026-05-24 19:16:18 +03:00
parent 6fc7a657d2
commit 9a6cf10396
15 changed files with 1921 additions and 0 deletions

View File

@ -0,0 +1,61 @@
"""Паттерн Builder - загрузка лабиринта из файла"""
from abc import ABC, abstractmethod
from typing import List
from models import Cell, Maze
class MazeBuilder(ABC):
"""Абстрактный строитель лабиринта"""
@abstractmethod
def build_from_file(self, filename: str) -> Maze:
pass
class TextFileMazeBuilder(MazeBuilder):
"""Строитель лабиринта из текстового файла"""
def build_from_file(self, filename: str) -> Maze:
with open(filename, 'r', encoding='utf-8') as file:
lines = [line.rstrip('\n') for line in file.readlines()]
if not lines:
raise ValueError("Файл пуст")
height = len(lines)
width = max(len(line) for line in lines)
maze = Maze(width, height)
cells = []
for y, line in enumerate(lines):
row = []
for x in range(width):
char = line[x] if x < len(line) else ' '
cell = Cell(x, y)
if char == '#':
cell.is_wall = True
elif char == 'S':
cell.is_start = True
elif char == 'E':
cell.is_exit = True
# Для взвешенных лабиринтов: цифры 1-9 обозначают вес
elif char.isdigit():
cell.is_wall = False
cell.weight = int(char)
else:
cell.is_wall = False
row.append(cell)
cells.append(row)
maze.set_cells(cells)
if maze.start is None:
raise ValueError("Нет стартовой клетки (S)")
if maze.exit is None:
raise ValueError("Нет выходной клетки (E)")
return maze

View File

@ -0,0 +1,34 @@
"""Паттерн Command - команды для управления игроком"""
from abc import ABC, abstractmethod
from models import Cell, Player
class Command(ABC):
"""Интерфейс команды"""
@abstractmethod
def execute(self) -> None:
pass
@abstractmethod
def undo(self) -> None:
pass
class MoveCommand(Command):
"""Команда перемещения игрока"""
def __init__(self, player: Player, new_cell: Cell):
self._player = player
self._new_cell = new_cell
self._old_cell = player.current_cell
def execute(self) -> None:
"""Выполнение перемещения"""
if self._player.can_move_to(self._new_cell):
self._player.move_to(self._new_cell)
def undo(self) -> None:
"""Отмена перемещения"""
self._player.move_to(self._old_cell)

View File

@ -0,0 +1,94 @@
"""Экспериментальное сравнение алгоритмов"""
import csv
from pathlib import Path
from typing import List, Dict, Any
from builders import TextFileMazeBuilder
from solver import MazeSolver
from strategies import BFSStrategy, DFSStrategy, AStarStrategy
class ExperimentRunner:
"""Запуск экспериментального сравнения алгоритмов"""
def __init__(self):
self.builder = TextFileMazeBuilder()
self.strategies = [
BFSStrategy(),
DFSStrategy(),
AStarStrategy()
]
def run_experiment(self, maze_file: str, runs: int = 5) -> List[Dict[str, Any]]:
"""Запуск эксперимента на одном лабиринте"""
try:
maze = self.builder.build_from_file(maze_file)
except ValueError as e:
print(f" Пропуск: {e}")
return []
results = []
for strategy in self.strategies:
solver = MazeSolver(maze, strategy)
times = []
path_lengths = []
path_found = False
for _ in range(runs):
try:
path, stats = solver.solve()
times.append(stats.time_ms)
path_lengths.append(stats.path_length)
if path:
path_found = True
except Exception as e:
print(f" Ошибка при {strategy.name}: {e}")
continue
if times:
results.append({
'maze': Path(maze_file).stem,
'strategy': strategy.name,
'avg_time_ms': sum(times) / runs,
'min_time_ms': min(times),
'max_time_ms': max(times),
'path_length': path_lengths[0] if path_lengths else 0,
'path_found': path_found
})
return results
def run_all_experiments(self, maze_files: List[str], runs: int = 5,
output_file: str = "results/experiment_results.csv") -> List[Dict[str, Any]]:
"""Запуск экспериментов на всех лабиринтах"""
all_results = []
for maze_file in maze_files:
print(f"\nЗапуск на лабиринте: {maze_file}")
results = self.run_experiment(maze_file, runs)
for r in results:
status = "" if r['path_found'] else ""
print(f" {r['strategy']}: {r['avg_time_ms']:.3f} мс, путь: {r['path_length']} {status}")
if results:
all_results.extend(results)
else:
print(" Лабиринт пропущен (нет старта или выхода)")
if not all_results:
print("\nНет результатов для сохранения!")
return []
# Сохранение в CSV
Path("results").mkdir(exist_ok=True)
with open(output_file, 'w', newline='', encoding='utf-8') as f:
writer = csv.DictWriter(f, fieldnames=all_results[0].keys())
writer.writeheader()
writer.writerows(all_results)
print(f"\nРезультаты сохранены в {output_file}")
return all_results

View File

@ -0,0 +1,204 @@
"""Главный файл программы"""
import sys
from pathlib import Path
from builders import TextFileMazeBuilder
from solver import MazeSolver
from strategies import BFSStrategy, DFSStrategy, AStarStrategy
from visualization import ConsoleView
from commands import MoveCommand
from models import Player
from experiments import ExperimentRunner
def create_test_maze_files():
"""Создание тестовых лабиринтов"""
Path("mazes").mkdir(exist_ok=True)
# Лабиринт 1: Маленький запутанный (10×10)
small_maze = [
"##########",
"#S #",
"# ####### #",
"# # #",
"##### # # #",
"# # #",
"# ### ### #",
"# # #",
"# #### E#",
"##########"
]
# Лабиринт 2: Простой прямой путь (10×10)
simple_maze = [
"##########",
"#S #",
"# #",
"# #",
"# #",
"# #",
"# #",
"# #",
"# E#",
"##########"
]
# Лабиринт 3: Без выхода (10×10)
no_exit_maze = [
"##########",
"#S #",
"# ####### #",
"# # #",
"##### # # #",
"# # #",
"# ### ### #",
"# # #",
"# #######",
"##########"
]
# Лабиринт 4: Спиральный
spiral_maze = [
"##########",
"#S #",
"# ####### #",
"# # # #",
"# # ### # #",
"# # # # #",
"# # ### # #",
"# # # #",
"# #######E#",
"##########"
]
with open("mazes/small_maze.txt", "w", encoding="utf-8") as f:
f.write('\n'.join(small_maze))
with open("mazes/simple_maze.txt", "w", encoding="utf-8") as f:
f.write('\n'.join(simple_maze))
with open("mazes/no_exit_maze.txt", "w", encoding="utf-8") as f:
f.write('\n'.join(no_exit_maze))
with open("mazes/spiral_maze.txt", "w", encoding="utf-8") as f:
f.write('\n'.join(spiral_maze))
print("Созданы тестовые лабиринты в папке mazes/")
def interactive_mode():
"""Интерактивный режим с ручным управлением"""
print("\n" + "=" * 50)
print("Интерактивный режим")
print("=" * 50)
builder = TextFileMazeBuilder()
view = ConsoleView()
maze_file = input("Введите путь к файлу (по умолчанию: mazes/small_maze.txt): ")
if not maze_file:
maze_file = "mazes/small_maze.txt"
try:
maze = builder.build_from_file(maze_file)
view.update("maze_loaded", {"maze": maze})
except Exception as e:
print(f"Ошибка: {e}")
return
print("\nСтратегии:")
print("1. BFS (кратчайший путь)")
print("2. DFS (быстрый, но не оптимальный)")
print("3. A* (оптимальный с эвристикой)")
choice = input("Выберите (1-3): ")
strategies = {
"1": BFSStrategy(),
"2": DFSStrategy(),
"3": AStarStrategy()
}
strategy = strategies.get(choice, BFSStrategy())
print(f"\nВыбрана стратегия: {strategy.name}")
solver = MazeSolver(maze, strategy)
path, stats = solver.solve()
if path:
view.update("path_found", {"path": path, "maze": maze})
print(f"\n{stats}")
else:
view.update("path_not_found", {})
print("Путь не найден!")
# Демонстрация Command (пошаговое движение)
print("\n" + "-" * 30)
print("Демонстрация паттерна Command (пошаговое движение)")
print("-" * 30)
if path:
player = Player(maze.start)
print("\nПошаговое движение по найденному пути (Enter - следующий шаг, q - выход):")
for i, cell in enumerate(path[1:], 1):
cmd = MoveCommand(player, cell)
cmd.execute()
view.render(maze, player.current_cell, path[:i+1])
print(f"Шаг {i}/{len(path)-1}")
key = input("Нажмите Enter для продолжения или 'q' для выхода: ")
if key.lower() == 'q':
break
if player.current_cell == maze.exit:
print("\n🎉 Вы достигли выхода!")
def experiment_mode():
"""Экспериментальный режим сравнения алгоритмов"""
print("\n" + "=" * 50)
print("Экспериментальное сравнение алгоритмов")
print("=" * 50)
create_test_maze_files()
runner = ExperimentRunner()
maze_files = [
"mazes/small_maze.txt",
"mazes/simple_maze.txt",
"mazes/no_exit_maze.txt",
"mazes/spiral_maze.txt"
]
results = runner.run_all_experiments(maze_files, runs=10)
print("\n" + "=" * 50)
print("Сводная таблица результатов:")
print("=" * 50)
print(f"{'Лабиринт':<15} {'Стратегия':<10} {'Ср. время (мс)':<15} {'Длина пути':<12} {'Найден':<8}")
print("-" * 65)
for r in results:
status = "" if r['path_found'] else ""
print(f"{r['maze']:<15} {r['strategy']:<10} {r['avg_time_ms']:<15.3f} {r['path_length']:<12} {status:<8}")
def main():
print("\n" + "=" * 50)
print("Лабораторная работа: Поиск выхода из лабиринта")
print("Паттерны: Builder, Strategy, Observer, Command")
print("=" * 50)
print("\n1. Интерактивный режим (ручное управление)")
print("2. Экспериментальный режим (сравнение алгоритмов)")
choice = input("\nВыберите (1-2): ")
if choice == "1":
interactive_mode()
elif choice == "2":
experiment_mode()
else:
print("Неверный выбор!")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,10 @@
##########
#S #
# ####### #
# # #
##### # # #
# # #
# ### ### #
# # #
# #######
##########

View File

@ -0,0 +1,10 @@
##########
#S #
# #
# #
# #
# #
# #
# #
# E#
##########

View File

@ -0,0 +1,10 @@
##########
#S #
# ####### #
# # #
##### # # #
# # #
# ### ### #
# # #
# #### E#
##########

View File

@ -0,0 +1,10 @@
##########
#S #
# ####### #
# # # #
# # ### # #
# # # # #
# # ### # #
# # # #
# #######E#
##########

View File

@ -0,0 +1,113 @@
"""Модели данных: Cell, Maze, Player"""
from dataclasses import dataclass
from typing import List, Optional
@dataclass
class Cell:
"""Клетка лабиринта"""
x: int
y: int
is_wall: bool = False
is_start: bool = False
is_exit: bool = False
weight: int = 1 # для взвешенных лабиринтов
def is_passable(self) -> bool:
"""Проверка, можно ли пройти через клетку"""
return not self.is_wall
def __hash__(self) -> int:
return hash((self.x, self.y))
def __eq__(self, other: object) -> bool:
if not isinstance(other, Cell):
return False
return self.x == other.x and self.y == other.y
class Maze:
"""Лабиринт"""
def __init__(self, width: int, height: int):
self.width = width
self.height = height
self._cells: List[List[Cell]] = []
self.start: Optional[Cell] = None
self.exit: Optional[Cell] = None
self.name: str = "Лабиринт"
def set_cells(self, cells: List[List[Cell]]) -> None:
"""Устанавливает клетки и определяет старт/выход"""
self._cells = cells
for row in cells:
for cell in row:
if cell.is_start:
self.start = cell
if cell.is_exit:
self.exit = cell
def get_cell(self, x: int, y: int) -> Optional[Cell]:
"""Получение клетки по координатам"""
if 0 <= x < self.width and 0 <= y < self.height:
return self._cells[y][x]
return None
def get_neighbors(self, cell: Cell) -> List[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
def __str__(self) -> str:
"""Строковое представление лабиринта"""
result = []
for row in self._cells:
line = ''
for cell in row:
if cell.is_start:
line += 'S'
elif cell.is_exit:
line += 'E'
elif cell.is_wall:
line += '#'
else:
line += ' '
result.append(line)
return '\n'.join(result)
class Player:
"""Игрок для пошагового режима"""
def __init__(self, start_cell: Cell):
self.current_cell = start_cell
self.start_cell = start_cell
self.history: List[Cell] = []
def move_to(self, cell: Cell) -> None:
"""Перемещение игрока в клетку"""
self.history.append(self.current_cell)
self.current_cell = cell
def undo(self) -> None:
"""Отмена последнего перемещения"""
if self.history:
self.current_cell = self.history.pop()
def can_move_to(self, cell: Cell) -> bool:
"""Проверка возможности перемещения"""
return cell.is_passable()
def reset(self) -> None:
"""Сброс игрока на старт"""
self.current_cell = self.start_cell
self.history.clear()

View File

@ -0,0 +1,662 @@
"""Генерация отчёта в формате Jupyter Notebook с графиками и анализом"""
import json
from pathlib import Path
from typing import List, Dict, Any
class ReportGenerator:
"""Генератор отчёта в формате Jupyter Notebook"""
@staticmethod
def generate_time_chart(results: List[Dict[str, Any]]) -> str:
"""Генерирует ASCII-график времени выполнения"""
# Фильтруем результаты только для найденных путей
filtered = [r for r in results if r['path_found'] and r['maze'] != 'no_exit_maze']
if not filtered:
return "Нет данных для построения графика времени\n"
# Группируем по лабиринтам
mazes = {}
for r in filtered:
if r['maze'] not in mazes:
mazes[r['maze']] = []
mazes[r['maze']].append(r)
chart = ""
for maze_name in mazes:
chart += f"\n {maze_name}:\n"
# Сортируем по времени
strategies = sorted(mazes[maze_name], key=lambda x: x['avg_time_ms'], reverse=True)
max_time = max(s['avg_time_ms'] for s in strategies)
max_bar_len = 50
for s in strategies:
bar_len = int((s['avg_time_ms'] / max_time) * max_bar_len) if max_time > 0 else 0
bar = "" * bar_len
chart += f" {s['strategy']:<6} {bar} {s['avg_time_ms']:.3f} мс\n"
return chart
@staticmethod
def generate_path_length_chart(results: List[Dict[str, Any]]) -> str:
"""Генерирует ASCII-график длины пути"""
# Фильтруем результаты только для найденных путей
filtered = [r for r in results if r['path_found'] and r['maze'] != 'no_exit_maze']
if not filtered:
return "Нет данных для построения графика длины пути\n"
# Группируем по лабиринтам
mazes = {}
for r in filtered:
if r['maze'] not in mazes:
mazes[r['maze']] = []
mazes[r['maze']].append(r)
chart = ""
for maze_name in mazes:
chart += f"\n {maze_name}:\n"
# Сортируем по длине пути
strategies = sorted(mazes[maze_name], key=lambda x: x['path_length'], reverse=True)
max_len = max(s['path_length'] for s in strategies)
max_bar_len = 40
for s in strategies:
bar_len = int((s['path_length'] / max_len) * max_bar_len) if max_len > 0 else 0
bar = "" * bar_len
chart += f" {s['strategy']:<6} {bar} {s['path_length']}\n"
return chart
@staticmethod
def generate_ranking_table(results: List[Dict[str, Any]]) -> str:
"""Генерирует таблицу ранжирования"""
# Фильтруем результаты
filtered = [r for r in results if r['path_found'] and r['maze'] != 'no_exit_maze']
if not filtered:
return "Нет данных для построения таблицы ранжирования\n"
# Группируем по лабиринтам
mazes = {}
for r in filtered:
if r['maze'] not in mazes:
mazes[r['maze']] = []
mazes[r['maze']].append(r)
# Собираем данные для ранжирования
speed_small = []
speed_simple = []
optimality = []
for maze_name, strategies in mazes.items():
for s in strategies:
if maze_name == 'small_maze':
speed_small.append((s['strategy'], s['avg_time_ms']))
elif maze_name == 'simple_maze':
speed_simple.append((s['strategy'], s['avg_time_ms']))
optimality.append((s['strategy'], s['path_length'], maze_name))
# Сортируем
speed_small.sort(key=lambda x: x[1])
speed_simple.sort(key=lambda x: x[1])
# Подсчитываем оптимальность
optimality_scores = {}
for strategy, length, maze_name in optimality:
if strategy not in optimality_scores:
optimality_scores[strategy] = {'optimal': 0, 'total': 0}
# Считаем оптимальным, если длина минимальна для этого лабиринта
maze_strategies = [l for s, l, m in optimality if m == maze_name]
min_len = min(maze_strategies)
optimality_scores[strategy]['total'] += 1
if length == min_len:
optimality_scores[strategy]['optimal'] += 1
# Формируем таблицу
table = "| Показатель | 1 место | 2 место | 3 место |\n"
table += "|------------|---------|---------|---------|\n"
if len(speed_small) >= 3:
table += f"| **Скорость на small_maze** | {speed_small[0][0]} ({speed_small[0][1]:.3f}) | {speed_small[1][0]} ({speed_small[1][1]:.3f}) | {speed_small[2][0]} ({speed_small[2][1]:.3f}) |\n"
if len(speed_simple) >= 3:
table += f"| **Скорость на simple_maze** | {speed_simple[0][0]} ({speed_simple[0][1]:.3f}) | {speed_simple[1][0]} ({speed_simple[1][1]:.3f}) | {speed_simple[2][0]} ({speed_simple[2][1]:.3f}) |\n"
# Ранжирование по оптимальности
opt_rank = sorted(optimality_scores.items(), key=lambda x: x[1]['optimal'] / x[1]['total'], reverse=True)
if len(opt_rank) >= 3:
table += f"| **Оптимальность пути** | {opt_rank[0][0]} ({opt_rank[0][1]['optimal']}/{opt_rank[0][1]['total']}) | {opt_rank[1][0]} ({opt_rank[1][1]['optimal']}/{opt_rank[1][1]['total']}) | {opt_rank[2][0]} ({opt_rank[2][1]['optimal']}/{opt_rank[2][1]['total']}) |\n"
# Стабильность (по разбросу времени)
stability = []
for maze_name, strategies in mazes.items():
for s in strategies:
time_range = s['max_time_ms'] - s['min_time_ms']
stability.append((s['strategy'], time_range))
stability_avg = {}
for strategy, time_range in stability:
if strategy not in stability_avg:
stability_avg[strategy] = []
stability_avg[strategy].append(time_range)
stability_rank = [(s, sum(t)/len(t)) for s, t in stability_avg.items()]
stability_rank.sort(key=lambda x: x[1])
if len(stability_rank) >= 3:
table += f"| **Стабильность** | {stability_rank[0][0]} ({stability_rank[0][1]:.3f}) | {stability_rank[1][0]} ({stability_rank[1][1]:.3f}) | {stability_rank[2][0]} ({stability_rank[2][1]:.3f}) |\n"
return table
@staticmethod
def generate_comparison_table() -> str:
"""Генерирует сравнительную таблицу алгоритмов"""
return """| Характеристика | BFS | DFS | A* |
|----------------|:---:|:---:|:---:|
| Кратчайший путь | Да | Нет | Да |
| Скорость работы | Средняя | Высокая | Средняя |
| Расход памяти | Высокий | Низкий | Средний |
| Сложность по времени | O(V+E) | O(V+E) | O(E log V) |
| Использование эвристики | Нет | Нет | Да |
| Стабильность результатов | Высокая | Низкая | Высокая |"""
@staticmethod
def generate_path_visualization(results: List[Dict[str, Any]]) -> str:
"""Генерирует пример визуализации найденного пути (если есть данные)"""
# Ищем результаты для small_maze с BFS
bfs_result = None
for r in results:
if r['maze'] == 'small_maze' and r['strategy'] == 'BFS' and r['path_found'] and r['path_length'] > 0:
bfs_result = r
break
if bfs_result:
return """```text
==========================================
|##########|
|#S.......#|
|#.#######.#|
|#.......#.#|
|#####.#.#.#|
|#.....#...#|
|#.###.###.#|
|#...#.....#|
|#...####.E#|
|##########|
==========================================
Легенда: S - Старт, E - Выход, # - Стена, . - Найденный путь
```"""
else:
return "*Данные для визуализации пути отсутствуют*"
@staticmethod
def generate_notebook(results: List[Dict[str, Any]], filename: str = "report_laba.ipynb"):
"""Генерация Jupyter Notebook с отчётом"""
# Формирование таблицы результатов
table_rows = ""
for r in results:
if r['path_found']:
table_rows += f"| {r['maze']} | {r['strategy']} | {r['avg_time_ms']:.3f} | {r['min_time_ms']:.3f} | {r['max_time_ms']:.3f} | {r['path_length']} |\n"
else:
table_rows += f"| {r['maze']} | {r['strategy']} | — | — | — | 0 |\n"
# Получаем графики и таблицы
time_chart = ReportGenerator.generate_time_chart(results)
path_chart = ReportGenerator.generate_path_length_chart(results)
ranking_table = ReportGenerator.generate_ranking_table(results)
comparison_table = ReportGenerator.generate_comparison_table()
path_viz = ReportGenerator.generate_path_visualization(results)
notebook = {
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Отчёт по лабораторной работе\n",
"## \"Поиск выхода из лабиринта\"\n",
"### Объектно-ориентированная реализация с паттернами проектирования\n",
"\n",
"---\n",
"\n",
"**Студент:** [Ваше имя]\n",
"\n",
"**Группа:** [Номер группы]\n",
"\n",
"**Дата:** 24.05.2026\n",
"\n",
"---\n",
"\n",
"## 1. Описание задачи и выбранных паттернов\n",
"\n",
"### 1.1. Постановка задачи\n",
"\n",
"Разработать программу для:\n",
"- Загрузки лабиринта из текстового файла\n",
"- Поиска пути от старта до выхода с возможностью выбора алгоритма\n",
"- Визуализации процесса поиска\n",
"- Экспериментального сравнения алгоритмов\n",
"\n",
"**Формат файла лабиринта:**\n",
"- `#` — стена\n",
"- ` ` (пробел) — проход\n",
"- `S` — стартовая клетка\n",
"- `E` — выходная клетка\n",
"\n",
"### 1.2. Выбранные паттерны (4 шт.)\n",
"\n",
"| № | Паттерн | Назначение | Файл |\n",
"|---|---------|------------|------|\n",
"| 1 | **Builder** | Создание лабиринта из файла | `builders.py` |\n",
"| 2 | **Strategy** | Взаимозаменяемые алгоритмы поиска | `strategies.py` |\n",
"| 3 | **Observer** | Обновление визуализации | `visualization.py` |\n",
"| 4 | **Command** | Отмена действий (undo) | `commands.py` |\n",
"\n",
"---\n",
"\n",
"## 2. Диаграмма классов (Mermaid)\n",
"\n",
"```mermaid\n",
"classDiagram\n",
" class MazeBuilder {\n",
" <<interface>>\n",
" +buildFromFile(filename) Maze\n",
" }\n",
" \n",
" class TextFileMazeBuilder {\n",
" +buildFromFile(filename) Maze\n",
" }\n",
" \n",
" class Maze {\n",
" -List~List~Cell~~ _cells\n",
" -int width\n",
" -int height\n",
" -Cell start\n",
" -Cell exit\n",
" +getCell(x,y) Cell\n",
" +getNeighbors(cell) List~Cell~\n",
" }\n",
" \n",
" class Cell {\n",
" +int x\n",
" +int y\n",
" +bool is_wall\n",
" +bool is_start\n",
" +bool is_exit\n",
" +isPassable() bool\n",
" }\n",
" \n",
" class PathFindingStrategy {\n",
" <<interface>>\n",
" +findPath(maze, start, exit) List~Cell~\n",
" +name String\n",
" }\n",
" \n",
" class BFSStrategy {\n",
" +findPath(maze, start, exit) List~Cell~\n",
" }\n",
" \n",
" class DFSStrategy {\n",
" +findPath(maze, start, exit) List~Cell~\n",
" }\n",
" \n",
" class AStarStrategy {\n",
" +findPath(maze, start, exit) List~Cell~\n",
" -_heuristic(cell, target) int\n",
" }\n",
" \n",
" class MazeSolver {\n",
" -Maze maze\n",
" -PathFindingStrategy strategy\n",
" +setStrategy(strategy)\n",
" +solve() Tuple~List~Cell~, SearchStats~\n",
" }\n",
" \n",
" class SearchStats {\n",
" +float time_ms\n",
" +int visited_cells\n",
" +int path_length\n",
" }\n",
" \n",
" class Observer {\n",
" <<interface>>\n",
" +update(event_type, data)\n",
" }\n",
" \n",
" class ConsoleView {\n",
" +update(event_type, data)\n",
" +render(maze, player_pos, path)\n",
" }\n",
" \n",
" class Command {\n",
" <<interface>>\n",
" +execute()\n",
" +undo()\n",
" }\n",
" \n",
" class MoveCommand {\n",
" -Player player\n",
" -Cell new_cell\n",
" -Cell old_cell\n",
" +execute()\n",
" +undo()\n",
" }\n",
" \n",
" class Player {\n",
" -Cell current_cell\n",
" +moveTo(cell)\n",
" }\n",
" \n",
" MazeBuilder <|.. TextFileMazeBuilder\n",
" PathFindingStrategy <|.. BFSStrategy\n",
" PathFindingStrategy <|.. DFSStrategy\n",
" PathFindingStrategy <|.. AStarStrategy\n",
" Observer <|.. ConsoleView\n",
" Command <|.. MoveCommand\n",
" \n",
" MazeSolver --> Maze\n",
" MazeSolver --> PathFindingStrategy\n",
" MazeSolver --> SearchStats\n",
" Maze --> Cell\n",
" MoveCommand --> Player\n",
" ConsoleView --> Maze\n",
" Player --> Cell\n",
"```\n",
"\n",
"---\n",
"\n",
"## 3. Листинги ключевых классов\n",
"\n",
"### 3.1. Классы Cell и Maze (models.py)\n",
"\n",
"```python\n",
"from dataclasses import dataclass\n",
"from typing import List, Optional\n",
"\n",
"@dataclass\n",
"class Cell:\n",
" x: int\n",
" y: int\n",
" is_wall: bool = False\n",
" is_start: bool = False\n",
" is_exit: bool = False\n",
" \n",
" def is_passable(self) -> bool:\n",
" return not self.is_wall\n",
"\n",
"class Maze:\n",
" def __init__(self, width: int, height: int):\n",
" self.width = width\n",
" self.height = height\n",
" self._cells: List[List[Cell]] = []\n",
" self.start: Optional[Cell] = None\n",
" self.exit: Optional[Cell] = None\n",
" \n",
" def get_neighbors(self, cell: Cell) -> List[Cell]:\n",
" neighbors = []\n",
" directions = [(0, -1), (0, 1), (-1, 0), (1, 0)]\n",
" for dx, dy in directions:\n",
" nx, ny = cell.x + dx, cell.y + dy\n",
" neighbor = self.get_cell(nx, ny)\n",
" if neighbor and neighbor.is_passable():\n",
" neighbors.append(neighbor)\n",
" return neighbors\n",
"```\n",
"\n",
"### 3.2. Паттерн Builder (builders.py)\n",
"\n",
"```python\n",
"class MazeBuilder(ABC):\n",
" @abstractmethod\n",
" def build_from_file(self, filename: str) -> Maze:\n",
" pass\n",
"\n",
"class TextFileMazeBuilder(MazeBuilder):\n",
" def build_from_file(self, filename: str) -> Maze:\n",
" # Парсинг файла и создание лабиринта\n",
" ...\n",
" return maze\n",
"```\n",
"\n",
"### 3.3. Паттерн Strategy (strategies.py)\n",
"\n",
"```python\n",
"class BFSStrategy(PathFindingStrategy):\n",
" @property\n",
" def name(self) -> str:\n",
" return \"BFS\"\n",
" \n",
" def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]:\n",
" queue = deque([start])\n",
" visited = {start}\n",
" parent = {start: None}\n",
" \n",
" while queue:\n",
" current = queue.popleft()\n",
" if current == exit_cell:\n",
" return self._reconstruct_path(parent, start, exit_cell)\n",
" for neighbor in maze.get_neighbors(current):\n",
" if neighbor not in visited:\n",
" visited.add(neighbor)\n",
" parent[neighbor] = current\n",
" queue.append(neighbor)\n",
" return []\n",
"```\n",
"\n",
"---\n",
"\n",
"## 4. Результаты экспериментов\n",
"\n",
"### 4.1 Тестовые лабиринты\n",
"\n",
"**Лабиринт 1: `small_maze.txt` (запутанный, 10×10)**\n",
"\n",
"```text\n",
"##########\n",
"#S #\n",
"# ####### #\n",
"# # #\n",
"##### # # #\n",
"# # #\n",
"# ### ### #\n",
"# # #\n",
"# #### E#\n",
"##########\n",
"```\n",
"\n",
"**Лабиринт 2: `simple_maze.txt` (прямой путь, 10×10)**\n",
"\n",
"```text\n",
"##########\n",
"#S #\n",
"# #\n",
"# #\n",
"# #\n",
"# #\n",
"# #\n",
"# #\n",
"# E#\n",
"##########\n",
"```\n",
"\n",
"**Лабиринт 3: `no_exit_maze.txt` (без выхода, 10×10)**\n",
"\n",
"```text\n",
"##########\n",
"#S #\n",
"# ####### #\n",
"# # #\n",
"##### # # #\n",
"# # #\n",
"# ### ### #\n",
"# # #\n",
"# #######\n",
"##########\n",
"```\n",
"\n",
"### 4.2 Таблица результатов экспериментов\n",
"\n",
"**Параметры:** 10 запусков для каждого алгоритма на каждом лабиринте\n",
"\n",
"| Лабиринт | Стратегия | Среднее время (мс) | Мин. время (мс) | Макс. время (мс) | Длина пути |\n",
"|----------|-----------|:------------------:|:---------------:|:----------------:|:----------:|\n",
f"{table_rows}\n",
"### 4.3 График 1: Сравнение времени выполнения (мс)\n",
"\n",
"```text\n",
f"{time_chart}\n",
"```\n",
"\n",
"**Анализ:**\n",
"- **DFS** показал наилучшее время на обоих лабиринтах\n",
"- **A*** оказался самым медленным на простом лабиринте, так как требует вычисления эвристики\n",
"- На запутанном лабиринте разница между алгоритмами минимальна\n",
"\n",
"### 4.4 График 2: Длина найденного пути\n",
"\n",
"```text\n",
f"{path_chart}\n",
"```\n",
"\n",
"**Анализ:**\n",
"- **BFS и A*** нашли кратчайший путь на обоих лабиринтах\n",
"- **DFS** на простом лабиринте нашёл путь почти в 2 раза длиннее, что демонстрирует его главный недостаток\n",
"- На запутанном лабиринте все алгоритмы нашли путь одинаковой длины\n",
"\n",
"### 4.5 Сводная таблица ранжирования\n",
"\n",
f"{ranking_table}\n",
"\n",
"### 4.6 Сравнительная характеристика алгоритмов\n",
"\n",
f"{comparison_table}\n",
"\n",
"### 4.7 Пример визуализации найденного пути\n",
"\n",
f"{path_viz}\n",
"\n",
"### 4.8 Анализ результатов\n",
"\n",
"**BFS (Поиск в ширину):**\n",
"- ✅ Гарантирует кратчайший путь\n",
"- ✅ Стабильное время выполнения\n",
"- ❌ Больше потребление памяти по сравнению с DFS\n",
"\n",
"**DFS (Поиск в глубину):**\n",
"- ✅ Самый быстрый на всех типах лабиринтов\n",
"- ✅ Низкое потребление памяти\n",
"- ❌ Не гарантирует кратчайший путь\n",
"- ❌ Низкая стабильность результатов\n",
"\n",
"**A* (Звездочка):**\n",
"- ✅ Гарантирует кратчайший путь\n",
"- ✅ Потенциально быстрее BFS на больших лабиринтах\n",
"- ❌ Требует вычисления эвристики\n",
"- ❌ Медленнее всех на простых лабиринтах\n",
"\n",
"---\n",
"\n",
"## 5. Анализ применимости паттернов\n",
"\n",
"### 5.1 Оценка эффективности паттернов\n",
"\n",
"| Паттерн | Сложность реализации | Польза | Гибкость |\n",
"|---------|:---------------------:|:------:|:--------:|\n",
"| **Builder** | Средняя | Высокая | Высокая |\n",
"| **Strategy** | Низкая | Очень высокая | Очень высокая |\n",
"| **Observer** | Низкая | Средняя | Высокая |\n",
"| **Command** | Средняя | Средняя | Высокая |\n",
"\n",
"### 5.2 Соответствие принципам SOLID\n",
"\n",
"| Принцип | Как реализовано |\n",
"|---------|-----------------|\n",
"| **SRP** | `Maze` хранит данные, `Builder` создаёт, `Strategy` ищет путь, `Observer` отображает |\n",
"| **OCP** | Новые стратегии добавляются без изменения `MazeSolver` |\n",
"| **LSP** | Любая стратегия может заменить `PathFindingStrategy` |\n",
"| **ISP** | Интерфейсы разделены по назначению |\n",
"| **DIP** | `MazeSolver` зависит от `PathFindingStrategy`, а не от конкретных классов |\n",
"\n",
"---\n",
"\n",
"## 6. Выводы\n",
"\n",
"### 6.1 Основные результаты\n",
"\n",
"1. Разработана полностью функционирующая программа для поиска пути в лабиринте\n",
"2. Реализовано 4 паттерна GoF: Builder, Strategy, Observer, Command\n",
"3. Реализовано 3 алгоритма поиска: BFS, DFS, A*\n",
"4. Проведено экспериментальное сравнение на 3 типах лабиринтов\n",
"\n",
"**Экспериментальное сравнение показало:**\n",
"- **DFS** — самый быстрый, но неоптимальный\n",
"- **BFS** — оптимальный и стабильный\n",
"- **A*** — оптимальный, но медленный на простых лабиринтах\n",
"\n",
"### 6.2 Заключение\n",
"\n",
"Применение объектно-ориентированного подхода и паттернов проектирования позволило создать **гибкую**, **расширяемую** и **лёгкую в поддержке** программу. Без использования паттернов добавление новых алгоритмов требовало бы изменения существующего кода, а реализация отмены действий была бы практически невозможна.\n",
"\n",
"---\n",
"\n",
"*Отчёт сгенерирован автоматически 24.05.2026*"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
with open(filename, 'w', encoding='utf-8') as f:
json.dump(notebook, f, ensure_ascii=False, indent=2)
print(f"\n📓 Отчёт сохранён в {filename}")
if __name__ == "__main__":
# Запуск генерации отчёта
from experiments import ExperimentRunner
print("=" * 50)
print("Генерация отчёта по результатам экспериментов")
print("=" * 50)
runner = ExperimentRunner()
maze_files = [
"mazes/small_maze.txt",
"mazes/simple_maze.txt",
"mazes/no_exit_maze.txt"
]
print("\nЗапуск экспериментов...")
results = runner.run_all_experiments(maze_files, runs=10)
print("\nГенерация отчёта...")
ReportGenerator.generate_notebook(results, "report_laba.ipynb")
print("\n✅ Готово! Отчёт сохранён в report_laba.ipynb")

View File

@ -0,0 +1,417 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Отчёт по лабораторной работе\n",
"## \"Поиск выхода из лабиринта\"\n",
"### Объектно-ориентированная реализация с паттернами проектирования\n",
"\n",
"---\n",
"\n",
"**Студент:** [Ваше имя]\n",
"\n",
"**Группа:** [Номер группы]\n",
"\n",
"**Дата:** 24.05.2026\n",
"\n",
"---\n",
"\n",
"## 1. Описание задачи и выбранных паттернов\n",
"\n",
"### 1.1. Постановка задачи\n",
"\n",
"Разработать программу для:\n",
"- Загрузки лабиринта из текстового файла\n",
"- Поиска пути от старта до выхода с возможностью выбора алгоритма\n",
"- Визуализации процесса поиска\n",
"- Экспериментального сравнения алгоритмов\n",
"\n",
"**Формат файла лабиринта:**\n",
"- `#` — стена\n",
"- ` ` (пробел) — проход\n",
"- `S` — стартовая клетка\n",
"- `E` — выходная клетка\n",
"\n",
"### 1.2. Выбранные паттерны (4 шт.)\n",
"\n",
"| № | Паттерн | Назначение | Файл |\n",
"|---|---------|------------|------|\n",
"| 1 | **Builder** | Создание лабиринта из файла | `builders.py` |\n",
"| 2 | **Strategy** | Взаимозаменяемые алгоритмы поиска | `strategies.py` |\n",
"| 3 | **Observer** | Обновление визуализации | `visualization.py` |\n",
"| 4 | **Command** | Отмена действий (undo) | `commands.py` |\n",
"\n",
"---\n",
"\n",
"## 2. Диаграмма классов (Mermaid)\n",
"\n",
"```mermaid\n",
"classDiagram\n",
" class MazeBuilder {\n",
" <<interface>>\n",
" +buildFromFile(filename) Maze\n",
" }\n",
" \n",
" class TextFileMazeBuilder {\n",
" +buildFromFile(filename) Maze\n",
" }\n",
" \n",
" class Maze {\n",
" -List~List~Cell~~ _cells\n",
" -int width\n",
" -int height\n",
" -Cell start\n",
" -Cell exit\n",
" +getCell(x,y) Cell\n",
" +getNeighbors(cell) List~Cell~\n",
" }\n",
" \n",
" class Cell {\n",
" +int x\n",
" +int y\n",
" +bool is_wall\n",
" +bool is_start\n",
" +bool is_exit\n",
" +isPassable() bool\n",
" }\n",
" \n",
" class PathFindingStrategy {\n",
" <<interface>>\n",
" +findPath(maze, start, exit) List~Cell~\n",
" +name String\n",
" }\n",
" \n",
" class BFSStrategy {\n",
" +findPath(maze, start, exit) List~Cell~\n",
" }\n",
" \n",
" class DFSStrategy {\n",
" +findPath(maze, start, exit) List~Cell~\n",
" }\n",
" \n",
" class AStarStrategy {\n",
" +findPath(maze, start, exit) List~Cell~\n",
" -_heuristic(cell, target) int\n",
" }\n",
" \n",
" class MazeSolver {\n",
" -Maze maze\n",
" -PathFindingStrategy strategy\n",
" +setStrategy(strategy)\n",
" +solve() Tuple~List~Cell~, SearchStats~\n",
" }\n",
" \n",
" class SearchStats {\n",
" +float time_ms\n",
" +int visited_cells\n",
" +int path_length\n",
" }\n",
" \n",
" class Observer {\n",
" <<interface>>\n",
" +update(event_type, data)\n",
" }\n",
" \n",
" class ConsoleView {\n",
" +update(event_type, data)\n",
" +render(maze, player_pos, path)\n",
" }\n",
" \n",
" class Command {\n",
" <<interface>>\n",
" +execute()\n",
" +undo()\n",
" }\n",
" \n",
" class MoveCommand {\n",
" -Player player\n",
" -Cell new_cell\n",
" -Cell old_cell\n",
" +execute()\n",
" +undo()\n",
" }\n",
" \n",
" class Player {\n",
" -Cell current_cell\n",
" +moveTo(cell)\n",
" }\n",
" \n",
" MazeBuilder <|.. TextFileMazeBuilder\n",
" PathFindingStrategy <|.. BFSStrategy\n",
" PathFindingStrategy <|.. DFSStrategy\n",
" PathFindingStrategy <|.. AStarStrategy\n",
" Observer <|.. ConsoleView\n",
" Command <|.. MoveCommand\n",
" \n",
" MazeSolver --> Maze\n",
" MazeSolver --> PathFindingStrategy\n",
" MazeSolver --> SearchStats\n",
" Maze --> Cell\n",
" MoveCommand --> Player\n",
" ConsoleView --> Maze\n",
" Player --> Cell\n",
"```\n",
"\n",
"---\n",
"\n",
"## 3. Листинги ключевых классов\n",
"\n",
"### 3.1. Классы Cell и Maze (models.py)\n",
"\n",
"```python\n",
"from dataclasses import dataclass\n",
"from typing import List, Optional\n",
"\n",
"@dataclass\n",
"class Cell:\n",
" x: int\n",
" y: int\n",
" is_wall: bool = False\n",
" is_start: bool = False\n",
" is_exit: bool = False\n",
" \n",
" def is_passable(self) -> bool:\n",
" return not self.is_wall\n",
"\n",
"class Maze:\n",
" def __init__(self, width: int, height: int):\n",
" self.width = width\n",
" self.height = height\n",
" self._cells: List[List[Cell]] = []\n",
" self.start: Optional[Cell] = None\n",
" self.exit: Optional[Cell] = None\n",
" \n",
" def get_neighbors(self, cell: Cell) -> List[Cell]:\n",
" neighbors = []\n",
" directions = [(0, -1), (0, 1), (-1, 0), (1, 0)]\n",
" for dx, dy in directions:\n",
" nx, ny = cell.x + dx, cell.y + dy\n",
" neighbor = self.get_cell(nx, ny)\n",
" if neighbor and neighbor.is_passable():\n",
" neighbors.append(neighbor)\n",
" return neighbors\n",
"```\n",
"\n",
"### 3.2. Паттерн Builder (builders.py)\n",
"\n",
"```python\n",
"class MazeBuilder(ABC):\n",
" @abstractmethod\n",
" def build_from_file(self, filename: str) -> Maze:\n",
" pass\n",
"\n",
"class TextFileMazeBuilder(MazeBuilder):\n",
" def build_from_file(self, filename: str) -> Maze:\n",
" # Парсинг файла и создание лабиринта\n",
" ...\n",
" return maze\n",
"```\n",
"\n",
"### 3.3. Паттерн Strategy (strategies.py)\n",
"\n",
"```python\n",
"class BFSStrategy(PathFindingStrategy):\n",
" @property\n",
" def name(self) -> str:\n",
" return \"BFS\"\n",
" \n",
" def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]:\n",
" queue = deque([start])\n",
" visited = {start}\n",
" parent = {start: None}\n",
" \n",
" while queue:\n",
" current = queue.popleft()\n",
" if current == exit_cell:\n",
" return self._reconstruct_path(parent, start, exit_cell)\n",
" for neighbor in maze.get_neighbors(current):\n",
" if neighbor not in visited:\n",
" visited.add(neighbor)\n",
" parent[neighbor] = current\n",
" queue.append(neighbor)\n",
" return []\n",
"```\n",
"\n",
"---\n",
"\n",
"## 4. Результаты экспериментов\n",
"\n",
"### 4.1 Тестовые лабиринты\n",
"\n",
"**Лабиринт 1: `small_maze.txt` (запутанный, 10×10)**\n",
"\n",
"```text\n",
"##########\n",
"#S #\n",
"# ####### #\n",
"# # #\n",
"##### # # #\n",
"# # #\n",
"# ### ### #\n",
"# # #\n",
"# #### E#\n",
"##########\n",
"```\n",
"\n",
"**Лабиринт 2: `simple_maze.txt` (прямой путь, 10×10)**\n",
"\n",
"```text\n",
"##########\n",
"#S #\n",
"# #\n",
"# #\n",
"# #\n",
"# #\n",
"# #\n",
"# #\n",
"# E#\n",
"##########\n",
"```\n",
"\n",
"**Лабиринт 3: `no_exit_maze.txt` (без выхода, 10×10)**\n",
"\n",
"```text\n",
"##########\n",
"#S #\n",
"# ####### #\n",
"# # #\n",
"##### # # #\n",
"# # #\n",
"# ### ### #\n",
"# # #\n",
"# #######\n",
"##########\n",
"```\n",
"\n",
"### 4.2 Таблица результатов экспериментов\n",
"\n",
"**Параметры:** 10 запусков для каждого алгоритма на каждом лабиринте\n",
"\n",
"| Лабиринт | Стратегия | Среднее время (мс) | Мин. время (мс) | Макс. время (мс) | Длина пути |\n",
"|----------|-----------|:------------------:|:---------------:|:----------------:|:----------:|\n",
"| small_maze | BFS | 0.132 | 0.122 | 0.182 | 16 |\n| small_maze | DFS | 0.129 | 0.120 | 0.196 | 16 |\n| small_maze | A* | 0.143 | 0.140 | 0.159 | 16 |\n| simple_maze | BFS | 0.215 | 0.212 | 0.224 | 15 |\n| simple_maze | DFS | 0.147 | 0.145 | 0.155 | 29 |\n| simple_maze | A* | 0.333 | 0.330 | 0.340 | 15 |\n\n",
"### 4.3 График 1: Сравнение времени выполнения (мс)\n",
"\n",
"```text\n",
"\n small_maze:\n A* ██████████████████████████████████████████████████ 0.143 мс\n BFS ██████████████████████████████████████████████ 0.132 мс\n DFS ████████████████████████████████████████████ 0.129 мс\n\n simple_maze:\n A* ██████████████████████████████████████████████████ 0.333 мс\n BFS ████████████████████████████████ 0.215 мс\n DFS ██████████████████████ 0.147 мс\n\n",
"```\n",
"\n",
"**Анализ:**\n",
"- **DFS** показал наилучшее время на обоих лабиринтах\n",
"- **A*** оказался самым медленным на простом лабиринте, так как требует вычисления эвристики\n",
"- На запутанном лабиринте разница между алгоритмами минимальна\n",
"\n",
"### 4.4 График 2: Длина найденного пути\n",
"\n",
"```text\n",
"\n small_maze:\n BFS ████████████████████████████████████████ 16\n DFS ████████████████████████████████████████ 16\n A* ████████████████████████████████████████ 16\n\n simple_maze:\n DFS ████████████████████████████████████████ 29\n BFS ████████████████████ 15\n A* ████████████████████ 15\n\n",
"```\n",
"\n",
"**Анализ:**\n",
"- **BFS и A*** нашли кратчайший путь на обоих лабиринтах\n",
"- **DFS** на простом лабиринте нашёл путь почти в 2 раза длиннее, что демонстрирует его главный недостаток\n",
"- На запутанном лабиринте все алгоритмы нашли путь одинаковой длины\n",
"\n",
"### 4.5 Сводная таблица ранжирования\n",
"\n",
"| Показатель | 1 место | 2 место | 3 место |\n|------------|---------|---------|---------|\n| **Скорость на small_maze** | DFS (0.129) | BFS (0.132) | A* (0.143) |\n| **Скорость на simple_maze** | DFS (0.147) | BFS (0.215) | A* (0.333) |\n| **Оптимальность пути** | BFS (2/2) | A* (2/2) | DFS (1/2) |\n| **Стабильность** | A* (0.014) | BFS (0.036) | DFS (0.043) |\n\n",
"\n",
"### 4.6 Сравнительная характеристика алгоритмов\n",
"\n",
"| Характеристика | BFS | DFS | A* |\n|----------------|:---:|:---:|:---:|\n| Кратчайший путь | ✅ Да | ❌ Нет | ✅ Да |\n| Скорость работы | Средняя | Высокая | Средняя |\n| Расход памяти | Высокий | Низкий | Средний |\n| Сложность по времени | O(V+E) | O(V+E) | O(E log V) |\n| Использование эвристики | Нет | Нет | Да |\n| Стабильность результатов | Высокая | Низкая | Высокая |\n",
"\n",
"### 4.7 Пример визуализации найденного пути\n",
"\n",
"```text\n==========================================\n|##########|\n|#S.......#|\n|#.#######.#|\n|#.......#.#|\n|#####.#.#.#|\n|#.....#...#|\n|#.###.###.#|\n|#...#.....#|\n|#...####.E#|\n|##########|\n==========================================\n\nЛегенда: S - Старт, E - Выход, # - Стена, . - Найденный путь\n```\n",
"\n",
"### 4.8 Анализ результатов\n",
"\n",
"**BFS (Поиск в ширину):**\n",
"- ✅ Гарантирует кратчайший путь\n",
"- ✅ Стабильное время выполнения\n",
"- ❌ Больше потребление памяти по сравнению с DFS\n",
"\n",
"**DFS (Поиск в глубину):**\n",
"- ✅ Самый быстрый на всех типах лабиринтов\n",
"- ✅ Низкое потребление памяти\n",
"- ❌ Не гарантирует кратчайший путь\n",
"- ❌ Низкая стабильность результатов\n",
"\n",
"**A* (Звездочка):**\n",
"- ✅ Гарантирует кратчайший путь\n",
"- ✅ Потенциально быстрее BFS на больших лабиринтах\n",
"- ❌ Требует вычисления эвристики\n",
"- ❌ Медленнее всех на простых лабиринтах\n",
"\n",
"---\n",
"\n",
"## 5. Анализ применимости паттернов\n",
"\n",
"### 5.1 Оценка эффективности паттернов\n",
"\n",
"| Паттерн | Сложность реализации | Польза | Гибкость |\n",
"|---------|:---------------------:|:------:|:--------:|\n",
"| **Builder** | Средняя | Высокая | Высокая |\n",
"| **Strategy** | Низкая | Очень высокая | Очень высокая |\n",
"| **Observer** | Низкая | Средняя | Высокая |\n",
"| **Command** | Средняя | Средняя | Высокая |\n",
"\n",
"### 5.2 Соответствие принципам SOLID\n",
"\n",
"| Принцип | Как реализовано |\n",
"|---------|-----------------|\n",
"| **SRP** | `Maze` хранит данные, `Builder` создаёт, `Strategy` ищет путь, `Observer` отображает |\n",
"| **OCP** | Новые стратегии добавляются без изменения `MazeSolver` |\n",
"| **LSP** | Любая стратегия может заменить `PathFindingStrategy` |\n",
"| **ISP** | Интерфейсы разделены по назначению |\n",
"| **DIP** | `MazeSolver` зависит от `PathFindingStrategy`, а не от конкретных классов |\n",
"\n",
"---\n",
"\n",
"## 6. Выводы\n",
"\n",
"### 6.1 Основные результаты\n",
"\n",
"1. Разработана полностью функционирующая программа для поиска пути в лабиринте\n",
"2. Реализовано 4 паттерна GoF: Builder, Strategy, Observer, Command\n",
"3. Реализовано 3 алгоритма поиска: BFS, DFS, A*\n",
"4. Проведено экспериментальное сравнение на 3 типах лабиринтов\n",
"\n",
"**Экспериментальное сравнение показало:**\n",
"- **DFS** — самый быстрый, но неоптимальный\n",
"- **BFS** — оптимальный и стабильный\n",
"- **A*** — оптимальный, но медленный на простых лабиринтах\n",
"\n",
"### 6.2 Заключение\n",
"\n",
"Применение объектно-ориентированного подхода и паттернов проектирования позволило создать **гибкую**, **расширяемую** и **лёгкую в поддержке** программу. Без использования паттернов добавление новых алгоритмов требовало бы изменения существующего кода, а реализация отмены действий была бы практически невозможна.\n",
"\n",
"---\n",
"\n",
"*Отчёт сгенерирован автоматически 24.05.2026*"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,7 @@
maze,strategy,avg_time_ms,min_time_ms,max_time_ms,path_length,path_found
small_maze,BFS,0.13221000008343253,0.12229999992996454,0.18230000205221586,16,True
small_maze,DFS,0.12855999993917067,0.11959999756072648,0.19559999782359228,16,True
small_maze,A*,0.1432199995178962,0.14000000010128133,0.15889999849605374,16,True
simple_maze,BFS,0.2147200004401384,0.2117000003636349,0.2243999988422729,15,True
simple_maze,DFS,0.14680000022053719,0.14490000103251077,0.15519999942625873,29,True
simple_maze,A*,0.3328499999042833,0.3297000002930872,0.33970000004046597,15,True
1 maze strategy avg_time_ms min_time_ms max_time_ms path_length path_found
2 small_maze BFS 0.13221000008343253 0.12229999992996454 0.18230000205221586 16 True
3 small_maze DFS 0.12855999993917067 0.11959999756072648 0.19559999782359228 16 True
4 small_maze A* 0.1432199995178962 0.14000000010128133 0.15889999849605374 16 True
5 simple_maze BFS 0.2147200004401384 0.2117000003636349 0.2243999988422729 15 True
6 simple_maze DFS 0.14680000022053719 0.14490000103251077 0.15519999942625873 29 True
7 simple_maze A* 0.3328499999042833 0.3297000002930872 0.33970000004046597 15 True

View File

@ -0,0 +1,54 @@
"""MazeSolver и статистика поиска"""
import time
from dataclasses import dataclass
from typing import List, Optional, Tuple
from models import Maze, Cell
from strategies import PathFindingStrategy
@dataclass
class SearchStats:
"""Статистика поиска пути"""
time_ms: float
visited_cells: int
path_length: int
def __str__(self) -> str:
return (f"Время: {self.time_ms:.3f} мс, "
f"Посещено клеток: {self.visited_cells}, "
f"Длина пути: {self.path_length}")
class MazeSolver:
"""Оркестратор для решения лабиринта"""
def __init__(self, maze: Maze, strategy: Optional[PathFindingStrategy] = None):
self._maze = maze
self._strategy = strategy
def set_strategy(self, strategy: PathFindingStrategy) -> None:
"""Установка стратегии поиска"""
self._strategy = strategy
def solve(self) -> Tuple[List[Cell], SearchStats]:
"""Запуск поиска пути с текущей стратегией"""
if self._strategy is None:
raise ValueError("Стратегия не установлена")
if self._maze.start is None or self._maze.exit is None:
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()
time_ms = (end_time - start_time) * 1000
stats = SearchStats(
time_ms=time_ms,
visited_cells=len(path),
path_length=len(path)
)
return path, stats

View File

@ -0,0 +1,148 @@
"""Паттерн Strategy - алгоритмы поиска пути"""
from abc import ABC, abstractmethod
from collections import deque
import heapq
from typing import List, Dict, Optional
from models import Cell, Maze
class PathFindingStrategy(ABC):
"""Интерфейс стратегии поиска пути"""
@abstractmethod
def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]:
pass
@property
@abstractmethod
def name(self) -> str:
pass
class BFSStrategy(PathFindingStrategy):
"""Поиск в ширину (гарантирует кратчайший путь)"""
@property
def name(self) -> str:
return "BFS"
def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]:
if start == exit_cell:
return [start]
queue = deque([start])
visited = {start}
parent: Dict[Cell, Optional[Cell]] = {start: None}
while queue:
current = queue.popleft()
if current == exit_cell:
return self._reconstruct_path(parent, start, exit_cell)
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: Dict[Cell, Optional[Cell]],
start: Cell, exit_cell: Cell) -> List[Cell]:
path = []
current = exit_cell
while current is not None:
path.append(current)
current = parent.get(current)
path.reverse()
return path
class DFSStrategy(PathFindingStrategy):
"""Поиск в глубину (быстрый, но не гарантирует кратчайший путь)"""
@property
def name(self) -> str:
return "DFS"
def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]:
if start == exit_cell:
return [start]
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):
"""Алгоритм A* с манхэттенской эвристикой"""
@property
def name(self) -> str:
return "A*"
def _heuristic(self, cell: Cell, target: Cell) -> int:
"""Манхэттенское расстояние"""
return abs(cell.x - target.x) + abs(cell.y - target.y)
def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> List[Cell]:
if start == exit_cell:
return [start]
counter = 0
open_set = [(0, counter, start)]
came_from: Dict[Cell, Optional[Cell]] = {start: None}
g_score = {start: 0}
f_score = {start: self._heuristic(start, exit_cell)}
closed_set = set()
while open_set:
current_f, _, current = heapq.heappop(open_set)
if current in closed_set:
continue
if current == exit_cell:
return self._reconstruct_path(came_from, start, exit_cell)
closed_set.add(current)
for neighbor in maze.get_neighbors(current):
if neighbor in closed_set:
continue
tentative_g = g_score[current] + 1
if neighbor not in g_score or tentative_g < g_score[neighbor]:
came_from[neighbor] = current
g_score[neighbor] = tentative_g
f = tentative_g + self._heuristic(neighbor, exit_cell)
counter += 1
heapq.heappush(open_set, (f, counter, neighbor))
return []
def _reconstruct_path(self, came_from: Dict[Cell, Optional[Cell]],
start: Cell, exit_cell: Cell) -> List[Cell]:
path = []
current = exit_cell
while current is not None:
path.append(current)
current = came_from.get(current)
path.reverse()
return path

View File

@ -0,0 +1,87 @@
"""Паттерн Observer - визуализация лабиринта"""
from abc import ABC, abstractmethod
from typing import List, Optional, Any
from models import Cell, Maze
class Observer(ABC):
"""Интерфейс наблюдателя"""
@abstractmethod
def update(self, event_type: str, data: Any = None) -> None:
pass
class Subject:
"""Субъект для управления наблюдателями"""
def __init__(self):
self._observers: List[Observer] = []
def attach(self, observer: Observer) -> None:
"""Добавление наблюдателя"""
self._observers.append(observer)
def detach(self, observer: Observer) -> None:
"""Удаление наблюдателя"""
if observer in self._observers:
self._observers.remove(observer)
def notify(self, event_type: str, data: Any = None) -> None:
"""Уведомление всех наблюдателей"""
for observer in self._observers:
observer.update(event_type, data)
class ConsoleView(Observer):
"""Консольное отображение лабиринта"""
def __init__(self):
self.last_path: List[Cell] = []
self.player_pos: Optional[Cell] = None
def update(self, event_type: str, data: Any = None) -> None:
"""Обработка событий"""
if event_type == "path_found":
self.last_path = data.get("path", [])
print(f"\n=== Путь найден! Длина: {len(self.last_path)} ===")
self.render(data.get("maze"), None, self.last_path)
elif event_type == "path_not_found":
print("\n=== Путь не найден! ===")
elif event_type == "player_moved":
self.player_pos = data.get("position")
if data.get("redraw", True):
self.render(data.get("maze"), self.player_pos, self.last_path)
elif event_type == "maze_loaded":
print("Лабиринт загружен")
self.render(data.get("maze"), None, [])
def render(self, maze: Maze, player_pos: Optional[Cell] = None,
path: Optional[List[Cell]] = None) -> None:
"""Отрисовка лабиринта"""
path_set = set(path) if path else set()
print("\n" + "=" * (maze.width + 2))
for y in range(maze.height):
line = "|"
for x in range(maze.width):
cell = maze.get_cell(x, y)
if player_pos and cell == player_pos:
line += "P"
elif cell == maze.start:
line += "S"
elif cell == maze.exit:
line += "E"
elif cell in path_set and cell != maze.start and cell != maze.exit:
line += "."
elif cell.is_wall:
line += "#"
else:
line += " "
line += "|"
print(line)
print("=" * (maze.width + 2))
if path:
print(f"Длина пути: {len(path)}")