forked from UNN/2026-rff_mp
Compare commits
No commits in common. "ezhovnd-patch-2" and "develop" have entirely different histories.
ezhovnd-pa
...
develop
|
|
@ -1,825 +0,0 @@
|
|||
"""
|
||||
maze_all.py — Полный проект лабиринта со всеми паттернами:
|
||||
Builder, Strategy, Observer, Command.
|
||||
|
||||
Содержит все модули: model, builder, strategy, solver, generator, experiment, main.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
import abc
|
||||
import csv
|
||||
import heapq
|
||||
import json
|
||||
import os
|
||||
import random
|
||||
import sys
|
||||
import time
|
||||
from collections import deque
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional
|
||||
|
||||
# ============================================================================
|
||||
# model.py — Модель лабиринта (классы Cell и Maze)
|
||||
# ============================================================================
|
||||
|
||||
class Cell:
|
||||
"""Одна клетка лабиринта."""
|
||||
|
||||
WALL = '#'
|
||||
PASS = ' '
|
||||
START = 'S'
|
||||
EXIT = 'E'
|
||||
SWAMP = 'W' # болото — вес 3
|
||||
SAND = 'N' # песок — вес 2
|
||||
ROAD = 'R' # асфальт — вес 1 (то же что PASS)
|
||||
|
||||
WEIGHTS = {WALL: 0, PASS: 1, ROAD: 1, START: 1, EXIT: 1, SAND: 2, SWAMP: 3}
|
||||
|
||||
def __init__(self, x: int, y: int, symbol: str = PASS):
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.symbol = symbol
|
||||
|
||||
@property
|
||||
def is_wall(self) -> bool:
|
||||
return self.symbol == self.WALL
|
||||
|
||||
@property
|
||||
def is_start(self) -> bool:
|
||||
return self.symbol == self.START
|
||||
|
||||
@property
|
||||
def is_exit(self) -> bool:
|
||||
return self.symbol == self.EXIT
|
||||
|
||||
@property
|
||||
def weight(self) -> int:
|
||||
return self.WEIGHTS.get(self.symbol, 1)
|
||||
|
||||
def is_passable(self) -> bool:
|
||||
return not self.is_wall
|
||||
|
||||
def __repr__(self):
|
||||
return f"Cell({self.x},{self.y},'{self.symbol}')"
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.x, self.y))
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, Cell) and self.x == other.x and self.y == other.y
|
||||
|
||||
|
||||
class Maze:
|
||||
"""Двумерный массив клеток + метаданные."""
|
||||
|
||||
def __init__(self, width: int, height: int, cells: list[list[Cell]],
|
||||
start: Optional[Cell] = None, exit_: Optional[Cell] = None):
|
||||
self.width = width
|
||||
self.height = height
|
||||
self._cells = cells # cells[y][x]
|
||||
self.start = start
|
||||
self.exit = exit_
|
||||
|
||||
def get_cell(self, x: int, y: int) -> Cell:
|
||||
return self._cells[y][x]
|
||||
|
||||
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
|
||||
if 0 <= nx < self.width and 0 <= ny < self.height:
|
||||
nb = self._cells[ny][nx]
|
||||
if nb.is_passable():
|
||||
neighbors.append(nb)
|
||||
return neighbors
|
||||
|
||||
def render(self, path: set = None, visited: set = None,
|
||||
player_pos: Optional[Cell] = None) -> str:
|
||||
"""Возвращает строковое представление для консоли."""
|
||||
path = path or set()
|
||||
visited = visited or set()
|
||||
lines = []
|
||||
for y in range(self.height):
|
||||
row = []
|
||||
for x in range(self.width):
|
||||
cell = self._cells[y][x]
|
||||
if player_pos and cell == player_pos:
|
||||
row.append('@')
|
||||
elif cell in path and not cell.is_start and not cell.is_exit:
|
||||
row.append('·')
|
||||
elif cell in visited and not cell.is_start and not cell.is_exit:
|
||||
row.append('░')
|
||||
else:
|
||||
row.append(cell.symbol if cell.symbol != ' ' else '.')
|
||||
lines.append(''.join(row))
|
||||
return '\n'.join(lines)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# builder.py — Паттерн Builder
|
||||
# ============================================================================
|
||||
|
||||
class MazeBuilder(abc.ABC):
|
||||
"""Интерфейс строителя лабиринта."""
|
||||
|
||||
@abc.abstractmethod
|
||||
def build_from_file(self, filename: str) -> Maze:
|
||||
...
|
||||
|
||||
@abc.abstractmethod
|
||||
def build_empty(self, width: int, height: int) -> Maze:
|
||||
"""Создать лабиринт без стен."""
|
||||
...
|
||||
|
||||
|
||||
class TextFileMazeBuilder(MazeBuilder):
|
||||
"""
|
||||
Строитель для текстовых файлов:
|
||||
# — стена
|
||||
' ' или '.' — проход (вес 1)
|
||||
S — старт
|
||||
E — выход
|
||||
W — болото (вес 3)
|
||||
N — песок (вес 2)
|
||||
R — асфальт (вес 1)
|
||||
"""
|
||||
|
||||
def build_from_file(self, filename: str) -> Maze:
|
||||
with open(filename, encoding='utf-8') as f:
|
||||
lines = f.read().splitlines()
|
||||
return self._parse_lines(lines)
|
||||
|
||||
def build_from_string(self, text: str) -> Maze:
|
||||
return self._parse_lines(text.splitlines())
|
||||
|
||||
def _parse_lines(self, lines: list[str]) -> Maze:
|
||||
if not lines:
|
||||
raise ValueError("Файл лабиринта пуст")
|
||||
|
||||
height = len(lines)
|
||||
width = max(len(line) for line in lines)
|
||||
|
||||
cells: list[list[Cell]] = []
|
||||
start = exit_ = None
|
||||
|
||||
for y, line in enumerate(lines):
|
||||
row = []
|
||||
for x in range(width):
|
||||
ch = line[x] if x < len(line) else ' '
|
||||
if ch == '.':
|
||||
ch = ' ' # нормализация
|
||||
cell = Cell(x, y, ch)
|
||||
if cell.is_start:
|
||||
start = cell
|
||||
if cell.is_exit:
|
||||
exit_ = cell
|
||||
row.append(cell)
|
||||
cells.append(row)
|
||||
|
||||
if start is None:
|
||||
raise ValueError("В лабиринте не найдена клетка 'S' (старт)")
|
||||
if exit_ is None:
|
||||
raise ValueError("В лабиринте не найдена клетка 'E' (выход)")
|
||||
|
||||
return Maze(width, height, cells, start, exit_)
|
||||
|
||||
def build_empty(self, width: int, height: int) -> Maze:
|
||||
"""Лабиринт без стен: рамка из стен, внутри проходы."""
|
||||
cells = []
|
||||
start = exit_ = None
|
||||
for y in range(height):
|
||||
row = []
|
||||
for x in range(width):
|
||||
if x == 0 or x == width - 1 or y == 0 or y == height - 1:
|
||||
ch = Cell.WALL
|
||||
else:
|
||||
ch = Cell.PASS
|
||||
cell = Cell(x, y, ch)
|
||||
row.append(cell)
|
||||
cells.append(row)
|
||||
# Старт и выход
|
||||
cells[1][1].symbol = Cell.START
|
||||
start = cells[1][1]
|
||||
cells[height - 2][width - 2].symbol = Cell.EXIT
|
||||
exit_ = cells[height - 2][width - 2]
|
||||
return Maze(width, height, cells, start, exit_)
|
||||
|
||||
|
||||
class JsonMazeBuilder(MazeBuilder):
|
||||
"""
|
||||
Альтернативный строитель — JSON-формат.
|
||||
Демонстрирует расширяемость паттерна Builder без изменения клиента.
|
||||
|
||||
Формат:
|
||||
{
|
||||
"width": 5, "height": 5,
|
||||
"cells": ["#####", "#S E#", "#####"]
|
||||
}
|
||||
"""
|
||||
|
||||
def build_from_file(self, filename: str) -> Maze:
|
||||
with open(filename, encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
lines = data['cells']
|
||||
text_builder = TextFileMazeBuilder()
|
||||
return text_builder._parse_lines(lines)
|
||||
|
||||
def build_empty(self, width: int, height: int) -> Maze:
|
||||
return TextFileMazeBuilder().build_empty(width, height)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# strategy.py — Паттерн Strategy (алгоритмы поиска пути)
|
||||
# ============================================================================
|
||||
|
||||
class PathFindingStrategy(abc.ABC):
|
||||
"""Интерфейс стратегии поиска пути."""
|
||||
|
||||
@abc.abstractmethod
|
||||
def find_path(self, maze: Maze, start: Cell, exit_: Cell
|
||||
) -> tuple[list[Cell], int]:
|
||||
"""
|
||||
Возвращает (path, visited_count).
|
||||
path — список клеток от start до exit_ включительно,
|
||||
или [] если пути нет.
|
||||
visited_count — количество клеток, посещённых во время поиска.
|
||||
"""
|
||||
...
|
||||
|
||||
def name(self) -> str:
|
||||
return self.__class__.__name__
|
||||
|
||||
|
||||
def _reconstruct(parent: dict, end: Cell) -> list[Cell]:
|
||||
"""Восстановить путь по словарю parent."""
|
||||
path, node = [], end
|
||||
while node is not None:
|
||||
path.append(node)
|
||||
node = parent.get(node)
|
||||
path.reverse()
|
||||
return path
|
||||
|
||||
|
||||
class BFSStrategy(PathFindingStrategy):
|
||||
"""Поиск в ширину — гарантирует кратчайший путь (по количеству шагов)."""
|
||||
|
||||
def find_path(self, maze: Maze, start: Cell, exit_: Cell):
|
||||
queue = deque([start])
|
||||
parent = {start: None}
|
||||
visited = 0
|
||||
|
||||
while queue:
|
||||
cell = queue.popleft()
|
||||
visited += 1
|
||||
if cell == exit_:
|
||||
return _reconstruct(parent, exit_), visited
|
||||
for nb in maze.get_neighbors(cell):
|
||||
if nb not in parent:
|
||||
parent[nb] = cell
|
||||
queue.append(nb)
|
||||
|
||||
return [], visited
|
||||
|
||||
def name(self): return "BFS"
|
||||
|
||||
|
||||
class DFSStrategy(PathFindingStrategy):
|
||||
"""Поиск в глубину — быстрый, но путь не оптимален."""
|
||||
|
||||
def find_path(self, maze: Maze, start: Cell, exit_: Cell):
|
||||
stack = [start]
|
||||
parent = {start: None}
|
||||
visited = 0
|
||||
|
||||
while stack:
|
||||
cell = stack.pop()
|
||||
visited += 1
|
||||
if cell == exit_:
|
||||
return _reconstruct(parent, exit_), visited
|
||||
for nb in maze.get_neighbors(cell):
|
||||
if nb not in parent:
|
||||
parent[nb] = cell
|
||||
stack.append(nb)
|
||||
|
||||
return [], visited
|
||||
|
||||
def name(self): return "DFS"
|
||||
|
||||
|
||||
class AStarStrategy(PathFindingStrategy):
|
||||
"""
|
||||
A* с манхэттенской эвристикой — оптимальный и быстрый.
|
||||
Учитывает веса клеток (болото, песок и т.д.).
|
||||
"""
|
||||
|
||||
def _h(self, cell: Cell, goal: Cell) -> int:
|
||||
return abs(cell.x - goal.x) + abs(cell.y - goal.y)
|
||||
|
||||
def find_path(self, maze: Maze, start: Cell, exit_: Cell):
|
||||
# (f, g, cell)
|
||||
heap = [(self._h(start, exit_), 0, id(start), start)]
|
||||
g_score = {start: 0}
|
||||
parent = {start: None}
|
||||
visited = 0
|
||||
|
||||
while heap:
|
||||
f, g, _, cell = heapq.heappop(heap)
|
||||
visited += 1
|
||||
if cell == exit_:
|
||||
return _reconstruct(parent, exit_), visited
|
||||
if g > g_score.get(cell, float('inf')):
|
||||
continue # устаревшая запись
|
||||
for nb in maze.get_neighbors(cell):
|
||||
new_g = g + nb.weight
|
||||
if new_g < g_score.get(nb, float('inf')):
|
||||
g_score[nb] = new_g
|
||||
parent[nb] = cell
|
||||
f_val = new_g + self._h(nb, exit_)
|
||||
heapq.heappush(heap, (f_val, new_g, id(nb), nb))
|
||||
|
||||
return [], visited
|
||||
|
||||
def name(self): return "A*"
|
||||
|
||||
|
||||
class DijkstraStrategy(PathFindingStrategy):
|
||||
"""
|
||||
Алгоритм Дейкстры — оптимален для взвешенных клеток.
|
||||
На равновесных лабиринтах совпадает с BFS.
|
||||
"""
|
||||
|
||||
def find_path(self, maze: Maze, start: Cell, exit_: Cell):
|
||||
heap = [(0, id(start), start)]
|
||||
dist = {start: 0}
|
||||
parent = {start: None}
|
||||
visited = 0
|
||||
|
||||
while heap:
|
||||
d, _, cell = heapq.heappop(heap)
|
||||
visited += 1
|
||||
if cell == exit_:
|
||||
return _reconstruct(parent, exit_), visited
|
||||
if d > dist.get(cell, float('inf')):
|
||||
continue
|
||||
for nb in maze.get_neighbors(cell):
|
||||
new_d = d + nb.weight
|
||||
if new_d < dist.get(nb, float('inf')):
|
||||
dist[nb] = new_d
|
||||
parent[nb] = cell
|
||||
heapq.heappush(heap, (new_d, id(nb), nb))
|
||||
|
||||
return [], visited
|
||||
|
||||
def name(self): return "Dijkstra"
|
||||
|
||||
|
||||
# Реестр всех стратегий (используется в экспериментальной части)
|
||||
ALL_STRATEGIES = [BFSStrategy(), DFSStrategy(), AStarStrategy(), DijkstraStrategy()]
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# solver.py — Паттерны Observer и Command, класс MazeSolver
|
||||
# ============================================================================
|
||||
|
||||
# ─────────────────────────────────────────────
|
||||
# Observer
|
||||
# ─────────────────────────────────────────────
|
||||
|
||||
class Observer(abc.ABC):
|
||||
@abc.abstractmethod
|
||||
def update(self, event: str, payload: dict) -> None: ...
|
||||
|
||||
|
||||
class Subject:
|
||||
"""Миксин: любой класс, у которого есть наблюдатели."""
|
||||
def __init__(self):
|
||||
self._observers: list[Observer] = []
|
||||
|
||||
def add_observer(self, obs: Observer):
|
||||
self._observers.append(obs)
|
||||
|
||||
def remove_observer(self, obs: Observer):
|
||||
self._observers.remove(obs)
|
||||
|
||||
def notify(self, event: str, payload: dict = None):
|
||||
for obs in self._observers:
|
||||
obs.update(event, payload or {})
|
||||
|
||||
|
||||
class ConsoleView(Observer):
|
||||
"""
|
||||
Консольный наблюдатель: печатает события и рендерит лабиринт.
|
||||
"""
|
||||
def update(self, event: str, payload: dict) -> None:
|
||||
if event == 'maze_loaded':
|
||||
maze: Maze = payload['maze']
|
||||
print(f"\n[ConsoleView] Лабиринт загружен ({maze.width}×{maze.height})")
|
||||
print(maze.render())
|
||||
|
||||
elif event == 'search_started':
|
||||
print(f"\n[ConsoleView] Начат поиск алгоритмом: {payload['strategy']}")
|
||||
|
||||
elif event == 'path_found':
|
||||
maze: Maze = payload['maze']
|
||||
path: list[Cell] = payload['path']
|
||||
stats = payload['stats']
|
||||
path_set = set(path)
|
||||
print(f"\n[ConsoleView] Путь найден! Длина: {stats.path_length}, "
|
||||
f"посещено: {stats.visited_cells}, "
|
||||
f"время: {stats.elapsed_ms:.2f} мс")
|
||||
print(maze.render(path=path_set))
|
||||
|
||||
elif event == 'no_path':
|
||||
print("\n[ConsoleView] Путь не найден.")
|
||||
|
||||
elif event == 'player_moved':
|
||||
maze: Maze = payload['maze']
|
||||
player = payload['player']
|
||||
path_set = set(payload.get('path', []))
|
||||
print(f"\n[ConsoleView] Игрок переместился → ({player.position.x}, {player.position.y})")
|
||||
print(maze.render(path=path_set, player_pos=player.position))
|
||||
|
||||
elif event == 'undo':
|
||||
print(f"[ConsoleView] Отмена хода. Игрок → ({payload['position'].x}, {payload['position'].y})")
|
||||
|
||||
|
||||
class FileLogObserver(Observer):
|
||||
"""Наблюдатель-логгер: записывает события в файл."""
|
||||
def __init__(self, filepath: str):
|
||||
self._path = filepath
|
||||
|
||||
def update(self, event: str, payload: dict) -> None:
|
||||
with open(self._path, 'a', encoding='utf-8') as f:
|
||||
f.write(f"[{event}] {payload.get('strategy','')}"
|
||||
f" visited={payload.get('stats', None) and payload['stats'].visited_cells}\n")
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────
|
||||
# SearchStats
|
||||
# ─────────────────────────────────────────────
|
||||
|
||||
@dataclass
|
||||
class SearchStats:
|
||||
strategy_name: str
|
||||
elapsed_ms: float
|
||||
visited_cells: int
|
||||
path_length: int
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────
|
||||
# MazeSolver — оркестратор
|
||||
# ─────────────────────────────────────────────
|
||||
|
||||
class MazeSolver(Subject):
|
||||
"""
|
||||
Принимает лабиринт и стратегию, выполняет поиск, собирает статистику,
|
||||
уведомляет наблюдателей.
|
||||
"""
|
||||
def __init__(self, maze: Maze, strategy: PathFindingStrategy):
|
||||
super().__init__()
|
||||
self._maze = maze
|
||||
self._strategy = strategy
|
||||
|
||||
def set_strategy(self, strategy: PathFindingStrategy):
|
||||
self._strategy = strategy
|
||||
|
||||
def load_maze(self, maze: Maze):
|
||||
self._maze = maze
|
||||
self.notify('maze_loaded', {'maze': maze})
|
||||
|
||||
def solve(self) -> tuple[SearchStats, list[Cell]]:
|
||||
self.notify('search_started', {'strategy': self._strategy.name()})
|
||||
|
||||
t0 = time.perf_counter()
|
||||
path, visited = self._strategy.find_path(
|
||||
self._maze, self._maze.start, self._maze.exit)
|
||||
elapsed_ms = (time.perf_counter() - t0) * 1000
|
||||
|
||||
stats = SearchStats(
|
||||
strategy_name=self._strategy.name(),
|
||||
elapsed_ms=elapsed_ms,
|
||||
visited_cells=visited,
|
||||
path_length=len(path),
|
||||
)
|
||||
|
||||
if path:
|
||||
self.notify('path_found', {'maze': self._maze, 'path': path, 'stats': stats})
|
||||
else:
|
||||
self.notify('no_path', {'maze': self._maze, 'stats': stats})
|
||||
|
||||
return stats, path
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────
|
||||
# Command
|
||||
# ─────────────────────────────────────────────
|
||||
|
||||
class Command(abc.ABC):
|
||||
@abc.abstractmethod
|
||||
def execute(self) -> None: ...
|
||||
@abc.abstractmethod
|
||||
def undo(self) -> None: ...
|
||||
|
||||
|
||||
class Player:
|
||||
"""Хранит текущую позицию игрока."""
|
||||
def __init__(self, position: Cell):
|
||||
self.position = position
|
||||
|
||||
|
||||
class MoveCommand(Command):
|
||||
"""
|
||||
Перемещение игрока в клетку target.
|
||||
Undo возвращает на previous_position.
|
||||
"""
|
||||
def __init__(self, player: Player, target: Cell,
|
||||
solver: 'MazeSolver', path: list[Cell] = None):
|
||||
self._player = player
|
||||
self._target = target
|
||||
self._previous = player.position
|
||||
self._solver = solver
|
||||
self._path = path or []
|
||||
|
||||
def execute(self):
|
||||
self._player.position = self._target
|
||||
self._solver.notify('player_moved', {
|
||||
'maze': self._solver._maze,
|
||||
'player': self._player,
|
||||
'path': self._path,
|
||||
})
|
||||
|
||||
def undo(self):
|
||||
self._player.position = self._previous
|
||||
self._solver.notify('undo', {'position': self._previous})
|
||||
|
||||
|
||||
class CommandHistory:
|
||||
"""Стек выполненных команд для поддержки undo."""
|
||||
def __init__(self):
|
||||
self._stack: list[Command] = []
|
||||
|
||||
def execute(self, cmd: Command):
|
||||
cmd.execute()
|
||||
self._stack.append(cmd)
|
||||
|
||||
def undo(self):
|
||||
if self._stack:
|
||||
self._stack.pop().undo()
|
||||
else:
|
||||
print("[CommandHistory] Нечего отменять.")
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# generator.py — Генерация тестовых лабиринтов
|
||||
# ============================================================================
|
||||
|
||||
def generate_maze(width: int, height: int, seed: int = 42,
|
||||
weighted: bool = False) -> Maze:
|
||||
"""
|
||||
Генерирует лабиринт алгоритмом DFS (Recursive Backtracker).
|
||||
width/height — размеры (лучше нечётные для красивого рисунка).
|
||||
"""
|
||||
rng = random.Random(seed)
|
||||
|
||||
# Начальный массив — всё стены
|
||||
cells = [[Cell(x, y, Cell.WALL) for x in range(width)]
|
||||
for y in range(height)]
|
||||
|
||||
def carve(x, y):
|
||||
cells[y][x].symbol = Cell.PASS
|
||||
directions = [(0, 2), (0, -2), (2, 0), (-2, 0)]
|
||||
rng.shuffle(directions)
|
||||
for dx, dy in directions:
|
||||
nx, ny = x + dx, y + dy
|
||||
if 0 <= nx < width and 0 <= ny < height:
|
||||
if cells[ny][nx].symbol == Cell.WALL:
|
||||
# Убрать стену между (x,y) и (nx,ny)
|
||||
cells[y + dy // 2][x + dx // 2].symbol = Cell.PASS
|
||||
carve(nx, ny)
|
||||
|
||||
sys.setrecursionlimit(width * height + 100)
|
||||
carve(1, 1)
|
||||
|
||||
# Старт и выход
|
||||
cells[1][1].symbol = Cell.START
|
||||
cells[height - 2][width - 2].symbol = Cell.EXIT
|
||||
start = cells[1][1]
|
||||
exit_ = cells[height - 2][width - 2]
|
||||
|
||||
# Взвешенные клетки (болото, песок)
|
||||
if weighted:
|
||||
for y in range(height):
|
||||
for x in range(width):
|
||||
if cells[y][x].symbol == Cell.PASS:
|
||||
r = rng.random()
|
||||
if r < 0.05:
|
||||
cells[y][x].symbol = Cell.SWAMP
|
||||
elif r < 0.15:
|
||||
cells[y][x].symbol = Cell.SAND
|
||||
|
||||
return Maze(width, height, cells, start, exit_)
|
||||
|
||||
|
||||
def generate_no_exit(width: int, height: int, seed: int = 42) -> Maze:
|
||||
"""Лабиринт без выхода — выход заблокирован стенами."""
|
||||
maze = generate_maze(width, height, seed)
|
||||
ex = maze.exit
|
||||
# Окружить выход стенами
|
||||
for dx, dy in ((0,-1),(0,1),(-1,0),(1,0)):
|
||||
nx, ny = ex.x + dx, ex.y + dy
|
||||
if 0 <= nx < maze.width and 0 <= ny < maze.height:
|
||||
maze._cells[ny][nx].symbol = Cell.WALL
|
||||
return maze
|
||||
|
||||
|
||||
def save_maze(maze: Maze, filepath: str):
|
||||
"""Сохранить лабиринт в текстовый файл."""
|
||||
lines = []
|
||||
for y in range(maze.height):
|
||||
row = ''.join(maze._cells[y][x].symbol for x in range(maze.width))
|
||||
lines.append(row)
|
||||
with open(filepath, 'w', encoding='utf-8') as f:
|
||||
f.write('\n'.join(lines))
|
||||
|
||||
|
||||
def make_empty_maze(width: int, height: int) -> Maze:
|
||||
"""Лабиринт-поле без внутренних стен (только рамка)."""
|
||||
return TextFileMazeBuilder().build_empty(width, height)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# experiment.py — Экспериментальная часть
|
||||
# ============================================================================
|
||||
|
||||
REPEATS = 7
|
||||
OUT_CSV = os.path.join(os.path.dirname(__file__), 'docs/data/maze_results.csv')
|
||||
MAZE_DIR = os.path.join(os.path.dirname(__file__), 'docs/data/mazes')
|
||||
|
||||
|
||||
def run_bench(maze, strategy, repeats=REPEATS):
|
||||
times, visited_list, path_lens = [], [], []
|
||||
for _ in range(repeats):
|
||||
solver = MazeSolver(maze, strategy)
|
||||
stats, path = solver.solve()
|
||||
times.append(stats.elapsed_ms)
|
||||
visited_list.append(stats.visited_cells)
|
||||
path_lens.append(stats.path_length)
|
||||
return {
|
||||
'time_ms_mean': statistics.mean(times),
|
||||
'time_ms_std': statistics.stdev(times) if len(times) > 1 else 0,
|
||||
'visited_mean': statistics.mean(visited_list),
|
||||
'path_length': path_lens[0], # детерминировано
|
||||
}
|
||||
|
||||
|
||||
def run_experiment():
|
||||
os.makedirs(MAZE_DIR, exist_ok=True)
|
||||
|
||||
# Определяем лабиринты
|
||||
configs = [
|
||||
('small_10x10', generate_maze(11, 11, seed=1)),
|
||||
('medium_50x50', generate_maze(51, 51, seed=2)),
|
||||
('large_100x100', generate_maze(101, 101, seed=3)),
|
||||
('empty_50x50', make_empty_maze(52, 52)),
|
||||
('no_exit_20x20', generate_no_exit(21, 21, seed=4)),
|
||||
('weighted_50x50', generate_maze(51, 51, seed=5, weighted=True)),
|
||||
]
|
||||
|
||||
# Сохраним лабиринты в файлы
|
||||
for name, maze in configs:
|
||||
save_maze(maze, os.path.join(MAZE_DIR, f'{name}.txt'))
|
||||
|
||||
rows = [['Лабиринт', 'Алгоритм', 'Время_мс', 'Посещено_клеток', 'Длина_пути',
|
||||
'Стд_время_мс']]
|
||||
|
||||
for maze_name, maze in configs:
|
||||
print(f'\n=== {maze_name} ({maze.width}×{maze.height}) ===')
|
||||
for strategy in ALL_STRATEGIES:
|
||||
print(f' [{strategy.name()}]', end=' ', flush=True)
|
||||
res = run_bench(maze, strategy)
|
||||
print(f"time={res['time_ms_mean']:.3f}ms visited={res['visited_mean']:.0f} "
|
||||
f"path={res['path_length']}")
|
||||
rows.append([
|
||||
maze_name,
|
||||
strategy.name(),
|
||||
round(res['time_ms_mean'], 4),
|
||||
round(res['visited_mean'], 1),
|
||||
res['path_length'],
|
||||
round(res['time_ms_std'], 4),
|
||||
])
|
||||
|
||||
with open(OUT_CSV, 'w', newline='', encoding='utf-8') as f:
|
||||
csv.writer(f).writerows(rows)
|
||||
print(f'\nРезультаты сохранены: {OUT_CSV}')
|
||||
|
||||
return rows
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# main.py — Точка входа
|
||||
# ============================================================================
|
||||
|
||||
STRATEGIES = {
|
||||
'1': BFSStrategy(),
|
||||
'2': DFSStrategy(),
|
||||
'3': AStarStrategy(),
|
||||
'4': DijkstraStrategy(),
|
||||
}
|
||||
|
||||
STRATEGY_LABELS = {
|
||||
'1': 'BFS (поиск в ширину)',
|
||||
'2': 'DFS (поиск в глубину)',
|
||||
'3': 'A* (эвристический)',
|
||||
'4': 'Dijkstra (взвешенный)',
|
||||
}
|
||||
|
||||
|
||||
def choose_strategy() -> PathFindingStrategy:
|
||||
print("\nВыберите алгоритм поиска:")
|
||||
for k, label in STRATEGY_LABELS.items():
|
||||
print(f" {k}. {label}")
|
||||
choice = input("Ваш выбор [1-4, Enter=3]: ").strip() or '3'
|
||||
return STRATEGIES.get(choice, AStarStrategy())
|
||||
|
||||
|
||||
def interactive_walk(solver: MazeSolver, path: list, maze):
|
||||
"""Пошаговое прохождение найденного пути командами."""
|
||||
if not path:
|
||||
print("Путь не найден, пошаговый режим недоступен.")
|
||||
return
|
||||
|
||||
player = Player(maze.start)
|
||||
history = CommandHistory()
|
||||
step = 0
|
||||
|
||||
print("\n=== Пошаговый режим ===")
|
||||
print("n — следующий шаг по пути | u — отмена | q — выход")
|
||||
print(maze.render(path=set(path), player_pos=player.position))
|
||||
|
||||
while True:
|
||||
cmd_input = input("\nКоманда: ").strip().lower()
|
||||
if cmd_input == 'q':
|
||||
break
|
||||
elif cmd_input == 'n':
|
||||
step += 1
|
||||
if step < len(path):
|
||||
cmd = MoveCommand(player, path[step], solver, path)
|
||||
history.execute(cmd)
|
||||
else:
|
||||
print("Вы достигли выхода! 🎉")
|
||||
elif cmd_input == 'u':
|
||||
step = max(0, step - 1)
|
||||
history.undo()
|
||||
else:
|
||||
print("Неизвестная команда.")
|
||||
|
||||
|
||||
def demo_auto(size: int = 21):
|
||||
"""Автоматическая демонстрация всех паттернов на сгенерированном лабиринте."""
|
||||
print("=" * 60)
|
||||
print(" Демонстрация: Поиск выхода из лабиринта")
|
||||
print("=" * 60)
|
||||
|
||||
# Builder
|
||||
print("\n[Builder] Генерация лабиринта...")
|
||||
maze = generate_maze(size, size, seed=77)
|
||||
|
||||
# Observer + Strategy
|
||||
strategy = choose_strategy()
|
||||
solver = MazeSolver(maze, strategy)
|
||||
view = ConsoleView()
|
||||
solver.add_observer(view)
|
||||
|
||||
solver.notify('maze_loaded', {'maze': maze})
|
||||
|
||||
stats, path = solver.solve()
|
||||
|
||||
print(f"\n[MazeSolver] Статистика:")
|
||||
print(f" Алгоритм: {stats.strategy_name}")
|
||||
print(f" Время: {stats.elapsed_ms:.3f} мс")
|
||||
print(f" Посещено клеток: {stats.visited_cells}")
|
||||
print(f" Длина пути: {stats.path_length}")
|
||||
|
||||
# Command / пошаговый режим
|
||||
choice = input("\nЗапустить пошаговый режим? [y/N]: ").strip().lower()
|
||||
if choice == 'y':
|
||||
interactive_walk(solver, path, maze)
|
||||
|
||||
# Strategy swap demo
|
||||
print("\n[Strategy] Смена алгоритма на BFS без изменения кода...")
|
||||
solver.set_strategy(BFSStrategy())
|
||||
stats2, _ = solver.solve()
|
||||
print(f" BFS: {stats2.elapsed_ms:.3f} мс, посещено: {stats2.visited_cells}")
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Запуск
|
||||
# ============================================================================
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
if len(sys.argv) > 1 and sys.argv[1] == 'experiment':
|
||||
run_experiment()
|
||||
else:
|
||||
demo_auto()
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 262 KiB |
|
|
@ -1,279 +0,0 @@
|
|||
# Задание 2 — Поиск выхода из лабиринта (ООП + паттерны GoF)
|
||||
|
||||
## 1. Описание задачи
|
||||
|
||||
Разработать расширяемую программу поиска пути в лабиринте с возможностью смены алгоритма, визуализации и пошагового управления. Применить минимум 3 паттерна GoF.
|
||||
|
||||
---
|
||||
|
||||
## 2. Применённые паттерны GoF
|
||||
|
||||
### 2.1 Builder (Строитель) — `builder.py`
|
||||
|
||||
**Проблема:** Создание `Maze` — многошаговый процесс: чтение файла → парсинг символов → валидация (есть ли S и E) → расстановка координат. Если делать всё в конструкторе `Maze` — нарушение Single Responsibility.
|
||||
|
||||
**Решение:** Интерфейс `MazeBuilder` с методом `build_from_file()`. Реализации:
|
||||
- `TextFileMazeBuilder` — текстовый формат (`#`, пробел, `S`, `E`, `W`, `N`)
|
||||
- `JsonMazeBuilder` — JSON-формат (для демонстрации расширяемости)
|
||||
|
||||
**Преимущество:** Добавление нового формата (бинарный, XML) не требует изменения `Maze`, `MazeSolver` или стратегий.
|
||||
|
||||
### 2.2 Strategy (Стратегия) — `strategy.py`
|
||||
|
||||
**Проблема:** Алгоритмы поиска (BFS, DFS, A*, Dijkstra) взаимозаменяемы по интерфейсу, но различаются реализацией. Жёсткая связь с конкретным алгоритмом нарушает Open/Closed Principle.
|
||||
|
||||
**Решение:** Интерфейс `PathFindingStrategy` с методом `find_path(maze, start, exit_)`. `MazeSolver` хранит ссылку на стратегию и вызывает `strategy.find_path()` — не зная, какой именно алгоритм работает. Метод `set_strategy()` меняет алгоритм в рантайме.
|
||||
|
||||
**Преимущество:** Новый алгоритм (IDA*, Bidirectional BFS) — это один новый класс без изменения остального кода.
|
||||
|
||||
### 2.3 Observer (Наблюдатель) — `solver.py`
|
||||
|
||||
**Проблема:** `MazeSolver` не должен знать о способе отображения. Консоль, GUI, файловый лог — разные задачи.
|
||||
|
||||
**Решение:** `MazeSolver` наследует `Subject` (хранит список `_observers`, метод `notify()`). При событиях (`maze_loaded`, `path_found`, `no_path`, `player_moved`) уведомляет всех наблюдателей. Реализации: `ConsoleView`, `FileLogObserver`.
|
||||
|
||||
**Преимущество:** GUI-интерфейс добавляется как новый `Observer` без изменения `MazeSolver`.
|
||||
|
||||
### 2.4 Command (Команда) — `solver.py`
|
||||
|
||||
**Проблема:** Пошаговое перемещение игрока должно поддерживать отмену (undo).
|
||||
|
||||
**Решение:** Интерфейс `Command` с методами `execute()` / `undo()`. `MoveCommand` хранит предыдущую позицию. `CommandHistory` — стек команд. Undo — `pop()` + `cmd.undo()`.
|
||||
|
||||
**Преимущество:** Любое новое действие (телепортация, открытие двери) реализуется как отдельная команда, не меняя `Player` или `MazeSolver`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Диаграмма классов (Mermaid)
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
%% ── Model ──────────────────────────────────
|
||||
class Cell {
|
||||
+int x, y
|
||||
+str symbol
|
||||
+bool is_wall
|
||||
+bool is_start
|
||||
+bool is_exit
|
||||
+int weight
|
||||
+is_passable() bool
|
||||
}
|
||||
|
||||
class Maze {
|
||||
+int width, height
|
||||
+Cell start, exit
|
||||
+get_cell(x,y) Cell
|
||||
+get_neighbors(cell) list
|
||||
+render(path, visited, player) str
|
||||
}
|
||||
Maze "1" *-- "many" Cell
|
||||
|
||||
%% ── Builder ─────────────────────────────────
|
||||
class MazeBuilder {
|
||||
<<interface>>
|
||||
+build_from_file(filename) Maze
|
||||
+build_empty(w, h) Maze
|
||||
}
|
||||
class TextFileMazeBuilder {
|
||||
+build_from_file(filename) Maze
|
||||
+build_from_string(text) Maze
|
||||
+build_empty(w, h) Maze
|
||||
}
|
||||
class JsonMazeBuilder {
|
||||
+build_from_file(filename) Maze
|
||||
+build_empty(w, h) Maze
|
||||
}
|
||||
MazeBuilder <|.. TextFileMazeBuilder
|
||||
MazeBuilder <|.. JsonMazeBuilder
|
||||
TextFileMazeBuilder ..> Maze : creates
|
||||
JsonMazeBuilder ..> Maze : creates
|
||||
|
||||
%% ── Strategy ────────────────────────────────
|
||||
class PathFindingStrategy {
|
||||
<<interface>>
|
||||
+find_path(maze, start, exit) tuple
|
||||
+name() str
|
||||
}
|
||||
class BFSStrategy { +find_path() tuple }
|
||||
class DFSStrategy { +find_path() tuple }
|
||||
class AStarStrategy { +find_path() tuple }
|
||||
class DijkstraStrategy { +find_path() tuple }
|
||||
PathFindingStrategy <|.. BFSStrategy
|
||||
PathFindingStrategy <|.. DFSStrategy
|
||||
PathFindingStrategy <|.. AStarStrategy
|
||||
PathFindingStrategy <|.. DijkstraStrategy
|
||||
|
||||
%% ── Observer ────────────────────────────────
|
||||
class Observer {
|
||||
<<interface>>
|
||||
+update(event, payload)
|
||||
}
|
||||
class Subject {
|
||||
-observers list
|
||||
+add_observer(obs)
|
||||
+notify(event, payload)
|
||||
}
|
||||
class ConsoleView { +update(event, payload) }
|
||||
class FileLogObserver { +update(event, payload) }
|
||||
Observer <|.. ConsoleView
|
||||
Observer <|.. FileLogObserver
|
||||
Subject "1" o-- "many" Observer
|
||||
|
||||
%% ── MazeSolver ──────────────────────────────
|
||||
class MazeSolver {
|
||||
-Maze maze
|
||||
-PathFindingStrategy strategy
|
||||
+set_strategy(strategy)
|
||||
+load_maze(maze)
|
||||
+solve() tuple
|
||||
}
|
||||
MazeSolver --|> Subject
|
||||
MazeSolver --> PathFindingStrategy : uses
|
||||
MazeSolver --> Maze : uses
|
||||
|
||||
%% ── Command ─────────────────────────────────
|
||||
class Command {
|
||||
<<interface>>
|
||||
+execute()
|
||||
+undo()
|
||||
}
|
||||
class MoveCommand {
|
||||
-Player player
|
||||
-Cell target, previous
|
||||
+execute()
|
||||
+undo()
|
||||
}
|
||||
class CommandHistory {
|
||||
-stack list
|
||||
+execute(cmd)
|
||||
+undo()
|
||||
}
|
||||
class Player { +Cell position }
|
||||
Command <|.. MoveCommand
|
||||
CommandHistory o-- Command
|
||||
MoveCommand --> Player
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Структура файлов
|
||||
|
||||
```
|
||||
maze/
|
||||
├── model.py — Cell, Maze
|
||||
├── builder.py — MazeBuilder, TextFileMazeBuilder, JsonMazeBuilder
|
||||
├── strategy.py — PathFindingStrategy, BFS, DFS, A*, Dijkstra
|
||||
├── solver.py — Observer, Subject, ConsoleView, MazeSolver, Command, Player
|
||||
├── generator.py — Генератор лабиринтов (DFS-backtracker)
|
||||
├── experiment.py — Экспериментальная часть, запись CSV
|
||||
├── main.py — Интерактивная демонстрация
|
||||
└── docs/
|
||||
└── data/
|
||||
├── maze_results.csv
|
||||
├── maze_benchmark.png
|
||||
├── path_lengths.png
|
||||
└── mazes/ — текстовые файлы лабиринтов
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Результаты экспериментов
|
||||
|
||||
### 5.1 Таблица результатов (N = 7 повторов, среднее)
|
||||
|
||||
| Лабиринт | Алгоритм | Время (мс) | Посещено | Длина пути |
|
||||
|---|---|---:|---:|---:|
|
||||
| Маленький 11×11 | BFS | 0.077 | 37 | 29 |
|
||||
| Маленький 11×11 | DFS | 0.059 | 29 | 29 |
|
||||
| Маленький 11×11 | A* | 0.111 | 29 | 29 |
|
||||
| Маленький 11×11 | Dijkstra | 0.115 | 37 | 29 |
|
||||
| Средний 51×51 | BFS | 2.228 | 755 | 405 |
|
||||
| Средний 51×51 | DFS | 0.935 | 423 | 405 |
|
||||
| Средний 51×51 | A* | 2.343 | 663 | 405 |
|
||||
| Средний 51×51 | Dijkstra | 2.528 | 755 | 405 |
|
||||
| Большой 101×101 | BFS | 3.299 | 1533 | 993 |
|
||||
| Большой 101×101 | DFS | 2.289 | 1061 | 993 |
|
||||
| Большой 101×101 | A* | 5.948 | 1515 | 993 |
|
||||
| Большой 101×101 | Dijkstra | 5.137 | 1533 | 993 |
|
||||
| Пустой 52×52 | BFS | 5.859 | 2500 | 99 |
|
||||
| Пустой 52×52 | DFS | 3.518 | 1275 | 1275 |
|
||||
| Пустой 52×52 | A* | 10.803 | 2500 | 99 |
|
||||
| Пустой 52×52 | Dijkstra | 10.227 | 2500 | 99 |
|
||||
| Без выхода 21×21 | BFS | 0.213 | 105 | 0 |
|
||||
| Без выхода 21×21 | DFS | 0.220 | 105 | 0 |
|
||||
| Без выхода 21×21 | A* | 0.361 | 105 | 0 |
|
||||
| Без выхода 21×21 | Dijkstra | 0.336 | 105 | 0 |
|
||||
| Взвешенный 51×51 | BFS | 0.722 | 335 | 221 |
|
||||
| Взвешенный 51×51 | DFS | 0.518 | 221 | 221 |
|
||||
| Взвешенный 51×51 | A* | 0.970 | 278 | 221 |
|
||||
| Взвешенный 51×51 | Dijkstra | 1.091 | 337 | 221 |
|
||||
|
||||
### 5.2 Графики
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## 6. Анализ алгоритмов
|
||||
|
||||
### BFS (поиск в ширину)
|
||||
- **Гарантирует кратчайший путь** по количеству шагов.
|
||||
- Посещает все клетки на расстоянии k до нахождения выхода на расстоянии k+1.
|
||||
- На пустом лабиринте (максимум свободного пространства) посещает **все 2500 клеток** — это худший сценарий по памяти (хранит всю «волну»).
|
||||
- Практически эквивалентен Dijkstra для равновесных лабиринтов.
|
||||
|
||||
### DFS (поиск в глубину)
|
||||
- **Самый быстрый по времени** на лабиринтах с длинными коридорами.
|
||||
- На правильно построенном лабиринте (DFS-backtracker) коридоры длинные → DFS «угадывает» направление и находит путь, посетив меньше клеток.
|
||||
- На пустом поле даёт **огромный зигзагообразный путь** (1275 клеток вместо оптимального 99) — классическая проблема DFS.
|
||||
- **Не гарантирует оптимальность**, зато экономит память (O(глубина) вместо O(ширина)).
|
||||
|
||||
### A* (с манхэттенской эвристикой)
|
||||
- На лабиринтах с коридорами посещает **меньше клеток**, чем BFS, но при этом тоже гарантирует оптимальный путь.
|
||||
- На пустом поле ведёт себя хуже BFS по времени из-за накладных расходов на приоритетную очередь (heapq).
|
||||
- Манхэттенская эвристика работает хуже на лабиринтах с длинными обходами препятствий: оценка расстояния «по прямой» слишком оптимистична.
|
||||
- **Выигрывает** когда карта большая и путь очевиден по направлению (прямые коридоры к выходу).
|
||||
|
||||
### Dijkstra
|
||||
- На равновесных лабиринтах идентичен BFS, но медленнее из-за приоритетной очереди.
|
||||
- **Незаменим на взвешенных картах**: учитывает клетки с весом 2 (песок) и 3 (болото) и находит **минимальный суммарный вес** пути, а не минимальное число шагов.
|
||||
- На взвешенном лабиринте Dijkstra и A* могут выдать более короткий (по весу) путь, чем BFS, при одинаковой длине в клетках.
|
||||
|
||||
### Без выхода
|
||||
- Все алгоритмы обошли все 105 достижимых клеток и вернули пустой путь — корректная обработка.
|
||||
- Время идентично для всех алгоритмов (доминирует полный обход, а не структура поиска).
|
||||
|
||||
---
|
||||
|
||||
## 7. Выводы: ООП и паттерны
|
||||
|
||||
### Что дали паттерны
|
||||
|
||||
**Builder** отделил детали парсинга от модели. Без него клиент сам читал бы файл и вручную собирал `Maze` — хрупкий, нерасширяемый код. С Builder: добавление JSON-формата заняло 10 строк.
|
||||
|
||||
**Strategy** позволил сравнить 4 алгоритма, не меняя `MazeSolver`. Переключение алгоритма — одна строка `solver.set_strategy(new_algo)`. Без паттерна потребовался бы if/elif на 4 ветки в каждом методе.
|
||||
|
||||
**Observer** полностью развязал логику поиска и отображение. `MazeSolver` не знает, куда идут уведомления. Добавление `FileLogObserver` — новый класс из 5 строк, нулевые изменения в `MazeSolver`.
|
||||
|
||||
**Command** дал возможность реализовать undo за счёт хранения предыдущего состояния в объекте команды. Без паттерна — ручное сохранение/восстановление переменных.
|
||||
|
||||
### Что было бы сложно без ООП и паттернов
|
||||
|
||||
| Задача | Без паттернов | С паттернами |
|
||||
|---|---|---|
|
||||
| Добавить новый алгоритм | Правка MazeSolver + if/elif | Новый класс, наследующий интерфейс |
|
||||
| Добавить формат файла | Правка загрузчика | Новый Builder |
|
||||
| Добавить GUI | Вставка GUI-кода в логику поиска | Новый Observer |
|
||||
| Отмена шага игрока | Ручное сохранение переменной | Command.undo() |
|
||||
|
||||
### Рекомендации по выбору алгоритма
|
||||
|
||||
| Задача | Рекомендация |
|
||||
|---|---|
|
||||
| Гарантированно кратчайший путь, равные веса | **BFS** |
|
||||
| Быстро найти *какой-нибудь* путь (поиск с препятствиями) | **DFS** |
|
||||
| Большая карта, путь по направлению к цели | **A\*** |
|
||||
| Взвешенная карта (болото, песок, дорога) | **Dijkstra** или **A\*** с весовой эвристикой |
|
||||
| Нужна гарантия оптимальности на взвешенном графе | **Dijkstra** |
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
Лабиринт,Алгоритм,Время_мс,Посещено_клеток,Длина_пути,Стд_время_мс
|
||||
small_10x10,BFS,0.0772,37,29,0.0122
|
||||
small_10x10,DFS,0.0588,29,29,0.0044
|
||||
small_10x10,A*,0.1112,29,29,0.029
|
||||
small_10x10,Dijkstra,0.1155,37,29,0.0135
|
||||
medium_50x50,BFS,2.228,755,405,1.6518
|
||||
medium_50x50,DFS,0.9346,423,405,0.0899
|
||||
medium_50x50,A*,2.3432,663,405,0.125
|
||||
medium_50x50,Dijkstra,2.5281,755,405,0.1245
|
||||
large_100x100,BFS,3.2987,1533,993,0.148
|
||||
large_100x100,DFS,2.2887,1061,993,0.0737
|
||||
large_100x100,A*,5.9477,1515,993,1.2811
|
||||
large_100x100,Dijkstra,5.1375,1533,993,0.1435
|
||||
empty_50x50,BFS,5.8593,2500,99,0.1605
|
||||
empty_50x50,DFS,3.5185,1275,1275,0.1171
|
||||
empty_50x50,A*,10.8025,2500,99,0.0902
|
||||
empty_50x50,Dijkstra,10.227,2500,99,0.1552
|
||||
no_exit_20x20,BFS,0.213,105,0,0.0434
|
||||
no_exit_20x20,DFS,0.2204,105,0,0.049
|
||||
no_exit_20x20,A*,0.3615,105,0,0.0489
|
||||
no_exit_20x20,Dijkstra,0.3361,105,0,0.033
|
||||
weighted_50x50,BFS,0.7224,335,221,0.069
|
||||
weighted_50x50,DFS,0.5183,221,221,0.0912
|
||||
weighted_50x50,A*,0.97,278,221,0.0444
|
||||
weighted_50x50,Dijkstra,1.0906,337,221,0.0414
|
||||
|
Binary file not shown.
|
Before Width: | Height: | Size: 53 KiB |
Loading…
Reference in New Issue
Block a user