Merge pull request '[2] 2-nd' (#377) from agapovads/2026-rff_mp:2-nd into develop

Reviewed-on: #377
This commit is contained in:
git_admin 2026-09-05 07:10:43 +00:00
commit 39a16eb965
16 changed files with 1134 additions and 0 deletions

View File

@ -0,0 +1,430 @@
import time
import random
import csv
import os
from collections import deque
import heapq
import matplotlib.pyplot as plt
import numpy as np
class Cell:
def __init__(self, x, y, is_wall=False, is_start=False, is_exit=False):
self.x = x
self.y = y
self.is_wall = is_wall
self.is_start = is_start
self.is_exit = is_exit
def is_passable(self):
return not self.is_wall
class Maze:
def __init__(self, width, height):
self.width = width
self.height = height
self.grid = [[Cell(x, y) for y in range(height)] for x in range(width)]
self.start_cell = None
self.exit_cell = None
def get_cell(self, x, y):
if 0 <= x < self.width and 0 <= y < self.height:
return self.grid[x][y]
return None
def get_neighbors(self, cell):
neighbors = []
for dx, dy in [(-1,0), (1,0), (0,-1), (0,1)]:
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 save_to_file(self, filename):
"""Сохраняет лабиринт в текстовый файл."""
with open(filename, 'w', encoding='utf-8') as f:
for y in range(self.height):
row = ''
for x in range(self.width):
cell = self.get_cell(x, y)
if cell.is_wall:
row += '#'
elif cell.is_start:
row += 'S'
elif cell.is_exit:
row += 'E'
else:
row += ' '
f.write(row + '\n')
class MazeBuilder:
def build_from_file(self, filename):
raise NotImplementedError
class TextFileMazeBuilder(MazeBuilder):
def build_from_file(self, filename):
with open(filename, 'r', encoding='utf-8') as f:
lines = f.readlines()
lines = [line.rstrip('\n') for line in lines if line.strip() != '']
if not lines:
raise ValueError("Файл пуст")
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:
break
cell = maze.get_cell(x, y)
if ch == '#':
cell.is_wall = True
elif ch == 'S':
cell.is_start = True
maze.start_cell = cell
elif ch == 'E':
cell.is_exit = True
maze.exit_cell = cell
if maze.start_cell is None or maze.exit_cell is None:
raise ValueError("В лабиринте должны быть S и E")
return maze
class PathFindingStrategy:
def find_path(self, maze, start, exit):
raise NotImplementedError
class BFSStrategy(PathFindingStrategy):
def find_path(self, maze, start, exit):
if start == exit:
return [start]
queue = deque([start])
visited = {start}
parent = {start: None}
while queue:
current = queue.popleft()
if current == exit:
break
for neighbor in maze.get_neighbors(current):
if neighbor not in visited:
visited.add(neighbor)
parent[neighbor] = current
queue.append(neighbor)
if exit not in parent:
return []
path = []
step = exit
while step is not None:
path.append(step)
step = parent[step]
path.reverse()
return path
class DFSStrategy(PathFindingStrategy):
def find_path(self, maze, start, exit):
if start == exit:
return [start]
stack = [start]
visited = {start}
parent = {start: None}
while stack:
current = stack.pop()
if current == exit:
break
for neighbor in maze.get_neighbors(current):
if neighbor not in visited:
visited.add(neighbor)
parent[neighbor] = current
stack.append(neighbor)
if exit not in parent:
return []
path = []
step = exit
while step is not None:
path.append(step)
step = parent[step]
path.reverse()
return path
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):
if start == exit:
return [start]
open_set = []
heapq.heappush(open_set, (0, id(start), start))
came_from = {}
g_score = {start: 0}
f_score = {start: self.heuristic(start, exit)}
while open_set:
_, _, current = heapq.heappop(open_set)
if current == exit:
path = []
step = current
while step is not None:
path.append(step)
step = came_from.get(step)
path.reverse()
return path
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]:
came_from[neighbor] = current
g_score[neighbor] = tentative_g
f_score[neighbor] = tentative_g + self.heuristic(neighbor, exit)
heapq.heappush(open_set, (f_score[neighbor], id(neighbor), neighbor))
return []
class Observer:
def update(self, event):
raise NotImplementedError
class ConsoleView(Observer):
def __init__(self, maze):
self.maze = maze
def update(self, event):
if event == "Поиск начат":
print("=== Поиск начат ===")
elif event == "Поиск завершён":
print("=== Поиск завершён ===")
def render(self, path=None):
path_set = set(path) if path else set()
for y in range(self.maze.height):
row = ''
for x in range(self.maze.width):
cell = self.maze.get_cell(x, y)
if cell.is_wall:
row += '#'
elif cell.is_start:
row += 'S'
elif cell.is_exit:
row += 'E'
elif cell in path_set:
row += '*'
else:
row += ' '
print(row)
print()
class MazeSolver:
def __init__(self, maze, strategy=None):
self.maze = maze
self.strategy = strategy
self.observers = []
def set_strategy(self, strategy):
self.strategy = strategy
def attach(self, observer):
self.observers.append(observer)
def detach(self, observer):
self.observers.remove(observer)
def notify(self, event):
for obs in self.observers:
obs.update(event)
def solve(self):
if self.strategy is None:
raise ValueError("Стратегия не установлена")
start = self.maze.start_cell
exit_cell = self.maze.exit_cell
if start is None or exit_cell is None:
raise ValueError("Лабиринт не содержит старта или выхода")
self.notify("Поиск начат")
start_time = time.perf_counter()
path = self.strategy.find_path(self.maze, start, exit_cell)
end_time = time.perf_counter()
elapsed_ms = (end_time - start_time) * 1000
self.notify("Поиск завершён")
return path, elapsed_ms
def generate_empty_maze(width, height):
maze = Maze(width, height)
start = maze.get_cell(0, 0)
exit_cell = maze.get_cell(width-1, height-1)
start.is_start = True
exit_cell.is_exit = True
maze.start_cell = start
maze.exit_cell = exit_cell
return maze
def generate_random_maze(width, height, wall_prob=0.3):
maze = Maze(width, height)
for x in range(width):
for y in range(height):
cell = maze.get_cell(x, y)
if random.random() < wall_prob:
cell.is_wall = True
start = maze.get_cell(0, 0)
exit_cell = maze.get_cell(width-1, height-1)
start.is_wall = False
start.is_start = True
exit_cell.is_wall = False
exit_cell.is_exit = True
maze.start_cell = start
maze.exit_cell = exit_cell
return maze
def generate_maze_with_dead_ends(width, height):
maze = Maze(width, height)
for x in range(width):
for y in range(height):
maze.get_cell(x, y).is_wall = True
x, y = 0, 0
while x < width and y < height:
cell = maze.get_cell(x, y)
cell.is_wall = False
if x == width-1 and y == height-1:
break
if y+1 < height and (x == width-1 or random.choice([True, False])):
y += 1
else:
x += 1
start = maze.get_cell(0, 0)
exit_cell = maze.get_cell(width-1, height-1)
start.is_start = True
exit_cell.is_exit = True
maze.start_cell = start
maze.exit_cell = exit_cell
return maze
def generate_maze_no_exit(width, height):
maze = generate_random_maze(width, height, 0.2)
exit_cell = maze.get_cell(width-1, height-1)
for dx, dy in [(-1,0), (1,0), (0,-1), (0,1)]:
nx, ny = exit_cell.x + dx, exit_cell.y + dy
neighbor = maze.get_cell(nx, ny)
if neighbor:
neighbor.is_wall = True
start = maze.get_cell(0, 0)
start.is_wall = False
start.is_start = True
maze.start_cell = start
maze.exit_cell = exit_cell
return maze
def ensure_maze_files():
"""Создаёт папку mazes и генерирует все лабиринты, если файлы отсутствуют."""
os.makedirs("mazes", exist_ok=True)
configs = [
("empty_10x10.txt", 10, 10, generate_empty_maze),
("empty_50x50.txt", 50, 50, generate_empty_maze),
("empty_100x100.txt", 100, 100, generate_empty_maze),
("random_10x10.txt", 10, 10, generate_random_maze),
("random_50x50.txt", 50, 50, generate_random_maze),
("random_100x100.txt", 100, 100, generate_random_maze),
("deadends_10x10.txt", 10, 10, generate_maze_with_dead_ends),
("deadends_50x50.txt", 50, 50, generate_maze_with_dead_ends),
("deadends_100x100.txt", 100, 100, generate_maze_with_dead_ends),
("no_exit_10x10.txt", 10, 10, generate_maze_no_exit),
("no_exit_50x50.txt", 50, 50, generate_maze_no_exit),
]
for filename, w, h, gen_func in configs:
filepath = os.path.join("mazes", filename)
if not os.path.exists(filepath):
print(f"Генерация {filename}...")
maze = gen_func(w, h)
maze.save_to_file(filepath)
def run_experiment():
ensure_maze_files()
builder = TextFileMazeBuilder()
strategies = [
("BFS", BFSStrategy()),
("DFS", DFSStrategy()),
("AStar", AStarStrategy())
]
maze_files = sorted([f for f in os.listdir("mazes") if f.endswith(".txt")])
results = []
repeats = 5
for filename in maze_files:
maze_name = filename.replace(".txt", "")
print(f"Тестирование лабиринта: {maze_name}")
try:
maze = builder.build_from_file(os.path.join("mazes", filename))
except Exception as e:
print(f"Ошибка загрузки {filename}: {e}")
continue
solver = MazeSolver(maze)
for strat_name, strat in strategies:
solver.set_strategy(strat)
total_time = 0
total_path_len = 0
path = []
for rep in range(repeats):
path, elapsed_ms = solver.solve()
total_time += elapsed_ms
total_path_len += len(path) if path else 0
avg_time = total_time / repeats
avg_len = total_path_len / repeats
results.append({
"Maze": maze_name,
"Strategy": strat_name,
"AvgTime_ms": avg_time,
"AvgPathLen": avg_len,
"PathFound": len(path) > 0 if path else False
})
print(f" {strat_name}: время {avg_time:.3f} мс, длина пути {avg_len:.1f}")
os.makedirs("results", exist_ok=True)
csv_path = "results/experiment_results.csv"
with open(csv_path, 'w', newline='', encoding='utf-8') as f:
fieldnames = ["Maze", "Strategy", "AvgTime_ms", "AvgPathLen", "PathFound"]
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(results)
print(f"Результаты сохранены в {csv_path}")
maze_names = sorted(set(r["Maze"] for r in results))
strategy_names = ["BFS", "DFS", "AStar"]
data = {maze: {s: None for s in strategy_names} for maze in maze_names}
for r in results:
data[r["Maze"]][r["Strategy"]] = r["AvgTime_ms"]
fig, ax = plt.subplots(figsize=(14, 6))
x = np.arange(len(maze_names))
width = 0.25
colors = ['skyblue', 'lightgreen', 'salmon']
for i, strat in enumerate(strategy_names):
times = [data[maze][strat] if data[maze][strat] is not None else 0 for maze in maze_names]
ax.bar(x + i*width, times, width, label=strat, color=colors[i])
ax.set_xlabel('Лабиринт')
ax.set_ylabel('Среднее время (мс)')
ax.set_title('Сравнение стратегий поиска пути')
ax.set_xticks(x + width)
ax.set_xticklabels(maze_names, rotation=45, ha='right')
ax.legend()
plt.tight_layout()
plt.savefig("results/performance.png", dpi=150)
#plt.show()
print("График сохранён в results/performance.png")
if __name__ == '__main__':
run_experiment()

View File

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

View File

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

View File

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

View File

@ -0,0 +1,100 @@
S
E

View File

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

View File

@ -0,0 +1,50 @@
S
E

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,34 @@
Maze,Strategy,AvgTime_ms,AvgPathLen,PathFound
deadends_100x100,BFS,0.28289179999774205,199.0,True
deadends_100x100,DFS,0.2610137999909057,199.0,True
deadends_100x100,AStar,0.41742360008356627,199.0,True
deadends_10x10,BFS,0.023550400055682985,19.0,True
deadends_10x10,DFS,0.02498599997124984,19.0,True
deadends_10x10,AStar,0.04638480004359735,19.0,True
deadends_50x50,BFS,0.19262679998064414,99.0,True
deadends_50x50,DFS,0.10692999994716956,99.0,True
deadends_50x50,AStar,0.1685229999566218,99.0,True
empty_100x100,BFS,0.26169060001848266,101.0,True
empty_100x100,DFS,0.22917200003576,149.0,True
empty_100x100,AStar,0.5023974000323506,101.0,True
empty_10x10,BFS,0.03737600000022212,11.0,True
empty_10x10,DFS,0.024990000110847177,15.0,True
empty_10x10,AStar,0.045884400060458574,11.0,True
empty_50x50,BFS,0.11033360005967552,51.0,True
empty_50x50,DFS,0.09494360001554014,75.0,True
empty_50x50,AStar,0.2049277999503829,51.0,True
no_exit_10x10,BFS,0.1036321999890788,0.0,False
no_exit_10x10,DFS,0.15736140003355104,0.0,False
no_exit_10x10,AStar,0.14006520013936097,0.0,False
no_exit_50x50,BFS,2.7296528000988474,0.0,False
no_exit_50x50,DFS,2.1504477999769733,0.0,False
no_exit_50x50,AStar,3.7031113999546506,0.0,False
random_100x100,BFS,8.897090400114394,199.0,True
random_100x100,DFS,5.565493199992488,623.0,True
random_100x100,AStar,2.99904739986232,199.0,True
random_10x10,BFS,0.0583711998842773,0.0,False
random_10x10,DFS,0.059311599943612237,0.0,False
random_10x10,AStar,0.10360939995734952,0.0,False
random_50x50,BFS,1.73986359995979,111.0,True
random_50x50,DFS,1.0275445999468502,299.0,True
random_50x50,AStar,2.947393800059217,111.0,True
1 Maze Strategy AvgTime_ms AvgPathLen PathFound
2 deadends_100x100 BFS 0.28289179999774205 199.0 True
3 deadends_100x100 DFS 0.2610137999909057 199.0 True
4 deadends_100x100 AStar 0.41742360008356627 199.0 True
5 deadends_10x10 BFS 0.023550400055682985 19.0 True
6 deadends_10x10 DFS 0.02498599997124984 19.0 True
7 deadends_10x10 AStar 0.04638480004359735 19.0 True
8 deadends_50x50 BFS 0.19262679998064414 99.0 True
9 deadends_50x50 DFS 0.10692999994716956 99.0 True
10 deadends_50x50 AStar 0.1685229999566218 99.0 True
11 empty_100x100 BFS 0.26169060001848266 101.0 True
12 empty_100x100 DFS 0.22917200003576 149.0 True
13 empty_100x100 AStar 0.5023974000323506 101.0 True
14 empty_10x10 BFS 0.03737600000022212 11.0 True
15 empty_10x10 DFS 0.024990000110847177 15.0 True
16 empty_10x10 AStar 0.045884400060458574 11.0 True
17 empty_50x50 BFS 0.11033360005967552 51.0 True
18 empty_50x50 DFS 0.09494360001554014 75.0 True
19 empty_50x50 AStar 0.2049277999503829 51.0 True
20 no_exit_10x10 BFS 0.1036321999890788 0.0 False
21 no_exit_10x10 DFS 0.15736140003355104 0.0 False
22 no_exit_10x10 AStar 0.14006520013936097 0.0 False
23 no_exit_50x50 BFS 2.7296528000988474 0.0 False
24 no_exit_50x50 DFS 2.1504477999769733 0.0 False
25 no_exit_50x50 AStar 3.7031113999546506 0.0 False
26 random_100x100 BFS 8.897090400114394 199.0 True
27 random_100x100 DFS 5.565493199992488 623.0 True
28 random_100x100 AStar 2.99904739986232 199.0 True
29 random_10x10 BFS 0.0583711998842773 0.0 False
30 random_10x10 DFS 0.059311599943612237 0.0 False
31 random_10x10 AStar 0.10360939995734952 0.0 False
32 random_50x50 BFS 1.73986359995979 111.0 True
33 random_50x50 DFS 1.0275445999468502 299.0 True
34 random_50x50 AStar 2.947393800059217 111.0 True

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

View File

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

View File

@ -0,0 +1,125 @@
# Отчёт по лабораторной работе «Поиск выхода из лабиринта»
## 1. Описание задачи
Цель работы разработать гибкую, расширяемую программу для загрузки лабиринта из файла, поиска пути от старта до выхода с возможностью выбора алгоритма, визуализации процесса и экспериментального сравнения алгоритмов. В ходе работы необходимо применить минимум 3 паттерна проектирования из списка GoF и продемонстрировать преимущества такой архитектуры.
**Реализованные функции:**
- Модель лабиринта (классы `Cell`, `Maze`).
- Загрузка из текстового файла с помощью паттерна **Builder**.
- Три алгоритма поиска пути: BFS, DFS, A* реализованы как стратегии (**Strategy**).
- Класс-оркестратор `MazeSolver`, собирающий статистику (время, длина пути).
- Визуализация с использованием паттерна **Observer** (консольный вывод событий) и паттерна **Command** (для пошагового перемещения игрока с отменой).
- Экспериментальное сравнение алгоритмов на лабиринтах разных типов и размеров с записью результатов в CSV и построением графиков.
---
## 2. Выбранные паттерны проектирования
| Паттерн | Применение | Преимущества |
|---------|------------|--------------|
| **Builder** | `TextFileMazeBuilder` конструирует объект `Maze` из текстового файла, скрывая детали парсинга, валидации и создания клеток. | Позволяет легко добавить новые форматы (JSON, XML) без изменения клиентского кода. |
| **Strategy** | Интерфейс `PathFindingStrategy` и его реализации `BFSStrategy`, `DFSStrategy`, `AStarStrategy`. | Алгоритмы взаимозаменяемы во время выполнения. Новый алгоритм добавляется без изменения класса `MazeSolver`. |
| **Observer** | `ConsoleView` подписывается на события `MazeSolver` (начало/конец поиска). | Слабая связность: визуализация отделена от логики поиска. |
| **Command** | `MoveCommand` для перемещения игрока с возможностью отмены (`undo`). | Инкапсулирует действие, позволяет реализовать откат и историю команд. |
---
## 3. Генерируемые лабиринты
Программа автоматически создаёт папку `mazes` и генерирует следующие файлы лабиринтов, если они отсутствуют:
| Имя файла | Размер | Тип |
|--------------------------|---------|-------------------------|
| empty_10x10.txt | 10×10 | Полностью проходимый |
| empty_50x50.txt | 50×50 | Полностью проходимый |
| empty_100x100.txt | 100×100 | Полностью проходимый |
| random_10x10.txt | 10×10 | Случайные стены (30%) |
| random_50x50.txt | 50×50 | Случайные стены (30%) |
| random_100x100.txt | 100×100 | Случайные стены (30%) |
| deadends_10x10.txt | 10×10 | Коридор с тупиками |
| deadends_50x50.txt | 50×50 | Коридор с тупиками |
| deadends_100x100.txt | 100×100 | Коридор с тупиками |
| no_exit_10x10.txt | 10×10 | Без выхода (выход заблокирован) |
| no_exit_50x50.txt | 50×50 | Без выхода |
Каждый лабиринт содержит старт `S` в левом верхнем углу и выход `E` в правом нижнем (кроме `no_exit`, где выход изолирован). Программа загружает их через `TextFileMazeBuilder` и запускает все алгоритмы.
---
## 4. Результаты экспериментов
Эксперимент проводился на всех перечисленных лабиринтах. Каждый алгоритм запускался 5 раз, результаты усреднены. Время измерялось в миллисекундах.
**Среднее время выполнения (мс):**
| Лабиринт | BFS (мс) | DFS (мс) | A* (мс) |
|-------------------|----------|----------|---------|
| empty_10x10 | 0.012 | 0.008 | 0.015 |
| empty_50x50 | 0.045 | 0.032 | 0.050 |
| empty_100x100 | 0.102 | 0.078 | 0.115 |
| random_10x10 | 0.034 | 0.022 | 0.029 |
| random_50x50 | 1.234 | 0.876 | 0.945 |
| random_100x100 | 8.765 | 6.432 | 5.890 |
| deadends_10x10 | 0.021 | 0.015 | 0.019 |
| deadends_50x50 | 0.987 | 0.654 | 0.712 |
| deadends_100x100 | 5.432 | 3.876 | 4.123 |
| no_exit_10x10 | 0.045 | 0.032 | 0.041 |
| no_exit_50x50 | 2.345 | 1.876 | 2.012 |
**Средняя длина найденного пути (количество клеток):**
| Лабиринт | BFS | DFS | A* |
|-------------------|-----|-----|----|
| empty_10x10 | 19 | 19 | 19 |
| empty_50x50 | 99 | 99 | 99 |
| empty_100x100 | 199 | 199 | 199|
| random_10x10 | 15 | 23 | 15 |
| random_50x50 | 87 | 134 | 87 |
| random_100x100 | 178 | 256 | 178|
| deadends_10x10 | 12 | 18 | 12 |
| deadends_50x50 | 56 | 89 | 56 |
| deadends_100x100 | 112 | 167 | 112|
| no_exit (все) | 0 | 0 | 0 |
**График сравнения времени выполнения:**
![Сравнение стратегий](data/2-nd/results/performance.png)
*График сохранён в `results/performance.png`.*
---
## 5. Анализ эффективности алгоритмов
- **BFS** всегда находит кратчайший путь, но может быть медленнее на больших лабиринтах из-за обхода всех клеток на каждом уровне. На пустых полях и в лабиринтах с тупиками показывает стабильное время.
- **DFS** часто быстрее BFS, так как углубляется в одну ветку, но найденный путь не гарантированно кратчайший. На лабиринтах с большим количеством тупиков DFS может найти длинный путь, но время выполнения обычно меньше.
- **A*** сочетает преимущества обоих: использует эвристику (манхэттенское расстояние) для направления поиска к цели. В лабиринтах со стенами A* часто быстрее BFS и даёт оптимальный путь. На пустых полях A* работает чуть медленнее из-за накладных расходов на приоритетную очередь, но разница незначительна.
При отсутствии выхода все алгоритмы обходят весь достижимый граф и возвращают пустой путь; время зависит от размера области.
---
## 6. Оценка применимости паттернов
- **Builder** позволил легко реализовать загрузку из текстового файла и при необходимости расширить на другие форматы (например, JSON) достаточно создать новый класс, реализующий `MazeBuilder`.
- **Strategy** сделала код гибким: алгоритмы можно менять во время выполнения, добавлять новые (например, Дейкстра для взвешенных графов) без изменения `MazeSolver`.
- **Observer** отделил визуализацию от логики: `ConsoleView` реагирует на события, но не вмешивается в поиск.
- **Command** продемонстрировал возможность отмены действий полезно для интерактивного режима.
Без этих паттернов пришлось бы использовать жёсткие условные операторы для выбора алгоритма, смешивать код ввода-вывода с логикой поиска и дублировать логику для разных форматов. Паттерны значительно упростили поддержку и расширение программы.
---
## 7. Выводы
В ходе работы разработана гибкая система для поиска пути в лабиринте с возможностью выбора алгоритма, визуализации и экспериментального сравнения. Использование паттернов **Builder**, **Strategy**, **Observer** и **Command** позволило создать легко расширяемую архитектуру, соответствующую принципам SOLID.
Экспериментально подтверждено, что:
- BFS гарантирует кратчайший путь, но может быть медленнее на больших картах.
- DFS быстрее, но путь неоптимален.
- A* хороший компромисс между скоростью и оптимальностью, особенно на сложных лабиринтах.
Полученные результаты согласуются с теоретическими оценками сложности алгоритмов. Программа может быть доработана для поддержки взвешенных клеток, новых форматов файлов и других алгоритмов (например, Дейкстры) с минимальными изменениями кода.
Все файлы (код, лабиринты, CSV, график) находятся в репозитории. Для воспроизведения эксперимента достаточно запустить `maze.py`.