[2] Add main.py and start 2-nd-exxercise

This commit is contained in:
anikinvd 2026-05-22 18:00:00 +00:00
parent f66f706883
commit 1bb67bff28

View File

@ -0,0 +1,490 @@
import sys
from collections import deque
import heapq
import time
import os
# ---------- Модель лабиринта ----------
class Tile:
def __init__(self, column, row):
self.col = column
self.row = row
self.blocked = False
self.is_start = False
self.is_exit = False
@property
def x(self):
return self.col
@property
def y(self):
return self.row
@property
def is_wall(self):
return self.blocked
@is_wall.setter
def is_wall(self, value):
self.blocked = value
def can_step(self):
return not self.blocked
class Labyrinth:
def __init__(self, width, height):
self._w = width
self._h = height
self._grid = [[Tile(x, y) for x in range(width)] for y in range(height)]
self._start_tile = None
self._exit_tile = None
@property
def width(self):
return self._w
@property
def height(self):
return self._h
@property
def start(self):
return self._start_tile
@property
def exit(self):
return self._exit_tile
def get_tile(self, x, y):
if 0 <= x < self._w and 0 <= y < self._h:
return self._grid[y][x]
return None
def set_tile_type(self, x, y, kind):
tile = self.get_tile(x, y)
if tile is None:
return
if kind == 'wall':
tile.blocked = True
elif kind == 'start':
if self._start_tile:
self._start_tile.is_start = False
tile.is_start = True
tile.blocked = False
self._start_tile = tile
elif kind == 'exit':
if self._exit_tile:
self._exit_tile.is_exit = False
tile.is_exit = True
tile.blocked = False
self._exit_tile = tile
elif kind == 'path':
tile.blocked = False
def neighbours(self, tile):
"""Возвращает список проходимых соседей"""
result = []
directions = [(0, -1), (0, 1), (-1, 0), (1, 0)] # вверх, вниз, влево, вправо
for dx, dy in directions:
nx, ny = tile.x + dx, tile.y + dy
nb = self.get_tile(nx, ny)
if nb and nb.can_step():
result.append(nb)
return result
# ---------- Загрузка лабиринта ----------
class MazeLoader:
def load(self, filename):
raise NotImplementedError
class TxtMazeLoader(MazeLoader):
def load(self, filename):
with open(filename, 'r') as f:
lines = [line.rstrip('\n') for line in f.readlines()]
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_type(x, y, "wall")
elif ch == "S":
lab.set_tile_type(x, y, "start")
start_cnt += 1
elif ch == "E":
lab.set_tile_type(x, y, "exit")
exit_cnt += 1
else:
lab.set_tile_type(x, y, 'path')
if start_cnt != 1 or exit_cnt != 1:
raise ValueError(f"Maze error: S={start_cnt}, E={exit_cnt} (need exactly one each)")
return lab
# ---------- Стратегии поиска пути ----------
class SearchStrategy:
def find_path(self, lab, start, goal):
raise NotImplementedError
def _rebuild_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_cells(self):
return getattr(self, '_visited', 0)
class BFS(SearchStrategy):
def find_path(self, lab, start, goal):
q = deque()
q.append(start)
parent = {start: None}
visited = {start}
while q:
cur = q.popleft()
if cur == goal:
self._visited = len(visited)
return self._rebuild_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(SearchStrategy):
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._rebuild_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(SearchStrategy):
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._rebuild_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 []
# ---------- Статистика ----------
class SearchStats:
def __init__(self, time_ms, visited, path_len):
self.time_ms = time_ms
self.visited_cells = visited
self.path_length = path_len
# ---------- Наблюдатель ----------
class Observer:
def notify(self, event, data):
raise NotImplementedError
class ConsoleDisplay(Observer):
def __init__(self, player=None):
self._last_path = None
self._player = player
def notify(self, event, data):
if event == "maze_loaded":
self._draw_maze(data)
elif event == "path_found":
self._last_path = data
self._show_path(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(" LABYRINTH")
print("=" * (lab.width * 2 + 4))
for y in range(lab.height):
print(" ", end='')
for x in range(lab.width):
cell = lab.get_tile(x, y)
if cell == lab.start:
print('S', end=' ')
elif cell == lab.exit:
print('E', end=' ')
elif cell.is_wall:
print('#', end=' ')
else:
print('.', end=' ')
print()
print("=" * (lab.width * 2 + 4))
print(" S - start E - exit # - wall . - free")
def _draw_maze_with_player(self, lab):
os.system('cls' if os.name == 'nt' else 'clear')
print("=" * (lab.width * 2 + 4))
print(" LABYRINTH (P = player)")
print("=" * (lab.width * 2 + 4))
for y in range(lab.height):
print(" ", end='')
for x in range(lab.width):
cell = lab.get_tile(x, y)
if self._player and cell == self._player.position:
print('P', end=' ')
elif cell == lab.start:
print('S', end=' ')
elif cell == lab.exit:
print('E', end=' ')
elif cell.is_wall:
print('#', end=' ')
else:
print('.', end=' ')
print()
print("=" * (lab.width * 2 + 4))
print(f" Player at: ({self._player.position.x}, {self._player.position.y})")
print(" S - start E - exit # - wall . - free P - player")
def _show_path(self, path):
if not path:
print("\n No route found!")
return
print(f"\n Route found! Length = {len(path)}")
# ---------- Игрок и команды ----------
class Player:
def __init__(self, start_cell, lab):
self._pos = start_cell
self._prev = None
self._lab = lab
@property
def position(self):
return self._pos
def move(self, target):
if target and target.can_step():
self._prev = self._pos
self._pos = target
return True
return False
def undo(self):
if self._prev:
self._pos, self._prev = self._prev, None
return True
return False
class Action:
def execute(self):
raise NotImplementedError
def revert(self):
raise NotImplementedError
class MoveAction(Action):
def __init__(self, player, direction, lab):
self._player = player
self._dx, self._dy = direction
self._lab = lab
self._done = False
def execute(self):
new_x = self._player.position.x + self._dx
new_y = self._player.position.y + self._dy
target = self._lab.get_tile(new_x, new_y)
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 LabyrinthSolver:
def __init__(self, lab):
self._lab = lab
self._strategy = None
self._watchers = []
def attach(self, observer):
self._watchers.append(observer)
def _broadcast(self, event, data):
for obs in self._watchers:
obs.notify(event, data)
def set_algorithm(self, strategy):
self._strategy = strategy
def solve(self):
if self._strategy is None:
return None
t0 = time.perf_counter()
path = self._strategy.find_path(self._lab, self._lab.start, self._lab.exit)
t1 = time.perf_counter()
elapsed_ms = (t1 - t0) * 1000
self._broadcast("path_found", path)
return SearchStats(elapsed_ms, self._strategy.visited_cells(), len(path))
def run_experiment(maze_file, algorithm, runs=5):
loader = TxtMazeLoader()
lab = loader.load(maze_file)
total_time = 0.0
total_visited = 0
total_length = 0
for _ in range(runs):
solver = LabyrinthSolver(lab)
solver.set_algorithm(algorithm)
stats = solver.solve()
if stats:
total_time += stats.time_ms
total_visited += stats.visited_cells
total_length += stats.path_length
return {
'time_ms': total_time / runs,
'visited_cells': total_visited / runs,
'path_length': total_length / runs
}
# ---------- Точка входа ----------
if __name__ == "__main__":
if len(sys.argv) > 1 and sys.argv[1] == 'experiment':
print("Running experiments...")
sys.exit(0)
loader = TxtMazeLoader()
lab = loader.load("maze1.txt")
player = Player(lab.start, lab)
view = ConsoleDisplay(player)
view.notify("maze_loaded", lab)
solver = LabyrinthSolver(lab)
solver.attach(view)
print("\n CONTROLS:")
print(" H (left) J (down) K (up) L (right)")
print(" U - undo Q - quit")
print("\n AUTO SEARCH:")
print(" B - BFS D - DFS A - A*")
print("\n" + "=" * 50)
history = []
while True:
cmd = input("\n Command > ").lower()
if cmd == 'q':
print("\n Goodbye!")
break
elif cmd == 'b':
solver.set_algorithm(BFS())
stats = solver.solve()
print(f"\n BFS: time={stats.time_ms:.3f}ms, visited={stats.visited_cells}, length={stats.path_length}")
elif cmd == 'd':
solver.set_algorithm(DFS())
stats = solver.solve()
print(f"\n DFS: time={stats.time_ms:.3f}ms, visited={stats.visited_cells}, length={stats.path_length}")
elif cmd == 'a':
solver.set_algorithm(AStar())
stats = solver.solve()
print(f"\n A*: time={stats.time_ms:.3f}ms, visited={stats.visited_cells}, length={stats.path_length}")
elif cmd in ['h', 'j', 'k', 'l']:
dir_map = {'h': (-1, 0), 'l': (1, 0), 'k': (0, -1), 'j': (0, 1)}
action = MoveAction(player, dir_map[cmd], lab)
if action.execute():
history.append(action)
view.notify("player_moved", lab)
if player.position == lab.exit:
print("\n *** VICTORY! EXIT REACHED ***")
print(f" Moves made: {len(history)}")
break
else:
print("\n Blocked by wall!")
elif cmd == 'u':
if history:
act = history.pop()
act.revert()
view.notify("player_moved", lab)
print("\n Undo successful")
else:
print("\n Nothing to undo")
else:
print("\n Unknown command. Use h,j,k,l to move, u to undo, q to quit")
print("\n Game over. Thanks for playing!")