2026-rff_mp/skorohodovsa/task_2/source/models/base.py

258 lines
9.3 KiB
Python
Raw Normal View History

from typing import Optional
from source.settings import cell_mapping
class Cell:
"""Представляет одну клетку поля лабиринта.
Каждая клетка хранит свои координаты и один из четырёх возможных
типов: стена, старт, выход или пустая клетка. Типы взаимоисключают
друг друга: установка одного автоматически сбрасывает остальные.
"""
def __init__(
self,
x: int,
y: int,
is_wall: bool = False,
is_start: bool = False,
is_exit: bool = False,
):
"""Инициализирует клетку с заданными координатами и типом.
Args:
x: Координата клетки по оси X.
y: Координата клетки по оси Y.
is_wall: Если True клетка является стеной.
is_start: Если True клетка является стартом.
is_exit: Если True клетка является выходом.
"""
self.x = x
self.y = y
self._is_wall = is_wall
self._is_start = is_start
self._is_exit = is_exit
def is_possible(self) -> bool:
"""Проверяет, можно ли переместиться в эту клетку.
Returns:
True, если клетка не является стеной, иначе False.
"""
return not self._is_wall
@property
def is_wall(self) -> bool:
"""True, если клетка является стеной."""
return self._is_wall
@property
def is_start(self) -> bool:
"""True, если клетка является стартовой позицией."""
return self._is_start
@property
def is_exit(self) -> bool:
"""True, если клетка является выходом из лабиринта."""
return self._is_exit
def _clear_flags(self) -> None:
"""Сбрасывает все флаги типа клетки в False."""
self._is_wall = False
self._is_start = False
self._is_exit = False
@is_wall.setter
def is_wall(self, value: bool) -> None:
"""Устанавливает флаг стены, сбрасывая остальные типы при value=True.
Args:
value: Новое значение флага стены.
"""
if value:
self._clear_flags()
self._is_wall = value
@is_start.setter
def is_start(self, value: bool) -> None:
"""Устанавливает флаг старта, сбрасывая остальные типы при value=True.
Args:
value: Новое значение флага старта.
"""
if value:
self._clear_flags()
self._is_start = value
@is_exit.setter
def is_exit(self, value: bool) -> None:
"""Устанавливает флаг выхода, сбрасывая остальные типы при value=True.
Args:
value: Новое значение флага выхода.
"""
if value:
self._clear_flags()
self._is_exit = value
def _get_type_cell(self) -> str:
"""Возвращает символ клетки согласно cell_mapping.
Returns:
Строковый символ, соответствующий текущему типу клетки.
"""
if self._is_wall:
return cell_mapping["wall"]
if self._is_start:
return cell_mapping["start"]
if self._is_exit:
return cell_mapping["exit"]
return cell_mapping["empty"]
def __str__(self) -> str:
return self._get_type_cell()
def __repr__(self) -> str:
return f"Cell(x={self.x}, y={self.y}, '{self._get_type_cell()}')"
class Maze:
"""Представляет двумерный лабиринт из клеток Cell.
Лабиринт хранится как список списков клеток. Доступ к отдельным
клеткам и их изменение возможны через индексацию вида maze[row, col].
"""
def __init__(self, size: tuple[int, int] = (10, 10)):
"""Создаёт пустой лабиринт заданного размера.
Args:
size: Кортеж (width, height) ширина и высота лабиринта в клетках.
"""
self._width, self._height = size
self._map: list[list[Cell]] = [
[Cell(x, y) for x in range(self._width)] for y in range(self._height)
]
def _check_point_in_map(self, x: int, y: int) -> bool:
"""Проверяет, находится ли точка в границах лабиринта.
Args:
x: Координата по оси X.
y: Координата по оси Y.
Returns:
True, если точка (x, y) находится внутри лабиринта.
"""
return 0 <= x < self._width and 0 <= y < self._height
def get_cell(self, x: int, y: int) -> Optional[Cell]:
"""Возвращает клетку по координатам или None, если координаты вне границ.
Args:
x: Координата по оси X.
y: Координата по оси Y.
Returns:
Объект Cell, если координаты корректны, иначе None.
"""
if not self._check_point_in_map(x, y):
return None
return self._map[y][x]
def get_neighbors(self, x: int, y: int) -> Optional[list[Cell]]:
"""Возвращает список проходимых соседей клетки (вверх, вправо, вниз, влево).
Args:
x: Координата клетки по оси X.
y: Координата клетки по оси Y.
Returns:
Список проходимых соседних клеток, или None если (x, y) вне границ.
"""
if not self._check_point_in_map(x, y):
return None
deltas = ((0, 1), (1, 0), (0, -1), (-1, 0))
neighbors = []
for dx, dy in deltas:
cell = self.get_cell(x + dx, y + dy)
if cell is not None and cell.is_possible():
neighbors.append(cell)
return neighbors
@property
def start(self) -> Optional[Cell]:
for y in range(self._height):
for x in range(self._width):
if self[y, x].is_start:
return self[y, x]
return None
@property
def exit(self) -> Optional[Cell]:
for y in range(self._height):
for x in range(self._width):
if self[y, x].is_exit:
return self[y, x]
return None
@property
def shape(self) -> tuple[int, int]:
return self._height, self._width
def __getitem__(self, index: tuple[int, int]) -> Cell:
"""Возвращает клетку по индексу [row, col].
Args:
index: Кортеж (row, col) строка и столбец.
Returns:
Объект Cell в позиции (row, col).
Raises:
IndexError: Если индекс выходит за пределы лабиринта.
"""
row, col = index
if not self._check_point_in_map(col, row):
raise IndexError(f"Индекс ({row}, {col}) выходит за пределы лабиринта")
return self._map[row][col]
def __setitem__(self, index: tuple[int, int], value: str) -> None:
"""Устанавливает тип клетки по индексу [row, col] через символ из cell_mapping.
Args:
index: Кортеж (row, col) строка и столбец.
value: Символ типа клетки согласно cell_mapping.
Raises:
IndexError: Если индекс выходит за пределы лабиринта.
ValueError: Если символ не найден в cell_mapping.
"""
row, col = index
if not self._check_point_in_map(col, row):
raise IndexError(f"Индекс ({row}, {col}) выходит за пределы лабиринта")
cell = self._map[row][col]
cell_type = next(
(t for t, s in cell_mapping.items() if s == value),
None,
)
if cell_type is None:
raise ValueError(f"Символ '{value}' не соответствует ни одному типу клетки")
if cell_type == "empty":
cell._clear_flags()
else:
setattr(cell, f"is_{cell_type}", True)
def __str__(self) -> str:
return "\n".join(
"".join(str(self._map[y][x]) for x in range(self._width))
for y in range(self._height)
)