2026-rff_mp/famutdinovmd/report.md

309 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Отчёт по лабораторной работе №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 {
<<interface>>
+build_from_file(filename)
}
class TextFileMazeBuilder {
+build_from_file(filename)
}
class PathFindingStrategy {
<<interface>>
+find_path(maze, start, exit)
}
class BFSStrategy
class DFSStrategy
class AStarStrategy
class MazeSolver {
-maze
-strategy
+set_strategy()
+solve()
}
class Observer {
<<interface>>
+update(event, data)
}
class ConsoleView {
+render(maze, path)
+update(event, data)
}
class Command {
<<interface>>
+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