2026-rff_mp/SimonovaMS/lab2/visualization.py

160 lines
4.4 KiB
Python
Raw Normal View History

2026-05-02 03:49:44 +00:00
from abc import ABC, abstractmethod
from typing import List, Optional, Set
from enum import Enum
from maze_model import Maze, Cell
class EventType(Enum):
PATH_FOUND = "path_found"
MOVE = "move"
MAZE_LOADED = "maze_loaded"
SOLVE_START = "solve_start"
SOLVE_END = "solve_end"
class Observer(ABC):
@abstractmethod
def update(self, event_type: EventType, data: any) -> None:
pass
class ConsoleView(Observer):
def __init__(self):
self.last_path: Optional[List[Cell]] = None
def update(self, event_type: EventType, data: any) -> None:
if event_type == EventType.MAZE_LOADED:
print("Лабиринт загружен")
elif event_type == EventType.SOLVE_START:
print("Начинается поиск пути...")
elif event_type == EventType.SOLVE_END:
print(f"Поиск завершён. Статистика: {data}")
elif event_type == EventType.PATH_FOUND:
self.last_path = data
def render(self, maze: Maze, player_pos: Optional[Cell] = None,
path: Optional[List[Cell]] = None) -> None: #рисует лаб
import os
os.system('cls' if os.name == 'nt' else 'clear')
path_set = set(path) if path else set()
# Верх
print("" + "" * maze.width + "")
for y in range(maze.height):
line = ""
for x in range(maze.width):
cell = maze.get_cell(x, y)
if player_pos and player_pos.x == x and player_pos.y == y:
line += "P"
elif cell == maze.start:
line += "S"
elif cell == maze.exit:
line += "E"
elif cell is not None and cell.is_wall:
line += "#"
elif path and cell in path_set:
line += "."
else:
line += " "
line += ""
print(line)
# Низ
print("" + "" * maze.width + "")
if path:
print(f"\nПуть найден! Длина: {len(path)} шагов")
elif path == []:
print("\nПуть не найден:(")
class Player:
def __init__(self, start_cell: Cell):
self.current_cell = start_cell
def move_to(self, cell: Cell) -> None:
self.current_cell = cell
def get_position(self) -> Cell:
return self.current_cell
class Direction(Enum):
UP = (0, -1)
DOWN = (0, 1)
LEFT = (-1, 0)
RIGHT = (1, 0)
class Command(ABC):
@abstractmethod
def execute(self) -> None:
pass
@abstractmethod
def undo(self) -> None:
pass
class MoveCommand(Command):
def __init__(self, player: Player, maze: Maze, direction: Direction):
self.player = player
self.maze = maze
self.direction = direction
self.previous_cell = player.current_cell
def execute(self) -> None:
dx, dy = self.direction.value
new_x = self.player.current_cell.x + dx
new_y = self.player.current_cell.y + dy
new_cell = self.maze.get_cell(new_x, new_y)
if new_cell and new_cell.is_passable():
self.previous_cell = self.player.current_cell
self.player.move_to(new_cell)
return True
return False
def undo(self) -> None:
self.player.move_to(self.previous_cell)
class GameController:
def __init__(self, maze: Maze, view: ConsoleView):
if maze.start is None:
raise ValueError("Лабиринт не имеет стартовой клетки")
self.maze = maze
self.view = view
self.player = Player(maze.start)
self.command_history: List[Command] = []
self.found_path: Optional[List[Cell]] = None
def move(self, direction: Direction) -> bool:
command = MoveCommand(self.player, self.maze, direction)
if command.execute():
self.command_history.append(command)
self._render()
return True
return False
def undo(self) -> None:
if self.command_history:
command = self.command_history.pop()
command.undo()
self._render()
def set_path(self, path: List[Cell]) -> None:
self.found_path = path
self._render()
def _render(self) -> None:
self.view.render(self.maze, self.player.get_position(), self.found_path)