2026-rff_mp/VolkovVA/cod.py

253 lines
8.6 KiB
Python
Raw Normal View History

2026-05-25 18:17:56 +00:00
import time, os
from collections import deque
2026-05-25 01:50:25 +00:00
from abc import ABC, abstractmethod
2026-05-25 18:17:56 +00:00
import heapq # <-- Добавлен импорт для A*
2026-05-25 18:17:56 +00:00
# --- ЭТАП 1: МОДЕЛЬ ---
2026-05-24 23:27:34 +00:00
class Cell:
2026-05-25 18:17:56 +00:00
def __init__(self, x, y, is_wall=False):
self.x = x
self.y = y
self.is_wall = is_wall
2026-05-25 18:17:56 +00:00
def isPassable(self):
return not self.is_wall
2026-05-25 03:35:06 +00:00
2026-05-24 23:27:34 +00:00
class Maze:
2026-05-25 18:17:56 +00:00
def __init__(self, width, height, grid):
self.width = width
self.height = height
2026-05-25 18:17:56 +00:00
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)]
2026-05-25 18:17:56 +00:00
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
2026-05-25 18:17:56 +00:00
# --- ЭТАП 2: BUILDER ---
2026-05-24 23:27:34 +00:00
class MazeBuilder:
def buildFromFile(self, filename):
2026-05-25 18:17:56 +00:00
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
2026-05-25 18:17:56 +00:00
# --- ЭТАП 3: STRATEGY ---
class SearchStats:
def __init__(self, time_ms, visited, length):
self.time_ms = time_ms
self.visited = visited
self.length = length
2026-05-25 18:17:56 +00:00
class PathFindingStrategy(ABC):
2026-05-25 03:35:06 +00:00
@abstractmethod
2026-05-25 18:17:56 +00:00
def findPath(self, maze, start, exit):
pass
2026-05-25 18:17:56 +00:00
# 1. Поиск в ширину (BFS) - оригинальный алгоритм
class BFSStrategy(PathFindingStrategy):
def findPath(self, maze, start, exit):
queue = deque([start])
2026-05-25 18:17:56 +00:00
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)
2026-05-25 18:17:56 +00:00
# 3. Алгоритм A*
class AStarStrategy(PathFindingStrategy):
def findPath(self, maze, start, exit):
2026-05-25 18:17:56 +00:00
counter = 0
queue = [(0, counter, start)]
came_from = {start: None}
g_score = {start: 0}
2026-05-25 18:17:56 +00:00
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))
2026-05-25 01:50:25 +00:00
2026-05-25 18:17:56 +00:00
path = []
curr = exit
while curr is not None:
path.append(curr)
curr = came_from.get(curr)
return path[::-1], len(came_from)
2026-05-25 01:50:25 +00:00
2026-05-25 18:17:56 +00:00
# --- ЭТАП 4: ORCHESTRATOR ---
class MazeSolver:
2026-05-25 18:17:56 +00:00
def __init__(self, maze, player=None):
self.maze = maze
2026-05-25 03:35:06 +00:00
self.player = player
2026-05-25 18:17:56 +00:00
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()
2026-05-25 18:17:56 +00:00
path, visited = strat.findPath(self.maze, self.maze.start_cell, self.maze.exit_cell)
t1 = time.perf_counter()
2026-05-25 18:17:56 +00:00
return SearchStats((t1 - t0) * 1000, visited, len(path))
2026-05-25 18:17:56 +00:00
# --- ЭТАП 5: OBSERVER & COMMAND ---
class Player:
def __init__(self, cell):
self.current_cell = cell
2026-05-25 03:35:06 +00:00
2026-05-25 18:17:56 +00:00
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()
2026-05-25 03:35:06 +00:00
2026-05-25 18:17:56 +00:00
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", "Стена!")