[2] add experimenal and plots

This commit is contained in:
YaroslavtsevAS 2026-05-24 15:34:48 +00:00
parent fc6b64b7cd
commit 11d65436e1

View File

@ -1,9 +1,13 @@
import sys import sys
import os import os
import time import time
import csv
from collections import deque from collections import deque
import heapq import heapq
import matplotlib.pyplot as plt
import numpy as np
# ---------------- Модель лабиринта ----------------
class Tile: class Tile:
def __init__(self, x, y): def __init__(self, x, y):
self._x = x self._x = x
@ -30,7 +34,6 @@ class Tile:
def is_goal(self, value): self._goal = value def is_goal(self, value): self._goal = value
def can_walk(self): return not self._wall def can_walk(self): return not self._wall
class Labyrinth: class Labyrinth:
def __init__(self, width, height): def __init__(self, width, height):
self._width = width self._width = width
@ -79,7 +82,7 @@ class Labyrinth:
res.append(nb) res.append(nb)
return res return res
# ---------------- Загрузка лабиринта ----------------
class LabyrinthBuilder: class LabyrinthBuilder:
def build(self, filename): raise NotImplementedError def build(self, filename): raise NotImplementedError
@ -93,24 +96,21 @@ class TextLabyrinthBuilder(LabyrinthBuilder):
lab = Labyrinth(w, h) lab = Labyrinth(w, h)
for y, row in enumerate(lines): for y, row in enumerate(lines):
for x, ch in enumerate(row): for x, ch in enumerate(row):
if ch == '#': if ch == '#': lab.configure_tile(x, y, 'wall')
lab.configure_tile(x, y, 'wall')
elif ch == 'S': elif ch == 'S':
lab.configure_tile(x, y, 'entry') lab.configure_tile(x, y, 'entry')
entries += 1 entries += 1
elif ch == 'E': elif ch == 'E':
lab.configure_tile(x, y, 'goal') lab.configure_tile(x, y, 'goal')
exits += 1 exits += 1
else: else: lab.configure_tile(x, y, 'floor')
lab.configure_tile(x, y, 'floor')
if entries != 1 or exits != 1: if entries != 1 or exits != 1:
raise ValueError(f"Некорректный лабиринт: найдено S={entries}, E={exits}") raise ValueError(f"Некорректный лабиринт: S={entries}, E={exits}")
return lab return lab
# ---------------- Алгоритмы поиска пути ----------------
class Pathfinder: class Pathfinder:
def find_path(self, lab, start, goal): raise NotImplementedError def find_path(self, lab, start, goal): raise NotImplementedError
def _build_path(self, preds, start, goal): def _build_path(self, preds, start, goal):
path = [] path = []
cur = goal cur = goal
@ -119,11 +119,8 @@ class Pathfinder:
cur = preds.get(cur) cur = preds.get(cur)
path.reverse() path.reverse()
return path return path
@property @property
def visited_count(self): def visited_count(self): return getattr(self, '_visited', 0)
return getattr(self, '_visited', 0)
class BFS_Pathfinder(Pathfinder): class BFS_Pathfinder(Pathfinder):
def find_path(self, lab, start, goal): def find_path(self, lab, start, goal):
@ -143,7 +140,6 @@ class BFS_Pathfinder(Pathfinder):
self._visited = len(seen) self._visited = len(seen)
return [] return []
class DFS_Pathfinder(Pathfinder): class DFS_Pathfinder(Pathfinder):
def find_path(self, lab, start, goal): def find_path(self, lab, start, goal):
stack = [start] stack = [start]
@ -162,11 +158,9 @@ class DFS_Pathfinder(Pathfinder):
self._visited = len(seen) self._visited = len(seen)
return [] return []
class AStar_Pathfinder(Pathfinder): class AStar_Pathfinder(Pathfinder):
def _heuristic(self, a, b): def _heuristic(self, a, b):
return abs(a.x - b.x) + abs(a.y - b.y) return abs(a.x - b.x) + abs(a.y - b.y)
def find_path(self, lab, start, goal): def find_path(self, lab, start, goal):
heap = [] heap = []
cnt = 0 cnt = 0
@ -197,43 +191,36 @@ class AStar_Pathfinder(Pathfinder):
self._visited = len(seen) self._visited = len(seen)
return [] return []
# ---------------- Компоненты интерактивной игры ----------------
class Explorer: class Explorer:
def __init__(self, start_tile, labyrinth): def __init__(self, start_tile, labyrinth):
self._current = start_tile self._current = start_tile
self._previous = None self._previous = None
self._lab = labyrinth self._lab = labyrinth
@property @property
def current(self): def current(self): return self._current
return self._current
def move(self, tile): def move(self, tile):
if tile and tile.can_walk(): if tile and tile.can_walk():
self._previous = self._current self._previous = self._current
self._current = tile self._current = tile
return True return True
return False return False
def undo(self): def undo(self):
if self._previous: if self._previous:
self._current, self._previous = self._previous, None self._current, self._previous = self._previous, None
return True return True
return False return False
class Action: class Action:
def execute(self): raise NotImplementedError def execute(self): raise NotImplementedError
def undo(self): raise NotImplementedError def undo(self): raise NotImplementedError
class MoveAction(Action): class MoveAction(Action):
def __init__(self, explorer, direction, lab): def __init__(self, explorer, direction, lab):
self._explorer = explorer self._explorer = explorer
self._dx, self._dy = direction self._dx, self._dy = direction
self._lab = lab self._lab = lab
self._done = False self._done = False
def execute(self): def execute(self):
nx = self._explorer.current.x + self._dx nx = self._explorer.current.x + self._dx
ny = self._explorer.current.y + self._dy ny = self._explorer.current.y + self._dy
@ -243,7 +230,6 @@ class MoveAction(Action):
self._done = True self._done = True
return True return True
return False return False
def undo(self): def undo(self):
if self._done: if self._done:
self._explorer.undo() self._explorer.undo()
@ -251,25 +237,17 @@ class MoveAction(Action):
return True return True
return False return False
class GameObserver: class GameObserver:
def update(self, event, data): raise NotImplementedError def update(self, event, data): raise NotImplementedError
class TerminalDisplay(GameObserver): class TerminalDisplay(GameObserver):
def __init__(self, explorer=None): def __init__(self, explorer=None):
self._explorer = explorer self._explorer = explorer
self._last_path = None self._last_path = None
def update(self, event, data): def update(self, event, data):
if event == 'labyrinth_loaded': if event == 'labyrinth_loaded': self._draw_lab(data)
self._draw_lab(data) elif event == 'path_found': self._show_path_info(data)
elif event == 'path_found': elif event == 'player_moved': self._draw_with_player(data)
self._last_path = data
self._show_path_info(data)
elif event == 'player_moved':
self._draw_with_player(data)
def _draw_lab(self, lab): def _draw_lab(self, lab):
os.system('cls' if os.name == 'nt' else 'clear') os.system('cls' if os.name == 'nt' else 'clear')
print('=' * (lab.width * 2 + 4)) print('=' * (lab.width * 2 + 4))
@ -285,8 +263,6 @@ class TerminalDisplay(GameObserver):
else: print('.', end=' ') else: print('.', end=' ')
print() print()
print('=' * (lab.width * 2 + 4)) print('=' * (lab.width * 2 + 4))
print(' S вход E выход # стена . пол')
def _draw_with_player(self, lab): def _draw_with_player(self, lab):
os.system('cls' if os.name == 'nt' else 'clear') os.system('cls' if os.name == 'nt' else 'clear')
print('=' * (lab.width * 2 + 4)) print('=' * (lab.width * 2 + 4))
@ -296,8 +272,7 @@ class TerminalDisplay(GameObserver):
print(' ', end='') print(' ', end='')
for x in range(lab.width): for x in range(lab.width):
t = lab.tile_at(x, y) t = lab.tile_at(x, y)
if self._explorer and t == self._explorer.current: if self._explorer and t == self._explorer.current: print('P', end=' ')
print('P', end=' ')
elif t == lab.start: print('S', end=' ') elif t == lab.start: print('S', end=' ')
elif t == lab.exit: print('E', end=' ') elif t == lab.exit: print('E', end=' ')
elif t.is_wall: print('#', end=' ') elif t.is_wall: print('#', end=' ')
@ -305,103 +280,175 @@ class TerminalDisplay(GameObserver):
print() print()
print('=' * (lab.width * 2 + 4)) print('=' * (lab.width * 2 + 4))
if self._explorer: if self._explorer:
print(f' Позиция игрока: ({self._explorer.current.x}, {self._explorer.current.y})') print(f' Позиция: ({self._explorer.current.x}, {self._explorer.current.y})')
print(' S вход E выход # стена . пол P игрок')
def _show_path_info(self, path): def _show_path_info(self, path):
if not path: if not path: print('\n Путь не найден!')
print('\n Путь не найден!') else: print(f'\n Длина пути: {len(path)} клеток.')
else:
print(f'\n Найден путь длиной {len(path)} клеток.')
class LabyrinthSolver: class LabyrinthSolver:
def __init__(self, lab): def __init__(self, lab):
self._lab = lab self._lab = lab
self._strategy = None self._strategy = None
self._observers = [] self._observers = []
def attach(self, obs): self._observers.append(obs)
def attach(self, obs):
self._observers.append(obs)
def _notify(self, event, data): def _notify(self, event, data):
for obs in self._observers: for obs in self._observers: obs.update(event, data)
obs.update(event, data) def set_strategy(self, strategy): self._strategy = strategy
def set_strategy(self, strategy):
self._strategy = strategy
def solve(self): def solve(self):
if self._strategy is None: if self._strategy is None: return None
return None
start_t = time.perf_counter() start_t = time.perf_counter()
path = self._strategy.find_path(self._lab, self._lab.start, self._lab.exit) path = self._strategy.find_path(self._lab, self._lab.start, self._lab.exit)
elapsed = (time.perf_counter() - start_t) * 1000 elapsed = (time.perf_counter() - start_t) * 1000
self._notify('path_found', path) self._notify('path_found', path)
return {'time_ms': elapsed, 'visited': self._strategy.visited_count, 'length': len(path)} return {'time_ms': elapsed, 'visited': self._strategy.visited_count, 'length': len(path)}
# ---------------- Экспериментальный режим ----------------
def run_interactive(lab): def run_benchmark(maze_file, strategy, runs=5):
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]
builder = TextLabyrinthBuilder() builder = TextLabyrinthBuilder()
labyrinth = builder.build(maze_file) lab = builder.build(maze_file)
run_interactive(labyrinth) 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')