[2] Добавление docstring к Cell, Maze

Добавлен интерфейс для алгоритмов
This commit is contained in:
SerKin0 2026-05-23 16:30:04 +03:00
parent de1170df68
commit 535c706af8
2 changed files with 179 additions and 94 deletions

View File

@ -0,0 +1,49 @@
from abc import ABC, abstractmethod
from collections import deque
from typing import Optional
from source.models.base import Maze, Cell
class PathFindingStrategy(ABC):
"""Интерфейс стратегии поиска пути в лабиринте."""
@abstractmethod
def find_path(self, maze: Maze, start: Cell, exit: Cell) -> list[Cell]:
"""Найти путь от start до exit.
Args:
maze: Объект лабиринта.
start: Стартовая клетка.
exit: Целевая клетка.
Returns:
Список клеток пути (от start до exit включительно).
Пустой список, если путь не найден.
"""
def _reconstruct_path(came_from: dict[Cell, Optional[Cell]], end: Cell) -> list[Cell]:
"""Восстанавливает путь от старта до end, идя по came_from в обратном порядке.
Args:
came_from: Словарь {клетка: родитель}, где родитель старта = None.
end: Конечная клетка.
Returns:
Список клеток пути от старта до end включительно.
Пустой список, если end отсутствует в came_from.
"""
if end not in came_from:
return []
path: list[Cell] = []
current: Optional[Cell] = end
while current is not None:
path.append(current)
current = came_from[current]
path.reverse()
return path
class BFS(PathFindingStrategy):
def find_path(self, maze: Maze, start: Cell, exit: Cell) -> list[Cell]:
pass

View File

@ -4,7 +4,12 @@ from source.settings import cell_mapping
class Cell: class Cell:
"""Класс отвечает за хранение характеристик поля лабиринта""" """Представляет одну клетку поля лабиринта.
Каждая клетка хранит свои координаты и один из четырёх возможных
типов: стена, старт, выход или пустая клетка. Типы взаимоисключают
друг друга: установка одного автоматически сбрасывает остальные.
"""
def __init__( def __init__(
self, self,
@ -14,17 +19,14 @@ class Cell:
is_start: bool = False, is_start: bool = False,
is_exit: bool = False, is_exit: bool = False,
): ):
""" """Инициализирует клетку с заданными координатами и типом.
:param x: Координата поля по оси X
:type x: int Args:
:param y: Координата поля по оси Y x: Координата клетки по оси X.
:type y: int y: Координата клетки по оси Y.
:param is_wall: Является ли поле **стеной**, defaults to False is_wall: Если True клетка является стеной.
:type is_wall: bool, optional is_start: Если True клетка является стартом.
:param is_start: Является ли поле **началом**, defaults to False is_exit: Если True клетка является выходом.
:type is_start: bool, optional
:param is_exit: Является ли поле **концом**, defaults to False
:type is_exit: bool, optional
""" """
self.x = x self.x = x
self.y = y self.y = y
@ -33,170 +35,204 @@ class Cell:
self._is_exit = is_exit self._is_exit = is_exit
def is_possible(self) -> bool: def is_possible(self) -> bool:
"""Проверка возможности перемещения в это поле """Проверяет, можно ли переместиться в эту клетку.
:return: Если перемещение возможно, то `True`, иначе `False` Returns:
:rtype: bool True, если клетка не является стеной, иначе False.
""" """
return not self.is_wall return not self._is_wall
@property @property
def is_wall(self): def is_wall(self) -> bool:
"""True, если клетка является стеной."""
return self._is_wall return self._is_wall
@property @property
def is_start(self): def is_start(self) -> bool:
"""True, если клетка является стартовой позицией."""
return self._is_start return self._is_start
@property @property
def is_exit(self): def is_exit(self) -> bool:
"""True, если клетка является выходом из лабиринта."""
return self._is_exit return self._is_exit
def _clear_flags(self) -> None: def _clear_flags(self) -> None:
"""Обнуляет все флаги поля""" """Сбрасывает все флаги типа клетки в False."""
self._is_wall = False
self._is_start = False self._is_start = False
self._is_exit = False self._is_exit = False
self._is_wall = False
@is_wall.setter @is_wall.setter
def is_wall(self, value: bool) -> None: def is_wall(self, value: bool) -> None:
"""Устанавливает флаг стены, сбрасывая остальные типы при value=True.
Args:
value: Новое значение флага стены.
"""
if value: if value:
self._clear_flags() self._clear_flags()
self._is_wall = value self._is_wall = value
@is_start.setter @is_start.setter
def is_start(self, value: bool) -> None: def is_start(self, value: bool) -> None:
"""Устанавливает флаг старта, сбрасывая остальные типы при value=True.
Args:
value: Новое значение флага старта.
"""
if value: if value:
self._clear_flags() self._clear_flags()
self._is_start = value self._is_start = value
@is_exit.setter @is_exit.setter
def is_exit(self, value: bool) -> None: def is_exit(self, value: bool) -> None:
"""Устанавливает флаг выхода, сбрасывая остальные типы при value=True.
Args:
value: Новое значение флага выхода.
"""
if value: if value:
self._clear_flags() self._clear_flags()
self._is_exit = value self._is_exit = value
def _get_type_cell(self) -> str: def _get_type_cell(self) -> str:
"""Возвращает символ клетки согласно cell_mapping.
Returns:
Строковый символ, соответствующий текущему типу клетки.
"""
if self._is_wall: if self._is_wall:
type_cell = cell_mapping.get('wall') return cell_mapping['wall']
elif self._is_start: if self._is_start:
type_cell = cell_mapping.get('start') return cell_mapping['start']
elif self._is_exit: if self._is_exit:
type_cell = cell_mapping.get('exit') return cell_mapping['exit']
else: return cell_mapping['empty']
type_cell = cell_mapping.get('empty')
return type_cell
def __str__(self) -> str: def __str__(self) -> str:
return self._get_type_cell() return self._get_type_cell()
def __repr__(self): def __repr__(self) -> str:
return f"Cell: (x={self.x}, y={self.y}), '{self._get_type_cell()}'" return f"Cell(x={self.x}, y={self.y}, '{self._get_type_cell()}')"
class Maze: class Maze:
def __init__( """Представляет двумерный лабиринт из клеток Cell.
self,
size: tuple[int, int] = (10, 10)
):
# Установка размеров лабиринта
self._width = size[0]
self._height = size[1]
# Создание двумерного списка лабиринта Лабиринт хранится как список списков клеток. Доступ к отдельным
self._map = [ клеткам и их изменение возможны через индексацию вида maze[row, col].
[Cell(x, y) for x in range(self._width)] for y in range(self._height) """
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: def _check_point_in_map(self, x: int, y: int) -> bool:
"""Проверка нахождения точки в границах поля """Проверяет, находится ли точка в границах лабиринта.
Args: Args:
x (int): Координата точки в оси X x: Координата по оси X.
y (int): Координата точки в оси Y y: Координата по оси Y.
Returns: Returns:
bool: True если поля в поле, иначе False True, если точка (x, y) находится внутри лабиринта.
""" """
return (0 <= x < self._width) and (0 <= y < self._height) return 0 <= x < self._width and 0 <= y < self._height
def get_cell(self, x: int, y: int) -> Optional[Cell]: def get_cell(self, x: int, y: int) -> Optional[Cell]:
"""Получение значения поля по координате в лабиринте """Возвращает клетку по координатам или None, если координаты вне границ.
Args: Args:
x (int): Координата точки в оси X x: Координата по оси X.
y (int): Координата точки в оси Y y: Координата по оси Y.
Returns: Returns:
Optional[Cell]: Объект поля, при его наличии Объект Cell, если координаты корректны, иначе None.
""" """
if not self._check_point_in_map(x, y): if not self._check_point_in_map(x, y):
return None return None
return self._map[y][x] return self._map[y][x]
def get_neighbors(self, x: int, y: int) -> Optional[list[Cell]]: def get_neighbors(self, x: int, y: int) -> Optional[list[Cell]]:
"""Получение соседних полей относительно заданного поля """Возвращает список проходимых соседей клетки (вверх, вправо, вниз, влево).
Под соседями поля в лабиринте имеется виду клетки сверху, справа,
снизу и слева относительно её. Если точка находится за границами,
то будет возвращено `None`
Args: Args:
x (int): Координата точки в оси X x: Координата клетки по оси X.
y (int): Координата точки в оси Y y: Координата клетки по оси Y.
Returns: Returns:
Optional[list[Cell]]: Список соседних полей Список проходимых соседних клеток, или None если (x, y) вне границ.
""" """
if not self._check_point_in_map(x, y): if not self._check_point_in_map(x, y):
return None return None
list()
vector_x = [0, 1, 0, -1]
vector_y = [1, 0, -1, 0]
deltas = ((0, 1), (1, 0), (0, -1), (-1, 0))
neighbors = [] neighbors = []
for vec_x, vec_y in zip(vector_x, vector_y): for dx, dy in deltas:
temp_x, temp_y = x + vec_x, y + vec_y cell = self.get_cell(x + dx, y + dy)
value = self.get_cell(temp_x, temp_y) if cell is not None and cell.is_possible():
if value is not None and value.is_possible(): neighbors.append(cell)
neighbors.append(value)
return neighbors return neighbors
def __getitem__(self, index: tuple[int, int]) -> Cell: def __getitem__(self, index: tuple[int, int]) -> Cell:
"""Возвращает клетку по индексу [row, col].
Args:
index: Кортеж (row, col) строка и столбец.
Returns:
Объект Cell в позиции (row, col).
Raises:
IndexError: Если индекс выходит за пределы лабиринта.
"""
row, col = index row, col = index
if not self._check_point_in_map(col, row): if not self._check_point_in_map(col, row):
raise IndexError(f"Поле с индексом ({row}, {col}) выходит за пределы лабиринта") raise IndexError(f"Индекс ({row}, {col}) выходит за пределы лабиринта")
return self._map[row][col] return self._map[row][col]
def __setitem__(self, index: tuple[int, int], value: str) -> None: 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 row, col = index
if not self._check_point_in_map(col, row): if not self._check_point_in_map(col, row):
raise IndexError(f"Поле с индексом ({row}, {col}) выходит за пределы лабиринта") raise IndexError(f"Индекс ({row}, {col}) выходит за пределы лабиринта")
cell = self._map[row][col] cell = self._map[row][col]
cell_type = next(
cell_type = None (t for t, s in cell_mapping.items() if s == value),
for type_name, symbol in cell_mapping.items(): None,
if symbol == value: )
cell_type = type_name
break
if cell_type is None: if cell_type is None:
raise ValueError(f"Значение '{value}' не соответствует ни одному типу клетки") raise ValueError(f"Символ '{value}' не соответствует ни одному типу клетки")
if cell_type == "empty": if cell_type == 'empty':
cell._clear_flags() cell._clear_flags()
else: else:
setattr(cell, f"is_{cell_type}", True) setattr(cell, f"is_{cell_type}", True)
def __str__(self) -> str: def __str__(self) -> str:
result = "" return '\n'.join(
''.join(str(self._map[y][x]) for x in range(self._width))
for y in range(self._height): for y in range(self._height)
for x in range(self._width): )
result += str(self[y, x])
result += '\n'
return result