import sys import os import time from collections import deque import heapq class Tile: def __init__(self, x, y): self._x = x self._y = y self._wall = False self._entry = False self._goal = False @property def x(self): return self._x @property def y(self): return self._y @property def is_wall(self): return self._wall @is_wall.setter def is_wall(self, value): self._wall = value @property def is_entry(self): return self._entry @is_entry.setter def is_entry(self, value): self._entry = value @property 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 class Labyrinth: def __init__(self, width, height): self._width = width self._height = height self._grid = [[Tile(x, y) for x in range(width)] for y in range(height)] self._start = None self._exit = None @property def width(self): return self._width @property def height(self): return self._height @property def start(self): return self._start @property def exit(self): return self._exit def tile_at(self, x, y): if 0 <= x < self._width and 0 <= y < self._height: return self._grid[y][x] return None def configure_tile(self, x, y, kind): tile = self.tile_at(x, y) if tile is None: return if kind == 'wall': tile.is_wall = True elif kind == 'entry': 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 tile.is_goal = True tile.is_wall = False self._exit = tile elif kind == 'floor': 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) if nb and nb.can_walk(): res.append(nb) return res class LabyrinthBuilder: def build(self, filename): raise NotImplementedError class TextLabyrinthBuilder(LabyrinthBuilder): def build(self, filename): with open(filename, 'r', encoding='utf-8') as f: lines = [line.rstrip('\n') for line in f] h = len(lines) w = max(len(l) for l in lines) if h else 0 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') 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') if entries != 1 or exits != 1: 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 while cur is not None: path.append(cur) cur = preds.get(cur) path.reverse() return path @property def visited_count(self): return getattr(self, '_visited', 0) class BFS_Pathfinder(Pathfinder): def find_path(self, lab, start, goal): q = deque([start]) preds = {start: None} seen = {start} while q: cur = q.popleft() if cur == goal: self._visited = len(seen) return self._build_path(preds, start, goal) for nb in lab.neighbours(cur): if nb not in seen: seen.add(nb) preds[nb] = cur q.append(nb) self._visited = len(seen) return [] class DFS_Pathfinder(Pathfinder): def find_path(self, lab, start, goal): stack = [start] preds = {start: None} seen = {start} while stack: cur = stack.pop() if cur == goal: self._visited = len(seen) return self._build_path(preds, start, goal) for nb in lab.neighbours(cur): if nb not in seen: seen.add(nb) preds[nb] = cur stack.append(nb) 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 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 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) 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] builder = TextLabyrinthBuilder() labyrinth = builder.build(maze_file) run_interactive(labyrinth)