Merge branch 'task-2' into task-1

This commit is contained in:
yanyaevaa 2026-05-23 22:35:49 +03:00
commit 70f49bb8c5
10 changed files with 750 additions and 0 deletions

View File

@ -0,0 +1,238 @@
# Поиск выхода из лабиринта (объектно-ориентированная реализация с паттернами)
Цель работы:
Разработать гибкую, расширяемую программу для загрузки лабиринта из файла, поиска пути от старта до выхода с возможностью выбора алгоритма, визуализации процесса и экспериментального сравнения алгоритмов. В ходе работы необходимо применить минимум 3 паттерна проектирования из списка GoF, обосновать их выбор и продемонстрировать преимущества такой архитектуры.
## Выбранные паттерны проектирования
- Builder: шаблон проектирования, который инкапсулирует создание объекта и позволяет разделить его на различные этапы. В программе позволяет загружать лабиринт из файла.
- Strategy: поведенческий шаблон проектирования, предназначенный для определения семейства алгоритмов, инкапсуляции каждого из них и обеспечения их взаимозаменяемости. Это позволяет выбирать алгоритм путём определения соответствующего класса. В программе помогает менять способ поиска пути.
- Observer: это поведенческий паттерн, который позволяет объектам оповещать другие объекты об изменениях своего состояния. В программе служит для визуализации лабиринта и пути к выходу, а также для уведомляет о событиях ("path_found").
## Диаграмма классов
![](data/mermaid_diagram.png)
# Листинги ключевых классов
## Паттерн Builder
```Python
class MazeBuilder(ABC):
@abstractmethod
def buildFromFile(self, filename):
pass
class TextFileMazeBuilder(MazeBuilder):
def buildFromFile(self, filename):
with open(filename, 'r') as f:
lines = [line.rstrip('\n') for line in f]
height = len(lines)
width = max(len(line) for line in lines)
grid=[]
start_cell=None
exit_cell=None
for y in range(height):
row=[]
for x in range(width):
char=lines[y][x]
isWall = (char == '#')
isStart = (char == 'S')
isExit = (char == 'E')
cell=Cell(x, y, isWall, isStart, isExit)
if isStart:
start_cell =cell
if isExit:
exit_cell =cell
row.append(cell)
grid.append(row)
return Maze(grid, width, height, start_cell, exit_cell)
```
## Паттерн Strategy
```Python
class PathFindingStrategy(ABC):
@abstractmethod
def findPath(self,maze, start, exit):
pass
class BFS(PathFindingStrategy):
def findPath(self, maze, start, exit):
queue = deque([start])
traveled_path={start: None}
while queue:
current = queue.popleft()
if current==exit:
path=[]
while current is not None:
path.append(current)
current = traveled_path[current]
return path[::-1], len(traveled_path)
for neighbor in maze.getNeighbors(current):
if neighbor not in traveled_path:
traveled_path[neighbor] = current
queue.append(neighbor)
return [], len(traveled_path)
class DFS(PathFindingStrategy):
def findPath(self, maze, start, exit):
stack = [start]
traveled_path={start: None}
while stack:
current = stack.pop()
if current == exit:
path = []
while current is not None:
path.append(current)
current = traveled_path[current]
return path[::-1], len(traveled_path)
for neighbor in maze.getNeighbors(current):
if neighbor not in traveled_path:
traveled_path[neighbor] = current
stack.append(neighbor)
return [], len(traveled_path)
class AStar(PathFindingStrategy):
def findPath(self, maze, start, exit):
count = 0
open_set = [(0, count, start)]
traveled_path = {start: None}
g_score = {start: 0}
while open_set:
_,_,current = heapq.heappop(open_set)
if current == exit:
path = []
while current is not None:
path.append(current)
current = traveled_path[current]
return path[::-1], len(traveled_path)
for neighbor in maze.getNeighbors(current):
g_score_new = g_score[current]+1
if neighbor not in g_score or g_score_new < g_score[neighbor]:
traveled_path[neighbor] = current
g_score[neighbor] = g_score_new
f_score = g_score_new + abs(neighbor.x - exit.x) + abs(neighbor.y - exit.y)
count += 1
heapq.heappush(open_set, (f_score, count, neighbor))
return [],len(traveled_path)
```
## Паттерн Observer с объектом Event
```Python
class Event:
def __init__(self, event_type, data=None):
self.event_type = event_type
self.data = data
class Observer(ABC):
@abstractmethod
def update(self, event):
pass
class ConsoleView(Observer):
def update(self, event):
if event.event_type == "path_found":
stats=event.data
print("Путь найден:")
print("Время выполнения:", stats.time)
print("Количество посещённых клеток:", stats.visited_cells)
print("Длина найденного пути:", stats.path_length)
if event.event_type == "maze_loaded":
print("Загружен новый лабиринт")
def render(self, maze, path):
for y in range(maze.height):
row_str=""
for x in range(maze.width):
cell=maze.getCell(x, y)
if cell == maze.start:
row_str += "S"
elif cell == maze.exit:
row_str += "E"
elif cell in path:
row_str += "·"
elif cell.isWall:
row_str += "#"
else:
row_str += " "
print(row_str)
```
## Реализация программы
```Python
mazes = ["10x10.txt","50x50.txt","100x100.txt","empty.txt","without_exit.txt"]
results =[["лабиринт",
"стратегия",
ремя_мс",
"посещено_клеток",
"длина_пути"]]
strategies = {
"BFS": BFS(),
"DFS": DFS(),
"AStar": AStar()
}
builder = TextFileMazeBuilder()
n=10
directory = os.path.join("docs", "data")
for maze_name in mazes:
print(maze_name)
file_name=os.path.join(directory, maze_name)
maze = builder.buildFromFile(file_name)
viewer=ConsoleView()
for strategy_name, strategy in strategies.items():
total_time = 0.0
total_visited = 0
total_path_length = 0
solver = MazeSolver(maze, strategy)
for i in range(n):
stats = solver.solve()
total_time += stats.time
total_visited += stats.visited_cells
total_path_length += stats.path_length
avg_time = total_time/n
avg_visited = total_visited/n
avg_path_length = total_path_length/n
print("-"*100)
print(f"{maze_name} стратегия: {strategy_name} время_мс: {avg_time} посещено_клеток: {avg_visited} длина_пути: {avg_path_length}")
results.append([maze_name, strategy_name, avg_time, avg_visited, avg_path_length])
path, _ = strategy.findPath(maze, maze.start, maze.exit)
viewer.render(maze, path)
csv_filename = os.path.join(directory, "maze_results.csv")
with open(csv_filename, "w", newline="", encoding="utf-8-sig") as f:
writer = csv.writer(f)
writer.writerows(results)
```
# Результаты экспериментов
В ходе тестирования каждый алгоритм запускался по 10 раз на каждом типе лабиринта
| Лабиринт | Стратегия | Время (мс) | Посещено клеток | Длина пути |
| :--- | :--- |:------------------------------:| :---: | :---: |
| **10x10.txt** | BFS <br> DFS <br> AStar | 0.0264 <br> 0.0368 <br> 0.0320 | 30.0 <br> 43.0 <br> 30.0 | 29.0 <br> 29.0 <br> 29.0 |
| **50x50.txt** | BFS <br> DFS <br> AStar | 0.6698 <br> 0.4722 <br> 0.5986 | 799.0 <br> 562.0 <br> 539.0 | 316.0 <br> 350.0 <br> 316.0 |
| **100x100.txt** | BFS <br> DFS <br> AStar | 3.0005 <br> 0.4454 <br> 0.5787 | 3576.0 <br> 595.0 <br> 536.0 | 196.0 <br> 364.0 <br> 196.0 |
| **empty.txt** | BFS <br> DFS <br> AStar | 0.2904 <br> 0.1618 <br> 0.4074 | 324.0 <br> 324.0 <br> 324.0 | 35.0 <br> 171.0 <br> 35.0 |
| **without_exit.txt** | BFS <br> DFS <br> AStar | 0.0407 <br> 0.0408 <br> 0.0519 | 48.0 <br> 48.0 <br> 48.0 | 0.0 <br> 0.0 <br> 0.0 |
Сравнительные графики:
![](data/maze_graphics.png)
# Анализ эффективности алгоритмов и применимости паттернов.
- **BFS**:
- Время: Алгоритм демонстрирует рост времени с увеличением площади лабиринта и количества ветвлений: минимальное время в лабиринте 10x10: 0.0264; максимальное время в лабиринте 100x100: 3.0005.
- Количество посещенных клеток: Также показывает рост количества посещенных клеток с увеличением площади лабиринта. В лабиринтах 50x50, 100x100 показывает неэффективность алгоритма с точки зрения объема работы: 799 и 3576 соответственно.
- Длина пути: Во всех алгоритмах показывает выбор оптимального пути.
- **DFS**:
- Время: В маленьком лабиринте (10x10) работает медленнее других, но на больших и пустом лабиринтах является быстрейшим алгоритмом. Это происходит, потому что DFS использует стек, который в Python имеет сложность O(1).
- Количество посещенных клеток: На полученных данных, мы видим хорошие значения посещенных клеток, но для такого же по размерам лабиринта, но с другими путями, количество посещенных клеток может измениться. Это происходит из-за того, что количество посещенных клеток зависит от лабиринта, и "повезло" ли алгоритму сразу же наткнуться на коридор, ведущий к выходу.
- Длина пути: На маленьком лабиринте (10x10) показывает одинаковую длину пути со всеми алгоритмами. Но для других лабиринтов показывает пути значительно больше чем у других. DFS не гарантирует оптимальность пути, потому что ищет до первого пути ведущего к выходу, который может быть большим. Но на лабиринте без развилок (10x10) показывает результат на ровне с другими алгоритмами.
- **A***:
- Время: На большинстве лабиринтах показывает средние результат, в лабиринте без выхода и в пустом лабиринте показывает худшие результаты. Это происходит, потому что алгоритм использует приоритетную очередь, которая имеет логарифмическую сложность O(log n), также постоянная перестройка очереди занимает чуть больше времени.
- Количество посещенных клеток: Во всех случаях имеет минимальное количество посещенных клеток. Это происходит благодаря эвристической функции (Манхэттенское расстояние). Она минимизирует количество посещенных клеток, избегая ложные направления. Преимущество алгоритма хорошо видно на больших лабиринтах (50x50, 100x100)
- Длина пути: Также, как и BFS, показывает минимальную длину пути. Алгоритм выбирает кратчайший путь, также благодаря эвристике.
## Выводы по алгоритмам
- **BFS**: Всегда находит кратчайший путь, но для больших лабиринтов тратит много времени, а также для поиска пути исследует большое количество клеток.
- **DFS**: Алгоритм подходит для быстрого нахождения пути для простых или линейных лабиринтов. Но не подходит для выбора кратчайшего пути.
- **A***: Алгоритм показывает наибольшую общую эффективность. Всегда находит кратчайший путь и для поиска посещает наименьшее количество клеток, имеет хорошую скорость выполнения.
## Применяемость паттернов
- **Builder**: Позволяет отделить структуру самого лабиринта от источника его данных. Позволяет свободно, при необходимости, добавлять другие форматы, из которых будет строиться лабиринт.
- **Strategy**: Инкапсулирует алгоритмы поиска пути в отдельные классы, что позволяет свободно добавлять другие алгоритмы поиска
- **Observer**: Реализует механизм уведомления о шагах алгоритма. Позволяет визуализировать процесс поиска клеток, полностью отделяя логику вычислений от графического интерфейса.
# Вывод
Применение принципов И паттернов ООП позволило создать устойчивую к изменениям программу. Можно свободно добавлять новые алгоритмы поиска или новые способы вывода данных, создавая новые подклассы и не меняя ни единой строчки кода. Без ООП стало бы невозможно легко переключиться с чтения текстовых файлов на файлы другого формата или на алгоритм случайной генерации карт. В итоге ООП позволяет расширять код, не затрагивая работоспособность других компонентов.

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

View File

@ -0,0 +1,16 @@
лабиринт,стратегия,время_мс,посещено_клеток,длина_пути
10x10.txt,BFS,0.02643000007083174,30.0,29.0
10x10.txt,DFS,0.03684999974211678,43.0,29.0
10x10.txt,AStar,0.0320400002237875,30.0,29.0
50x50.txt,BFS,0.6697899993014289,799.0,316.0
50x50.txt,DFS,0.4721500004961854,562.0,350.0
50x50.txt,AStar,0.5986000000120839,539.0,316.0
100x100.txt,BFS,3.000480001355754,3576.0,196.0
100x100.txt,DFS,0.4453900000953581,595.0,364.0
100x100.txt,AStar,0.5786999998235842,522.0,196.0
empty.txt,BFS,0.29044999937468674,324.0,35.0
empty.txt,DFS,0.16180000056920107,324.0,171.0
empty.txt,AStar,0.40738000025157817,324.0,35.0
without_exit.txt,BFS,0.04074000025866553,48.0,0.0
without_exit.txt,DFS,0.040809999700286426,48.0,0.0
without_exit.txt,AStar,0.05192000025999732,48.0,0.0
1 лабиринт стратегия время_мс посещено_клеток длина_пути
2 10x10.txt BFS 0.02643000007083174 30.0 29.0
3 10x10.txt DFS 0.03684999974211678 43.0 29.0
4 10x10.txt AStar 0.0320400002237875 30.0 29.0
5 50x50.txt BFS 0.6697899993014289 799.0 316.0
6 50x50.txt DFS 0.4721500004961854 562.0 350.0
7 50x50.txt AStar 0.5986000000120839 539.0 316.0
8 100x100.txt BFS 3.000480001355754 3576.0 196.0
9 100x100.txt DFS 0.4453900000953581 595.0 364.0
10 100x100.txt AStar 0.5786999998235842 522.0 196.0
11 empty.txt BFS 0.29044999937468674 324.0 35.0
12 empty.txt DFS 0.16180000056920107 324.0 171.0
13 empty.txt AStar 0.40738000025157817 324.0 35.0
14 without_exit.txt BFS 0.04074000025866553 48.0 0.0
15 without_exit.txt DFS 0.040809999700286426 48.0 0.0
16 without_exit.txt AStar 0.05192000025999732 48.0 0.0

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 KiB

View File

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

299
YanyaevAA/task2/task_2.py Normal file
View File

@ -0,0 +1,299 @@
from abc import ABC, abstractmethod
from collections import deque
import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns
import heapq
import time
import os
import csv
#Этап 1
class Cell:
def __init__(self, x, y, isWall=False, isStart=False, isExit=False):
self.x = x
self.y = y
self.isWall = isWall
self.isStart = isStart
self.isExit = isExit
def isPassable(self):
return not self.isWall
class Maze:
def __init__(self, cells, width, height, start, exit):
self.width = width
self.height = height
self.cells =cells
self.start = start
self.exit = exit
def getCell(self, x, y):
if 0 <= x< self.width and 0 <=y< self.height:
return self.cells[y][x]
return None
def getNeighbors(self, cell: Cell):
neighbors = []
directions = [(0, -1), (0, 1), (-1, 0), (1, 0)]
for dir_x, dir_y in directions:
neigh_x = cell.x+dir_x
neigh_y = cell.y+dir_y
neighbor = self.getCell(neigh_x, neigh_y)
if neighbor and neighbor.isPassable():
neighbors.append(neighbor)
return neighbors
#Этап 2
class MazeBuilder(ABC):
@abstractmethod
def buildFromFile(self, filename):
pass
class TextFileMazeBuilder(MazeBuilder):
def buildFromFile(self, filename):
with open(filename, 'r') as f:
lines = [line.rstrip('\n') for line in f]
height = len(lines)
width = max(len(line) for line in lines)
grid=[]
start_cell=None
exit_cell=None
for y in range(height):
row=[]
for x in range(width):
char=lines[y][x]
isWall = (char == '#')
isStart = (char == 'S')
isExit = (char == 'E')
cell=Cell(x, y, isWall, isStart, isExit)
if isStart:
start_cell =cell
if isExit:
exit_cell =cell
row.append(cell)
grid.append(row)
return Maze(grid, width, height, start_cell, exit_cell)
#Этап 3
class PathFindingStrategy(ABC):
@abstractmethod
def findPath(self,maze, start, exit):
pass
class BFS(PathFindingStrategy):
def findPath(self, maze, start, exit):
queue = deque([start])
traveled_path={start: None}
while queue:
current = queue.popleft()
if current==exit:
path=[]
while current is not None:
path.append(current)
current = traveled_path[current]
return path[::-1], len(traveled_path)
for neighbor in maze.getNeighbors(current):
if neighbor not in traveled_path:
traveled_path[neighbor] = current
queue.append(neighbor)
return [], len(traveled_path)
class DFS(PathFindingStrategy):
def findPath(self, maze, start, exit):
stack = [start]
traveled_path={start: None}
while stack:
current = stack.pop()
if current == exit:
path = []
while current is not None:
path.append(current)
current = traveled_path[current]
return path[::-1], len(traveled_path)
for neighbor in maze.getNeighbors(current):
if neighbor not in traveled_path:
traveled_path[neighbor] = current
stack.append(neighbor)
return [], len(traveled_path)
class AStar(PathFindingStrategy):
def findPath(self, maze, start, exit):
count = 0
open_set = [(0, count, start)]
traveled_path = {start: None}
g_score = {start: 0}
while open_set:
_,_,current = heapq.heappop(open_set)
if current == exit:
path = []
while current is not None:
path.append(current)
current = traveled_path[current]
return path[::-1], len(traveled_path)
for neighbor in maze.getNeighbors(current):
g_score_new = g_score[current]+1
if neighbor not in g_score or g_score_new < g_score[neighbor]:
traveled_path[neighbor] = current
g_score[neighbor] = g_score_new
f_score = g_score_new + abs(neighbor.x - exit.x) + abs(neighbor.y - exit.y)
count += 1
heapq.heappush(open_set, (f_score, count, neighbor))
return [],len(traveled_path)
#Этап 4
class SearchStats:
def __init__(self, time, visited_cells, path_length):
self.time = time
self.visited_cells = visited_cells
self.path_length = path_length
class MazeSolver:
def __init__(self, maze, strategy):
self.maze = maze
self.strategy = strategy
self.observers = []
def addObserver(self, observer):
self.observers.append(observer)
def setStrategy(self, strategy):
self.strategy = strategy
def solve(self):
start_cell = self.maze.start
exit_cell = self.maze.exit
start_time = time.perf_counter()
path, visited_cells = self.strategy.findPath(self.maze, start_cell, exit_cell)
end_time = time.perf_counter()
time_ms = (end_time - start_time) * 1000
path_length = len(path)
stats=SearchStats(time_ms, visited_cells, path_length)
event = Event("path_found", data=stats)
for observer in self.observers:
observer.update(event)
return stats
#Этап 5
#5.1
class Event:
def __init__(self, event_type, data=None):
self.event_type = event_type
self.data = data
class Observer(ABC):
@abstractmethod
def update(self, event):
pass
class ConsoleView(Observer):
def update(self, event):
if event.event_type == "path_found":
stats=event.data
print("Путь найден:")
print("Время выполнения:", stats.time)
print("Количество посещённых клеток:", stats.visited_cells)
print("Длина найденного пути:", stats.path_length)
if event.event_type == "move":
x, y = event.data
print(f"Игрок переместился в ячейку: {x}, {y}")
if event.event_type == "maze_loaded":
print("Загружен новый лабиринт")
def render(self, maze, path):
for y in range(maze.height):
row_str=""
for x in range(maze.width):
cell=maze.getCell(x, y)
if cell == maze.start:
row_str += "S"
elif cell == maze.exit:
row_str += "E"
elif cell in path:
row_str += "·"
elif cell.isWall:
row_str += "#"
else:
row_str += " "
print(row_str)
#Этап 6
mazes = ["10x10.txt","50x50.txt","100x100.txt","empty.txt","without_exit.txt"]
results =[["лабиринт",
"стратегия",
"время_мс",
"посещено_клеток",
"длина_пути"]]
strategies = {
"BFS": BFS(),
"DFS": DFS(),
"AStar": AStar()
}
builder = TextFileMazeBuilder()
n=10
directory = os.path.join("docs", "data")
for maze_name in mazes:
print(maze_name)
file_name=os.path.join(directory, maze_name)
maze = builder.buildFromFile(file_name)
viewer=ConsoleView()
for strategy_name, strategy in strategies.items():
total_time = 0.0
total_visited = 0
total_path_length = 0
solver = MazeSolver(maze, strategy)
for _ in range(n):
stats = solver.solve()
total_time += stats.time
total_visited += stats.visited_cells
total_path_length += stats.path_length
avg_time = total_time/n
avg_visited = total_visited/n
avg_path_length = total_path_length/n
print(f"{maze_name} стратегия: {strategy_name} время_мс: {avg_time} посещено_клеток: {avg_visited} длина_пути: {avg_path_length}")
results.append([maze_name, strategy_name, avg_time, avg_visited, avg_path_length])
path, _ = strategy.findPath(maze, maze.start, maze.exit)
viewer.render(maze, path)
csv_filename = os.path.join(directory, "maze_results.csv")
with open(csv_filename, "w", newline="", encoding="utf-8-sig") as f:
writer = csv.writer(f)
writer.writerows(results)
#Графики
df = pd.read_csv(csv_filename)
fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(16, 6))
sns.barplot(data=df, x='лабиринт', y='время_мс', hue='стратегия', ax=ax1)
ax1.set_title('Время выполнения алгоритмов')
ax1.set_xlabel('Лабиринты')
ax1.set_ylabel('Время (мс)')
ax1.grid(axis='y', linestyle='--', alpha=0.7)
ax1.legend()
sns.barplot(data=df, x='лабиринт', y='посещено_клеток', hue='стратегия', ax=ax2)
ax2.set_title('Количество посещенных клеток')
ax2.set_xlabel('Лабиринты')
ax2.set_ylabel('Количество клеток')
ax2.grid(axis='y', linestyle='--', alpha=0.7)
ax2.legend()
plt.tight_layout()
sns.barplot(data=df, x='лабиринт', y='длина_пути', hue='стратегия', ax=ax3)
ax3.set_title('Длина пути')
ax3.set_xlabel('Лабиринты')
ax3.set_ylabel('Количество клеток')
ax3.grid(axis='y', linestyle='--', alpha=0.7)
ax3.legend()
plt.tight_layout()
img = os.path.join(directory, "maze_graphics.png")
plt.savefig(img, dpi=300)
plt.show()