2026-rff_mp/VolkovVA/cod.py
2026-05-25 21:17:56 +03:00

253 lines
8.6 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 time, os
from collections import deque
from abc import ABC, abstractmethod
import heapq # <-- Добавлен импорт для A*
# --- ЭТАП 1: МОДЕЛЬ ---
class Cell:
def __init__(self, x, y, is_wall=False):
self.x = x
self.y = y
self.is_wall = is_wall
def isPassable(self):
return not self.is_wall
class Maze:
def __init__(self, width, height, grid):
self.width = width
self.height = height
self.grid = grid
self.start_cell = grid[0][0]
self.exit_cell = grid[height-1][width-1]
def getNeighbors(self, cell):
neighbors = []
directions = [(0, 1), (0, -1), (1, 0), (-1, 0)]
for d in directions:
nx = cell.x + d[0]
ny = cell.y + d[1]
if nx >= 0 and nx < self.width and ny >= 0 and ny < self.height:
if not self.grid[ny][nx].is_wall:
neighbors.append(self.grid[ny][nx])
return neighbors
# --- ЭТАП 2: BUILDER ---
class MazeBuilder:
def buildFromFile(self, filename):
path = filename
if "docs/data/" not in path:
path = os.path.join("docs", "data", filename)
with open(path, 'r') as f:
lines = []
for line in f:
stripped = line.strip()
if stripped:
lines.append(stripped)
h = len(lines)
w = len(lines[0])
grid = []
for y in range(h):
row = []
for x in range(w):
is_wall = False
if x < len(lines[y]):
if lines[y][x] == '#':
is_wall = True
row.append(Cell(x, y, is_wall))
grid.append(row)
maze = Maze(w, h, grid)
for y in range(h):
for x in range(len(lines[y])):
if lines[y][x] == 'S':
maze.start_cell = maze.grid[y][x]
if lines[y][x] == 'E':
maze.exit_cell = maze.grid[y][x]
return maze
# --- ЭТАП 3: STRATEGY ---
class SearchStats:
def __init__(self, time_ms, visited, length):
self.time_ms = time_ms
self.visited = visited
self.length = length
class PathFindingStrategy(ABC):
@abstractmethod
def findPath(self, maze, start, exit):
pass
# 1. Поиск в ширину (BFS) - оригинальный алгоритм
class BFSStrategy(PathFindingStrategy):
def findPath(self, maze, start, exit):
queue = deque([start])
visited = {start: None}
while len(queue) > 0:
curr = queue.popleft()
if curr == exit:
break
for n in maze.getNeighbors(curr):
if n not in visited:
visited[n] = curr
queue.append(n)
path = []
curr = exit
while curr is not None:
path.append(curr)
curr = visited.get(curr)
return path[::-1], len(visited)
# 2. Поиск в глубину (DFS) - добавлен
class DFSStrategy(PathFindingStrategy):
def findPath(self, maze, start, exit):
stack = [start]
visited = {start: None}
while len(stack) > 0:
curr = stack.pop()
if curr == exit:
break
for n in maze.getNeighbors(curr):
if n not in visited:
visited[n] = curr
stack.append(n)
path = []
curr = exit
while curr is not None:
path.append(curr)
curr = visited.get(curr)
return path[::-1], len(visited)
# 3. Алгоритм A*
class AStarStrategy(PathFindingStrategy):
def findPath(self, maze, start, exit):
counter = 0
queue = [(0, counter, start)]
came_from = {start: None}
g_score = {start: 0}
while len(queue) > 0:
_, _, curr = heapq.heappop(queue)
if curr == exit:
break
for n in maze.getNeighbors(curr):
tentative_g_score = g_score[curr] + 1
if n not in g_score or tentative_g_score < g_score[n]:
came_from[n] = curr
g_score[n] = tentative_g_score
# Эвристика: Манхэттенское расстояние
f_score = tentative_g_score + abs(n.x - exit.x) + abs(n.y - exit.y)
counter += 1
heapq.heappush(queue, (f_score, counter, n))
path = []
curr = exit
while curr is not None:
path.append(curr)
curr = came_from.get(curr)
return path[::-1], len(came_from)
# --- ЭТАП 4: ORCHESTRATOR ---
class MazeSolver:
def __init__(self, maze, player=None):
self.maze = maze
self.player = player
self.observers = []
def attach(self, obs):
self.observers.append(obs)
def notify(self, event, data):
for o in self.observers:
o.update(event, data)
def solve(self, strat):
t0 = time.perf_counter()
path, visited = strat.findPath(self.maze, self.maze.start_cell, self.maze.exit_cell)
t1 = time.perf_counter()
return SearchStats((t1 - t0) * 1000, visited, len(path))
# --- ЭТАП 5: OBSERVER & COMMAND ---
class Player:
def __init__(self, cell):
self.current_cell = cell
class MoveCommand:
def __init__(self, player, dx, dy, maze):
self.player = player
self.dx = dx
self.dy = dy
self.maze = maze
def execute(self):
nx = self.player.current_cell.x + self.dx
ny = self.player.current_cell.y + self.dy
if nx >= 0 and nx < self.maze.width and ny >= 0 and ny < self.maze.height:
target = self.maze.grid[ny][nx]
if target.isPassable():
self.player.current_cell = target
return True
return False
class ConsoleView:
def update(self, event, data):
print(f"[INFO] {event.upper()}: {data}")
# --- ЗАПУСК ---
if __name__ == "__main__":
files = ["maze10-10.txt", "maze50-50.txt", "maze100-100.txt", "maze0.txt", "maze777.txt"]
mode = input("Эксперимент (e) или игра (i)? ").lower()
if mode == 'e':
# Обновленная таблица с колонкой "Метод"
print(f"{'Файл':<15} | {'Метод':<5} | {'Время(мс)':<10} | {'Посещено':<10} | {'Путь':<6}")
print("-" * 58)
for f in files:
try:
m = MazeBuilder().buildFromFile(f)
# Словарь с нашими тремя методами
strategies = {
"BFS": BFSStrategy(),
"DFS": DFSStrategy(),
"A*": AStarStrategy()
}
# Запускаем каждый метод для текущего лабиринта
for strat_name, strat_obj in strategies.items():
t_sum, v_sum, l_sum = 0, 0, 0
for _ in range(10):
s = MazeSolver(m).solve(strat_obj)
t_sum += s.time_ms
v_sum += s.visited
l_sum += s.length
print(f"{f:<15} | {strat_name:<5} | {t_sum/10:<10.2f} | {v_sum/10:<10.1f} | {l_sum/10:<6.1f}")
# Линия-разделитель между разными лабиринтами для удобства чтения
print("-" * 58)
except FileNotFoundError:
print(f"{f:<15} | ОШИБКА: Файл не найден")
print("-" * 58)
elif mode == 'i':
name = input("Имя файла: ")
m = MazeBuilder().buildFromFile(name)
p = Player(m.start_cell)
s = MazeSolver(m, p)
s.attach(ConsoleView())
while True:
cmd = input("WASD (q-выход): ").lower()
if cmd == 'q': break
dx, dy = 0, 0
if cmd == 'w': dy = -1
elif cmd == 'a': dx = -1
elif cmd == 's': dy = 1
elif cmd == 'd': dx = 1
if dx != 0 or dy != 0:
if MoveCommand(p, dx, dy, m).execute():
s.notify("move", f"Направление {cmd}, Координата ({p.current_cell.x}, {p.current_cell.y})")
else:
s.notify("error", "Стена!")