2026-rff_mp/BoriskovaDV/docs/data/2-nd-exercise/main.py

341 lines
10 KiB
Python
Raw Normal View History

2026-05-24 20:01:11 +00:00
import sys
import os
2026-05-24 20:07:50 +00:00
from collections import deque
import heapq
import time
2026-05-24 20:01:11 +00:00
class GridPoint:
def __init__(self, x, y):
self.x = x
self.y = y
self.blocked = False
self.is_start = False
self.is_exit = False
def can_step(self):
return not self.blocked
class Labyrinth:
def __init__(self, w, h):
self.w = w
self.h = h
self.grid = [[GridPoint(x, y) for x in range(w)] for y in range(h)]
self.start_point = None
self.exit_point = None
def get_point(self, x, y):
if 0 <= x < self.w and 0 <= y < self.h:
return self.grid[y][x]
return None
def set_point(self, x, y, typ):
p = self.get_point(x, y)
if not p:
return
if typ == 'wall':
p.blocked = True
elif typ == 'start':
if self.start_point:
self.start_point.is_start = False
p.is_start = True
p.blocked = False
self.start_point = p
elif typ == 'exit':
if self.exit_point:
self.exit_point.is_exit = False
p.is_exit = True
p.blocked = False
self.exit_point = p
elif typ == 'path':
p.blocked = False
2026-05-24 20:07:50 +00:00
def neighbors(self, p):
dirs = [(0, -1), (0, 1), (-1, 0), (1, 0)]
res = []
for dx, dy in dirs:
nx, ny = p.x + dx, p.y + dy
nb = self.get_point(nx, ny)
if nb and nb.can_step():
res.append(nb)
return res
2026-05-24 20:01:11 +00:00
class MazeLoader:
def load(self, filename):
raise NotImplementedError
class TextMazeLoader(MazeLoader):
def load(self, filename):
with open(filename, 'r') 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_point(x, y, 'wall')
elif ch == 'S':
lab.set_point(x, y, 'start')
start_cnt += 1
elif ch == 'E':
lab.set_point(x, y, 'exit')
exit_cnt += 1
else:
lab.set_point(x, y, 'path')
if start_cnt != 1 or exit_cnt != 1:
raise ValueError(f"Need exactly one S and one E. Found S={start_cnt}, E={exit_cnt}")
return lab
2026-05-24 20:07:50 +00:00
class SearchAlgorithm:
def find_way(self, lab, start, goal):
raise NotImplementedError
def _build_path(self, prev, start, goal):
path = []
cur = goal
while cur:
path.append(cur)
cur = prev.get(cur)
path.reverse()
return path
def get_visited(self):
return getattr(self, '_visited', 0)
class BreadthFirst(SearchAlgorithm):
def find_way(self, lab, start, goal):
q = deque([start])
prev = {start: None}
seen = {start}
while q:
cur = q.popleft()
if cur == goal:
self._visited = len(seen)
return self._build_path(prev, start, goal)
for nb in lab.neighbors(cur):
if nb not in seen:
seen.add(nb)
prev[nb] = cur
q.append(nb)
self._visited = len(seen)
return []
class DepthFirst(SearchAlgorithm):
def find_way(self, lab, start, goal):
stack = [start]
prev = {start: None}
seen = {start}
while stack:
cur = stack.pop()
if cur == goal:
self._visited = len(seen)
return self._build_path(prev, start, goal)
for nb in lab.neighbors(cur):
if nb not in seen:
seen.add(nb)
prev[nb] = cur
stack.append(nb)
self._visited = len(seen)
return []
class AStar(SearchAlgorithm):
def _dist(self, a, b):
return abs(a.x - b.x) + abs(a.y - b.y)
def find_way(self, lab, start, goal):
heap = []
cnt = 0
start_f = self._dist(start, goal)
heapq.heappush(heap, (start_f, cnt, start))
cnt += 1
prev = {}
g = {start: 0}
f = {start: start_f}
seen = set()
while heap:
cur_f, _, cur = heapq.heappop(heap)
seen.add(cur)
if cur == goal:
self._visited = len(seen)
return self._build_path(prev, start, goal)
if cur_f > f.get(cur, float('inf')):
continue
for nb in lab.neighbors(cur):
new_g = g[cur] + 1
if new_g < g.get(nb, float('inf')):
prev[nb] = cur
g[nb] = new_g
new_f = new_g + self._dist(nb, goal)
f[nb] = new_f
heapq.heappush(heap, (new_f, cnt, nb))
cnt += 1
self._visited = len(seen)
return []
class LabyrinthSolver:
def __init__(self, lab):
self.lab = lab
self.algorithm = None
def set_algorithm(self, algo):
self.algorithm = algo
def solve(self):
if not self.algorithm:
return None
t0 = time.perf_counter()
path = self.algorithm.find_way(self.lab, self.lab.start_point, self.lab.exit_point)
t1 = time.perf_counter()
ms = (t1 - t0) * 1000
return ms, self.algorithm.get_visited(), len(path)
class Player:
def __init__(self, start, lab):
self.current = start
self.last = None
self.lab = lab
def move(self, cell):
if cell and cell.can_step():
self.last = self.current
self.current = cell
return True
return False
def undo(self):
if self.last:
self.current, self.last = self.last, None
return True
return False
class Command:
def do(self):
raise NotImplementedError
def revert(self):
raise NotImplementedError
class MoveCommand(Command):
def __init__(self, player, dx, dy, lab):
self.player = player
self.dx = dx
self.dy = dy
self.lab = lab
self.done = False
def do(self):
nx = self.player.current.x + self.dx
ny = self.player.current.y + self.dy
target = self.lab.get_point(nx, ny)
if target and target.can_step():
self.player.move(target)
self.done = True
return True
return False
def revert(self):
if self.done:
self.player.undo()
self.done = False
return True
return False
class InteractiveView:
def __init__(self, lab, player):
self.lab = lab
self.player = player
def render(self):
os.system('cls' if os.name == 'nt' else 'clear')
print("=" * (self.lab.w * 2 + 4))
print(" LABYRINTH (P = player)")
print("=" * (self.lab.w * 2 + 4))
for y in range(self.lab.h):
print(" ", end='')
for x in range(self.lab.w):
p = self.lab.get_point(x, y)
if self.player.current == p:
print('P', end=' ')
elif p == self.lab.start_point:
print('S', end=' ')
elif p == self.lab.exit_point:
print('E', end=' ')
elif p.blocked:
print('#', end=' ')
else:
print('.', end=' ')
print()
print("=" * (self.lab.w * 2 + 4))
print(f" Position: ({self.player.current.x},{self.player.current.y})")
print(" Controls: h(left) j(down) k(up) l(right) u=undo q=quit")
print(" Auto-search: b=BFS d=DFS a=A*")
def run_experiment(maze_file, algo, runs=5):
loader = TextMazeLoader()
lab = loader.load(maze_file)
total_ms = 0
total_visited = 0
total_len = 0
for _ in range(runs):
solver = LabyrinthSolver(lab)
solver.set_algorithm(algo)
stats = solver.solve()
if stats:
ms, vis, plen = stats
total_ms += ms
total_visited += vis
total_len += plen
return total_ms / runs, total_visited / runs, total_len / runs
2026-05-24 20:01:11 +00:00
if __name__ == "__main__":
loader = TextMazeLoader()
lab = loader.load("maze/maze1.txt")
player = Player(lab.start_point, lab)
view = InteractiveView(lab, player)
view.render()
solver = LabyrinthSolver(lab)
history = []
while True:
key = input("\n > ").lower()
if key == 'q':
print("Goodbye!")
break
elif key == 'b':
solver.set_algorithm(BreadthFirst())
ms, vis, plen = solver.solve()
print(f"BFS: {ms:.3f}ms, visited={vis}, length={plen}")
elif key == 'd':
solver.set_algorithm(DepthFirst())
ms, vis, plen = solver.solve()
print(f"DFS: {ms:.3f}ms, visited={vis}, length={plen}")
elif key == 'a':
solver.set_algorithm(AStar())
ms, vis, plen = solver.solve()
print(f"A*: {ms:.3f}ms, visited={vis}, length={plen}")
elif key in ('h','j','k','l'):
moves = {'h': (-1,0), 'l': (1,0), 'k': (0,-1), 'j': (0,1)}
dx, dy = moves[key]
cmd = MoveCommand(player, dx, dy, lab)
if cmd.do():
history.append(cmd)
view.render()
if player.current == lab.exit_point:
print("\n*** YOU REACHED THE EXIT! ***")
print(f"Total moves: {len(history)}")
break
else:
print("Can't go there - wall!")
elif key == 'u':
if history:
cmd = history.pop()
cmd.revert()
view.render()
print("Undo last move")
else:
print("Nothing to undo")
else:
print("Unknown command")