forked from UNN/2026-rff_mp
243 lines
7.2 KiB
Python
243 lines
7.2 KiB
Python
import sys
|
|
import os
|
|
from collections import deque
|
|
import heapq
|
|
import time
|
|
|
|
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
|
|
|
|
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
|
|
|
|
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
|
|
|
|
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)
|
|
|
|
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
|
|
|
|
class TextView:
|
|
def display(self, lab):
|
|
os.system('cls' if os.name == 'nt' else 'clear')
|
|
print("=" * (lab.w * 2 + 4))
|
|
print(" LABYRINTH")
|
|
print("=" * (lab.w * 2 + 4))
|
|
for y in range(lab.h):
|
|
print(" ", end='')
|
|
for x in range(lab.w):
|
|
p = lab.get_point(x, y)
|
|
if p == lab.start_point:
|
|
print('S', end=' ')
|
|
elif p == lab.exit_point:
|
|
print('E', end=' ')
|
|
elif p.blocked:
|
|
print('#', end=' ')
|
|
else:
|
|
print('.', end=' ')
|
|
print()
|
|
print("=" * (lab.w * 2 + 4))
|
|
print(" S - start E - exit # - wall . - path")
|
|
|
|
if __name__ == "__main__":
|
|
# quick demo
|
|
loader = TextMazeLoader()
|
|
lab = loader.load("maze/maze1.txt")
|
|
view = TextView()
|
|
view.display(lab)
|
|
print("\nRunning experiment on maze1.txt with BFS...")
|
|
bfs = BreadthFirst()
|
|
avg_t, avg_v, avg_l = run_experiment("maze/maze1.txt", bfs, runs=3)
|
|
print(f"BFS: time={avg_t:.3f}ms visited={avg_v:.0f} length={avg_l:.0f}") |