2026-05-24 15:00:14 +00:00
|
|
|
|
from abc import ABC, abstractmethod
|
|
|
|
|
|
from typing import Optional
|
|
|
|
|
|
|
|
|
|
|
|
from source.models.base import Maze, Cell
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------- #
|
|
|
|
|
|
# Игрок #
|
|
|
|
|
|
# ---------------------------------------------------------------------------- #
|
|
|
|
|
|
|
2026-05-25 07:23:00 +00:00
|
|
|
|
|
2026-05-24 15:00:14 +00:00
|
|
|
|
class Player:
|
|
|
|
|
|
"""Хранит текущее положение игрока в лабиринте.
|
|
|
|
|
|
|
|
|
|
|
|
Attributes:
|
|
|
|
|
|
cell: Текущая клетка игрока.
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, cell: Cell) -> None:
|
|
|
|
|
|
"""Инициализирует игрока на заданной клетке.
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
cell: Начальная клетка игрока.
|
|
|
|
|
|
"""
|
|
|
|
|
|
self.cell = cell
|
|
|
|
|
|
|
|
|
|
|
|
def __repr__(self) -> str:
|
|
|
|
|
|
return f"Player(x={self.cell.x}, y={self.cell.y})"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------- #
|
|
|
|
|
|
# Интерфейс команды #
|
|
|
|
|
|
# ---------------------------------------------------------------------------- #
|
|
|
|
|
|
|
2026-05-25 07:23:00 +00:00
|
|
|
|
|
2026-05-24 15:00:14 +00:00
|
|
|
|
class Command(ABC):
|
|
|
|
|
|
"""Интерфейс команды с поддержкой отмены."""
|
|
|
|
|
|
|
|
|
|
|
|
@abstractmethod
|
|
|
|
|
|
def execute(self) -> bool:
|
|
|
|
|
|
"""Выполняет команду.
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
True если команда выполнена успешно, False иначе.
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
@abstractmethod
|
|
|
|
|
|
def undo(self) -> None:
|
|
|
|
|
|
"""Отменяет команду, восстанавливая предыдущее состояние."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------- #
|
|
|
|
|
|
# Команда перемещения #
|
|
|
|
|
|
# ---------------------------------------------------------------------------- #
|
|
|
|
|
|
|
|
|
|
|
|
DIRECTIONS = {
|
|
|
|
|
|
"w": (0, -1),
|
2026-05-25 07:23:00 +00:00
|
|
|
|
"s": (0, 1),
|
2026-05-24 15:00:14 +00:00
|
|
|
|
"a": (-1, 0),
|
2026-05-25 07:23:00 +00:00
|
|
|
|
"d": (1, 0),
|
2026-05-24 15:00:14 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class MoveCommand(Command):
|
|
|
|
|
|
"""Перемещает игрока в заданном направлении.
|
|
|
|
|
|
|
|
|
|
|
|
Сохраняет предыдущую клетку для возможности отмены хода.
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, player: Player, direction: str, maze: Maze) -> None:
|
|
|
|
|
|
"""Инициализирует команду перемещения.
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
player: Объект игрока.
|
|
|
|
|
|
direction: Направление ('w', 'a', 's', 'd').
|
|
|
|
|
|
maze: Объект лабиринта для проверки проходимости.
|
|
|
|
|
|
|
|
|
|
|
|
Raises:
|
|
|
|
|
|
ValueError: Если направление не распознано.
|
|
|
|
|
|
"""
|
|
|
|
|
|
if direction not in DIRECTIONS:
|
2026-05-25 07:23:00 +00:00
|
|
|
|
raise ValueError(
|
|
|
|
|
|
f"Неизвестное направление '{direction}'. Используй: w/a/s/d"
|
|
|
|
|
|
)
|
2026-05-24 15:00:14 +00:00
|
|
|
|
|
2026-05-25 07:23:00 +00:00
|
|
|
|
self._player = player
|
2026-05-24 15:00:14 +00:00
|
|
|
|
self._direction = direction
|
2026-05-25 07:23:00 +00:00
|
|
|
|
self._maze = maze
|
2026-05-24 15:00:14 +00:00
|
|
|
|
self._prev_cell: Optional[Cell] = None
|
|
|
|
|
|
|
|
|
|
|
|
def execute(self) -> bool:
|
|
|
|
|
|
"""Перемещает игрока если целевая клетка проходима.
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
True если перемещение выполнено, False если клетка непроходима.
|
|
|
|
|
|
"""
|
|
|
|
|
|
dx, dy = DIRECTIONS[self._direction]
|
|
|
|
|
|
target = self._maze.get_cell(
|
|
|
|
|
|
self._player.cell.x + dx,
|
|
|
|
|
|
self._player.cell.y + dy,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if target is None or not target.is_possible():
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
2026-05-25 07:23:00 +00:00
|
|
|
|
self._prev_cell = self._player.cell
|
2026-05-24 15:00:14 +00:00
|
|
|
|
self._player.cell = target
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
def undo(self) -> None:
|
|
|
|
|
|
"""Возвращает игрока на предыдущую клетку."""
|
|
|
|
|
|
if self._prev_cell is not None:
|
|
|
|
|
|
self._player.cell = self._prev_cell
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------------- #
|
|
|
|
|
|
# История команд #
|
|
|
|
|
|
# ---------------------------------------------------------------------------- #
|
|
|
|
|
|
|
2026-05-25 07:23:00 +00:00
|
|
|
|
|
2026-05-24 15:00:14 +00:00
|
|
|
|
class CommandHistory:
|
|
|
|
|
|
"""Хранит историю выполненных команд и позволяет отменять их.
|
|
|
|
|
|
|
|
|
|
|
|
Example:
|
|
|
|
|
|
history = CommandHistory()
|
|
|
|
|
|
cmd = MoveCommand(player, 'w', maze)
|
|
|
|
|
|
if cmd.execute():
|
|
|
|
|
|
history.push(cmd)
|
|
|
|
|
|
|
|
|
|
|
|
history.undo() # отменяет последний успешный ход
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self) -> None:
|
|
|
|
|
|
self._history: list[Command] = []
|
|
|
|
|
|
|
|
|
|
|
|
def push(self, command: Command) -> None:
|
|
|
|
|
|
"""Добавляет выполненную команду в историю.
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
command: Успешно выполненная команда.
|
|
|
|
|
|
"""
|
|
|
|
|
|
self._history.append(command)
|
|
|
|
|
|
|
|
|
|
|
|
def undo(self) -> bool:
|
|
|
|
|
|
"""Отменяет последнюю команду из истории.
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
True если отмена выполнена, False если история пуста.
|
|
|
|
|
|
"""
|
|
|
|
|
|
if not self._history:
|
|
|
|
|
|
print("Нечего отменять.")
|
|
|
|
|
|
return False
|
|
|
|
|
|
self._history.pop().undo()
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
def clear(self) -> None:
|
|
|
|
|
|
"""Очищает историю команд."""
|
2026-05-25 07:23:00 +00:00
|
|
|
|
self._history.clear()
|