2026-05-07 12:09:26 +00:00
|
|
|
|
import abc
|
|
|
|
|
|
import heapq
|
|
|
|
|
|
import time
|
|
|
|
|
|
from collections import deque
|
|
|
|
|
|
from dataclasses import dataclass
|
|
|
|
|
|
from typing import List, Optional, Dict, Set, Tuple, Any
|
2026-05-09 13:08:21 +00:00
|
|
|
|
import csv
|
|
|
|
|
|
import os
|
|
|
|
|
|
import sys
|
2026-05-07 12:09:26 +00:00
|
|
|
|
|
|
|
|
|
|
class Cell:
|
|
|
|
|
|
#тут что такое клетка
|
|
|
|
|
|
def __init__(self, x: int, y: int, is_wall: bool = False,
|
|
|
|
|
|
is_exit: bool = False, is_start: bool = False):
|
|
|
|
|
|
self.x = x
|
|
|
|
|
|
self.y = y
|
|
|
|
|
|
self.is_wall = is_wall
|
|
|
|
|
|
self.is_exit = is_exit
|
|
|
|
|
|
self.is_start = is_start
|
2026-05-09 13:08:21 +00:00
|
|
|
|
|
|
|
|
|
|
def __eq__(self, other):
|
|
|
|
|
|
return isinstance(other, Cell) and self.x == other.x and self.y == other.y
|
|
|
|
|
|
|
|
|
|
|
|
def __hash__(self):
|
|
|
|
|
|
return hash((self.x, self.y))
|
2026-05-07 12:09:26 +00:00
|
|
|
|
|
2026-05-09 13:08:21 +00:00
|
|
|
|
def is_passable(self) -> bool:
|
2026-05-07 12:09:26 +00:00
|
|
|
|
return not self.is_wall
|
|
|
|
|
|
|
|
|
|
|
|
def __repr__(self) -> str:
|
|
|
|
|
|
return f"Cell({self.x},{self.y})"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Maze:
|
|
|
|
|
|
def __init__(self, width: int, height: int): #что содержит лабиринт, начало конец и тд
|
|
|
|
|
|
self.width = width
|
|
|
|
|
|
self.height = height
|
|
|
|
|
|
self.grid: List[List[Cell]] = []
|
|
|
|
|
|
self.start_cell: Optional[Cell] = None
|
|
|
|
|
|
self.exit_cell: Optional[Cell] = None
|
|
|
|
|
|
|
|
|
|
|
|
def set_cell(self, x: int, y: int, cell: Cell) -> None: #ставим клетку куда надо или не ставим если в границы не попала
|
|
|
|
|
|
if not (0 <= x < self.width and 0 <= y < self.height):
|
|
|
|
|
|
raise IndexError("координаты вне границ лабиринта")
|
|
|
|
|
|
self.grid[y][x] = cell
|
|
|
|
|
|
|
|
|
|
|
|
def get_cell(self, x: int, y: int) -> Optional[Cell]: #тут уже из коррдинат клетку вытаскиваем
|
|
|
|
|
|
if 0 <= x < self.width and 0 <= y < self.height:
|
|
|
|
|
|
return self.grid[y][x]
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
def get_neighbors(self, cell: Cell) -> List[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.ABC):
|
|
|
|
|
|
@abc.abstractmethod
|
|
|
|
|
|
def build_from_file(self, filename: str) -> Maze:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TextFileMazeBuilder(MazeBuilder):
|
|
|
|
|
|
def build_from_file(self, filename: str) -> Maze:
|
|
|
|
|
|
lines = []
|
|
|
|
|
|
with open(filename, 'r', encoding='utf-8') as f:
|
|
|
|
|
|
for line in f:
|
|
|
|
|
|
line = line.rstrip('\n')
|
2026-05-07 12:54:10 +00:00
|
|
|
|
if line: #игнорируем пустые строки
|
2026-05-07 12:09:26 +00:00
|
|
|
|
lines.append(line)
|
|
|
|
|
|
|
|
|
|
|
|
if not lines:
|
|
|
|
|
|
raise ValueError("Файл пуст")
|
|
|
|
|
|
|
|
|
|
|
|
height = len(lines)
|
|
|
|
|
|
width = max(len(line) for line in lines)
|
|
|
|
|
|
|
|
|
|
|
|
maze = Maze(width, height)
|
2026-05-07 12:54:10 +00:00
|
|
|
|
#инициализируем сетку пустыми клетками,по умолчанию стенами
|
2026-05-07 12:09:26 +00:00
|
|
|
|
maze.grid = [[Cell(x, y, is_wall=True) for x in range(width)] for y in range(height)]
|
|
|
|
|
|
|
|
|
|
|
|
start_cell = None
|
|
|
|
|
|
exit_cell = None
|
|
|
|
|
|
|
|
|
|
|
|
for y, line in enumerate(lines):
|
|
|
|
|
|
for x, ch in enumerate(line):
|
|
|
|
|
|
if x >= width:
|
|
|
|
|
|
continue
|
|
|
|
|
|
if ch == '#':
|
|
|
|
|
|
continue
|
|
|
|
|
|
elif ch == ' ':
|
2026-05-07 12:54:10 +00:00
|
|
|
|
cell = Cell(x, y, is_wall=False)
|
2026-05-07 12:09:26 +00:00
|
|
|
|
elif ch == 'S':
|
|
|
|
|
|
cell = Cell(x, y, is_wall=False, is_start=True)
|
|
|
|
|
|
start_cell = cell
|
|
|
|
|
|
elif ch == 'E':
|
|
|
|
|
|
cell = Cell(x, y, is_wall=False, is_exit=True)
|
|
|
|
|
|
exit_cell = cell
|
|
|
|
|
|
else:
|
2026-05-07 12:54:10 +00:00
|
|
|
|
#любой другой символ считаем проходом
|
2026-05-07 12:09:26 +00:00
|
|
|
|
cell = Cell(x, y, is_wall=False)
|
|
|
|
|
|
maze.set_cell(x, y, cell)
|
|
|
|
|
|
|
|
|
|
|
|
if start_cell is None:
|
2026-05-07 12:54:10 +00:00
|
|
|
|
raise ValueError("отсутствует стартовая клетка (S)") #invalid check
|
2026-05-07 12:09:26 +00:00
|
|
|
|
if exit_cell is None:
|
2026-05-07 12:54:10 +00:00
|
|
|
|
raise ValueError("отсутствует выход (E)")
|
2026-05-07 12:09:26 +00:00
|
|
|
|
|
|
|
|
|
|
maze.start_cell = start_cell
|
|
|
|
|
|
maze.exit_cell = exit_cell
|
|
|
|
|
|
return maze
|
|
|
|
|
|
|
|
|
|
|
|
class PathFindingStrategy(abc.ABC):
|
|
|
|
|
|
@abc.abstractmethod
|
|
|
|
|
|
def find_path(self, maze: Maze, start: Cell, exit_: Cell) -> Tuple[List[Cell], int]:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
2026-05-07 12:54:10 +00:00
|
|
|
|
#дальше скорее математика, методы вроде ещё в том семаке разбирали
|
2026-05-07 12:09:26 +00:00
|
|
|
|
class BFSStrategy(PathFindingStrategy):
|
|
|
|
|
|
def find_path(self, maze: Maze, start: Cell, exit_: Cell) -> Tuple[List[Cell], int]:
|
|
|
|
|
|
if start is exit_:
|
|
|
|
|
|
return [start], 1
|
|
|
|
|
|
|
2026-05-07 12:54:10 +00:00
|
|
|
|
queue = deque([start]) #используйте deque 👍
|
2026-05-07 12:09:26 +00:00
|
|
|
|
visited: Set[Cell] = {start}
|
|
|
|
|
|
parent: Dict[Cell, Optional[Cell]] = {start: None}
|
|
|
|
|
|
|
|
|
|
|
|
while queue:
|
|
|
|
|
|
current = queue.popleft()
|
|
|
|
|
|
if current is exit_:
|
2026-05-07 12:54:10 +00:00
|
|
|
|
#восстановление пути
|
2026-05-07 12:09:26 +00:00
|
|
|
|
path = []
|
|
|
|
|
|
cur = current
|
|
|
|
|
|
while cur is not None:
|
|
|
|
|
|
path.append(cur)
|
|
|
|
|
|
cur = parent[cur]
|
|
|
|
|
|
path.reverse()
|
|
|
|
|
|
return path, len(visited)
|
|
|
|
|
|
|
|
|
|
|
|
for neighbor in maze.get_neighbors(current):
|
|
|
|
|
|
if neighbor not in visited:
|
|
|
|
|
|
visited.add(neighbor)
|
|
|
|
|
|
parent[neighbor] = current
|
|
|
|
|
|
queue.append(neighbor)
|
|
|
|
|
|
|
|
|
|
|
|
return [], len(visited)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class DFSStrategy(PathFindingStrategy):
|
|
|
|
|
|
def find_path(self, maze: Maze, start: Cell, exit_: Cell) -> Tuple[List[Cell], int]:
|
|
|
|
|
|
if start is exit_:
|
|
|
|
|
|
return [start], 1
|
|
|
|
|
|
|
|
|
|
|
|
stack = [start]
|
|
|
|
|
|
visited: Set[Cell] = {start}
|
|
|
|
|
|
parent: Dict[Cell, Optional[Cell]] = {start: None}
|
|
|
|
|
|
|
|
|
|
|
|
while stack:
|
|
|
|
|
|
current = stack.pop()
|
|
|
|
|
|
if current is exit_:
|
|
|
|
|
|
path = []
|
|
|
|
|
|
cur = current
|
|
|
|
|
|
while cur is not None:
|
|
|
|
|
|
path.append(cur)
|
|
|
|
|
|
cur = parent[cur]
|
|
|
|
|
|
path.reverse()
|
|
|
|
|
|
return path, len(visited)
|
|
|
|
|
|
|
|
|
|
|
|
for neighbor in maze.get_neighbors(current):
|
|
|
|
|
|
if neighbor not in visited:
|
|
|
|
|
|
visited.add(neighbor)
|
|
|
|
|
|
parent[neighbor] = current
|
|
|
|
|
|
stack.append(neighbor)
|
|
|
|
|
|
|
|
|
|
|
|
return [], len(visited)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AStarStrategy(PathFindingStrategy):
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
|
def _heuristic(cell: Cell, target: Cell) -> int:
|
2026-05-07 12:54:10 +00:00
|
|
|
|
return abs(cell.x - target.x) + abs(cell.y - target.y) #самая простая эвристика
|
2026-05-07 12:09:26 +00:00
|
|
|
|
|
|
|
|
|
|
def find_path(self, maze: Maze, start: Cell, exit_: Cell) -> Tuple[List[Cell], int]:
|
|
|
|
|
|
if start is exit_:
|
|
|
|
|
|
return [start], 1
|
|
|
|
|
|
counter = 0
|
|
|
|
|
|
open_set = [(0, counter, start)]
|
|
|
|
|
|
g_score: Dict[Cell, int] = {start: 0}
|
|
|
|
|
|
f_score: Dict[Cell, int] = {start: self._heuristic(start, exit_)}
|
|
|
|
|
|
parent: Dict[Cell, Optional[Cell]] = {start: None}
|
|
|
|
|
|
closed_set: Set[Cell] = set()
|
|
|
|
|
|
visited_count = 0
|
|
|
|
|
|
|
|
|
|
|
|
while open_set:
|
|
|
|
|
|
_, _, current = heapq.heappop(open_set)
|
|
|
|
|
|
if current in closed_set:
|
|
|
|
|
|
continue
|
|
|
|
|
|
closed_set.add(current)
|
2026-05-07 12:54:10 +00:00
|
|
|
|
visited_count = len(closed_set)
|
2026-05-07 12:09:26 +00:00
|
|
|
|
|
|
|
|
|
|
if current is exit_:
|
|
|
|
|
|
path = []
|
|
|
|
|
|
cur = current
|
|
|
|
|
|
while cur is not None:
|
|
|
|
|
|
path.append(cur)
|
|
|
|
|
|
cur = parent[cur]
|
|
|
|
|
|
path.reverse()
|
|
|
|
|
|
return path, visited_count
|
|
|
|
|
|
|
|
|
|
|
|
for neighbor in maze.get_neighbors(current):
|
|
|
|
|
|
if neighbor in closed_set:
|
|
|
|
|
|
continue
|
|
|
|
|
|
tentative_g = g_score[current] + 1
|
|
|
|
|
|
if neighbor not in g_score or tentative_g < g_score[neighbor]:
|
|
|
|
|
|
parent[neighbor] = current
|
|
|
|
|
|
g_score[neighbor] = tentative_g
|
|
|
|
|
|
f = tentative_g + self._heuristic(neighbor, exit_)
|
|
|
|
|
|
f_score[neighbor] = f
|
|
|
|
|
|
counter += 1
|
|
|
|
|
|
heapq.heappush(open_set, (f, counter, neighbor))
|
|
|
|
|
|
|
|
|
|
|
|
return [], visited_count
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
|
|
class SearchStats:
|
2026-05-07 12:54:10 +00:00
|
|
|
|
time_ms: float #время выполнения в мс
|
|
|
|
|
|
visited_cells: int #количество посещённых клеток
|
|
|
|
|
|
path_length: int #длина найденного пути (0 если пути нет)
|
|
|
|
|
|
path_found: bool #найден ли путь
|
2026-05-07 12:09:26 +00:00
|
|
|
|
|
2026-05-07 12:54:10 +00:00
|
|
|
|
class Observer(abc.ABC): #я забыл что я там писать хотел после наблюдателя удачи мне завтра разобрать
|
|
|
|
|
|
@abc.abstractmethod #а я (гугл + хабр) разобрал снова балбесина!!!
|
2026-05-07 12:09:26 +00:00
|
|
|
|
def update(self, event_type: str, data: Any = None) -> None:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Subject:
|
|
|
|
|
|
def __init__(self):
|
|
|
|
|
|
self._observers: List[Observer] = []
|
|
|
|
|
|
|
|
|
|
|
|
def attach(self, observer: Observer) -> None:
|
|
|
|
|
|
if observer not in self._observers:
|
|
|
|
|
|
self._observers.append(observer)
|
|
|
|
|
|
|
|
|
|
|
|
def detach(self, observer: Observer) -> None:
|
|
|
|
|
|
if observer in self._observers:
|
|
|
|
|
|
self._observers.remove(observer)
|
|
|
|
|
|
|
|
|
|
|
|
def notify(self, event_type: str, data: Any = None) -> None:
|
2026-05-09 13:08:21 +00:00
|
|
|
|
for obs in self._observers:
|
|
|
|
|
|
obs.update(event_type, data)
|
|
|
|
|
|
|
2026-05-07 12:09:26 +00:00
|
|
|
|
|
|
|
|
|
|
class MazeSolver(Subject):
|
|
|
|
|
|
def __init__(self, maze: Maze, strategy: Optional[PathFindingStrategy] = None):
|
|
|
|
|
|
super().__init__()
|
|
|
|
|
|
self.maze = maze
|
|
|
|
|
|
self._strategy = strategy
|
2026-05-09 13:08:21 +00:00
|
|
|
|
|
|
|
|
|
|
def set_strategy(self, strategy: PathFindingStrategy) -> None:
|
2026-05-07 12:09:26 +00:00
|
|
|
|
self._strategy = strategy
|
2026-05-09 13:08:21 +00:00
|
|
|
|
|
2026-05-07 12:09:26 +00:00
|
|
|
|
def solve(self) -> Optional[SearchStats]:
|
|
|
|
|
|
if self._strategy is None:
|
|
|
|
|
|
return None
|
|
|
|
|
|
start_time = time.perf_counter()
|
|
|
|
|
|
path, visited = self._strategy.find_path(self.maze, self.maze.start_cell, self.maze.exit_cell)
|
|
|
|
|
|
end_time = time.perf_counter()
|
|
|
|
|
|
time_ms = (end_time - start_time) * 1000.0
|
2026-05-09 13:08:21 +00:00
|
|
|
|
return SearchStats(time_ms, visited, len(path), len(path) > 0)
|
2026-05-07 12:09:26 +00:00
|
|
|
|
|
2026-05-09 13:08:21 +00:00
|
|
|
|
class Benchmark:
|
|
|
|
|
|
def __init__(self, maze_files: List[str], runs_per_strategy: int = 5):
|
|
|
|
|
|
self.maze_files = maze_files
|
|
|
|
|
|
self.runs = runs_per_strategy
|
|
|
|
|
|
self.strategies = {
|
|
|
|
|
|
"BFS": BFSStrategy(),
|
|
|
|
|
|
"DFS": DFSStrategy(),
|
|
|
|
|
|
"AStar": AStarStrategy()
|
|
|
|
|
|
}
|
|
|
|
|
|
self.builder = TextFileMazeBuilder()
|
|
|
|
|
|
self.results = []
|
|
|
|
|
|
|
|
|
|
|
|
def run(self, output_csv: str):
|
|
|
|
|
|
for maze_file in self.maze_files:
|
|
|
|
|
|
if not os.path.exists(maze_file):
|
|
|
|
|
|
print(f"файл {maze_file} не найден")
|
|
|
|
|
|
continue
|
|
|
|
|
|
try:
|
|
|
|
|
|
maze = self.builder.build_from_file(maze_file)
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
print(f"ошибка загрузки {maze_file}: {e}")
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
|
|
print(f"обработка лабиринта: {maze_file} (размер {maze.width}x{maze.height})")
|
|
|
|
|
|
for strat_name, strategy in self.strategies.items():
|
|
|
|
|
|
solver = MazeSolver(maze, strategy)
|
|
|
|
|
|
times = []
|
|
|
|
|
|
visited_list = []
|
|
|
|
|
|
path_lengths = []
|
|
|
|
|
|
path_found = False
|
|
|
|
|
|
for run_idx in range(self.runs):
|
|
|
|
|
|
stats = solver.solve()
|
|
|
|
|
|
if stats is None:
|
|
|
|
|
|
continue
|
|
|
|
|
|
times.append(stats.time_ms)
|
|
|
|
|
|
visited_list.append(stats.visited_cells)
|
|
|
|
|
|
path_lengths.append(stats.path_length)
|
|
|
|
|
|
path_found = stats.path_found
|
|
|
|
|
|
if times:
|
|
|
|
|
|
avg_time = sum(times) / len(times)
|
|
|
|
|
|
avg_visited = sum(visited_list) / len(visited_list)
|
|
|
|
|
|
avg_length = sum(path_lengths) / len(path_lengths)
|
2026-05-07 12:09:26 +00:00
|
|
|
|
else:
|
2026-05-09 13:08:21 +00:00
|
|
|
|
avg_time = avg_visited = avg_length = 0.0
|
|
|
|
|
|
self.results.append({
|
|
|
|
|
|
"лабиринт": os.path.basename(maze_file),
|
|
|
|
|
|
"стратегия": strat_name,
|
|
|
|
|
|
"время_мс": round(avg_time, 3),
|
|
|
|
|
|
"посещено_клеток": round(avg_visited, 1),
|
|
|
|
|
|
"длина_пути": round(avg_length, 1),
|
|
|
|
|
|
"путь_найден": path_found
|
|
|
|
|
|
})
|
|
|
|
|
|
print(f" {strat_name}: {avg_time:.3f} мс, посещено {avg_visited:.1f}, длина {avg_length:.1f}")
|
2026-05-07 12:09:26 +00:00
|
|
|
|
|
2026-05-09 13:08:21 +00:00
|
|
|
|
with open(output_csv, 'w', newline='', encoding='utf-8') as csvfile:
|
|
|
|
|
|
fieldnames = ["лабиринт", "стратегия", "время_мс", "посещено_клеток", "длина_пути", "путь_найден"]
|
|
|
|
|
|
writer = csv.DictWriter(csvfile, fieldnames=fieldnames, delimiter=',')
|
|
|
|
|
|
writer.writeheader()
|
|
|
|
|
|
for row in self.results:
|
|
|
|
|
|
writer.writerow(row)
|
|
|
|
|
|
print(f"\nрезультаты сохранены в {output_csv}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ----------------------------- Консольный режим (для одного лабиринта, с визуализацией) -----------------------------
|
|
|
|
|
|
def interactive_mode(maze_file: str):
|
2026-05-07 12:09:26 +00:00
|
|
|
|
builder = TextFileMazeBuilder()
|
|
|
|
|
|
try:
|
2026-05-09 13:08:21 +00:00
|
|
|
|
maze = builder.build_from_file(maze_file)
|
2026-05-07 12:09:26 +00:00
|
|
|
|
except Exception as e:
|
2026-05-07 12:54:10 +00:00
|
|
|
|
print(f"ошибка загрузки лабиринта: {e}")
|
2026-05-07 12:09:26 +00:00
|
|
|
|
return
|
2026-05-09 13:08:21 +00:00
|
|
|
|
|
2026-05-07 12:09:26 +00:00
|
|
|
|
strategies = {
|
2026-05-09 13:08:21 +00:00
|
|
|
|
"1": ("BFS", BFSStrategy()),
|
|
|
|
|
|
"2": ("DFS", DFSStrategy()),
|
|
|
|
|
|
"3": ("A*", AStarStrategy())
|
2026-05-07 12:09:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
print("\nвыберите алгоритм поиска:")
|
|
|
|
|
|
print("1. BFS")
|
|
|
|
|
|
print("2. DFS")
|
|
|
|
|
|
print("3. A*")
|
|
|
|
|
|
choice = input("введите (1/2/3): ").strip()
|
2026-05-09 13:08:21 +00:00
|
|
|
|
|
|
|
|
|
|
if choice not in strategies:
|
2026-05-07 12:09:26 +00:00
|
|
|
|
print("неверный выбор, по умолчанию используется BFS.")
|
2026-05-09 13:08:21 +00:00
|
|
|
|
strat_name, strategy = strategies["1"]
|
|
|
|
|
|
else:
|
|
|
|
|
|
strat_name, strategy = strategies[choice]
|
|
|
|
|
|
|
|
|
|
|
|
solver = MazeSolver(maze, strategy)
|
2026-05-07 12:09:26 +00:00
|
|
|
|
stats = solver.solve()
|
2026-05-09 13:08:21 +00:00
|
|
|
|
if stats is None:
|
|
|
|
|
|
print("ошибка с решением")
|
|
|
|
|
|
return
|
2026-05-07 12:09:26 +00:00
|
|
|
|
|
2026-05-09 13:08:21 +00:00
|
|
|
|
path, _ = strategy.find_path(maze, maze.start_cell, maze.exit_cell)
|
|
|
|
|
|
path_set = set(path)
|
|
|
|
|
|
for y in range(maze.height):
|
|
|
|
|
|
row = []
|
|
|
|
|
|
for x in range(maze.width):
|
|
|
|
|
|
cell = maze.get_cell(x, y)
|
|
|
|
|
|
if cell is maze.start_cell:
|
|
|
|
|
|
row.append('S')
|
|
|
|
|
|
elif cell is maze.exit_cell:
|
|
|
|
|
|
row.append('E')
|
|
|
|
|
|
elif cell in path_set:
|
|
|
|
|
|
row.append('*')
|
|
|
|
|
|
elif cell and cell.is_wall:
|
|
|
|
|
|
row.append('#')
|
|
|
|
|
|
else:
|
|
|
|
|
|
row.append(' ')
|
|
|
|
|
|
print(''.join(row))
|
|
|
|
|
|
|
|
|
|
|
|
print(f"\nстатистика ({strat_name}):")
|
|
|
|
|
|
print(f"время выполнения: {stats.time_ms:.3f} мс")
|
|
|
|
|
|
print(f"посещено клеток: {stats.visited_cells}")
|
|
|
|
|
|
print(f"длина пути: {stats.path_length}")
|
|
|
|
|
|
print(f"путь найден: {'да' if stats.path_found else 'нет'}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
|
|
if len(sys.argv) < 2:
|
|
|
|
|
|
print("использование:")
|
|
|
|
|
|
print("режим визуализации: python main.py <файл_лабиринта>")
|
|
|
|
|
|
print("режим замера: python main.py --benchmark <список_лабиринтов> --runs <кол_во_итераций> --output <название_таблицы>.csv")
|
|
|
|
|
|
return
|
2026-05-07 12:09:26 +00:00
|
|
|
|
|
2026-05-09 13:08:21 +00:00
|
|
|
|
if sys.argv[1] == "--benchmark":
|
|
|
|
|
|
args = sys.argv[2:]
|
|
|
|
|
|
maze_files = []
|
|
|
|
|
|
runs = 5
|
|
|
|
|
|
output = "benchmark_results.csv"
|
|
|
|
|
|
i = 0
|
|
|
|
|
|
while i < len(args):
|
|
|
|
|
|
if args[i] == "--runs" and i+1 < len(args):
|
|
|
|
|
|
runs = int(args[i+1])
|
|
|
|
|
|
i += 2
|
|
|
|
|
|
elif args[i] == "--output" and i+1 < len(args):
|
|
|
|
|
|
output = args[i+1]
|
|
|
|
|
|
i += 2
|
|
|
|
|
|
else:
|
|
|
|
|
|
maze_files.append(args[i])
|
|
|
|
|
|
i += 1
|
|
|
|
|
|
if not maze_files:
|
|
|
|
|
|
print("Ошибка: не указаны файлы лабиринтов.")
|
|
|
|
|
|
return
|
|
|
|
|
|
benchmark = Benchmark(maze_files, runs_per_strategy=runs)
|
|
|
|
|
|
benchmark.run(output)
|
|
|
|
|
|
else:
|
|
|
|
|
|
interactive_mode(sys.argv[1])
|
2026-05-07 12:09:26 +00:00
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
|
main()
|