diff --git a/AgapovaDS/docs/data/2-nd/maze.py b/AgapovaDS/docs/data/2-nd/maze.py new file mode 100644 index 0000000..c91dffd --- /dev/null +++ b/AgapovaDS/docs/data/2-nd/maze.py @@ -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() diff --git a/AgapovaDS/docs/data/2-nd/mazes/deadends_100x100.txt b/AgapovaDS/docs/data/2-nd/mazes/deadends_100x100.txt new file mode 100644 index 0000000..924478c --- /dev/null +++ b/AgapovaDS/docs/data/2-nd/mazes/deadends_100x100.txt @@ -0,0 +1,100 @@ +S ################################################################################################## +# ################################################################################################## +# ################################################################################################## +# ################################################################################################# +## ############################################################################################## +##### ############################################################################################## +##### ############################################################################################ +####### ########################################################################################### +######## ###################################################################################### +############# ################################################################################## +################# ################################################################################# +################## ################################################################################# +################## ################################################################################# +################## ################################################################################ +################### ################################################################################ +################### ############################################################################# +###################### ############################################################################ +####################### ############################################################################ +####################### ############################################################################ +####################### ############################################################################ +####################### ########################################################################### +######################## ########################################################################## +######################### ######################################################################### +########################## ######################################################################## +########################### ##################################################################### +############################## ##################################################################### +############################## ##################################################################### +############################## #################################################################### +############################### #################################################################### +############################### #################################################################### +############################### ################################################################## +################################# ################################################################## +################################# ################################################################# +################################## ############################################################### +#################################### ############################################################## +##################################### ############################################################## +##################################### ############################################################## +##################################### ############################################################ +####################################### ############################################################ +####################################### ############################################################ +####################################### ############################################################ +####################################### ########################################################## +######################################### ######################################################### +########################################## ######################################################## +########################################### ######################################################## +########################################### ######################################################## +########################################### ######################################################## +########################################### ####################################################### +############################################ ##################################################### +############################################## ################################################## +################################################# ################################################## +################################################# ############################################## +##################################################### ############################################# +###################################################### ############################################ +####################################################### ############################################ +####################################################### ############################################ +####################################################### ########################################### +######################################################## ########################################### +######################################################## ########################################## +######################################################### ########################################## +######################################################### ########################################## +######################################################### ########################################## +######################################################### ######################################### +########################################################## ####################################### +############################################################ ##################################### +############################################################## ##################################### +############################################################## #################################### +############################################################### ################################### +################################################################ ################################### +################################################################ ################################## +################################################################# ############################### +#################################################################### ############################# +###################################################################### ########################### +######################################################################## ########################## +######################################################################### ######################## +########################################################################### ####################### +############################################################################ ####################### +############################################################################ ################### +################################################################################ ################## +################################################################################# ################# +################################################################################## ################# +################################################################################## ################# +################################################################################## ############## +##################################################################################### ############ +####################################################################################### ########### +######################################################################################## ########### +######################################################################################## ########## +######################################################################################### ########## +######################################################################################### ######## +########################################################################################### ####### +############################################################################################ ##### +############################################################################################## ### +################################################################################################ ## +################################################################################################# ## +################################################################################################# ## +################################################################################################# ## +################################################################################################# ## +################################################################################################# # +################################################################################################## +###################################################################################################E diff --git a/AgapovaDS/docs/data/2-nd/mazes/deadends_10x10.txt b/AgapovaDS/docs/data/2-nd/mazes/deadends_10x10.txt new file mode 100644 index 0000000..db296be --- /dev/null +++ b/AgapovaDS/docs/data/2-nd/mazes/deadends_10x10.txt @@ -0,0 +1,10 @@ +S ####### +## ####### +## ####### +## ####### +## ####### +## ###### +### ##### +#### ## +####### ## +####### E diff --git a/AgapovaDS/docs/data/2-nd/mazes/deadends_50x50.txt b/AgapovaDS/docs/data/2-nd/mazes/deadends_50x50.txt new file mode 100644 index 0000000..705b3f8 --- /dev/null +++ b/AgapovaDS/docs/data/2-nd/mazes/deadends_50x50.txt @@ -0,0 +1,50 @@ +S ################################################ +# ############################################### +## ############################################### +## ############################################### +## ############################################## +### ############################################## +### ############################################ +##### ########################################### +###### ###################################### +########### ###################################### +########### ##################################### +############ ################################## +############### ################################# +################ ############################### +################## ############################### +################## ########################### +###################### ########################### +###################### ######################## +######################### ####################### +########################## ####################### +########################## ###################### +########################### ##################### +############################ ##################### +############################ #################### +############################# ################### +############################## ################### +############################## ################### +############################## ################ +################################# ########### +###################################### ########## +####################################### ######### +######################################## ######### +######################################## ######## +######################################### ####### +########################################## ##### +############################################ #### +############################################# ### +############################################## ### +############################################## ## +############################################### +################################################# +################################################# +################################################# +################################################# +################################################# +################################################# +################################################# +################################################# +################################################# +#################################################E diff --git a/AgapovaDS/docs/data/2-nd/mazes/empty_100x100.txt b/AgapovaDS/docs/data/2-nd/mazes/empty_100x100.txt new file mode 100644 index 0000000..8f5959f --- /dev/null +++ b/AgapovaDS/docs/data/2-nd/mazes/empty_100x100.txt @@ -0,0 +1,100 @@ +S + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + E diff --git a/AgapovaDS/docs/data/2-nd/mazes/empty_10x10.txt b/AgapovaDS/docs/data/2-nd/mazes/empty_10x10.txt new file mode 100644 index 0000000..4c6a7c6 --- /dev/null +++ b/AgapovaDS/docs/data/2-nd/mazes/empty_10x10.txt @@ -0,0 +1,10 @@ +S + + + + + + + + + E diff --git a/AgapovaDS/docs/data/2-nd/mazes/empty_50x50.txt b/AgapovaDS/docs/data/2-nd/mazes/empty_50x50.txt new file mode 100644 index 0000000..a92cf1d --- /dev/null +++ b/AgapovaDS/docs/data/2-nd/mazes/empty_50x50.txt @@ -0,0 +1,50 @@ +S + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + E diff --git a/AgapovaDS/docs/data/2-nd/mazes/no_exit_10x10.txt b/AgapovaDS/docs/data/2-nd/mazes/no_exit_10x10.txt new file mode 100644 index 0000000..e7fbfcb --- /dev/null +++ b/AgapovaDS/docs/data/2-nd/mazes/no_exit_10x10.txt @@ -0,0 +1,10 @@ +S ## + # + # # + ### + # # # + # # # + ## # + ### + # # + #E diff --git a/AgapovaDS/docs/data/2-nd/mazes/no_exit_50x50.txt b/AgapovaDS/docs/data/2-nd/mazes/no_exit_50x50.txt new file mode 100644 index 0000000..55ea97f --- /dev/null +++ b/AgapovaDS/docs/data/2-nd/mazes/no_exit_50x50.txt @@ -0,0 +1,50 @@ +S # # # ## # + # ### ## # # # # # # +# # # # # # # # # # + # # # # # # # # ## +### # # # # ## # # # # ## ## +### # # # # # # # + # ## # +## ### ## # # # +# # # # # # ### # ## + # # # # # # # + #### ### # # # # # # # + ## # # # # # # # # ## # + # ## # ## ## # # + ## # # ## # # # # # # + ### # # # # # # # + # ## ## # # # # # # # + # # # # # # # # #### +# # ## # # # # # # # + ## # ### # + # ## # # # # + # # # # # # + # ## # ## #### ### # # # # + # # # # ### +### ## ## # # # ## +# # # ## # + # # ## ### # # # # +# # # # ### # ## # # # # +## # # # # ## # + # # # # # ## # # # + # # # # # # + # # # # ## # # # # # # # # + # # # # # ## # # # + # # # ## # + # # # # # # # # # ### # + # # # # # # # + # ## # # ## # ## # # + # # # # ## # ## +# # # # # # # # # + ## ### # ### # # # # + ## ## ## # # # ## # # # # + # # ### # # # # # +# # # # ## ## ## # + # # # ## ## ### # # + # # # ## # # +## # # # # # # + # # # # # # + # # # # ## +## # # # # # ## # + # # ## # # ### # # +# ## # # # # # # ## ## #E diff --git a/AgapovaDS/docs/data/2-nd/mazes/random_100x100.txt b/AgapovaDS/docs/data/2-nd/mazes/random_100x100.txt new file mode 100644 index 0000000..dcf71fe --- /dev/null +++ b/AgapovaDS/docs/data/2-nd/mazes/random_100x100.txt @@ -0,0 +1,100 @@ +S ## ## # # # # # # # # # # # ## ### # # + ### # # ### # ##### # # ### ## ## # # # # # ### # # # # +## # # # # # # ### # ## # # # # # # ### # # ### ## + # ## # # # # # ## # # # ## # ## # ### ## ## # ## # + ## # # # # # # # # # # # # # # # # ## # # # # # # # +# ## ## # ## # # ### ## # # # # # ## # # # # ## # + ## # # ### # # # ## # # ## # ## #### #### ## ## # +# # ##### # ### # # # ### ### # ## # ### # # ## # # # + # ## # # #### # # # ### ### # # # # ### # # ## # # # + # # # ## ## # # ### # # ## # ## # # # # ## + # ## # # # ## ## # # ## ## # # # # # # ## ## # # # ## # ### + # ## ## ## # ## # # # # # # # # ## # ####### # #### + # ## # # # ## # # ## # # # # # # # # # ### # # ### + # ## # ### # ## # # # # # # # ## ### # # # # # ## ## # + # # ### # # ##### # # # # # # # # ## # # # # +## # # # # # # # # # # # # # # ## ## # ## # + # # # # # # # # ### # ## # ## # # ## # # # ## ## + ## # # # # #### # # # # ## # # # # # # # # + # # # ## ## # # # # # # # # # # # # # ### ## # # # +# # # ## ### # ### # ## # # # # # # #### #### ## + # # # # #### # ## # # # ## ## # ## ### # # # # # # # # + ## # # ### # # # ## ## # # # ## # # # # # ## # + # # ### ## # ## # ## # ## ## ## ## # ## # ## # # + ## # #### # # # # # # ## ## # ## # # ## # # # ## +# # # ## ## # ### # # #### # # ### # # # ## # # ## # + # ### #### # # # # # # ## # ### # # ### ## # # +## # # ## # # # # # # # # ## ## ## # ## # # # # # # # + # ### # # # # # # ## ## # ### # # ### ### + # ## # # # ## # # ## # # # # # # # # ## # # # +# # ## # # # # # # ## #### # # ## # # # # # + ## # # ## ## ## # # ### # # # # # ## # # # + # # # # # # # ### # # # # # # ### # ## # ### +# ## # ## # # # # # # # # ## # # # ## # # +# # # # #### # # # # # ### # ## + ### # # # # # # # ## # ## ## # # # # ### ## ### # + ## # ## # # # # ## ## # # # # # ## # # # # ## ### + # # # # ### # ## # ### # # ## # # # ## # # # ## ## ## +## # # # # # # # # ## # ## #### # ### # # # # # # ## + ## # ## # # # # # # # ## ## # # # # # # # # # # +# ## # # # # # # # ## ##### # ## # ### ## # # ## ## # # # + # # # ## ## ### # # # # # # ## ## # # # # # + # ## # # # # ## # # ## # # # ## # # # # ## # # # # ## + ## # # # # # ## # # # ### # # # ## ## # ## # ### # + # # # ## ##### # # ## #### # # # # # # # ### ## #### # # + # ## ## # ## # ## # # # ## # # # # # ### + # # # # ## ### # ### # ## # # # # # # #### ## ## ## # # +## # ### ## # # ## ## # #### # # ## ## ## # # # ## ## # + # ####### # # # # # # # # # # # ### # ## # ### # +# ## # # # ## ## # # ## # # ## # ## ## # # # # # +# # # ## # # ### # # # # ### # # # # # ## # # # ## # # + ## ## # ## ## # # # # ## ## # # # ## # # # # ### # + # ## ## # # # ## # # ## # # # # # ### # # ## # # # ## +# ##### # # # # # # # # # # # # # # # # # # # + # # # # # # # ## # ### # ## ## # # # # # ## # ## # # +# # # ## # # ### ## ## ## # ## ## # # # # # ## ## # +# # # ### # # # # # # # # #### # # ## # #### # ## +# # # #### ## # # # ## # # # # ## # ### # # ## # # # # + # #### ## # # # ## # # # # # # ### # # # # # # ## # +# # ### ## ## # # # # ## # ## # # # # # # ## ## # # + # # # # ## ## ## # # # ## # # ## # # ### # # # ### + # # ## # ## ### # # # # ## # ## # # # ## ## # ## # # ### # # + # #### # # # # # # ## # ## # # # # # # # # +# # # # # # # # # # # # ## ## # # ## # # # # # ## # ##### + ### # # ## # ### # # # # ## # # #### # ## # ## # # ## # # # ### +## # # ### ## # # # ## ### # ## # # ### ### # +# # # # # ## ## # # # # # ### # # ## ## ## ## # # # # # +# ## # # ### # # # # # ### # # # # ## # # # ### ### # + # # ## ## # # # # # # #### ### # ## ### # ## ## # ## +# # # # ### # ### ### ##### # # ## # # ### # # # # + ### # # # # # # # ## ## ## # ## ### ### # # ## # +# # ### # # # # # # ### # # ## ### # # # # # # # ## # + ### # # # # ## # ## # # # # # # # # # # # # # ### # ## # +## # # ## ## ## ## # # # # # # # ## ## # # # # + # ### ## # # ## # # # # ## # ## # # # # # ## ## ## # ## # ### +# # # ### # # # # ## # # # # ### # # # # ### +# # # ### # # # # # # # ## ## # # ## # # # # # # # +# # # # # # # ### ##### # ## # # # # ## ## # ## # # #### # ### # +# ##### # # ## # # # ## # ## ## # ### # # # ### # +# # ## # ## ## ## # # # # # ## # # ## # # ## # # # ## # ### # # # +# # # ####### ## ## ### # ### # # ## ## # ## # # # # + ## # # # # #### # # # # ## # # # # # # # ## # # # ## +# # # # # # # # # ## # # # # ## # ## # # # # ### # + ## # # ### # ## # # # ## # # # ## ## ## # # # # # ## ## # # ### +# # ## ### # # # ## # # ## # ## ## # # # # ## ## ## # # ## ### + ## # # ### ## ## # # # ## # # # # # # ## ## ## ## ### # # # # + # # # ## # # # # ## ## # # # ## # ## # ## # + # # # ## # ## # ## # ## ##### # # # # # ## ## # # ## # # ### # + # #### # ## # # #### # # # # # # ## # # # # # ### # +# ## # ## # ## ## # # #### # # # # # ## ## # + # ## # # ## ### ### # # # ## # # ## # # # # +# # # ### # # ## # # # # ## ## # # # # # # # # +### # # # # # # ## ## ## # # # # ## # # ## ## # ## +# # # ##### ## # # # # # ## ## ## # ## # # # ## # # + # # # # # # # ## # # # # ## #### # ## # ## # # ## ## # +# ## ## ### # # ### ## ## # # ## # # ## ## # # # # ## ## # + # # # # ## ## # ## ### # # # ### ### # # # # + #### ## # ## # ## # # # # ## ### # ## ## ## ### # # # ### # # # # + # ## ## # # # # # # # ### # # ## # ## # # # # # +### ### ## # # ## # # ## #### ## # ##### ## # # # # # + # # ### # ## # # ## # ## # ### # # # # # ## # E diff --git a/AgapovaDS/docs/data/2-nd/mazes/random_10x10.txt b/AgapovaDS/docs/data/2-nd/mazes/random_10x10.txt new file mode 100644 index 0000000..7ae93b3 --- /dev/null +++ b/AgapovaDS/docs/data/2-nd/mazes/random_10x10.txt @@ -0,0 +1,10 @@ +S + # # ## +## # + # # # + # ## # +# # ### # +# # ## +# ### # + ### # + ##E diff --git a/AgapovaDS/docs/data/2-nd/mazes/random_50x50.txt b/AgapovaDS/docs/data/2-nd/mazes/random_50x50.txt new file mode 100644 index 0000000..9802581 --- /dev/null +++ b/AgapovaDS/docs/data/2-nd/mazes/random_50x50.txt @@ -0,0 +1,50 @@ +S # # # ## # # # # + # # ## ## # # ## # # # +## # ## # ## # # ##### ### # + # # ## ## ### ### # # # +# # # # # # ## +### # # ## # ## # # # +## # ### ### ## # # # # + ## # ## ## # # # # + # # # ### # # # # # ## + # ## ## # # # ## # # ## # + #### ## ### # # ### # # # # # + # # ## # # ## # # ## # ## +# # # # # # # # # ###### +### # ### # # ## # # # +## ##### # # # ## # ### ## + # ### # ## # # # # ## # # +# # # # # ### # # # # + # # ### # # # # ### ### # # # + # # ## # # ### # # # + ## # # # ### # # # # # # # +## # # # # ## # # ## # # + # #### # # ### # # # # +# # # # # # # ###### ## # # + # # ## # # ### ## + # # # ## # # ## ### # +### # ## # # # ## # # # ## # # # +# # ### # # # # # # ## # # ## +### # # ##### # # # # ### ### # +# ## # ## # # # ### # # # + # ## # ## ### ### # # # ## + # ## # # ## # # # ### ## # ## # # # # +# ## # # # # # # # # # # # + # ### # # # # # # # +## ## # ### # # ## ## ## # # # + # ### ## ### # # ## # ## + ## # # # # #### # # ## ## + # ### ### # ## # # # # + # # # # # # # # ### ##### +# # # # # # # ### ## # # ### ### +# # # # # # # # # # # # # + ## # # # # ### # # # +## # # # # ### # # ### ### # # ## # ## +## # # ### ## # ## ##### + # # ### # # # # # # ## # +# # ### ## # # # # ## ## # + ## # # # ## ## # # # ### +##### ## # # ## # ## ## # # ## +# # ### ### # # # # # ## # + ## ## # ### # ## ### + # # # # ## ## # # ## E diff --git a/AgapovaDS/docs/data/2-nd/results/experiment_results.csv b/AgapovaDS/docs/data/2-nd/results/experiment_results.csv new file mode 100644 index 0000000..ac02f11 --- /dev/null +++ b/AgapovaDS/docs/data/2-nd/results/experiment_results.csv @@ -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 diff --git a/AgapovaDS/docs/data/2-nd/results/performance.png b/AgapovaDS/docs/data/2-nd/results/performance.png new file mode 100644 index 0000000..1b89351 Binary files /dev/null and b/AgapovaDS/docs/data/2-nd/results/performance.png differ diff --git a/AgapovaDS/docs/data/2-nd/test_maze.txt b/AgapovaDS/docs/data/2-nd/test_maze.txt new file mode 100644 index 0000000..2eacde7 --- /dev/null +++ b/AgapovaDS/docs/data/2-nd/test_maze.txt @@ -0,0 +1,5 @@ +####### +#S # +# ### # +# E # +####### \ No newline at end of file diff --git a/AgapovaDS/docs/report-2-nd.md b/AgapovaDS/docs/report-2-nd.md new file mode 100644 index 0000000..17eec3b --- /dev/null +++ b/AgapovaDS/docs/report-2-nd.md @@ -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`.