From fc6b64b7cdb4e7f9d8c9fcaa004e0d67c35fe6ff Mon Sep 17 00:00:00 2001 From: YaroslavtsevAS Date: Sun, 24 May 2026 15:34:20 +0000 Subject: [PATCH] [2] add A* --- YaroslavtsevAS/docs/2-nd-lab/main.py | 268 +++++++++++++++++++++++---- 1 file changed, 227 insertions(+), 41 deletions(-) diff --git a/YaroslavtsevAS/docs/2-nd-lab/main.py b/YaroslavtsevAS/docs/2-nd-lab/main.py index e8c4e0e..92103f5 100644 --- a/YaroslavtsevAS/docs/2-nd-lab/main.py +++ b/YaroslavtsevAS/docs/2-nd-lab/main.py @@ -1,6 +1,8 @@ import sys import os +import time from collections import deque +import heapq class Tile: def __init__(self, x, y): @@ -109,12 +111,12 @@ class TextLabyrinthBuilder(LabyrinthBuilder): class Pathfinder: def find_path(self, lab, start, goal): raise NotImplementedError - def _build_path(self, predecessors, start, goal): + def _build_path(self, preds, start, goal): path = [] cur = goal while cur is not None: path.append(cur) - cur = predecessors.get(cur) + cur = preds.get(cur) path.reverse() return path @@ -161,10 +163,170 @@ class DFS_Pathfinder(Pathfinder): 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 + f_start = self._heuristic(start, goal) + heapq.heappush(heap, (f_start, cnt, start)) + cnt += 1 + preds = {} + g = {start: 0} + f = {start: f_start} + seen = set() + while heap: + cur_f, _, cur = heapq.heappop(heap) + seen.add(cur) + if cur == goal: + self._visited = len(seen) + return self._build_path(preds, start, goal) + if cur_f > f.get(cur, float('inf')): + continue + for nb in lab.neighbours(cur): + tent_g = g[cur] + 1 + if tent_g < g.get(nb, float('inf')): + preds[nb] = cur + g[nb] = tent_g + new_f = tent_g + self._heuristic(nb, goal) + f[nb] = new_f + heapq.heappush(heap, (new_f, cnt, nb)) + cnt += 1 + 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 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 + target = self._lab.tile_at(nx, ny) + if target and target.can_walk(): + self._explorer.move(target) + self._done = True + return True + return False + + def undo(self): + if self._done: + self._explorer.undo() + self._done = False + 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) + + def _draw_lab(self, lab): + os.system('cls' if os.name == 'nt' else 'clear') + print('=' * (lab.width * 2 + 4)) + print(' ЛАБИРИНТ') + print('=' * (lab.width * 2 + 4)) + for y in range(lab.height): + print(' ', end='') + for x in range(lab.width): + t = lab.tile_at(x, y) + if t == lab.start: print('S', end=' ') + elif t == lab.exit: print('E', end=' ') + elif t.is_wall: print('#', end=' ') + 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)) + print(' ЛАБИРИНТ (P – игрок)') + print('=' * (lab.width * 2 + 4)) + for y in range(lab.height): + 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=' ') + elif t == lab.start: print('S', end=' ') + elif t == lab.exit: print('E', end=' ') + elif t.is_wall: print('#', end=' ') + else: print('.', end=' ') + print() + print('=' * (lab.width * 2 + 4)) + if self._explorer: + print(f' Позиция игрока: ({self._explorer.current.x}, {self._explorer.current.y})') + print(' S – вход E – выход # – стена . – пол P – игрок') + + def _show_path_info(self, 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 _notify(self, event, data): + for obs in self._observers: + obs.update(event, data) def set_strategy(self, strategy): self._strategy = strategy @@ -172,50 +334,74 @@ class LabyrinthSolver: def solve(self): 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) - return path + 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 show_labyrinth(lab): - os.system('cls' if os.name == 'nt' else 'clear') - print('=' * (lab.width * 2 + 4)) - print(' ЛАБИРИНТ') - print('=' * (lab.width * 2 + 4)) - for y in range(lab.height): - print(' ', end='') - for x in range(lab.width): - t = lab.tile_at(x, y) - if t == lab.start: print('S', end=' ') - elif t == lab.exit: print('E', end=' ') - elif t.is_wall: print('#', end=' ') - else: print('.', end=' ') - print() - print('=' * (lab.width * 2 + 4)) - print(' S – вход E – выход # – стена . – пол') +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 = sys.argv[1] if len(sys.argv) > 1 else 'maze1.txt' + maze_file = 'maze1.txt' + if len(sys.argv) > 1: + maze_file = sys.argv[1] builder = TextLabyrinthBuilder() labyrinth = builder.build(maze_file) - show_labyrinth(labyrinth) - - solver = LabyrinthSolver(labyrinth) - print("\nВыберите алгоритм: 1 – BFS, 2 – DFS") - choice = input("> ").strip() - if choice == '1': - solver.set_strategy(BFS_Pathfinder()) - print("Запущен BFS...") - elif choice == '2': - solver.set_strategy(DFS_Pathfinder()) - print("Запущен DFS...") - else: - print("Неверный выбор, выход.") - sys.exit(0) - - path = solver.solve() - if path: - print(f"Путь найден! Длина: {len(path)} клеток") - print(f"Посещено клеток: {solver._strategy.visited_count}") - else: - print("Путь не найден!") \ No newline at end of file + run_interactive(labyrinth) \ No newline at end of file