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