1
0
forked from UNN/2026-rff_mp
2026-rff_mp/lukovnikovde/docs/data/2-nd-exercise/main.py

408 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import sys
import os
from collections import deque
import heapq
import time
# ---------- 1. Модель клетки и лабиринта ----------
class Tile:
def __init__(self, x, y):
self.x = x
self.y = y
self.wall = False
self.start = False
self.exit = False
def passable(self):
return not self.wall
class Labyrinth:
def __init__(self, width, height):
self.width = width
self.height = height
self._tiles = [[Tile(x, y) for x in range(width)] for y in range(height)]
self.start_tile = None
self.exit_tile = None
def get_tile(self, x, y):
if 0 <= x < self.width and 0 <= y < self.height:
return self._tiles[y][x]
return None
def set_tile(self, x, y, kind):
tile = self.get_tile(x, y)
if tile is None:
return
if kind == 'wall':
tile.wall = True
elif kind == 'start':
if self.start_tile:
self.start_tile.start = False
tile.start = True
tile.wall = False
self.start_tile = tile
elif kind == 'exit':
if self.exit_tile:
self.exit_tile.exit = False
tile.exit = True
tile.wall = False
self.exit_tile = tile
elif kind == 'path':
tile.wall = False
def neighbours(self, tile):
dirs = [(0, -1), (0, 1), (-1, 0), (1, 0)]
result = []
for dx, dy in dirs:
nx, ny = tile.x + dx, tile.y + dy
nb = self.get_tile(nx, ny)
if nb and nb.passable():
result.append(nb)
return result
# ---------- 2. Загрузка лабиринта из файла ----------
class LabyrinthLoader:
def load(self, filename):
raise NotImplementedError
class TextLabyrinthLoader(LabyrinthLoader):
def load(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(line) for line in lines) if h > 0 else 0
start_cnt = 0
exit_cnt = 0
lab = Labyrinth(w, h)
for y, line in enumerate(lines):
for x, ch in enumerate(line):
if ch == '#':
lab.set_tile(x, y, 'wall')
elif ch == 'S':
lab.set_tile(x, y, 'start')
start_cnt += 1
elif ch == 'E':
lab.set_tile(x, y, 'exit')
exit_cnt += 1
else:
lab.set_tile(x, y, 'path')
if start_cnt != 1 or exit_cnt != 1:
raise ValueError(f"Нужны ровно S и E, найдено S={start_cnt}, E={exit_cnt}")
return lab
# ---------- 3. Алгоритмы поиска пути (стратегии) ----------
class Pathfinder:
def find_path(self, lab, start, goal):
raise NotImplementedError
def _build_path(self, came_from, start, goal):
path = []
cur = goal
while cur is not None:
path.append(cur)
cur = came_from.get(cur)
path.reverse()
return path
def visited_count(self):
return getattr(self, '_visited', 0)
class BFS(Pathfinder):
def find_path(self, lab, start, goal):
q = deque([start])
parent = {start: None}
visited = {start}
while q:
cur = q.popleft()
if cur == goal:
self._visited = len(visited)
return self._build_path(parent, start, goal)
for nb in lab.neighbours(cur):
if nb not in visited:
visited.add(nb)
parent[nb] = cur
q.append(nb)
self._visited = len(visited)
return []
class DFS(Pathfinder):
def find_path(self, lab, start, goal):
stack = [start]
parent = {start: None}
visited = {start}
while stack:
cur = stack.pop()
if cur == goal:
self._visited = len(visited)
return self._build_path(parent, start, goal)
for nb in lab.neighbours(cur):
if nb not in visited:
visited.add(nb)
parent[nb] = cur
stack.append(nb)
self._visited = len(visited)
return []
class AStar(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 = []
counter = 0
start_f = self._heuristic(start, goal)
heapq.heappush(heap, (start_f, counter, start))
counter += 1
parent = {}
g = {start: 0}
f = {start: start_f}
visited = set()
while heap:
cur_f, _, cur = heapq.heappop(heap)
visited.add(cur)
if cur == goal:
self._visited = len(visited)
return self._build_path(parent, start, goal)
if cur_f > f.get(cur, float('inf')):
continue
for nb in lab.neighbours(cur):
new_g = g[cur] + 1
if new_g < g.get(nb, float('inf')):
parent[nb] = cur
g[nb] = new_g
new_f = new_g + self._heuristic(nb, goal)
f[nb] = new_f
heapq.heappush(heap, (new_f, counter, nb))
counter += 1
self._visited = len(visited)
return []
# ---------- 4. Оркестратор с поддержкой наблюдателей ----------
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.notify(event, data)
def set_strategy(self, strategy):
self.strategy = strategy
def solve(self):
if not self.strategy:
return None
t0 = time.perf_counter()
path = self.strategy.find_path(self.lab, self.lab.start_tile, self.lab.exit_tile)
t1 = time.perf_counter()
self.notify("path_found", path)
return {
'time_ms': (t1 - t0) * 1000,
'visited': self.strategy.visited_count(),
'length': len(path)
}
# ---------- 5. Визуализация (наблюдатель) ----------
class EventListener:
def notify(self, event, data):
raise NotImplementedError
class ConsoleRenderer(EventListener):
def __init__(self, walker=None):
self.last_path = None
self.walker = walker
def notify(self, event, data):
if event == "maze_loaded":
self._draw_maze(data)
elif event == "path_found":
self.last_path = data
self._show_path_info(data)
elif event == "player_moved":
self._draw_maze_with_player(data)
def _draw_maze(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.get_tile(x, y)
if t == lab.start_tile:
print('S', end=' ')
elif t == lab.exit_tile:
print('E', end=' ')
elif t.wall:
print('#', end=' ')
else:
print('.', end=' ')
print()
print("=" * (lab.width * 2 + 4))
print(" S - старт E - выход # - стена . - проход")
def _draw_maze_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.get_tile(x, y)
if self.walker and t == self.walker.current:
print('P', end=' ')
elif t == lab.start_tile:
print('S', end=' ')
elif t == lab.exit_tile:
print('E', end=' ')
elif t.wall:
print('#', end=' ')
else:
print('.', end=' ')
print()
print("=" * (lab.width * 2 + 4))
if self.walker:
print(f" Позиция игрока: ({self.walker.current.x}, {self.walker.current.y})")
def _show_path_info(self, path):
if not path:
print("\n Путь не найден!")
else:
print(f"\n Путь найден! Длина = {len(path)}")
# ---------- 6. Игрок и команды с отменой ----------
class Walker:
def __init__(self, start_tile, lab):
self.current = start_tile
self.prev = None
self.lab = lab
def move(self, target_tile):
if target_tile and target_tile.passable():
self.prev = self.current
self.current = target_tile
return True
return False
def undo(self):
if self.prev:
self.current, self.prev = self.prev, None
return True
return False
class Action:
def do(self):
raise NotImplementedError
def undo(self):
raise NotImplementedError
class MoveAction(Action):
def __init__(self, walker, dx, dy, lab):
self.walker = walker
self.dx = dx
self.dy = dy
self.lab = lab
self.done = False
def do(self):
new_x = self.walker.current.x + self.dx
new_y = self.walker.current.y + self.dy
target = self.lab.get_tile(new_x, new_y)
if target and target.passable():
self.walker.move(target)
self.done = True
return True
return False
def undo(self):
if self.done:
self.walker.undo()
self.done = False
return True
return False
# ---------- 7. Главный интерактивный цикл (коммит №4) ----------
if __name__ == "__main__":
# Загружаем лабиринт (предполагается, что файл maze/maze1.txt существует)
loader = TextLabyrinthLoader()
lab = loader.load("maze/maze1.txt")
player = Walker(lab.start_tile, lab)
view = ConsoleRenderer(player)
view.notify("maze_loaded", lab)
solver = LabyrinthSolver(lab)
solver.attach(view)
print("\n УПРАВЛЕНИЕ:")
print(" H (влево) J (вниз) K (вверх) L (вправо)")
print(" U - отменить ход Q - выход")
print("\n АВТОПОИСК:")
print(" B - BFS D - DFS A - A*")
print("\n" + "=" * 50)
command_stack = []
while True:
key = input("\n Команда > ").lower()
if key == 'q':
print("\n До свидания!")
break
elif key == 'b':
solver.set_strategy(BFS())
stats = solver.solve()
if stats:
print(f"\n BFS: время={stats['time_ms']:.3f}мс, посещено={stats['visited']}, длина={stats['length']}")
elif key == 'd':
solver.set_strategy(DFS())
stats = solver.solve()
print(f"\n DFS: время={stats['time_ms']:.3f}мс, посещено={stats['visited']}, длина={stats['length']}")
elif key == 'a':
solver.set_strategy(AStar())
stats = solver.solve()
print(f"\n A*: время={stats['time_ms']:.3f}мс, посещено={stats['visited']}, длина={stats['length']}")
elif key in ['h', 'j', 'k', 'l']:
dirs = {'h': (-1,0), 'l': (1,0), 'k': (0,-1), 'j': (0,1)}
act = MoveAction(player, dirs[key][0], dirs[key][1], lab)
if act.do():
command_stack.append(act)
view.notify("player_moved", lab)
if player.current == lab.exit_tile:
print("\n ПОЗДРАВЛЯЮ! ВЫ НАШЛИ ВЫХОД!")
print(f" Сделано ходов: {len(command_stack)}")
break
else:
print("\n Туда нельзя стена!")
elif key == 'u':
if command_stack:
cmd = command_stack.pop()
cmd.undo()
view.notify("player_moved", lab)
print("\n Отмена последнего хода")
else:
print("\n Нечего отменять")
else:
print("\n Неизвестная команда. Используйте h,j,k,l, u, b, d, a, q")
print("\n Игра окончена. Спасибо!")