2026-rff_mp/tseremonnikovaaa/lab2/docs/data/main2.py

478 lines
15 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
import csv
import heapq
from collections import deque
from abc import ABC, abstractmethod
import matplotlib.pyplot as plt
import pandas as pd
from dataclasses import dataclass
import os
class Cell:
"""Клетка лабиринта"""
def __init__(self, x, y, is_wall=False):
self.x = x
self.y = y
self.is_wall = is_wall
self.is_start = False
self.is_exit = False
def is_passable(self):
return not self.is_wall
class Maze:
"""Лабиринт"""
def __init__(self, width, height):
self.width = width
self.height = height
self.cells = [[Cell(x, y) for x in range(width)] for y in range(height)]
self.start = None
self.exit = None
def get_cell(self, x, y):
if 0 <= x < self.width and 0 <= y < self.height:
return self.cells[y][x]
return None
def get_neighbors(self, cell):
neighbors = []
for dx, dy in [(0, 1), (0, -1), (1, 0), (-1, 0)]:
nx, ny = cell.x + dx, cell.y + dy
nb = self.get_cell(nx, ny)
if nb and nb.is_passable():
neighbors.append(nb)
return neighbors
def __str__(self):
result = ""
for y in range(self.height):
for x in range(self.width):
cell = self.get_cell(x, y)
if cell is None:
result += "?"
elif cell.is_wall:
result += "#"
elif cell.is_start:
result += "S"
elif cell.is_exit:
result += "E"
else:
result += " "
result += "\n"
return result
class MazeBuilder(ABC):
@abstractmethod
def build_from_file(self, filename):
pass
class TextFileMazeBuilder(MazeBuilder):
def build_from_file(self, filename):
with open(filename, 'r', encoding='utf-8') as f:
lines = [line.rstrip('\n') for line in f.readlines()]
height = len(lines)
width = max(len(line) for line in lines)
maze = Maze(width, height)
for y, line in enumerate(lines):
for x, ch in enumerate(line):
cell = maze.get_cell(x, y)
if ch == '#':
cell.is_wall = True
elif ch == 'S':
cell.is_start = True
maze.start = cell
elif ch == 'E':
cell.is_exit = True
maze.exit = cell
else:
cell.is_wall = False
return maze
class PathFindingStrategy(ABC):
@abstractmethod
def find_path(self, maze, start, exit):
pass
class BFSStrategy(PathFindingStrategy):
"""Поиск в ширину"""
def find_path(self, maze, start, exit):
visited = set()
if start == exit:
return [start], 1
queue = deque([start])
visited.add(start)
parent = {start: None}
while queue:
current = queue.popleft()
for nb in maze.get_neighbors(current):
if nb not in visited:
visited.add(nb)
parent[nb] = current
if nb == exit:
path = []
node = nb
while node is not None:
path.append(node)
node = parent[node]
path.reverse()
return path, len(visited)
queue.append(nb)
return [], len(visited)
class DFSStrategy(PathFindingStrategy):
"""Поиск в глубину"""
def find_path(self, maze, start, exit):
visited = set()
stack = [(start, [start])]
while stack:
current, path = stack.pop()
if current == exit:
return path, len(visited)
visited.add(current)
for nb in maze.get_neighbors(current):
if nb not in visited:
stack.append((nb, path + [nb]))
return [], len(visited)
class AStarStrategy(PathFindingStrategy):
"""Алгоритм A"""
def heuristic(self, cell, exit):
return abs(cell.x - exit.x) + abs(cell.y - exit.y)
def find_path(self, maze, start, exit):
open_set = []
counter = 0
heapq.heappush(open_set, (0, counter, start))
counter += 1
came_from = {}
g_score = {start: 0}
f_score = {start: self.heuristic(start, exit)}
visited = set()
while open_set:
_, _, current = heapq.heappop(open_set)
visited.add(current)
if current == exit:
path = []
node = current
while node in came_from:
path.append(node)
node = came_from[node]
path.append(start)
path.reverse()
return path, len(visited)
for nb in maze.get_neighbors(current):
tentative_g = g_score[current] + 1
if tentative_g < g_score.get(nb, float('inf')):
came_from[nb] = current
g_score[nb] = tentative_g
f = tentative_g + self.heuristic(nb, exit)
heapq.heappush(open_set, (f, counter, nb))
counter += 1
return [], len(visited)
@dataclass
class SearchStats:
time_ms: float
visited_cells: int
path_length: int
algorithm: str
class MazeSolver:
def __init__(self, maze, strategy):
self.maze = maze
self.strategy = strategy
def set_strategy(self, strategy):
self.strategy = strategy
def solve(self):
if self.maze.start is None or self.maze.exit is None:
raise ValueError("Лабиринт не имеет старта или выхода")
start_time = time.perf_counter()
path, visited = self.strategy.find_path(self.maze, self.maze.start, self.maze.exit)
end_time = time.perf_counter()
stats = SearchStats(
time_ms=(end_time - start_time) * 1000,
visited_cells=visited,
path_length=len(path),
algorithm=self.strategy.__class__.__name__
)
return path, stats
class Observer(ABC):
@abstractmethod
def update(self, event_type, data=None):
pass
class ConsoleLogger(Observer):
def update(self, event_type, data=None):
if event_type == "search_start":
print(f"[LOG] Поиск пути начат")
elif event_type == "path_found":
print(f"[LOG] Путь найден! Длина: {data}")
elif event_type == "no_path":
print("[LOG] Путь не найден")
elif event_type == "step":
print(f"[LOG] Шаг: {data}")
class MazeSolverWithObserver(MazeSolver):
def __init__(self, maze, strategy, observers=None):
super().__init__(maze, strategy)
self.observers = observers if observers else []
def attach(self, observer):
self.observers.append(observer)
def detach(self, observer):
self.observers.remove(observer)
def notify(self, event_type, data=None):
for obs in self.observers:
obs.update(event_type, data)
def solve(self):
if self.maze.start is None or self.maze.exit is None:
raise ValueError("Лабиринт не имеет старта или выхода")
self.notify("search_start")
start_time = time.perf_counter()
path, visited = self.strategy.find_path(self.maze, self.maze.start, self.maze.exit)
end_time = time.perf_counter()
if path:
self.notify("path_found", len(path))
else:
self.notify("no_path")
stats = SearchStats(
time_ms=(end_time - start_time) * 1000,
visited_cells=visited,
path_length=len(path),
algorithm=self.strategy.__class__.__name__
)
return path, stats
class Command(ABC):
@abstractmethod
def execute(self):
pass
@abstractmethod
def undo(self):
pass
class MoveCommand(Command):
def __init__(self, player, direction, maze):
self.player = player
self.direction = direction
self.maze = maze
self.prev_pos = None
def execute(self):
self.prev_pos = self.player.current_cell
dx, dy = self.direction
nx, ny = self.player.current_cell.x + dx, self.player.current_cell.y + dy
new_cell = self.maze.get_cell(nx, ny)
if new_cell and new_cell.is_passable():
self.player.current_cell = new_cell
return True
return False
def undo(self):
if self.prev_pos:
self.player.current_cell = self.prev_pos
return True
return False
class Player:
def __init__(self, start_cell):
self.current_cell = start_cell
def interactive_move_demo(maze, path):
"""Демонстрация движения с отменой последнего шага"""
if not path:
print("Путь не найден, демонстрация движения невозможна.")
return
player = Player(maze.start)
command_history = []
print("\n Интерактивное движение по найденному пути")
print("Текущая позиция: старт")
for step, cell in enumerate(path):
if cell == maze.start:
continue
prev = path[step-1]
dx = cell.x - prev.x
dy = cell.y - prev.y
cmd = MoveCommand(player, (dx, dy), maze)
cmd.execute()
command_history.append(cmd)
print(f"Шаг {step}: перемещение на ({dx},{dy}), позиция ({player.current_cell.x},{player.current_cell.y})")
if cell == maze.exit:
print("Достигнут выход!")
break
if command_history:
print("\nДемонстрация отмены последнего шага")
cmd = command_history[-1]
cmd.undo()
print(f"Отменён последний шаг, позиция: ({player.current_cell.x},{player.current_cell.y})")
def test_single_maze(filename, strategies, repeats=5):
"""Тестирование одного лабиринта с разными стратегиями"""
builder = TextFileMazeBuilder()
maze = builder.build_from_file(filename)
results = []
for strategy in strategies:
solver = MazeSolver(maze, strategy)
times = []
visits = []
lengths = []
for _ in range(repeats):
_, stats = solver.solve()
times.append(stats.time_ms)
visits.append(stats.visited_cells)
lengths.append(stats.path_length)
results.append({
'algorithm': strategy.__class__.__name__,
'avg_time_ms': sum(times) / repeats,
'avg_visited': sum(visits) / repeats,
'avg_path_len': sum(lengths) / repeats
})
return results
def save_maze_to_file(maze, filename):
"""Сохранение лабиринта в файл"""
os.makedirs(os.path.dirname(filename), exist_ok=True)
with open(filename, 'w', encoding='utf-8') as f:
for y in range(maze.height):
line = ""
for x in range(maze.width):
cell = maze.get_cell(x, y)
if cell.is_wall:
line += "#"
elif cell.is_start:
line += "S"
elif cell.is_exit:
line += "E"
else:
line += " "
f.write(line + "\n")
def create_test_mazes():
"""Создание тестовых лабиринтов"""
os.makedirs("mazes", exist_ok=True)
# 1. Простой лабиринт 10x10 (tiny.txt)
maze1 = Maze(10, 10)
for y in range(10):
for x in range(10):
is_start = (x == 0 and y == 0)
is_exit = (x == 9 and y == 0)
is_wall = False
if y == 1 and x not in [0, 1, 9]:
is_wall = True
if y == 2 and x not in [9]:
is_wall = True
if y == 3 and x not in [0, 9]:
is_wall = True
if y == 4 and x not in [0, 1, 9]:
is_wall = True
if y == 5 and x not in [9]:
is_wall = True
if y == 6 and x not in [0, 9]:
is_wall = True
if y == 7 and x not in [9]:
is_wall = True
if y == 8 and x not in [0, 9]:
is_wall = True
cell = Cell(x, y, is_wall=is_wall)
cell.is_start = is_start
cell.is_exit = is_exit
maze1.cells[y][x] = cell
if is_start:
maze1.start = cell
if is_exit:
maze1.exit = cell
save_maze_to_file(maze1, "mazes/tiny.txt")
# 2. Средний лабиринт 15x15 (medium.txt)
maze2 = Maze(15, 15)
for y in range(15):
for x in range(15):
is_start = (x == 0 and y == 0)
is_exit = (x == 14 and y == 14)
is_wall = (x % 3 == 1 and y % 2 == 0) and not is_start and not is_exit
cell = Cell(x, y, is_wall=is_wall)
cell.is_start = is_start
cell.is_exit = is_exit
maze2.cells[y][x] = cell
if is_start:
maze2.start = cell
if is_exit:
maze2.exit = cell
save_maze_to_file(maze2, "mazes/medium.txt")
# 3. Большой лабиринт 30x30 (large.txt)
maze3 = Maze(30, 30)
for y in range(30):
for x in range(30):
is_start = (x == 0 and y == 0)
is_exit = (x == 29 and y == 29)
is_wall = (x % 2 == 0 and y % 3 == 0) and not is_start and not is_exit
cell = Cell(x, y, is_wall=is_wall)
cell.is_start = is_start
cell.is_exit = is_exit
maze3.cells[y][x] = cell
if is_start:
maze3.start = cell
if is_exit:
maze3.exit = cell
save_maze_to_file(maze3, "mazes/large.txt")
# 4. Пустой лабиринт 15x15 (empty.txt)
maze4 = Maze(15, 15)
for y in range(15):
for x in range(15):
is_start = (x == 0 and y == 0)
is_exit = (x == 14 and y == 14)
cell = Cell(x, y, is_wall=False)
cell.is_start = is_start
cell.is_exit = is_exit
maze4.cells[y][x] = cell
if is_start:
maze4.start = cell
if is_exit:
maze4.exit = cell
save_maze_to_file(maze4, "mazes/empty.txt")
# 5. Лабиринт без выхода 10x10 (no_exit.txt)
maze5 = Maze(10, 10)
for y in range(10):
for x in range(10):
is_start = (x == 0 and y == 0)
is_exit = (x == 9 and y == 9)
is_wall = (x > 0 and y > 0) and not is_start
cell = Cell(x, y, is_wall=is_wall)
cell.is_start = is_start
cell.is_exit = is_exit
maze5.cells[y][x] = cell
if is_start:
maze5.start = cell
if is_exit:
maze5.exit = cell
save_maze_to_file(maze5, "mazes/no_exit.txt")