2026-rff_mp/kalinovskiymi/docs_2/data_2/task_2_2.py

483 lines
19 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 heapq
import time
import os
import csv
from collections import deque
from abc import ABC, abstractmethod
import matplotlib.pyplot as plt
import numpy as np
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.grid = [[Cell(x, y, True) for y in range(height)] for x in range(width)]
self.start = None
self.exit = None
def get_cell(self, x, y):
if 0 <= x < self.width and 0 <= y < self.height:
return self.grid[x][y]
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
neighbor = self.get_cell(nx, ny)
if neighbor and neighbor.is_passable():
neighbors.append(neighbor)
return neighbors
class MazeBuilder(ABC):
@abstractmethod
def build_from_file(self, filename):
pass
class TextFileMazeBuilder(MazeBuilder):
def build_from_file(self, filename):
with open(filename, 'r') as f:
lines = [line.rstrip('\n') for line in f.readlines()]
height = len(lines)
width = max(len(line) for line in lines) if height > 0 else 0
maze = Maze(width, height)
for y, line in enumerate(lines):
for x, char in enumerate(line):
if char == '#':
maze.grid[x][y] = Cell(x, y, True)
else:
cell = Cell(x, y, False)
if char == 'S':
cell.is_start = True
maze.start = cell
elif char == 'E':
cell.is_exit = True
maze.exit = cell
maze.grid[x][y] = cell
if not maze.start or not maze.exit:
raise ValueError("Лабиринт должен содержать старт (S) и выход (E)")
return maze
class PathFindingStrategy(ABC):
@abstractmethod
def find_path(self, maze, start, exit_cell):
pass
class BFSPathFinding(PathFindingStrategy):
def find_path(self, maze, start, exit_cell):
queue = deque([start])
visited = {start: None}
visited_count = 0
while queue:
current = queue.popleft()
visited_count += 1
if exit_cell is not None and current == exit_cell:
path = []
while current:
path.append(current)
current = visited[current]
return path[::-1], visited_count
for neighbor in maze.get_neighbors(current):
if neighbor not in visited:
visited[neighbor] = current
queue.append(neighbor)
return [], visited_count
class DFSPathFinding(PathFindingStrategy):
def find_path(self, maze, start, exit_cell):
stack = [start]
visited = {start: None}
visited_count = 0
while stack:
current = stack.pop()
visited_count += 1
if exit_cell is not None and current == exit_cell:
path = []
while current:
path.append(current)
current = visited[current]
return path[::-1], visited_count
for neighbor in maze.get_neighbors(current):
if neighbor not in visited:
visited[neighbor] = current
stack.append(neighbor)
return [], visited_count
class AStarPathFinding(PathFindingStrategy):
def heuristic(self, a, b):
if b is None:
return 0
return abs(a.x - b.x) + abs(a.y - b.y)
def find_path(self, maze, start, exit_cell):
open_set = [(0, 0, start, None)]
heapq.heapify(open_set)
g_score = {start: 0}
came_from = {}
visited_count = 0
while open_set:
_, _, current, parent = heapq.heappop(open_set)
if current in came_from:
continue
visited_count += 1
came_from[current] = parent
if exit_cell is not None and current == exit_cell:
path = []
while current:
path.append(current)
current = came_from[current]
return path[::-1], visited_count
for neighbor in maze.get_neighbors(current):
tentative_g = g_score[current] + 1
if neighbor not in g_score or tentative_g < g_score[neighbor]:
g_score[neighbor] = tentative_g
f_score = tentative_g + self.heuristic(neighbor, exit_cell)
heapq.heappush(open_set, (f_score, id(neighbor), neighbor, current))
return [], visited_count
class SearchStats:
def __init__(self, path, visited_count, time_ms):
self.path = path
self.path_length = len(path)
self.visited_count = visited_count
self.time_ms = time_ms
class Observer(ABC):
@abstractmethod
def update(self, event):
pass
class ConsoleView(Observer):
def update(self, event):
if event['type'] == 'path_found':
self.render(event['maze'], event.get('player_pos'), event['path'])
elif event['type'] == 'maze_loaded':
print(f"Лабиринт загружен: {event['maze'].width}x{event['maze'].height}")
elif event['type'] == 'search_start':
print(f"Поиск пути алгоритмом {event['strategy']}...")
elif event['type'] == 'search_end':
print(
f"Путь найден: длина {event['stats'].path_length}, посещено клеток {event['stats'].visited_count}, время {event['stats'].time_ms:.3f}мс")
def render(self, maze, player_pos=None, path=None):
os.system('cls' if os.name == 'nt' else 'clear')
path_set = set((c.x, c.y) for c in path) if path else set()
for y in range(maze.height):
for x in range(maze.width):
cell = maze.get_cell(x, y)
if player_pos and (x, y) == (player_pos.x, player_pos.y):
print('P', end='')
elif cell.is_start:
print('S', end='')
elif cell.is_exit:
print('E', end='')
elif (x, y) in path_set:
print('.', end='')
elif cell.is_wall:
print('#', end='')
else:
print(' ', end='')
print()
class MazeSolver:
def __init__(self, maze, strategy=None):
self.maze = maze
self.strategy = strategy
self.observers = []
def set_strategy(self, strategy):
self.strategy = strategy
def add_observer(self, observer):
self.observers.append(observer)
def notify(self, event):
for observer in self.observers:
observer.update(event)
def solve(self):
if not self.strategy:
raise ValueError("Стратегия не задана")
self.notify({'type': 'search_start', 'strategy': type(self.strategy).__name__})
start_time = time.perf_counter()
if self.maze.exit is None:
path, visited = self.strategy.find_path(self.maze, self.maze.start, None)
else:
path, visited = self.strategy.find_path(self.maze, self.maze.start, self.maze.exit)
end_time = time.perf_counter()
time_ms = (end_time - start_time) * 1000
stats = SearchStats(path, visited, time_ms)
self.notify({'type': 'search_end', 'stats': stats, 'strategy': type(self.strategy).__name__})
self.notify({'type': 'path_found', 'maze': self.maze, 'path': path})
return stats
def is_path_exists(maze):
if maze.exit is None:
return False
queue = deque([maze.start])
visited = {maze.start}
while queue:
current = queue.popleft()
if current == maze.exit:
return True
for neighbor in maze.get_neighbors(current):
if neighbor not in visited:
visited.add(neighbor)
queue.append(neighbor)
return False
def generate_maze(width, height, wall_density=0.3, seed=42):
np.random.seed(seed)
maze = Maze(width, height)
for x in range(width):
for y in range(height):
if x == 0 or x == width - 1 or y == 0 or y == height - 1:
maze.grid[x][y] = Cell(x, y, True)
else:
is_wall = np.random.random() < wall_density
maze.grid[x][y] = Cell(x, y, is_wall)
maze.start = maze.get_cell(1, 1)
maze.start.is_wall = False
maze.start.is_start = True
maze.grid[1][1] = maze.start
maze.grid[1][2] = Cell(1, 2, False)
maze.grid[2][1] = Cell(2, 1, False)
maze.exit = maze.get_cell(width - 2, height - 2)
maze.exit.is_wall = False
maze.exit.is_exit = True
maze.grid[width - 2][height - 3] = Cell(width - 2, height - 3, False)
maze.grid[width - 3][height - 2] = Cell(width - 3, height - 2, False)
if not is_path_exists(maze):
for x in range(1, width - 1):
for y in range(1, height - 1):
if np.random.random() < 0.5:
maze.grid[x][y].is_wall = False
if not is_path_exists(maze):
for x in range(1, width - 1):
for y in range(1, height - 1):
if x == 1 and y == 1:
continue
if x == width - 2 and y == height - 2:
continue
maze.grid[x][y].is_wall = False
return maze
def generate_empty_maze(width, height):
maze = Maze(width, height)
for x in range(width):
for y in range(height):
maze.grid[x][y] = Cell(x, y, False)
maze.start = maze.get_cell(0, 0)
maze.start.is_start = True
maze.exit = maze.get_cell(width - 1, height - 1)
maze.exit.is_exit = True
return maze
def generate_no_exit_maze(width, height):
maze = Maze(width, height)
np.random.seed(123)
for x in range(width):
for y in range(height):
if x == 0 or x == width - 1 or y == 0 or y == height - 1:
maze.grid[x][y] = Cell(x, y, True)
else:
is_wall = np.random.random() < 0.3
maze.grid[x][y] = Cell(x, y, is_wall)
maze.start = maze.get_cell(1, 1)
maze.start.is_wall = False
maze.start.is_start = True
maze.grid[1][1] = maze.start
maze.grid[1][2] = Cell(1, 2, False)
maze.grid[2][1] = Cell(2, 1, False)
maze.exit = None
return maze
def save_maze_to_file(maze, filename):
with open(filename, 'w') as f:
for y in range(maze.height):
for x in range(maze.width):
cell = maze.get_cell(x, y)
if cell.is_start:
f.write('S')
elif cell.is_exit:
f.write('E')
elif cell.is_wall:
f.write('#')
else:
f.write(' ')
f.write('\n')
def visualize_maze(maze, path=None, title="Лабиринт", ax=None):
grid = np.zeros((maze.height, maze.width))
for y in range(maze.height):
for x in range(maze.width):
cell = maze.get_cell(x, y)
if cell.is_wall:
grid[y, x] = 1
elif cell.is_start:
grid[y, x] = 2
elif cell.is_exit:
grid[y, x] = 3
if ax is None:
fig, ax = plt.subplots(figsize=(8, 8))
cmap = plt.cm.colors.ListedColormap(['white', 'black', 'green', 'red'])
ax.imshow(grid, cmap=cmap, interpolation='nearest')
if path:
path_x = [cell.x for cell in path]
path_y = [cell.y for cell in path]
ax.plot(path_x, path_y, 'b-', linewidth=2, label='Путь')
ax.set_title(title)
ax.set_xticks([])
ax.set_yticks([])
def run_experiments():
mazes_data = {
"Маленький (10x10)": generate_maze(10, 10, 0.2, 10),
"Средний (50x50)": generate_maze(50, 50, 0.3, 20),
"Большой (100x100)": generate_maze(100, 100, 0.3, 30),
"Пустой (50x50)": generate_empty_maze(50, 50),
"Без выхода (50x50)": generate_no_exit_maze(50, 50)
}
os.makedirs("mazes", exist_ok=True)
for name, maze in mazes_data.items():
filename = f"mazes/{name.replace(' ', '_').replace('(', '').replace(')', '')}.txt"
save_maze_to_file(maze, filename)
print(f"Сохранён {filename}")
strategies = {
"BFS": BFSPathFinding(),
"DFS": DFSPathFinding(),
"A*": AStarPathFinding()
}
results = []
runs = 5
fig_mazes, axes_mazes = plt.subplots(len(mazes_data), len(strategies) + 1, figsize=(18, 4 * len(mazes_data)))
if len(mazes_data) == 1:
axes_mazes = [axes_mazes]
for row_idx, (maze_name, maze) in enumerate(mazes_data.items()):
visualize_maze(maze, title=f"{maze_name}", ax=axes_mazes[row_idx][0])
for col_idx, (strat_name, strategy) in enumerate(strategies.items()):
solver = MazeSolver(maze, strategy)
times = []
visited_counts = []
path_lengths = []
best_path = None
for _ in range(runs):
stats = solver.solve()
times.append(stats.time_ms)
visited_counts.append(stats.visited_count)
path_lengths.append(stats.path_length)
if stats.path:
best_path = stats.path
avg_time = np.mean(times)
avg_visited = np.mean(visited_counts)
avg_path = np.mean(path_lengths)
results.append([maze_name, strat_name, avg_time, avg_visited, avg_path])
print(f"{maze_name} - {strat_name}: Время={avg_time:.3f}мс, Посещено={avg_visited:.0f}, Длина пути={avg_path:.0f}")
visualize_maze(maze, best_path, f"{maze_name} - {strat_name}", ax=axes_mazes[row_idx][col_idx + 1])
plt.tight_layout()
plt.savefig('mazes_visualization.png')
plt.close()
with open('results.csv', 'w', newline='', encoding='utf-8-sig') as f:
writer = csv.writer(f)
writer.writerow(["Лабиринт", "Стратегия", "Время_мс", "Посещено", "Длина_пути"])
writer.writerows(results)
print("\nРезультаты сохранены в results.csv")
return results
def plot_results(results):
strategies = ["BFS", "DFS", "A*"]
mazes = ["Маленький (10x10)", "Средний (50x50)", "Большой (100x100)", "Пустой (50x50)", "Без выхода (50x50)"]
data = {}
for strat in strategies:
data[strat] = {"times": [], "visited": [], "paths": []}
for row in results:
maze, strat, time_ms, visited, path_len = row
data[strat]["times"].append(time_ms)
data[strat]["visited"].append(visited)
data[strat]["paths"].append(path_len)
fig, axes = plt.subplots(1, 3, figsize=(18, 6))
x = np.arange(len(mazes))
width = 0.25
colors = {'BFS': 'skyblue', 'DFS': 'lightgreen', 'A*': 'salmon'}
for i, strat in enumerate(strategies):
offset = (i - 1) * width
times_display = [t if t > 0 else 0.001 for t in data[strat]["times"]]
bars = axes[0].bar(x + offset, times_display, width, label=strat, color=colors[strat])
for bar, val in zip(bars, data[strat]["times"]):
if val > 0:
axes[0].text(bar.get_x() + bar.get_width() / 2, bar.get_height() * 1.1,
f'{val:.2f}', ha='center', va='bottom', fontsize=8, rotation=90)
axes[0].set_title("Время выполнения (мс)")
axes[0].set_xticks(x)
axes[0].set_xticklabels(mazes, rotation=15, ha='right')
axes[0].set_ylabel("Время (мс)")
axes[0].set_yscale('log')
axes[0].legend()
axes[0].grid(axis='y', alpha=0.3)
for i, strat in enumerate(strategies):
offset = (i - 1) * width
visited_display = [v if v > 0 else 1 for v in data[strat]["visited"]]
bars = axes[1].bar(x + offset, visited_display, width, label=strat, color=colors[strat])
for bar, val in zip(bars, data[strat]["visited"]):
if val > 0:
axes[1].text(bar.get_x() + bar.get_width() / 2, bar.get_height() * 1.1,
f'{val:.0f}', ha='center', va='bottom', fontsize=8, rotation=90)
axes[1].set_title("Посещено клеток")
axes[1].set_xticks(x)
axes[1].set_xticklabels(mazes, rotation=15, ha='right')
axes[1].set_ylabel("Количество клеток")
axes[1].set_yscale('log')
axes[1].legend()
axes[1].grid(axis='y', alpha=0.3)
for i, strat in enumerate(strategies):
offset = (i - 1) * width
paths_display = [p if p > 0 else 1 for p in data[strat]["paths"]]
bars = axes[2].bar(x + offset, paths_display, width, label=strat, color=colors[strat])
for bar, val in zip(bars, data[strat]["paths"]):
height = bar.get_height()
axes[2].text(bar.get_x() + bar.get_width() / 2, height * 1.1,
f'{val:.0f}', ha='center', va='bottom', fontsize=8, rotation=90)
axes[2].set_title("Длина найденного пути")
axes[2].set_xticks(x)
axes[2].set_xticklabels(mazes, rotation=15, ha='right')
axes[2].set_ylabel("Длина пути")
axes[2].set_yscale('log')
axes[2].legend()
axes[2].grid(axis='y', alpha=0.3)
plt.tight_layout()
plt.savefig('comparative_results.png')
plt.show()
print("Сравнительные графики сохранены в comparative_results.png")
if __name__ == "__main__":
print("\nГенерация лабиринтов и запуск экспериментов\n")
results = run_experiments()
print("\nСоздание графиков")
plot_results(results)
print("\nЭксперименты завершены")
print("\nСозданные файлы:")
print(" - 5 текстовых файлов с лабиринтами")
print(" - mazes_visualization.png: Визуализация всех лабиринтов с путями")
print(" - results.csv: Таблица с числовыми результатами")
print(" - comparative_results.png: Сравнительные графики (Время, Посещено, Длина пути)")
print("\nСводка результатов:")
for row in results:
maze, strat, time_ms, visited, path_len = row
print(f"{maze:20s} | {strat:5s} | Время: {time_ms:8.3f}мс | Посещено: {visited:6.0f} | Путь: {path_len:4.0f}")