[2] Добавление этапа 5
This commit is contained in:
parent
86b31473f3
commit
27c2f99467
13
skorohodovsa/task_2/source/view/__init__.py
Normal file
13
skorohodovsa/task_2/source/view/__init__.py
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
from source.view.observer import Observer, ConsoleView, Event
|
||||
from source.view.command import Player, Command, MoveCommand, CommandHistory, DIRECTIONS
|
||||
|
||||
__all__ = [
|
||||
"Observer",
|
||||
"ConsoleView",
|
||||
"Event",
|
||||
"Player",
|
||||
"Command",
|
||||
"MoveCommand",
|
||||
"CommandHistory",
|
||||
"DIRECTIONS",
|
||||
]
|
||||
153
skorohodovsa/task_2/source/view/command.py
Normal file
153
skorohodovsa/task_2/source/view/command.py
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
from abc import ABC, abstractmethod
|
||||
from typing import Optional
|
||||
|
||||
from source.models.base import Maze, Cell
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------- #
|
||||
# Игрок #
|
||||
# ---------------------------------------------------------------------------- #
|
||||
|
||||
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})"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------- #
|
||||
# Интерфейс команды #
|
||||
# ---------------------------------------------------------------------------- #
|
||||
|
||||
class Command(ABC):
|
||||
"""Интерфейс команды с поддержкой отмены."""
|
||||
|
||||
@abstractmethod
|
||||
def execute(self) -> bool:
|
||||
"""Выполняет команду.
|
||||
|
||||
Returns:
|
||||
True если команда выполнена успешно, False иначе.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def undo(self) -> None:
|
||||
"""Отменяет команду, восстанавливая предыдущее состояние."""
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------- #
|
||||
# Команда перемещения #
|
||||
# ---------------------------------------------------------------------------- #
|
||||
|
||||
DIRECTIONS = {
|
||||
"w": (0, -1),
|
||||
"s": (0, 1),
|
||||
"a": (-1, 0),
|
||||
"d": (1, 0),
|
||||
}
|
||||
|
||||
|
||||
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:
|
||||
raise ValueError(f"Неизвестное направление '{direction}'. Используй: w/a/s/d")
|
||||
|
||||
self._player = player
|
||||
self._direction = direction
|
||||
self._maze = maze
|
||||
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
|
||||
|
||||
self._prev_cell = self._player.cell
|
||||
self._player.cell = target
|
||||
return True
|
||||
|
||||
def undo(self) -> None:
|
||||
"""Возвращает игрока на предыдущую клетку."""
|
||||
if self._prev_cell is not None:
|
||||
self._player.cell = self._prev_cell
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------- #
|
||||
# История команд #
|
||||
# ---------------------------------------------------------------------------- #
|
||||
|
||||
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:
|
||||
"""Очищает историю команд."""
|
||||
self._history.clear()
|
||||
111
skorohodovsa/task_2/source/view/observer.py
Normal file
111
skorohodovsa/task_2/source/view/observer.py
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
from source.models.base import Maze, Cell
|
||||
from source.settings import cell_mapping
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------- #
|
||||
# События #
|
||||
# ---------------------------------------------------------------------------- #
|
||||
|
||||
@dataclass
|
||||
class Event:
|
||||
"""Событие, передаваемое наблюдателям.
|
||||
|
||||
Attributes:
|
||||
type: Тип события ('maze_loaded', 'path_found', 'move', 'no_path').
|
||||
payload: Дополнительные данные события.
|
||||
"""
|
||||
type: str
|
||||
payload: dict = None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------- #
|
||||
# Интерфейс наблюдателя #
|
||||
# ---------------------------------------------------------------------------- #
|
||||
|
||||
class Observer(ABC):
|
||||
"""Интерфейс наблюдателя за событиями лабиринта."""
|
||||
|
||||
@abstractmethod
|
||||
def update(self, event: Event) -> None:
|
||||
"""Обрабатывает входящее событие.
|
||||
|
||||
Args:
|
||||
event: Объект события с типом и данными.
|
||||
"""
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Консольный наблюдатель
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class ConsoleView(Observer):
|
||||
"""Отображает состояние лабиринта и события в консоли."""
|
||||
|
||||
# Символ игрока на карте
|
||||
PLAYER_SYMBOL = "P"
|
||||
PATH_SYMBOL = "·"
|
||||
|
||||
def update(self, event: Event) -> None:
|
||||
"""Реагирует на события и выводит информацию в консоль.
|
||||
|
||||
Args:
|
||||
event: Объект события.
|
||||
"""
|
||||
match event.type:
|
||||
case "maze_loaded":
|
||||
print("Лабиринт загружен.")
|
||||
self.render(event.payload["maze"])
|
||||
case "path_found":
|
||||
print(f"Путь найден! Длина: {event.payload['length']} шагов.")
|
||||
self.render(
|
||||
event.payload["maze"],
|
||||
path=event.payload["path"],
|
||||
)
|
||||
case "no_path":
|
||||
print("Путь не найден.")
|
||||
case "move":
|
||||
print(f"Ход: {event.payload['direction']}")
|
||||
self.render(
|
||||
event.payload["maze"],
|
||||
player=event.payload["player_cell"],
|
||||
path=event.payload.get("path"),
|
||||
)
|
||||
case _:
|
||||
print(f"[событие] {event.type}")
|
||||
|
||||
def render(
|
||||
self,
|
||||
maze: Maze,
|
||||
player: Optional[Cell] = None,
|
||||
path: Optional[list[Cell]] = None,
|
||||
) -> None:
|
||||
"""Рисует лабиринт в консоли.
|
||||
|
||||
Путь отмечается символом '·', позиция игрока — 'P'.
|
||||
|
||||
Args:
|
||||
maze: Объект лабиринта.
|
||||
player: Текущая клетка игрока (опционально).
|
||||
path: Список клеток найденного пути (опционально).
|
||||
"""
|
||||
path_set = set(path) if path else set()
|
||||
rows, cols = maze.shape
|
||||
|
||||
print("+" + "─" * cols + "+")
|
||||
for y in range(rows):
|
||||
row_str = "|"
|
||||
for x in range(cols):
|
||||
cell = maze[y, x]
|
||||
if player and cell is player:
|
||||
row_str += self.PLAYER_SYMBOL
|
||||
elif cell in path_set:
|
||||
row_str += self.PATH_SYMBOL
|
||||
else:
|
||||
row_str += str(cell)
|
||||
row_str += "|"
|
||||
print(row_str)
|
||||
print("+" + "─" * cols + "+")
|
||||
57
skorohodovsa/task_2/test/play.py
Normal file
57
skorohodovsa/task_2/test/play.py
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
from source.build.builder import TextFileBuilder
|
||||
from source.models.base import Maze
|
||||
from source.view.observer import ConsoleView, Event
|
||||
from source.view.command import Player, MoveCommand, CommandHistory
|
||||
|
||||
|
||||
maze: Maze = TextFileBuilder().build_from_file("source/templates/10x10_path_v1.txt")
|
||||
|
||||
rows, cols = maze.shape
|
||||
|
||||
start = None
|
||||
for y in range(rows):
|
||||
for x in range(cols):
|
||||
if maze[y, x].is_start:
|
||||
start = maze[y, x]
|
||||
break
|
||||
|
||||
if start is None:
|
||||
print("Стартовая клетка не найдена!")
|
||||
exit()
|
||||
|
||||
player = Player(start)
|
||||
history = CommandHistory()
|
||||
view = ConsoleView()
|
||||
|
||||
view.update(Event("maze_loaded", {"maze": maze}))
|
||||
print("Управление: w/a/s/d — движение, z — отмена, q — выход\n")
|
||||
|
||||
while True:
|
||||
key = input(">>> ").strip().lower()
|
||||
|
||||
if key == 'q':
|
||||
print("Выход.")
|
||||
break
|
||||
|
||||
elif key == 'z':
|
||||
if history.undo():
|
||||
print("Ход отменён.")
|
||||
view.render(maze, player=player.cell)
|
||||
|
||||
elif key in ('w', 'a', 's', 'd'):
|
||||
cmd = MoveCommand(player, key, maze)
|
||||
if cmd.execute():
|
||||
history.push(cmd)
|
||||
view.update(Event("move", {
|
||||
"maze": maze,
|
||||
"player_cell": player.cell,
|
||||
"direction": key,
|
||||
}))
|
||||
if player.cell.is_exit:
|
||||
print("Выход найден! Победа!")
|
||||
break
|
||||
else:
|
||||
print("Туда нельзя — стена или граница.")
|
||||
|
||||
else:
|
||||
print("Неизвестная команда. Используй: w/a/s/d, z, q")
|
||||
Loading…
Reference in New Issue
Block a user