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) )