238 lines
8.7 KiB
Python
238 lines
8.7 KiB
Python
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
|
||
|
||
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)
|
||
) |