122 lines
3.7 KiB
Python
122 lines
3.7 KiB
Python
|
|
import time
|
|||
|
|
from abc import ABC, abstractmethod
|
|||
|
|
|
|||
|
|
class SearchStats:
|
|||
|
|
def __init__(self, time_ms, visited_cells, path_length, path):
|
|||
|
|
self.time_ms = time_ms
|
|||
|
|
self.visited_cells = visited_cells
|
|||
|
|
self.path_length = path_length
|
|||
|
|
self.path = path
|
|||
|
|
|
|||
|
|
def __repr__(self):
|
|||
|
|
return (f"SearchStats(time={self.time_ms:.3f}ms, "
|
|||
|
|
f"visited={self.visited_cells}, "
|
|||
|
|
f"path_len={self.path_length})")
|
|||
|
|
|
|||
|
|
class Observer(ABC):
|
|||
|
|
|
|||
|
|
@abstractmethod
|
|||
|
|
def update(self, event, data=None):
|
|||
|
|
pass
|
|||
|
|
|
|||
|
|
|
|||
|
|
class ConsoleView(Observer):
|
|||
|
|
|
|||
|
|
def update(self, event, data=None):
|
|||
|
|
if event == 'maze_loaded':
|
|||
|
|
print(f"\n[ConsoleView] Лабиринт загружен: "
|
|||
|
|
f"{data['width']}×{data['height']}")
|
|||
|
|
|
|||
|
|
elif event == 'path_found':
|
|||
|
|
stats = data['stats']
|
|||
|
|
strategy_name = data['strategy']
|
|||
|
|
if stats.path_length > 0:
|
|||
|
|
print(f"\n[ConsoleView] [{strategy_name}] Путь найден! "
|
|||
|
|
f"Длина: {stats.path_length}, "
|
|||
|
|
f"Посещено клеток: {stats.visited_cells}, "
|
|||
|
|
f"Время: {stats.time_ms:.3f} мс")
|
|||
|
|
else:
|
|||
|
|
print(f"\n[ConsoleView] [{strategy_name}] Путь не найден. "
|
|||
|
|
f"Посещено клеток: {stats.visited_cells}")
|
|||
|
|
|
|||
|
|
elif event == 'move':
|
|||
|
|
print(f"[ConsoleView] Игрок переместился в "
|
|||
|
|
f"({data['x']}, {data['y']})")
|
|||
|
|
|
|||
|
|
class MazeSolver:
|
|||
|
|
def __init__(self, maze, strategy=None):
|
|||
|
|
self.maze = maze
|
|||
|
|
self.strategy = strategy
|
|||
|
|
self._observers = []
|
|||
|
|
|
|||
|
|
def set_strategy(self, strategy):
|
|||
|
|
self.strategy = strategy
|
|||
|
|
|
|||
|
|
def add_observer(self, observer):
|
|||
|
|
self._observers.append(observer)
|
|||
|
|
|
|||
|
|
def _notify(self, event, data=None):
|
|||
|
|
for obs in self._observers:
|
|||
|
|
obs.update(event, data)
|
|||
|
|
|
|||
|
|
def solve(self):
|
|||
|
|
if self.strategy is None:
|
|||
|
|
raise RuntimeError("Стратегия не задана. Используйте set_strategy().")
|
|||
|
|
|
|||
|
|
start = time.perf_counter()
|
|||
|
|
path = self.strategy.find_path(self.maze, self.maze.start, self.maze.exit)
|
|||
|
|
end = time.perf_counter()
|
|||
|
|
|
|||
|
|
stats = SearchStats(
|
|||
|
|
time_ms=(end - start) * 1000,
|
|||
|
|
visited_cells=getattr(self.strategy, 'visited_count', 0),
|
|||
|
|
path_length=len(path),
|
|||
|
|
path=path
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
self._notify('path_found', {
|
|||
|
|
'stats': stats,
|
|||
|
|
'strategy': type(self.strategy).__name__
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
return stats
|
|||
|
|
|
|||
|
|
class Command(ABC):
|
|||
|
|
@abstractmethod
|
|||
|
|
def execute(self):
|
|||
|
|
pass
|
|||
|
|
|
|||
|
|
@abstractmethod
|
|||
|
|
def undo(self):
|
|||
|
|
pass
|
|||
|
|
|
|||
|
|
|
|||
|
|
class Player:
|
|||
|
|
def __init__(self, start_cell):
|
|||
|
|
self.current_cell = start_cell
|
|||
|
|
|
|||
|
|
def move_to(self, cell):
|
|||
|
|
self.current_cell = cell
|
|||
|
|
|
|||
|
|
|
|||
|
|
class MoveCommand(Command):
|
|||
|
|
def __init__(self, player, target_cell, observers=None):
|
|||
|
|
self.player = player
|
|||
|
|
self.target_cell = target_cell
|
|||
|
|
self.previous_cell = None
|
|||
|
|
self._observers = observers or []
|
|||
|
|
|
|||
|
|
def execute(self):
|
|||
|
|
self.previous_cell = self.player.current_cell
|
|||
|
|
self.player.move_to(self.target_cell)
|
|||
|
|
for obs in self._observers:
|
|||
|
|
obs.update('move', {'x': self.target_cell.x,
|
|||
|
|
'y': self.target_cell.y})
|
|||
|
|
|
|||
|
|
def undo(self):
|
|||
|
|
if self.previous_cell is not None:
|
|||
|
|
self.player.move_to(self.previous_cell)
|
|||
|
|
for obs in self._observers:
|
|||
|
|
obs.update('move', {'x': self.previous_cell.x,
|
|||
|
|
'y': self.previous_cell.y})
|