From 11d65436e1660ae625e3f7a258055097eded97cc Mon Sep 17 00:00:00 2001 From: YaroslavtsevAS Date: Sun, 24 May 2026 15:34:48 +0000 Subject: [PATCH] [2] add experimenal and plots --- YaroslavtsevAS/docs/2-nd-lab/main.py | 297 ++++++++++++++++----------- 1 file changed, 172 insertions(+), 125 deletions(-) diff --git a/YaroslavtsevAS/docs/2-nd-lab/main.py b/YaroslavtsevAS/docs/2-nd-lab/main.py index 92103f5..a71ee34 100644 --- a/YaroslavtsevAS/docs/2-nd-lab/main.py +++ b/YaroslavtsevAS/docs/2-nd-lab/main.py @@ -1,9 +1,13 @@ import sys import os import time +import csv from collections import deque import heapq +import matplotlib.pyplot as plt +import numpy as np +# ---------------- Модель лабиринта ---------------- class Tile: def __init__(self, x, y): self._x = x @@ -30,7 +34,6 @@ class Tile: def is_goal(self, value): self._goal = value def can_walk(self): return not self._wall - class Labyrinth: def __init__(self, width, height): self._width = width @@ -79,7 +82,7 @@ class Labyrinth: res.append(nb) return res - +# ---------------- Загрузка лабиринта ---------------- class LabyrinthBuilder: def build(self, filename): raise NotImplementedError @@ -93,24 +96,21 @@ class TextLabyrinthBuilder(LabyrinthBuilder): lab = Labyrinth(w, h) for y, row in enumerate(lines): for x, ch in enumerate(row): - if ch == '#': - lab.configure_tile(x, y, 'wall') + if ch == '#': lab.configure_tile(x, y, 'wall') elif ch == 'S': lab.configure_tile(x, y, 'entry') entries += 1 elif ch == 'E': lab.configure_tile(x, y, 'goal') exits += 1 - else: - lab.configure_tile(x, y, 'floor') + else: lab.configure_tile(x, y, 'floor') if entries != 1 or exits != 1: - raise ValueError(f"Некорректный лабиринт: найдено S={entries}, E={exits}") + raise ValueError(f"Некорректный лабиринт: S={entries}, E={exits}") return lab - +# ---------------- Алгоритмы поиска пути ---------------- class Pathfinder: def find_path(self, lab, start, goal): raise NotImplementedError - def _build_path(self, preds, start, goal): path = [] cur = goal @@ -119,11 +119,8 @@ class Pathfinder: cur = preds.get(cur) path.reverse() return path - @property - def visited_count(self): - return getattr(self, '_visited', 0) - + def visited_count(self): return getattr(self, '_visited', 0) class BFS_Pathfinder(Pathfinder): def find_path(self, lab, start, goal): @@ -143,7 +140,6 @@ class BFS_Pathfinder(Pathfinder): self._visited = len(seen) return [] - class DFS_Pathfinder(Pathfinder): def find_path(self, lab, start, goal): stack = [start] @@ -162,11 +158,9 @@ class DFS_Pathfinder(Pathfinder): self._visited = len(seen) return [] - class AStar_Pathfinder(Pathfinder): def _heuristic(self, a, b): return abs(a.x - b.x) + abs(a.y - b.y) - def find_path(self, lab, start, goal): heap = [] cnt = 0 @@ -197,43 +191,36 @@ class AStar_Pathfinder(Pathfinder): self._visited = len(seen) return [] - +# ---------------- Компоненты интерактивной игры ---------------- class Explorer: def __init__(self, start_tile, labyrinth): self._current = start_tile self._previous = None self._lab = labyrinth - @property - def current(self): - return self._current - + def current(self): return self._current def move(self, tile): if tile and tile.can_walk(): self._previous = self._current self._current = tile return True return False - def undo(self): if self._previous: self._current, self._previous = self._previous, None return True return False - class Action: def execute(self): raise NotImplementedError def undo(self): raise NotImplementedError - class MoveAction(Action): def __init__(self, explorer, direction, lab): self._explorer = explorer self._dx, self._dy = direction self._lab = lab self._done = False - def execute(self): nx = self._explorer.current.x + self._dx ny = self._explorer.current.y + self._dy @@ -243,7 +230,6 @@ class MoveAction(Action): self._done = True return True return False - def undo(self): if self._done: self._explorer.undo() @@ -251,25 +237,17 @@ class MoveAction(Action): return True return False - class GameObserver: def update(self, event, data): raise NotImplementedError - class TerminalDisplay(GameObserver): def __init__(self, explorer=None): self._explorer = explorer self._last_path = None - def update(self, event, data): - if event == 'labyrinth_loaded': - self._draw_lab(data) - elif event == 'path_found': - self._last_path = data - self._show_path_info(data) - elif event == 'player_moved': - self._draw_with_player(data) - + if event == 'labyrinth_loaded': self._draw_lab(data) + elif event == 'path_found': self._show_path_info(data) + elif event == 'player_moved': self._draw_with_player(data) def _draw_lab(self, lab): os.system('cls' if os.name == 'nt' else 'clear') print('=' * (lab.width * 2 + 4)) @@ -285,8 +263,6 @@ class TerminalDisplay(GameObserver): else: print('.', end=' ') print() print('=' * (lab.width * 2 + 4)) - print(' S – вход E – выход # – стена . – пол') - def _draw_with_player(self, lab): os.system('cls' if os.name == 'nt' else 'clear') print('=' * (lab.width * 2 + 4)) @@ -296,8 +272,7 @@ class TerminalDisplay(GameObserver): print(' ', end='') for x in range(lab.width): t = lab.tile_at(x, y) - if self._explorer and t == self._explorer.current: - print('P', end=' ') + if self._explorer and t == self._explorer.current: print('P', end=' ') elif t == lab.start: print('S', end=' ') elif t == lab.exit: print('E', end=' ') elif t.is_wall: print('#', end=' ') @@ -305,103 +280,175 @@ class TerminalDisplay(GameObserver): print() print('=' * (lab.width * 2 + 4)) if self._explorer: - print(f' Позиция игрока: ({self._explorer.current.x}, {self._explorer.current.y})') - print(' S – вход E – выход # – стена . – пол P – игрок') - + print(f' Позиция: ({self._explorer.current.x}, {self._explorer.current.y})') def _show_path_info(self, path): - if not path: - print('\n Путь не найден!') - else: - print(f'\n Найден путь длиной {len(path)} клеток.') - + if not path: print('\n Путь не найден!') + else: print(f'\n Длина пути: {len(path)} клеток.') class LabyrinthSolver: def __init__(self, lab): self._lab = lab self._strategy = None self._observers = [] - - def attach(self, obs): - self._observers.append(obs) - + def attach(self, obs): self._observers.append(obs) def _notify(self, event, data): - for obs in self._observers: - obs.update(event, data) - - def set_strategy(self, strategy): - self._strategy = strategy - + for obs in self._observers: obs.update(event, data) + def set_strategy(self, strategy): self._strategy = strategy def solve(self): - if self._strategy is None: - return None + if self._strategy is None: return None start_t = time.perf_counter() path = self._strategy.find_path(self._lab, self._lab.start, self._lab.exit) elapsed = (time.perf_counter() - start_t) * 1000 self._notify('path_found', path) return {'time_ms': elapsed, 'visited': self._strategy.visited_count, 'length': len(path)} - -def run_interactive(lab): - player = Explorer(lab.start, lab) - display = TerminalDisplay(player) - display.update('labyrinth_loaded', lab) - - solver = LabyrinthSolver(lab) - solver.attach(display) - - print('\n УПРАВЛЕНИЕ:') - print(' H – влево J – вниз K – вверх L – вправо') - print(' U – отменить ход Q – выход') - print(' Автопоиск: B – BFS D – DFS A – A*') - print('=' * 50) - - history = [] - - while True: - cmd = input('\n Команда > ').lower().strip() - if cmd == 'q': - print('\n До свидания!') - break - elif cmd == 'b': - solver.set_strategy(BFS_Pathfinder()) - stats = solver.solve() - print(f"\n BFS: время={stats['time_ms']:.3f}мс, посещено={stats['visited']}, длина={stats['length']}") - elif cmd == 'd': - solver.set_strategy(DFS_Pathfinder()) - stats = solver.solve() - print(f"\n DFS: время={stats['time_ms']:.3f}мс, посещено={stats['visited']}, длина={stats['length']}") - elif cmd == 'a': - solver.set_strategy(AStar_Pathfinder()) - stats = solver.solve() - print(f"\n A*: время={stats['time_ms']:.3f}мс, посещено={stats['visited']}, длина={stats['length']}") - elif cmd in ('h','j','k','l'): - dirs = {'h': (-1,0), 'l': (1,0), 'k': (0,-1), 'j': (0,1)} - action = MoveAction(player, dirs[cmd], lab) - if action.execute(): - history.append(action) - display.update('player_moved', lab) - if player.current == lab.exit: - print('\n ПОЗДРАВЛЯЕМ! ВЫ ВЫБРАЛИСЬ ИЗ ЛАБИРИНТА!') - print(f' Всего ходов: {len(history)}') - break - else: - print('\n Там стена!') - elif cmd == 'u': - if history: - act = history.pop() - act.undo() - display.update('player_moved', lab) - print('\n Ход отменён') - else: - print('\n Нечего отменять') - else: - print('\n Неизвестная команда. Используйте H,J,K,L,U,Q,B,D,A') - - -if __name__ == '__main__': - maze_file = 'maze1.txt' - if len(sys.argv) > 1: - maze_file = sys.argv[1] +# ---------------- Экспериментальный режим ---------------- +def run_benchmark(maze_file, strategy, runs=5): builder = TextLabyrinthBuilder() - labyrinth = builder.build(maze_file) - run_interactive(labyrinth) \ No newline at end of file + lab = builder.build(maze_file) + total_t = total_v = total_l = 0 + for _ in range(runs): + solver = LabyrinthSolver(lab) + solver.set_strategy(strategy) + stats = solver.solve() + if stats: + total_t += stats['time_ms'] + total_v += stats['visited'] + total_l += stats['length'] + return { + 'time_ms': total_t / runs, + 'visited_cells': total_v / runs, + 'path_length': total_l / runs + } + +def create_charts(results): + mazes = sorted({r['maze'] for r in results}) + strategies = ['BFS', 'DFS', 'AStar'] + fig, axes = plt.subplots(1, 3, figsize=(15, 5)) + x = np.arange(len(mazes)) + width = 0.25 + for i, strat in enumerate(strategies): + times = [next((r['time_ms'] for r in results if r['maze']==m and r['strategy']==strat), 0) for m in mazes] + axes[0].bar(x + i*width, times, width, label=strat) + axes[0].set_title('Время выполнения (мс)') + axes[0].set_xticks(x + width) + axes[0].set_xticklabels(mazes, rotation=30, ha='right') + axes[0].legend(); axes[0].grid(alpha=0.3) + for i, strat in enumerate(strategies): + visited = [next((r['visited_cells'] for r in results if r['maze']==m and r['strategy']==strat), 0) for m in mazes] + axes[1].bar(x + i*width, visited, width, label=strat) + axes[1].set_title('Посещено клеток') + axes[1].set_xticks(x + width) + axes[1].set_xticklabels(mazes, rotation=30, ha='right') + axes[1].legend(); axes[1].grid(alpha=0.3) + for i, strat in enumerate(strategies): + lengths = [next((r['path_length'] for r in results if r['maze']==m and r['strategy']==strat), 0) for m in mazes] + axes[2].bar(x + i*width, lengths, width, label=strat) + axes[2].set_title('Длина пути') + axes[2].set_xticks(x + width) + axes[2].set_xticklabels(mazes, rotation=30, ha='right') + axes[2].legend(); axes[2].grid(alpha=0.3) + plt.tight_layout() + plt.savefig('maze_performance.png', dpi=150, bbox_inches='tight') + plt.show() + +# ---------------- Главная точка входа ---------------- +if __name__ == '__main__': + if len(sys.argv) > 1 and sys.argv[1] == 'experiment': + print('Запуск экспериментов...') + maze_list = [ + ('maze/maze1.txt', 'Small 10x6'), + ('maze/maze10x10.txt', 'Medium 10x10'), + ('maze/maze20x20.txt', 'Large 20x20'), + ('maze/maze_empty.txt', 'Empty 15x15'), + ('maze/maze_no_exit.txt', 'No exit 10x10') + ] + strategies = [ + ('BFS', BFS_Pathfinder()), + ('DFS', DFS_Pathfinder()), + ('AStar', AStar_Pathfinder()) + ] + all_results = [] + for file, name in maze_list: + print(f'\nТестируем {name}...') + for sname, strat in strategies: + try: + stats = run_benchmark(file, strat, runs=3) + all_results.append({ + 'maze': name, + 'strategy': sname, + 'time_ms': stats['time_ms'], + 'visited_cells': stats['visited_cells'], + 'path_length': stats['path_length'] + }) + print(f' {sname}: время={stats["time_ms"]:.3f}мс, клеток={stats["visited_cells"]:.0f}, длина={stats["path_length"]:.0f}') + except Exception as e: + print(f' {sname}: ошибка – {e}') + with open('experiment_data.csv', 'w', newline='', encoding='utf-8') as f: + writer = csv.DictWriter(f, fieldnames=['maze','strategy','time_ms','visited_cells','path_length']) + writer.writeheader() + writer.writerows(all_results) + if all_results: + create_charts(all_results) + print('\nРезультаты сохранены в experiment_data.csv и maze_performance.png') + else: + # Интерактивная игра + maze_file = 'maze/maze1.txt' + if len(sys.argv) > 1: + maze_file = sys.argv[1] + builder = TextLabyrinthBuilder() + labyrinth = builder.build(maze_file) + + player = Explorer(labyrinth.start, labyrinth) + display = TerminalDisplay(player) + display.update('labyrinth_loaded', labyrinth) + + solver = LabyrinthSolver(labyrinth) + solver.attach(display) + + print('\n УПРАВЛЕНИЕ:') + print(' H – влево J – вниз K – вверх L – вправо') + print(' U – отменить ход Q – выход') + print(' Автопоиск: B – BFS D – DFS A – A*') + print('=' * 50) + + history = [] + while True: + cmd = input('\n Команда > ').lower().strip() + if cmd == 'q': + print('\n До свидания!') + break + elif cmd == 'b': + solver.set_strategy(BFS_Pathfinder()) + stats = solver.solve() + print(f"\n BFS: время={stats['time_ms']:.3f}мс, посещено={stats['visited']}, длина={stats['length']}") + elif cmd == 'd': + solver.set_strategy(DFS_Pathfinder()) + stats = solver.solve() + print(f"\n DFS: время={stats['time_ms']:.3f}мс, посещено={stats['visited']}, длина={stats['length']}") + elif cmd == 'a': + solver.set_strategy(AStar_Pathfinder()) + stats = solver.solve() + print(f"\n A*: время={stats['time_ms']:.3f}мс, посещено={stats['visited']}, длина={stats['length']}") + elif cmd in ('h','j','k','l'): + dirs = {'h': (-1,0), 'l': (1,0), 'k': (0,-1), 'j': (0,1)} + action = MoveAction(player, dirs[cmd], labyrinth) + if action.execute(): + history.append(action) + display.update('player_moved', labyrinth) + if player.current == labyrinth.exit: + print('\n ПОЗДРАВЛЯЕМ! ВЫ ВЫБРАЛИСЬ ИЗ ЛАБИРИНТА!') + print(f' Всего ходов: {len(history)}') + break + else: + print('\n Там стена!') + elif cmd == 'u': + if history: + act = history.pop() + act.undo() + display.update('player_moved', labyrinth) + print('\n Ход отменён') + else: + print('\n Нечего отменять') + else: + print('\n Неизвестная команда. Используйте H,J,K,L,U,Q,B,D,A') \ No newline at end of file