[2] Final add some coments and rewrite some defs
This commit is contained in:
parent
11d65436e1
commit
aae08b7336
|
|
@ -7,8 +7,9 @@ import heapq
|
||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
# ---------------- Модель лабиринта ----------------
|
# ========== Модель данных ==========
|
||||||
class Tile:
|
class Tile:
|
||||||
|
"""Одна клетка лабиринта."""
|
||||||
def __init__(self, x, y):
|
def __init__(self, x, y):
|
||||||
self._x = x
|
self._x = x
|
||||||
self._y = y
|
self._y = y
|
||||||
|
|
@ -32,9 +33,14 @@ class Tile:
|
||||||
def is_goal(self): return self._goal
|
def is_goal(self): return self._goal
|
||||||
@is_goal.setter
|
@is_goal.setter
|
||||||
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
|
||||||
self._height = height
|
self._height = height
|
||||||
|
|
@ -58,16 +64,19 @@ class Labyrinth:
|
||||||
|
|
||||||
def configure_tile(self, x, y, kind):
|
def configure_tile(self, x, y, kind):
|
||||||
tile = self.tile_at(x, y)
|
tile = self.tile_at(x, y)
|
||||||
if tile is None: return
|
if tile is None:
|
||||||
|
return
|
||||||
if kind == 'wall':
|
if kind == 'wall':
|
||||||
tile.is_wall = True
|
tile.is_wall = True
|
||||||
elif kind == 'entry':
|
elif kind == 'entry':
|
||||||
if self._start: self._start.is_entry = False
|
if self._start:
|
||||||
|
self._start.is_entry = False
|
||||||
tile.is_entry = True
|
tile.is_entry = True
|
||||||
tile.is_wall = False
|
tile.is_wall = False
|
||||||
self._start = tile
|
self._start = tile
|
||||||
elif kind == 'goal':
|
elif kind == 'goal':
|
||||||
if self._exit: self._exit.is_goal = False
|
if self._exit:
|
||||||
|
self._exit.is_goal = False
|
||||||
tile.is_goal = True
|
tile.is_goal = True
|
||||||
tile.is_wall = False
|
tile.is_wall = False
|
||||||
self._exit = tile
|
self._exit = tile
|
||||||
|
|
@ -75,16 +84,19 @@ class Labyrinth:
|
||||||
tile.is_wall = False
|
tile.is_wall = False
|
||||||
|
|
||||||
def neighbours(self, tile):
|
def neighbours(self, tile):
|
||||||
|
"""Соседние проходимые клетки."""
|
||||||
res = []
|
res = []
|
||||||
for dx, dy in ((0,-1),(0,1),(-1,0),(1,0)):
|
for dx, dy in ((0, -1), (0, 1), (-1, 0), (1, 0)):
|
||||||
nb = self.tile_at(tile.x+dx, tile.y+dy)
|
nb = self.tile_at(tile.x + dx, tile.y + dy)
|
||||||
if nb and nb.can_walk():
|
if nb and nb.can_walk():
|
||||||
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
|
||||||
|
|
||||||
class TextLabyrinthBuilder(LabyrinthBuilder):
|
class TextLabyrinthBuilder(LabyrinthBuilder):
|
||||||
def build(self, filename):
|
def build(self, filename):
|
||||||
|
|
@ -92,25 +104,32 @@ class TextLabyrinthBuilder(LabyrinthBuilder):
|
||||||
lines = [line.rstrip('\n') for line in f]
|
lines = [line.rstrip('\n') for line in f]
|
||||||
h = len(lines)
|
h = len(lines)
|
||||||
w = max(len(l) for l in lines) if h else 0
|
w = max(len(l) for l in lines) if h else 0
|
||||||
|
if h == 0 or w == 0:
|
||||||
|
raise ValueError("Файл лабиринта пуст.")
|
||||||
entries = exits = 0
|
entries = exits = 0
|
||||||
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 == '#': lab.configure_tile(x, y, 'wall')
|
if ch == '#':
|
||||||
|
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: lab.configure_tile(x, y, 'floor')
|
else:
|
||||||
|
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,8 +138,11 @@ class Pathfinder:
|
||||||
cur = preds.get(cur)
|
cur = preds.get(cur)
|
||||||
path.reverse()
|
path.reverse()
|
||||||
return path
|
return path
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def visited_count(self): return getattr(self, '_visited', 0)
|
def visited_count(self):
|
||||||
|
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):
|
||||||
|
|
@ -140,6 +162,7 @@ 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]
|
||||||
|
|
@ -158,9 +181,11 @@ 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
|
||||||
|
|
@ -191,36 +216,44 @@ 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): return self._current
|
def current(self):
|
||||||
|
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
|
||||||
|
|
@ -230,6 +263,7 @@ 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()
|
||||||
|
|
@ -237,17 +271,25 @@ 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': self._draw_lab(data)
|
if event == 'labyrinth_loaded':
|
||||||
elif event == 'path_found': self._show_path_info(data)
|
self._draw_lab(data)
|
||||||
elif event == 'player_moved': self._draw_with_player(data)
|
elif event == 'path_found':
|
||||||
|
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))
|
||||||
|
|
@ -263,6 +305,8 @@ 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))
|
||||||
|
|
@ -272,7 +316,8 @@ 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: print('P', end=' ')
|
if self._explorer and t == self._explorer.current:
|
||||||
|
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=' ')
|
||||||
|
|
@ -281,28 +326,45 @@ class TerminalDisplay(GameObserver):
|
||||||
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})')
|
||||||
|
|
||||||
def _show_path_info(self, path):
|
def _show_path_info(self, path):
|
||||||
if not path: print('\n Путь не найден!')
|
if not path:
|
||||||
else: print(f'\n Длина пути: {len(path)} клеток.')
|
print('\n Путь не найден!')
|
||||||
|
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: obs.update(event, data)
|
for obs in self._observers:
|
||||||
def set_strategy(self, strategy): self._strategy = strategy
|
obs.update(event, data)
|
||||||
|
|
||||||
|
def set_strategy(self, strategy):
|
||||||
|
self._strategy = strategy
|
||||||
|
|
||||||
def solve(self):
|
def solve(self):
|
||||||
if self._strategy is None: return None
|
if self._strategy is 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_benchmark(maze_file, strategy, runs=5):
|
def run_benchmark(maze_file, strategy, runs=5):
|
||||||
builder = TextLabyrinthBuilder()
|
builder = TextLabyrinthBuilder()
|
||||||
lab = builder.build(maze_file)
|
lab = builder.build(maze_file)
|
||||||
|
|
@ -321,38 +383,47 @@ def run_benchmark(maze_file, strategy, runs=5):
|
||||||
'path_length': total_l / runs
|
'path_length': total_l / runs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def create_charts(results):
|
def create_charts(results):
|
||||||
mazes = sorted({r['maze'] for r in results})
|
mazes = sorted({r['maze'] for r in results})
|
||||||
strategies = ['BFS', 'DFS', 'AStar']
|
strategies = ['BFS', 'DFS', 'AStar']
|
||||||
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
|
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
|
||||||
x = np.arange(len(mazes))
|
x = np.arange(len(mazes))
|
||||||
width = 0.25
|
width = 0.25
|
||||||
|
|
||||||
for i, strat in enumerate(strategies):
|
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]
|
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].bar(x + i*width, times, width, label=strat)
|
||||||
axes[0].set_title('Время выполнения (мс)')
|
axes[0].set_title('Время выполнения (мс)')
|
||||||
axes[0].set_xticks(x + width)
|
axes[0].set_xticks(x + width)
|
||||||
axes[0].set_xticklabels(mazes, rotation=30, ha='right')
|
axes[0].set_xticklabels(mazes, rotation=30, ha='right')
|
||||||
axes[0].legend(); axes[0].grid(alpha=0.3)
|
axes[0].legend()
|
||||||
|
axes[0].grid(alpha=0.3)
|
||||||
|
|
||||||
for i, strat in enumerate(strategies):
|
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]
|
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].bar(x + i*width, visited, width, label=strat)
|
||||||
axes[1].set_title('Посещено клеток')
|
axes[1].set_title('Посещено клеток')
|
||||||
axes[1].set_xticks(x + width)
|
axes[1].set_xticks(x + width)
|
||||||
axes[1].set_xticklabels(mazes, rotation=30, ha='right')
|
axes[1].set_xticklabels(mazes, rotation=30, ha='right')
|
||||||
axes[1].legend(); axes[1].grid(alpha=0.3)
|
axes[1].legend()
|
||||||
|
axes[1].grid(alpha=0.3)
|
||||||
|
|
||||||
for i, strat in enumerate(strategies):
|
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]
|
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].bar(x + i*width, lengths, width, label=strat)
|
||||||
axes[2].set_title('Длина пути')
|
axes[2].set_title('Длина пути')
|
||||||
axes[2].set_xticks(x + width)
|
axes[2].set_xticks(x + width)
|
||||||
axes[2].set_xticklabels(mazes, rotation=30, ha='right')
|
axes[2].set_xticklabels(mazes, rotation=30, ha='right')
|
||||||
axes[2].legend(); axes[2].grid(alpha=0.3)
|
axes[2].legend()
|
||||||
|
axes[2].grid(alpha=0.3)
|
||||||
|
|
||||||
plt.tight_layout()
|
plt.tight_layout()
|
||||||
plt.savefig('maze_performance.png', dpi=150, bbox_inches='tight')
|
plt.savefig('maze_performance.png', dpi=150, bbox_inches='tight')
|
||||||
plt.show()
|
plt.show()
|
||||||
|
|
||||||
# ---------------- Главная точка входа ----------------
|
|
||||||
|
# ========== Главный вход ==========
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
if len(sys.argv) > 1 and sys.argv[1] == 'experiment':
|
if len(sys.argv) > 1 and sys.argv[1] == 'experiment':
|
||||||
print('Запуск экспериментов...')
|
print('Запуск экспериментов...')
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user