[2] task 2
5
zaharoves/задание 2/.idea/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
||||
7
zaharoves/задание 2/.idea/misc.xml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Black">
|
||||
<option name="sdkName" value="Python 3.13" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.13" project-jdk-type="Python SDK" />
|
||||
</project>
|
||||
8
zaharoves/задание 2/.idea/modules.xml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/задание 2.iml" filepath="$PROJECT_DIR$/.idea/задание 2.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
6
zaharoves/задание 2/.idea/vcs.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$/../.." vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
10
zaharoves/задание 2/.idea/задание 2.iml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="Python 3.13" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
|
|
@ -0,0 +1,660 @@
|
|||
|
||||
import os
|
||||
import time
|
||||
import heapq
|
||||
import csv
|
||||
import random
|
||||
from abc import ABC, abstractmethod
|
||||
from collections import deque
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
|
||||
#1. Модель лабиринта — классы Cell и Maze
|
||||
|
||||
class Cell:
|
||||
"""Клетка лабиринта."""
|
||||
|
||||
def __init__(self, x: int, y: int, is_wall: bool = False,
|
||||
is_start: bool = False, is_exit: bool = False, weight: int = 1):
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.is_wall = is_wall
|
||||
self.is_start = is_start
|
||||
self.is_exit = is_exit
|
||||
self.weight = weight # для взвешенного лабиринта (Этап 6 доп.)
|
||||
|
||||
def is_passable(self) -> bool:
|
||||
return not self.is_wall
|
||||
|
||||
def __repr__(self):
|
||||
return f"Cell({self.x},{self.y})"
|
||||
|
||||
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))
|
||||
|
||||
def __lt__(self, other):
|
||||
return (self.x, self.y) < (other.x, other.y)
|
||||
|
||||
|
||||
class Maze:
|
||||
"""Лабиринт — двумерный массив клеток."""
|
||||
|
||||
def __init__(self, width: int, height: int):
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.cells: list[list[Cell]] = []
|
||||
self.start: Optional[Cell] = None
|
||||
self.exit: Optional[Cell] = None
|
||||
|
||||
def get_cell(self, x: int, y: int) -> Optional[Cell]:
|
||||
if 0 <= x < self.width and 0 <= y < self.height:
|
||||
return self.cells[y][x]
|
||||
return None
|
||||
|
||||
def get_neighbors(self, cell: Cell) -> list[Cell]:
|
||||
"""Возвращает проходимых соседей (вверх, вниз, влево, вправо)."""
|
||||
directions = [(0, -1), (0, 1), (-1, 0), (1, 0)]
|
||||
neighbors = []
|
||||
for dx, dy in directions:
|
||||
neighbor = self.get_cell(cell.x + dx, cell.y + dy)
|
||||
if neighbor and neighbor.is_passable():
|
||||
neighbors.append(neighbor)
|
||||
return neighbors
|
||||
|
||||
|
||||
#2. Загрузка лабиринта из файла — паттерн Builder
|
||||
|
||||
class MazeBuilder(ABC):
|
||||
"""Интерфейс строителя лабиринта."""
|
||||
|
||||
@abstractmethod
|
||||
def build_from_file(self, filename: str) -> Maze:
|
||||
pass
|
||||
|
||||
|
||||
class TextFileMazeBuilder(MazeBuilder):
|
||||
|
||||
WEIGHT_MAP = {' ': 1, 'S': 1, 'E': 1, '.': 2, '~': 3, '#': 0}
|
||||
|
||||
def build_from_file(self, filename: str) -> Maze:
|
||||
with open(filename, 'r', encoding='utf-8') as f:
|
||||
lines = f.read().splitlines()
|
||||
|
||||
if not lines:
|
||||
raise ValueError("Файл лабиринта пуст")
|
||||
|
||||
height = len(lines)
|
||||
width = max(len(line) for line in lines)
|
||||
maze = Maze(width, height)
|
||||
|
||||
for y, line in enumerate(lines):
|
||||
row = []
|
||||
for x in range(width):
|
||||
ch = line[x] if x < len(line) else ' '
|
||||
is_wall = ch == '#'
|
||||
is_start = ch == 'S'
|
||||
is_exit = ch == 'E'
|
||||
weight = self.WEIGHT_MAP.get(ch, 1)
|
||||
cell = Cell(x, y, is_wall=is_wall, is_start=is_start,
|
||||
is_exit=is_exit, weight=weight)
|
||||
row.append(cell)
|
||||
if is_start:
|
||||
maze.start = cell
|
||||
if is_exit:
|
||||
maze.exit = cell
|
||||
maze.cells.append(row)
|
||||
|
||||
if maze.start is None:
|
||||
raise ValueError("В лабиринте не задана стартовая клетка (S)")
|
||||
if maze.exit is None:
|
||||
raise ValueError("В лабиринте не задана выходная клетка (E)")
|
||||
|
||||
return maze
|
||||
|
||||
|
||||
#3. Стратегии поиска пути — паттерн Strategy
|
||||
|
||||
@dataclass
|
||||
class SearchStats:
|
||||
"""Статистика одного запуска поиска."""
|
||||
time_ms: float = 0.0
|
||||
visited_cells: int = 0
|
||||
path_length: int = 0
|
||||
|
||||
|
||||
class PathFindingStrategy(ABC):
|
||||
"""Интерфейс стратегии поиска пути."""
|
||||
|
||||
@abstractmethod
|
||||
def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> list[Cell]:
|
||||
pass
|
||||
|
||||
def _reconstruct_path(self, parent: dict, start: Cell, exit_cell: Cell) -> list[Cell]:
|
||||
path = []
|
||||
current = exit_cell
|
||||
while current is not None:
|
||||
path.append(current)
|
||||
current = parent.get(current)
|
||||
path.reverse()
|
||||
if path and path[0] == start:
|
||||
return path
|
||||
return []
|
||||
|
||||
|
||||
class BFSStrategy(PathFindingStrategy):
|
||||
"""Поиск в ширину"""
|
||||
|
||||
def __init__(self):
|
||||
self.visited_count = 0
|
||||
|
||||
def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> list[Cell]:
|
||||
queue = deque([start])
|
||||
parent: dict[Cell, Optional[Cell]] = {start: None}
|
||||
self.visited_count = 0
|
||||
|
||||
while queue:
|
||||
current = queue.popleft()
|
||||
self.visited_count += 1
|
||||
|
||||
if current == exit_cell:
|
||||
return self._reconstruct_path(parent, start, exit_cell)
|
||||
|
||||
for neighbor in maze.get_neighbors(current):
|
||||
if neighbor not in parent:
|
||||
parent[neighbor] = current
|
||||
queue.append(neighbor)
|
||||
|
||||
return []
|
||||
|
||||
|
||||
class DFSStrategy(PathFindingStrategy):
|
||||
"""Поиск в глубину"""
|
||||
|
||||
def __init__(self):
|
||||
self.visited_count = 0
|
||||
|
||||
def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> list[Cell]:
|
||||
stack = [start]
|
||||
parent: dict[Cell, Optional[Cell]] = {start: None}
|
||||
self.visited_count = 0
|
||||
|
||||
while stack:
|
||||
current = stack.pop()
|
||||
self.visited_count += 1
|
||||
|
||||
if current == exit_cell:
|
||||
return self._reconstruct_path(parent, start, exit_cell)
|
||||
|
||||
for neighbor in maze.get_neighbors(current):
|
||||
if neighbor not in parent:
|
||||
parent[neighbor] = current
|
||||
stack.append(neighbor)
|
||||
|
||||
return []
|
||||
|
||||
|
||||
class AStarStrategy(PathFindingStrategy):
|
||||
"""A* с манхэттенской эвристикой"""
|
||||
|
||||
def __init__(self):
|
||||
self.visited_count = 0
|
||||
|
||||
def _heuristic(self, a: Cell, b: Cell) -> int:
|
||||
return abs(a.x - b.x) + abs(a.y - b.y)
|
||||
|
||||
def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> list[Cell]:
|
||||
g_score = {start: 0}
|
||||
parent: dict[Cell, Optional[Cell]] = {start: None}
|
||||
open_heap = [(self._heuristic(start, exit_cell), 0, start)]
|
||||
closed_set: set[Cell] = set() # уже обработанные клетки
|
||||
self.visited_count = 0
|
||||
counter = 0 # счётчик для устранения неоднозначности
|
||||
|
||||
while open_heap:
|
||||
_, _, current = heapq.heappop(open_heap)
|
||||
|
||||
if current in closed_set:
|
||||
continue
|
||||
closed_set.add(current)
|
||||
self.visited_count += 1
|
||||
|
||||
if current == exit_cell:
|
||||
return self._reconstruct_path(parent, start, exit_cell)
|
||||
|
||||
for neighbor in maze.get_neighbors(current):
|
||||
if neighbor in closed_set:
|
||||
continue
|
||||
tentative_g = g_score[current] + neighbor.weight
|
||||
if tentative_g < g_score.get(neighbor, float('inf')):
|
||||
g_score[neighbor] = tentative_g
|
||||
parent[neighbor] = current
|
||||
f = tentative_g + self._heuristic(neighbor, exit_cell)
|
||||
counter += 1
|
||||
heapq.heappush(open_heap, (f, counter, neighbor))
|
||||
|
||||
return []
|
||||
|
||||
|
||||
class DijkstraStrategy(PathFindingStrategy):
|
||||
"""Дейкстра"""
|
||||
|
||||
def __init__(self):
|
||||
self.visited_count = 0
|
||||
|
||||
def find_path(self, maze: Maze, start: Cell, exit_cell: Cell) -> list[Cell]:
|
||||
dist = {start: 0}
|
||||
parent: dict[Cell, Optional[Cell]] = {start: None}
|
||||
open_heap = [(0, 0, start)]
|
||||
self.visited_count = 0
|
||||
counter = 0
|
||||
|
||||
while open_heap:
|
||||
cost, _, current = heapq.heappop(open_heap)
|
||||
if cost > dist.get(current, float('inf')):
|
||||
continue
|
||||
self.visited_count += 1
|
||||
|
||||
if current == exit_cell:
|
||||
return self._reconstruct_path(parent, start, exit_cell)
|
||||
|
||||
for neighbor in maze.get_neighbors(current):
|
||||
new_cost = dist[current] + neighbor.weight
|
||||
if new_cost < dist.get(neighbor, float('inf')):
|
||||
dist[neighbor] = new_cost
|
||||
parent[neighbor] = current
|
||||
counter += 1
|
||||
heapq.heappush(open_heap, (new_cost, counter, neighbor))
|
||||
|
||||
return []
|
||||
|
||||
|
||||
#4. Оркестратор — MazeSolver
|
||||
|
||||
class MazeSolver:
|
||||
"""Оркестратор"""
|
||||
|
||||
def __init__(self, maze: Maze, strategy: PathFindingStrategy):
|
||||
self.maze = maze
|
||||
self.strategy = strategy
|
||||
self._observers: list['Observer'] = []
|
||||
self._last_path: list[Cell] = []
|
||||
|
||||
def set_strategy(self, strategy: PathFindingStrategy):
|
||||
self.strategy = strategy
|
||||
|
||||
def add_observer(self, observer: 'Observer'):
|
||||
self._observers.append(observer)
|
||||
|
||||
def _notify(self, event: str, **kwargs):
|
||||
for obs in self._observers:
|
||||
obs.update(event, **kwargs)
|
||||
|
||||
def solve(self) -> SearchStats:
|
||||
start = self.maze.start
|
||||
exit_cell = self.maze.exit
|
||||
self._notify("search_start", strategy=type(self.strategy).__name__)
|
||||
|
||||
t0 = time.perf_counter()
|
||||
path = self.strategy.find_path(self.maze, start, exit_cell)
|
||||
t1 = time.perf_counter()
|
||||
|
||||
self._last_path = path
|
||||
visited = getattr(self.strategy, 'visited_count', 0)
|
||||
stats = SearchStats(
|
||||
time_ms=(t1 - t0) * 1000,
|
||||
visited_cells=visited,
|
||||
path_length=len(path)
|
||||
)
|
||||
self._notify("path_found", path=path, stats=stats)
|
||||
return stats
|
||||
|
||||
def get_last_path(self) -> list[Cell]:
|
||||
return self._last_path
|
||||
|
||||
|
||||
#5.1. Observer — ConsoleView
|
||||
|
||||
class Observer(ABC):
|
||||
"""Интерфейс наблюдателя."""
|
||||
|
||||
@abstractmethod
|
||||
def update(self, event: str, **kwargs):
|
||||
pass
|
||||
|
||||
|
||||
class ConsoleView(Observer):
|
||||
"""Консольный вид"""
|
||||
|
||||
SYMBOLS = {
|
||||
'wall': '█',
|
||||
'path': '·',
|
||||
'start': 'S',
|
||||
'exit': 'E',
|
||||
'player': '@',
|
||||
'visited': '°',
|
||||
'empty': ' ',
|
||||
}
|
||||
|
||||
def update(self, event: str, **kwargs):
|
||||
if event == "search_start":
|
||||
print(f"\n[Поиск] Алгоритм: {kwargs.get('strategy', '?')}")
|
||||
elif event == "path_found":
|
||||
path = kwargs.get('path', [])
|
||||
stats = kwargs.get('stats')
|
||||
if path:
|
||||
print(f"[Готово] Путь найден! Длина: {stats.path_length}, "
|
||||
f"Посещено: {stats.visited_cells}, Время: {stats.time_ms:.2f} мс")
|
||||
else:
|
||||
print("[Готово] Путь не найден!")
|
||||
elif event == "move":
|
||||
cell = kwargs.get('cell')
|
||||
print(f"[Ход] Игрок перемещается в {cell}")
|
||||
|
||||
def render(self, maze: Maze, player_pos: Optional[Cell] = None,
|
||||
path: Optional[list[Cell]] = None):
|
||||
"""Рисует лабиринт в консоли."""
|
||||
path_set = set(path) if path else set()
|
||||
print()
|
||||
for y in range(maze.height):
|
||||
row = ''
|
||||
for x in range(maze.width):
|
||||
cell = maze.cells[y][x]
|
||||
if cell.is_wall:
|
||||
row += self.SYMBOLS['wall']
|
||||
elif player_pos and cell == player_pos:
|
||||
row += self.SYMBOLS['player']
|
||||
elif cell.is_start:
|
||||
row += self.SYMBOLS['start']
|
||||
elif cell.is_exit:
|
||||
row += self.SYMBOLS['exit']
|
||||
elif cell in path_set:
|
||||
row += self.SYMBOLS['path']
|
||||
else:
|
||||
row += self.SYMBOLS['empty']
|
||||
print(row)
|
||||
print()
|
||||
|
||||
|
||||
#5.2. Command — Player и MoveCommand
|
||||
|
||||
class Player:
|
||||
"""Игрок с текущей позицией в лабиринте."""
|
||||
|
||||
def __init__(self, start_cell: Cell):
|
||||
self.current_cell = start_cell
|
||||
|
||||
def move_to(self, cell: Cell):
|
||||
self.current_cell = cell
|
||||
|
||||
|
||||
class Command(ABC):
|
||||
"""Интерфейс команды."""
|
||||
|
||||
@abstractmethod
|
||||
def execute(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def undo(self):
|
||||
pass
|
||||
|
||||
|
||||
class MoveCommand(Command):
|
||||
"""Команда перемещения игрока в указанную клетку."""
|
||||
|
||||
def __init__(self, player: Player, target_cell: Cell, solver: MazeSolver):
|
||||
self.player = player
|
||||
self.target_cell = target_cell
|
||||
self.previous_cell = player.current_cell
|
||||
self.solver = solver
|
||||
|
||||
def execute(self):
|
||||
self.previous_cell = self.player.current_cell
|
||||
self.player.move_to(self.target_cell)
|
||||
self.solver._notify("move", cell=self.target_cell)
|
||||
|
||||
def undo(self):
|
||||
self.player.move_to(self.previous_cell)
|
||||
self.solver._notify("move", cell=self.previous_cell)
|
||||
|
||||
|
||||
#6. Экспериментальная часть
|
||||
|
||||
def generate_maze_file(filename: str, width: int, height: int,
|
||||
wall_density: float = 0.3, no_exit: bool = False):
|
||||
"""Генерирует случайный лабиринт и сохраняет в файл."""
|
||||
random.seed(42)
|
||||
grid = []
|
||||
for y in range(height):
|
||||
row = []
|
||||
for x in range(width):
|
||||
if x == 0 or y == 0 or x == width - 1 or y == height - 1:
|
||||
row.append('#')
|
||||
else:
|
||||
row.append('#' if random.random() < wall_density else ' ')
|
||||
grid.append(row)
|
||||
|
||||
# Старт и выход
|
||||
grid[1][1] = 'S'
|
||||
grid[height - 2][width - 2] = 'E'
|
||||
if no_exit:
|
||||
ex, ey = width - 2, height - 2
|
||||
for dx, dy in [(0, -1), (0, 1), (-1, 0), (1, 0)]:
|
||||
nx, ny = ex + dx, ey + dy
|
||||
if 0 < nx < width - 1 and 0 < ny < height - 1:
|
||||
grid[ny][nx] = '#'
|
||||
|
||||
with open(filename, 'w', encoding='utf-8') as f:
|
||||
for row in grid:
|
||||
f.write(''.join(row) + '\n')
|
||||
|
||||
|
||||
def run_experiments(mazes_config: list[dict], strategies: dict,
|
||||
runs: int = 5) -> list[dict]:
|
||||
builder = TextFileMazeBuilder()
|
||||
results = []
|
||||
|
||||
for maze_cfg in mazes_config:
|
||||
name = maze_cfg['name']
|
||||
filepath = maze_cfg['file']
|
||||
|
||||
# Генерируем файл лабиринта
|
||||
if 'generate' in maze_cfg:
|
||||
gen = maze_cfg['generate']
|
||||
generate_maze_file(filepath, gen['width'], gen['height'],
|
||||
gen.get('density', 0.3),
|
||||
gen.get('no_exit', False))
|
||||
|
||||
try:
|
||||
maze = builder.build_from_file(filepath)
|
||||
except Exception as e:
|
||||
print(f"[Пропуск] {name}: {e}")
|
||||
continue
|
||||
|
||||
for strat_name, strategy_cls in strategies.items():
|
||||
times, visited, lengths = [], [], []
|
||||
for _ in range(runs):
|
||||
strategy = strategy_cls()
|
||||
solver = MazeSolver(maze, strategy)
|
||||
stats = solver.solve()
|
||||
times.append(stats.time_ms)
|
||||
visited.append(stats.visited_cells)
|
||||
lengths.append(stats.path_length)
|
||||
|
||||
results.append({
|
||||
'лабиринт': name,
|
||||
'стратегия': strat_name,
|
||||
'время_мс': round(sum(times) / runs, 4),
|
||||
'посещено_клеток': int(sum(visited) / runs),
|
||||
'длина_пути': int(sum(lengths) / runs),
|
||||
})
|
||||
print(f" {name} / {strat_name}: "
|
||||
f"time={results[-1]['время_мс']:.3f}ms, "
|
||||
f"visited={results[-1]['посещено_клеток']}, "
|
||||
f"path={results[-1]['длина_пути']}")
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def save_csv(results: list[dict], filename: str):
|
||||
if not results:
|
||||
return
|
||||
with open(filename, 'w', newline='', encoding='utf-8-sig') as f:
|
||||
writer = csv.DictWriter(f, fieldnames=results[0].keys())
|
||||
writer.writeheader()
|
||||
writer.writerows(results)
|
||||
print(f"\nРезультаты сохранены в {filename}")
|
||||
|
||||
|
||||
def print_table(results: list[dict]):
|
||||
if not results:
|
||||
print("Нет результатов.")
|
||||
return
|
||||
header = f"{'Лабиринт':<20} {'Стратегия':<12} {'Время,мс':>10} {'Посещено':>10} {'Путь':>8}"
|
||||
print("\n" + "=" * len(header))
|
||||
print(header)
|
||||
print("=" * len(header))
|
||||
for r in results:
|
||||
print(f"{r['лабиринт']:<20} {r['стратегия']:<12} "
|
||||
f"{r['время_мс']:>10.4f} {r['посещено_клеток']:>10} {r['длина_пути']:>8}")
|
||||
print("=" * len(header))
|
||||
|
||||
|
||||
#Демонстрация
|
||||
|
||||
def demo_interactive(maze: Maze, solver: MazeSolver, view: ConsoleView):
|
||||
"""Пошаговый режим с Command."""
|
||||
path = solver.get_last_path()
|
||||
if not path:
|
||||
print("Путь не найден — пошаговый режим недоступен.")
|
||||
return
|
||||
|
||||
player = Player(maze.start)
|
||||
history: list[MoveCommand] = []
|
||||
view.render(maze, player_pos=player.current_cell, path=path)
|
||||
|
||||
print("Пошаговый режим: [N] — следующий шаг, [U] — отмена, [Q] — выход")
|
||||
step_index = 1 # 0-й шаг — старт, уже там
|
||||
|
||||
while True:
|
||||
cmd = input("Команда: ").strip().upper()
|
||||
if cmd == 'Q':
|
||||
break
|
||||
elif cmd == 'N':
|
||||
if step_index < len(path):
|
||||
move = MoveCommand(player, path[step_index], solver)
|
||||
move.execute()
|
||||
history.append(move)
|
||||
step_index += 1
|
||||
os.system('cls' if os.name == 'nt' else 'clear')
|
||||
view.render(maze, player_pos=player.current_cell, path=path)
|
||||
if player.current_cell == maze.exit:
|
||||
print("🎉 Вы достигли выхода!")
|
||||
break
|
||||
else:
|
||||
print("Вы уже в конце пути.")
|
||||
elif cmd == 'U':
|
||||
if history:
|
||||
move = history.pop()
|
||||
move.undo()
|
||||
step_index -= 1
|
||||
os.system('cls' if os.name == 'nt' else 'clear')
|
||||
view.render(maze, player_pos=player.current_cell, path=path)
|
||||
else:
|
||||
print("Нечего отменять.")
|
||||
else:
|
||||
print("Неизвестная команда.")
|
||||
|
||||
|
||||
def main():
|
||||
print("=" * 60)
|
||||
print(" Поиск выхода из лабиринта — ООП + паттерны GoF")
|
||||
print("=" * 60)
|
||||
|
||||
small_maze_file = "maze_small.txt"
|
||||
|
||||
builder = TextFileMazeBuilder()
|
||||
maze = builder.build_from_file(small_maze_file)
|
||||
|
||||
#2. Создаём представление (Observer)
|
||||
view = ConsoleView()
|
||||
|
||||
#3. Демонстрация стратегий
|
||||
strategies = {
|
||||
'BFS': BFSStrategy,
|
||||
'DFS': DFSStrategy,
|
||||
'A*': AStarStrategy,
|
||||
'Дейкстра': DijkstraStrategy,
|
||||
}
|
||||
|
||||
print(f"\nЛабиринт ({maze.width}×{maze.height}):")
|
||||
view.render(maze)
|
||||
|
||||
for name, cls in strategies.items():
|
||||
strategy = cls()
|
||||
solver = MazeSolver(maze, strategy)
|
||||
solver.add_observer(view)
|
||||
stats = solver.solve()
|
||||
|
||||
#Визуализация пути A*
|
||||
print("\n--- Путь, найденный A* ---")
|
||||
a_star = AStarStrategy()
|
||||
solver = MazeSolver(maze, a_star)
|
||||
solver.add_observer(view)
|
||||
solver.solve()
|
||||
view.render(maze, path=solver.get_last_path())
|
||||
|
||||
#4. Пошаговый режим Command
|
||||
ans = input("Запустить пошаговый режим? (y/n): ").strip().lower()
|
||||
if ans == 'y':
|
||||
demo_interactive(maze, solver, view)
|
||||
|
||||
#5. Экспериментальная часть
|
||||
print("\n" + "=" * 60)
|
||||
print(" Экспериментальная часть")
|
||||
print("=" * 60)
|
||||
|
||||
mazes_config = [
|
||||
{
|
||||
'name': 'Маленький 10×10',
|
||||
'file': 'maze_small.txt',
|
||||
},
|
||||
{
|
||||
'name': 'Средний 50×50',
|
||||
'file': 'maze_medium.txt',
|
||||
},
|
||||
{
|
||||
'name': 'Большой 100×100',
|
||||
'file': 'maze_large.txt',
|
||||
},
|
||||
{
|
||||
'name': 'Пустой 50×50',
|
||||
'file': 'maze_empty.txt',
|
||||
},
|
||||
{
|
||||
'name': 'Без выхода 20×20',
|
||||
'file': 'maze_no_exit.txt',
|
||||
},
|
||||
]
|
||||
|
||||
all_strategies = {
|
||||
'BFS': BFSStrategy,
|
||||
'DFS': DFSStrategy,
|
||||
'A*': AStarStrategy,
|
||||
'Дейкстра': DijkstraStrategy,
|
||||
}
|
||||
|
||||
print("\nЗапуск экспериментов (5 прогонов каждого)...")
|
||||
results = run_experiments(mazes_config, all_strategies, runs=5)
|
||||
print_table(results)
|
||||
save_csv(results, "results.csv")
|
||||
|
||||
print("\nГотово!")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
import pandas as pd
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
|
||||
df = pd.read_csv('results.csv', encoding='utf-8-sig')
|
||||
|
||||
print(df)
|
||||
|
||||
# Получаем список лабиринтов
|
||||
mazes = df['лабиринт'].unique()
|
||||
|
||||
#График времени
|
||||
for maze in mazes:
|
||||
subset = df[df['лабиринт'] == maze]
|
||||
|
||||
plt.figure(figsize=(8, 5))
|
||||
plt.bar(subset['стратегия'], subset['время_мс'])
|
||||
|
||||
plt.title(f'Время выполнения — {maze}')
|
||||
plt.xlabel('Алгоритм')
|
||||
plt.ylabel('Время (мс)')
|
||||
|
||||
plt.tight_layout()
|
||||
plt.savefig(f'time_{maze}.png')
|
||||
plt.close()
|
||||
|
||||
#График посещенных клеток
|
||||
for maze in mazes:
|
||||
subset = df[df['лабиринт'] == maze]
|
||||
|
||||
plt.figure(figsize=(8, 5))
|
||||
plt.bar(subset['стратегия'], subset['посещено_клеток'])
|
||||
|
||||
plt.title(f'Посещённые клетки — {maze}')
|
||||
plt.xlabel('Алгоритм')
|
||||
plt.ylabel('Количество клеток')
|
||||
|
||||
plt.tight_layout()
|
||||
plt.savefig(f'visited_{maze}.png')
|
||||
plt.close()
|
||||
|
||||
# ===== ГРАФИК ДЛИНЫ ПУТИ =====
|
||||
for maze in mazes:
|
||||
subset = df[df['лабиринт'] == maze]
|
||||
|
||||
plt.figure(figsize=(8, 5))
|
||||
plt.bar(subset['стратегия'], subset['длина_пути'])
|
||||
|
||||
plt.title(f'Длина пути — {maze}')
|
||||
plt.xlabel('Алгоритм')
|
||||
plt.ylabel('Длина пути')
|
||||
|
||||
plt.tight_layout()
|
||||
plt.savefig(f'path_{maze}.png')
|
||||
plt.close()
|
||||
|
||||
print('Графики успешно построены!')
|
||||
50
zaharoves/задание 2/maze_empty.txt
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
##################################################
|
||||
#S #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# E#
|
||||
##################################################
|
||||
100
zaharoves/задание 2/maze_large.txt
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
####################################################################################################
|
||||
#S ## # ## ## # # # ## ####### ## # # ## ## ## # # ### # #
|
||||
# # # # # # # # # # # # # # # # ## ### # ## ## ## # ## ## #
|
||||
# # # # # # ## # # ## ## # # # # # ### # # # ### # # # # ## ##
|
||||
# ## ## ### # # # # # ### # # # ## # # # ## #
|
||||
# ## # ## #### # # # # # # ## ## #### ## # # # #
|
||||
### # # # # # # # # ### #### # # # ## # # # # # # # # #
|
||||
# # ## ## # ## ##### ## ###### # # ## # ## # # ## #### #
|
||||
# ## ## ## ## ## ## # # # # # # ## # # #
|
||||
## # # # # # # # # ## # # # # ## # # ###
|
||||
## # # # # # # # # ## ## # # # # ### ## # #
|
||||
## # # # # # ## # ## # ## # # #### ## # ## # # # ## ## # #
|
||||
# # # # # # ## # # ## # ## # # # # ### # # # # # # ### # #
|
||||
## # ## ## # # # # ### # ## ## # # ### ## # #
|
||||
## ## # # ## ### # # # # # # # # ## # # # # # #
|
||||
# ## # # ## # ### ## # # # ## # # # ## # # # #### # # # #
|
||||
# # # # # # # ## ## ## # # # # ### # # #
|
||||
# # # ### # # # # ## # ### # # #### # # # # # #
|
||||
# # # # # # ## # # # # # # # ## # ### # ##
|
||||
## # ### ## ## # # # # # # # # # # # # # # ### ## # #
|
||||
## ## ### # # # # ### ## # # # # ## # # # # # # # #
|
||||
##### # # # #### # ## # # # # # # ### # ## # # # # #
|
||||
## # # ### # # # # ## # # # # # # #### # # # ### #
|
||||
# # ## ## ### # # ## # ## ## ### # # # # # # # ###
|
||||
## ## # # # # # # # # # # ## ## # # ##
|
||||
# # # ### # # # # ## # # # ### # # # # # ## ## ## # ## #
|
||||
# # # # # ##### # ## # # # # # # # # # ## ## # # # ##
|
||||
# # # # # # # ## # ## # # # # # # ## ### ## # # ##### #
|
||||
# # # # # ## # # ## # # ## # ## # # # # ## # # # ## #
|
||||
## ## # # # # # # ### # ## ### ## # ### # ## # # # ## # # ## # #
|
||||
# # # # #### # ## #### # # # # # # # # # # ### # ## # #
|
||||
# # ## # # # # # # # # # # ###### # ## # ## # # # #### #### # #
|
||||
# # ##### # # # ### # # # # # # # # # ## ### # #
|
||||
# # # # # # # ## # # ## # # ## # # # # # # # ## # # ###
|
||||
## # ## # # # #### # # ## # ## ## # ## # # ## # #
|
||||
## # # # ## # # # # # # # # # # # ###### # ## # # ## ### # #### # #
|
||||
## # # # # # # # # # # # ## # # # # # # ## # # # ## # ##
|
||||
## # # # ### # # # # # # # # # # # # # # ###
|
||||
# # ### # # # # # ## ## ## # # ## # ### ### # # #
|
||||
# # # # # ## # # ## ## # # # # # # ## ## ## #
|
||||
# ### # # ### # # # # ### # # # # # # # ## # ##
|
||||
# # ### ## ## ## ## # # ### # ## # # # # ## ## # # # # # #
|
||||
# ## # # # ## # # # # ## # ### #### # ## ###### ### #
|
||||
# # # # ### ### # # ## # # # ### ## # ## # # ## ##
|
||||
# # # ### #### # # # # ### # # # ## ### ## # ## #### # #
|
||||
# ### ## # # # # # # # # ### # # # # ## # ### ### ## #
|
||||
# # # # # # # # # # # ### ## ### # ## # # # ## # #### # ## # #
|
||||
# # # # # # # # # # # ### # # # # # ## # # # # # # #
|
||||
# ## # # # # ## # # # ## ## ## # # ## # ## # # ## # ## #
|
||||
# # # ## # # # # ### # # # # # # ## # # # ## # ### ## # # #
|
||||
## # ## # ## ### ## # # # # ## # # # # # # #
|
||||
## ## # # ### # # # # # ## # # # # # ## # ## # # # #
|
||||
# # # ## # ### # ## # # ## # # # # # # # #
|
||||
# # # # # ## #### # # ### # ## # # ## # # ## #
|
||||
# # # # ## # ### # ## ## # # # # ### # # #
|
||||
# # # # # # # # # ## # ## ## ### ### # # ## # # # ## #
|
||||
# # # # ## # # ### ##### # # # # ## # # # # # ## # # #
|
||||
## # # # ## # # ## # ## ## # ## # ### # # # # #
|
||||
# ## ## # ### # ## ### # # ## # # # # # # # # # # # ###
|
||||
# ## # # # # # # # # # # # ## # # # # # # # # # # # ## #
|
||||
# # # # ## # # # # # ## # # ## # # ## # # # ### ### # # # ##
|
||||
# # # # ## # ## # # # # # # # ## # # ## # ### ##
|
||||
### # # ## ### # ## # # #### # # # # ##### # ## #### #
|
||||
# # # # # # # #### ## # ### ### # ## # # # # ## # # # # # # ###
|
||||
# #### # ## # # # # # # ## # # # # # # # #
|
||||
# ## # # # # # # ## # ## ## # ### #### # # # # ## #
|
||||
# # ## # ## # # # # ## ## # ## # ## #
|
||||
# # # # # # # ## # # # # # # ### ## ### # ## # # ###
|
||||
### # # # ##### # ## ## # # # ## # ## ## # # # # # #
|
||||
# # # # # # ## ##### # ### # ## # # # ## # ### #### # #
|
||||
# # ### # ## # # ### ## ## # ## # ### # ## ### # ###
|
||||
# ## ## ## # # # # # # ### # ## # # ## # # # #
|
||||
## ## ## # ## # ## # # # ## # ## # ## # ## # # # #
|
||||
# # # # # # # # ## # # # ####### # ## ## ## ##
|
||||
# # # # # # # # # ## # # # # # ## # # ### # ##
|
||||
# # ## #### # # # # # ## ### # ### # ### # ### ## # # #
|
||||
## # # ## # # # # # # # # ## # ##### # ## ##### #### ###
|
||||
# # # # ## # ## # # ## # # ### ## ## # ######
|
||||
# # ## # # # # # # # # # ## ## # ## ## ## # ## # #
|
||||
### #### # # ## # # # # # ## # # ## # # # #### # # ## # #
|
||||
# ## ## # # ## # ## ## # # ## # # # # # #### # #
|
||||
# ## # # # ## ### ## #### # # # # # # ## ### # # ##
|
||||
## # # # # # # # ## # ## ### # ## # ## # # #
|
||||
# # # # # # # # # ### # # # ## # # ## ## # #### #
|
||||
# # ## # # # # # # # # # # ## ### # # # ##
|
||||
## ## # ## # # # ## # # # # # #### # # ## ### #
|
||||
## # ## ## # # # # ### # # ## # # # ## ## # # # # ## #
|
||||
# ## # ## # # #### # # # # # # ## # # # # # # ### #
|
||||
# ## # #### # # ## # # # # ### ## # ## ### # ## ## ##
|
||||
# # # # # # ## # # # ## # #### # ##### # # # # # # #
|
||||
# # ## ## ### # ### ### # # #### # # # # ## # ## # # # # #### # #
|
||||
# # # # ## # # ## # # ## # # ## # ## # # # ## ## #
|
||||
# # ## # # # ## ## # ### ## # ## # # # # # # # ## # # #
|
||||
# # ## # ## ## ## # # ## # # # # # ## # # # # ### # #
|
||||
# # # ## # # # # # # # # # # # # ## # # # ## # # #
|
||||
## # ## # # # # ## # # ## # # # # # # ## # # # # # # # #
|
||||
# # ## # ## # ### # # ### # ## # # # ## # ### # ## # #
|
||||
# # # ## # # ## # # # ## # # #### ## # # # ### # ##
|
||||
# #### ## ### ### # # ### # # ## # # # ### # ####### # ## # E#
|
||||
####################################################################################################
|
||||
50
zaharoves/задание 2/maze_medium.txt
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
##################################################
|
||||
#S# # ## ## # # ## ### ########
|
||||
# # # # ##### # ## # # ### # #
|
||||
# # # # # # # # ### ## # # # # #
|
||||
# # ## ### ### # ## ## ## # # ## ##
|
||||
## # # # # ## # # ## ## # #
|
||||
# # # ## ### # # # ### # # # ##
|
||||
# ## # ## ### ### # # # # # #
|
||||
## # #### # # # ## # # # # ## ## #
|
||||
# ## # ## # ## # #### # # # # # #
|
||||
## ## ## ##### ## # # #
|
||||
# # ## ## # # # # # ## ### # ##
|
||||
#### # # # ### # # # # # # # #
|
||||
# # # # # ## # ## # ## # # ######
|
||||
# ## ###### # ## # ## # # # #
|
||||
# # ## #### ## ### ## ## ###
|
||||
## ## # ### # # ## # # #
|
||||
## ## # # # # # ## # # ## ##
|
||||
# # # # # # ## # #
|
||||
## # # ## # # ### # # # # # # # #
|
||||
## # ## ## # ## # # #
|
||||
# ### ## # # # # # # # # ###
|
||||
# # ## # ## ## # #### ## ### # # ##
|
||||
# ## ## # # # ## # # # ## ## #
|
||||
# # ## # ## ### # # # # ### #
|
||||
# # # # # # ####### # # ## ### ##
|
||||
# # #### # # ### # ## ### # # # #
|
||||
# # ### # ## # # # # # # ## #
|
||||
# # ## ### # ## # # # # # # #
|
||||
# # ## # # # # # ### # # ## # ##
|
||||
### #### # # ## # # # ## # ## #
|
||||
## # #### # # ## # ## # #
|
||||
# # # # # ## ## ## # # #
|
||||
# # ### ### # ## # # # ###
|
||||
#### # # ## # # ## # ### # # #
|
||||
# # # #### # ## # ## # # # ##
|
||||
# # # # # ### # # ## # # #
|
||||
# # # # #### #### ## # ## # ### ## #
|
||||
### ## # # # # # # # # # # # #
|
||||
# # # # # # ### ## # ###### #
|
||||
## # ### ## # ### ## # # # ## ## # #
|
||||
# # ### # # ## #### #
|
||||
#### # #### # ## # # # ### ### ##
|
||||
# # # ### # ## # # # # # # #
|
||||
# # # ### ## # # # ## # #
|
||||
## # # # # #### # # # ### #
|
||||
## ## ## # # ### # # # # ## #
|
||||
# # ## #### ### # # # # # # ## # ### #
|
||||
# ### # ## ## ## # # # # # E#
|
||||
##################################################
|
||||
20
zaharoves/задание 2/maze_no_exit.txt
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
####################
|
||||
#S### # ## ## # #
|
||||
# # # ## #
|
||||
# ####### ## #
|
||||
# # # ## ## #
|
||||
# ## # # ###
|
||||
## # # # # #
|
||||
# # # # # # #
|
||||
## # # # # # #
|
||||
# # # ## ### #
|
||||
## ## ## ## #
|
||||
# # ## ## ##
|
||||
# # # # # #
|
||||
# ## # # ## #
|
||||
### # # # # #
|
||||
## ### # ##
|
||||
# # ### # # # ##
|
||||
# ## # ## ###
|
||||
# ### # #E#
|
||||
####################
|
||||
10
zaharoves/задание 2/maze_small.txt
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
##########
|
||||
#S #
|
||||
# # ######
|
||||
# # # #
|
||||
# # # # #
|
||||
# # # # #
|
||||
# #
|
||||
#### #####
|
||||
# E #
|
||||
##########
|
||||
BIN
zaharoves/задание 2/path_Без выхода 20×20.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
zaharoves/задание 2/path_Большой 100×100.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
zaharoves/задание 2/path_Маленький 10×10.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
zaharoves/задание 2/path_Пустой 50×50.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
zaharoves/задание 2/path_Средний 50×50.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
21
zaharoves/задание 2/results.csv
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
лабиринт,стратегия,время_мс,посещено_клеток,длина_пути
|
||||
Маленький 10×10,BFS,0.0791,39,14
|
||||
Маленький 10×10,DFS,0.0605,30,18
|
||||
Маленький 10×10,A*,0.1048,34,14
|
||||
Маленький 10×10,Дейкстра,0.114,39,14
|
||||
Средний 50×50,BFS,1.9826,1305,95
|
||||
Средний 50×50,DFS,0.8278,529,199
|
||||
Средний 50×50,A*,0.909,303,95
|
||||
Средний 50×50,Дейкстра,3.9224,1305,95
|
||||
Большой 100×100,BFS,10.3434,6583,195
|
||||
Большой 100×100,DFS,8.13,5190,521
|
||||
Большой 100×100,A*,6.0666,1978,195
|
||||
Большой 100×100,Дейкстра,20.0361,6583,195
|
||||
Пустой 50×50,BFS,3.7195,2304,95
|
||||
Пустой 50×50,DFS,2.5223,1223,1129
|
||||
Пустой 50×50,A*,7.1408,2304,95
|
||||
Пустой 50×50,Дейкстра,7.6677,2304,95
|
||||
Без выхода 20×20,BFS,0.2898,193,0
|
||||
Без выхода 20×20,DFS,0.2849,193,0
|
||||
Без выхода 20×20,A*,0.5278,193,0
|
||||
Без выхода 20×20,Дейкстра,0.5431,193,0
|
||||
|
BIN
zaharoves/задание 2/time_Без выхода 20×20.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
zaharoves/задание 2/time_Большой 100×100.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
zaharoves/задание 2/time_Маленький 10×10.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
zaharoves/задание 2/time_Пустой 50×50.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
zaharoves/задание 2/time_Средний 50×50.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
zaharoves/задание 2/visited_Без выхода 20×20.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
zaharoves/задание 2/visited_Большой 100×100.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
zaharoves/задание 2/visited_Маленький 10×10.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
zaharoves/задание 2/visited_Пустой 50×50.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
zaharoves/задание 2/visited_Средний 50×50.png
Normal file
|
After Width: | Height: | Size: 18 KiB |