# Отчёт: Задание 2 — Поиск выхода из лабиринта 

## Цель работы

Разработать гибкую, расширяемую программу для загрузки лабиринта из файла, поиска пути от старта до выхода с возможностью выбора алгоритма, визуализации процесса и экспериментального сравнения алгоритмов

## Выбранные паттерны и их обоснование

### Builder

Для загрузки лабиринта из файла был использован паттерн Builder.
Создан интерфейс:
class MazeBuilder():
и его реализация:
class TextFileMazeBuilder(MazeBuilder):
Преимущества использования Builder:
пользоватеь не знает деталей создания лабиринта;
можно добавить новые форматы (JSON, XML, бинарный);
код загрузки изолирован от остальной программы.

### Strategy

Для алгоритмов поиска пути использован паттерн Strategy
Создан общий интерфейс:
class PathFindingStrategy():
Реализованы стратегии:
BFSStrategy;
DFSStrategy;
AStrategy;
Каждая стратегия реализует собственный алгоритм поиска пути по правилам.
Преимущества паттерна:
алгоритмы можно менять во время выполнения;
код MazeSolver не зависит от конкретного алгоритма;
новые алгоритмы можно добавлять без изменения существующего кода.

### Observer

Для уведомления интерфейса о событиях использован паттерн Observer
Создан интерфейс:
class Observer():
и реализация:
class ConsoleView(Observer):
MazeSolver хранит список наблюдателей и уведомляет их о событиях:
начало поиска;
окончание поиска;
перемещение игрока.
Преимущества:
логика интерфейса отделена от логики поиска;
можно легко добавить графический интерфейс;

### Command

Для пошагового перемещения игрока использован паттерн Command.
Создан интерфейс:
class Command():
и реализация:
class MoveCommand(Command):
Каждая команда умеет:
execute() — выполнить действие;
undo() — отменить действие
Преимущества:
поддержка undo;
возможность расширения системы команд

## Листинги ключевых классов

### Паттерн Strategy 

class PathFindingStrategy:
    def findPath(self, maze, start, exit):
        pass


class BFSStrategy(PathFindingStrategy):
    def findPath(self, maze, start, exit):
        if exit is None:
            return []
        queue = deque([start])
        visited = {start}
        parent = {start: None}
        
        while queue:
            current = queue.popleft()
            
            if current == exit:
                return self._reconstruct_path(parent, start, exit)
            
            for neighbor in maze.getNeighbors(current):
                if neighbor not in visited:
                    visited.add(neighbor)
                    parent[neighbor] = current
                    queue.append(neighbor)
        
        return []  
class AStrategy(PathFindingStrategy):
    def _heuristic(self, cell, exit):
        if exit is None:
            return 0
        return abs(cell.x - exit.x) + abs(cell.y - exit.y)
    
    def findPath(self, maze, start, exit):
        if exit is None:
            return []
        open_set = []
        heapq.heappush(open_set, (0, start))
        
        came_from = {start: None}
        g_score = {start: 0}
        
        while open_set:
            current = heapq.heappop(open_set)[1]
            
            if current == exit:
                return self._reconstruct_path(came_from, start, exit)
            
            for neighbor in maze.getNeighbors(current):
                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_score = tentative_g + self._heuristic(neighbor, exit)
                    heapq.heappush(open_set, (f_score, neighbor))
        
        return []

### Паттерн Command

class Command:
    def execute(self): pass
    def undo(self): pass

class MoveCommand(Command):
    def __init__(self, player, direction, maze):
        self.player = player
        self.dx, self.dy = direction
        self.maze = maze
        self.executed = False
    
    def execute(self):
        new_x = self.player.currentCell.x + self.dx
        new_y = self.player.currentCell.y + self.dy
        new_cell = self.maze.getCell(new_x, new_y)
        if new_cell and new_cell.isPassable():
            self.player.moveTo(new_cell)
            self.executed = True
            return True
        return False
    
    def undo(self):
        if self.executed:
            self.player.undoMove()
            self.executed = False
            return True
        return False



## Результаты

| Лабиринт | Стратегия | Время (с) | Посещено | Длина пути | Путь найден |
|---|---|---|---|---|---|
| маленький (10x10) | BFS | 0.9148200158961117 | 19.0 | 19.0 | True |
| маленький (10x10) | DFS | 0.717819994315505 | 39.0 | 39.0 | True |
| маленький (10x10) | A | 1.577159995213151 | 19.0 | 19.0 | True |
| средний (50x50) | BFS | 14.496059995144606 | 99.0 | 99.0 | True |
| средний (50x50) | DFS | 8.470179990399629 | 393.0 | 393.0 |True |
| средний (50x50) | A | 9.11291999509558 | 99.0 | 99.0 | True |
| большой (100x100) | BFS | 0.013179995585232973 | 0.0 | 0.0 | False |
| большой (100x100) | A | 0.013079994823783636 | 0.0 | 0.0 | False |
| пустой (50x50) | BFS | 29.2012800113298 | 99.0 | 99.0 | True |
| пустой (50x50) | DFS | 13.176999986171722 | 1275.0 | 1275.0 | True |
| пустой (50x50) | A | 50.366899999789894 | 99.0 | 99.0 | True |
| без выхода (20x20) | BFS | 0.004239997360855341 | 0.0 | 0.0 | False |
| без выхода (20x20) | DFS | 0.006399990525096655 | 0.0 | 0.0 | False |
| без выхода (20x20) | A | 0.008680007886141539 | 0.0 | 0.0 | False |

### Графики

![Сравнение длины](maze_path_length.png)

![Сравнение времён](maze_time_comparison.png)

## Анализ эффективности алгоритмов

В ходе экспериментов были получены следующие результаты.

### BFS
Преимущества:
всегда находит кратчайший путь;
простая реализация.
Недостатки:
посещает большое количество клеток;
требует много памяти.
Выходит, что наиболее эффективен в небольших невзвешенных лабиринтах.

### DFS
Преимущества:
простая реализация;
самым быстрым находит произвольный путь.
Недостатки:
не гарантирует кратчайший путь;
может уходить в тупики.
Подходит для быстрого поиска любого решения.

### A
Преимущества:
высокая скорость;
посещает меньше клеток;
Недостатки:
требует выбора хорошей эвристики.
Показал хорошие результаты на больших лабиринтах.

## Анализ применимости паттернов

### Builder
Без Builder код загрузки лабиринта был бы жёстко связан с классом Maze, а добавление нового формата потребовало бы изменения существующего кода.
Strategy
Без Strategy пришлось бы:
хранить все алгоритмы внутри одного класса;
использовать большое количество условных операторов;
изменять код MazeSolver при добавлении новых алгоритмов
Strategy помог полностью отделить алгоритмы друг от друга.

### Observer
Без Observer логика интерфейса смешивалась бы с логикой поиска.
Это усложнило бы:
добавление GUI;
логирование;
визуализацию.

### Command
Без Command было бы сложно реализовать:
undo;
историю действий;
расширяемую систему управления.

## Выводы

### В проекте были успешно реализованы:
загрузка лабиринта из файла;
несколько алгоритмов поиска пути;
визуализация;
система наблюдателей;
система команд;
экспериментальное сравнение алгоритмов.

### Использование паттернов GoF позволило:
сделать архитектуру гибкой;
уменьшить связанность компонентов;
упростить расширение программы;
облегчить сопровождение кода.
