From aae08b7336cda9fd60769750cd923e4805ae17bb Mon Sep 17 00:00:00 2001 From: YaroslavtsevAS Date: Sun, 24 May 2026 15:37:16 +0000 Subject: [PATCH] [2] Final add some coments and rewrite some defs --- YaroslavtsevAS/docs/2-nd-lab/main.py | 143 ++++++++++++++++++++------- 1 file changed, 107 insertions(+), 36 deletions(-) diff --git a/YaroslavtsevAS/docs/2-nd-lab/main.py b/YaroslavtsevAS/docs/2-nd-lab/main.py index a71ee34..78e1afc 100644 --- a/YaroslavtsevAS/docs/2-nd-lab/main.py +++ b/YaroslavtsevAS/docs/2-nd-lab/main.py @@ -7,8 +7,9 @@ import heapq import matplotlib.pyplot as plt import numpy as np -# ---------------- Модель лабиринта ---------------- +# ========== Модель данных ========== class Tile: + """Одна клетка лабиринта.""" def __init__(self, x, y): self._x = x self._y = y @@ -32,9 +33,14 @@ class Tile: def is_goal(self): return self._goal @is_goal.setter 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: + """Прямоугольный лабиринт.""" def __init__(self, width, height): self._width = width self._height = height @@ -58,16 +64,19 @@ class Labyrinth: def configure_tile(self, x, y, kind): tile = self.tile_at(x, y) - if tile is None: return + if tile is None: + return if kind == 'wall': tile.is_wall = True 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_wall = False self._start = tile 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_wall = False self._exit = tile @@ -75,16 +84,19 @@ class Labyrinth: tile.is_wall = False def neighbours(self, tile): + """Соседние проходимые клетки.""" res = [] - for dx, dy in ((0,-1),(0,1),(-1,0),(1,0)): - nb = self.tile_at(tile.x+dx, tile.y+dy) + for dx, dy in ((0, -1), (0, 1), (-1, 0), (1, 0)): + nb = self.tile_at(tile.x + dx, tile.y + dy) if nb and nb.can_walk(): res.append(nb) return res -# ---------------- Загрузка лабиринта ---------------- + +# ========== Загрузка из файла ========== class LabyrinthBuilder: - def build(self, filename): raise NotImplementedError + def build(self, filename): + raise NotImplementedError class TextLabyrinthBuilder(LabyrinthBuilder): def build(self, filename): @@ -92,25 +104,32 @@ class TextLabyrinthBuilder(LabyrinthBuilder): lines = [line.rstrip('\n') for line in f] h = len(lines) w = max(len(l) for l in lines) if h else 0 + if h == 0 or w == 0: + raise ValueError("Файл лабиринта пуст.") entries = exits = 0 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 find_path(self, lab, start, goal): + raise NotImplementedError + def _build_path(self, preds, start, goal): path = [] cur = goal @@ -119,8 +138,11 @@ 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): @@ -140,6 +162,7 @@ class BFS_Pathfinder(Pathfinder): self._visited = len(seen) return [] + class DFS_Pathfinder(Pathfinder): def find_path(self, lab, start, goal): stack = [start] @@ -158,9 +181,11 @@ 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 @@ -191,36 +216,44 @@ 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 @@ -230,6 +263,7 @@ class MoveAction(Action): self._done = True return True return False + def undo(self): if self._done: self._explorer.undo() @@ -237,17 +271,25 @@ 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._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._last_path = data + 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)) @@ -263,6 +305,8 @@ 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)) @@ -272,7 +316,8 @@ 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=' ') @@ -281,28 +326,45 @@ class TerminalDisplay(GameObserver): print('=' * (lab.width * 2 + 4)) if self._explorer: 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)} + return { + 'time_ms': elapsed, + 'visited': self._strategy.visited_count, + 'length': len(path) + } -# ---------------- Экспериментальный режим ---------------- + +# ========== Эксперименты и визуализация ========== def run_benchmark(maze_file, strategy, runs=5): builder = TextLabyrinthBuilder() lab = builder.build(maze_file) @@ -321,38 +383,47 @@ def run_benchmark(maze_file, strategy, runs=5): '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] + 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) + 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] + 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) + 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] + 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) + 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('Запуск экспериментов...')