diff --git a/AgapovaDS/docs/data/2-nd/maze.py b/AgapovaDS/docs/data/2-nd/maze.py index 0e6d656..dc20d6b 100644 --- a/AgapovaDS/docs/data/2-nd/maze.py +++ b/AgapovaDS/docs/data/2-nd/maze.py @@ -1,6 +1,11 @@ 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): @@ -165,10 +170,21 @@ class MazeSolver: def __init__(self, maze, strategy=None): self.maze = maze self.strategy = strategy + self.observers = [] # для Observer 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("Стратегия не установлена") @@ -176,30 +192,249 @@ class MazeSolver: 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 -# Test search +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 Command: + def execute(self): + raise NotImplementedError + + def undo(self): + raise NotImplementedError + + +class MoveCommand(Command): + def __init__(self, player, dx, dy): + self.player = player + self.dx = dx + self.dy = dy + self.prev_x = player.x + self.prev_y = player.y + + def execute(self): + new_x = self.player.x + self.dx + new_y = self.player.y + self.dy + maze = self.player.maze + cell = maze.get_cell(new_x, new_y) + if cell and cell.is_passable(): + self.player.x = new_x + self.player.y = new_y + self.player.current_cell = cell + return True + return False + + def undo(self): + self.player.x = self.prev_x + self.player.y = self.prev_y + self.player.current_cell = self.player.maze.get_cell(self.prev_x, self.prev_y) + + +class Player: + def __init__(self, maze, start_cell): + self.maze = maze + self.x = start_cell.x + self.y = start_cell.y + self.current_cell = start_cell + + +# EEEEEEEEEKSPERIMENTY +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 run_experiment(): + os.makedirs("results", exist_ok=True) + + maze_generators = [ + ("empty_10x10", lambda: generate_empty_maze(10, 10)), + ("empty_50x50", lambda: generate_empty_maze(50, 50)), + ("empty_100x100", lambda: generate_empty_maze(100, 100)), + ("random_10x10", lambda: generate_random_maze(10, 10, 0.3)), + ("random_50x50", lambda: generate_random_maze(50, 50, 0.3)), + ("random_100x100", lambda: generate_random_maze(100, 100, 0.3)), + ("dead_ends_10x10", lambda: generate_maze_with_dead_ends(10, 10)), + ("dead_ends_50x50", lambda: generate_maze_with_dead_ends(50, 50)), + ("dead_ends_100x100", lambda: generate_maze_with_dead_ends(100, 100)), + ("no_exit_10x10", lambda: generate_maze_no_exit(10, 10)), + ("no_exit_50x50", lambda: generate_maze_no_exit(50, 50)), + ] + + strategies = [ + ("BFS", BFSStrategy()), + ("DFS", DFSStrategy()), + ("AStar", AStarStrategy()) + ] + + repeats = 5 + all_results = [] + + for maze_name, gen_func in maze_generators: + print(f"Тестирование лабиринта: {maze_name}") + maze = gen_func() + 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 + all_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}") + + # Сохраняем CSV + 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(all_results) + print(f"Результаты сохранены в {csv_path}") + + # Построение графика + maze_names = sorted(set(r["Maze"] for r in all_results)) + strategy_names = ["BFS", "DFS", "AStar"] + data = {maze: {s: None for s in strategy_names} for maze in maze_names} + for r in all_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__': - builder = TextFileMazeBuilder() - maze = builder.build_from_file('test_maze.txt') - solver = MazeSolver(maze) - - solver.set_strategy(BFSStrategy()) - path, ms = solver.solve() - print("BFS путь:", [f"({c.x},{c.y})" for c in path]) - print(f"Время: {ms:.3f} мс") - - solver.set_strategy(DFSStrategy()) - path, ms = solver.solve() - print("DFS путь:", [f"({c.x},{c.y})" for c in path]) - print(f"Время: {ms:.3f} мс") - - solver.set_strategy(AStarStrategy()) - path, ms = solver.solve() - print("A* путь:", [f"({c.x},{c.y})" for c in path]) - print(f"Время: {ms:.3f} мс") + run_experiment() 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..e080fe7 --- /dev/null +++ b/AgapovaDS/docs/data/2-nd/results/experiment_results.csv @@ -0,0 +1,34 @@ +Maze,Strategy,AvgTime_ms,AvgPathLen,PathFound +empty_10x10,BFS,0.13590119997388683,19.0,True +empty_10x10,DFS,0.0658330000078422,55.0,True +empty_10x10,AStar,0.12148100004196749,19.0,True +empty_50x50,BFS,4.080367400092655,99.0,True +empty_50x50,DFS,1.490334600021015,1275.0,True +empty_50x50,AStar,4.539874399961263,99.0,True +empty_100x100,BFS,11.864865199822816,199.0,True +empty_100x100,DFS,6.421647800107166,4951.0,True +empty_100x100,AStar,13.146192800104473,199.0,True +random_10x10,BFS,0.08931020001909928,19.0,True +random_10x10,DFS,0.0637794000795111,23.0,True +random_10x10,AStar,0.08288600010928349,19.0,True +random_50x50,BFS,2.3307791999286565,0.0,False +random_50x50,DFS,2.358275200094795,0.0,False +random_50x50,AStar,3.1945947999702184,0.0,False +random_100x100,BFS,8.76705820001007,0.0,False +random_100x100,DFS,8.292441200046596,0.0,False +random_100x100,AStar,13.397702600013872,0.0,False +dead_ends_10x10,BFS,0.023798799975338625,19.0,True +dead_ends_10x10,DFS,0.024454199865431292,19.0,True +dead_ends_10x10,AStar,0.02576019996922696,19.0,True +dead_ends_50x50,BFS,0.10447879994899267,99.0,True +dead_ends_50x50,DFS,0.1395262001096853,99.0,True +dead_ends_50x50,AStar,0.1517965998573345,99.0,True +dead_ends_100x100,BFS,0.27448000000731554,199.0,True +dead_ends_100x100,DFS,0.2607183998406981,199.0,True +dead_ends_100x100,AStar,0.42436699995960225,199.0,True +no_exit_10x10,BFS,0.09290340003644815,0.0,False +no_exit_10x10,DFS,0.09840180009632604,0.0,False +no_exit_10x10,AStar,0.11763999991671881,0.0,False +no_exit_50x50,BFS,2.5957402001040464,0.0,False +no_exit_50x50,DFS,2.327834000061557,0.0,False +no_exit_50x50,AStar,4.334011999981158,0.0,False 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..167884b Binary files /dev/null and b/AgapovaDS/docs/data/2-nd/results/performance.png differ