diff --git a/skorohodovsa/task_2/source/algorithms/strategy.py b/skorohodovsa/task_2/source/algorithms/strategy.py new file mode 100644 index 0000000..ea956f1 --- /dev/null +++ b/skorohodovsa/task_2/source/algorithms/strategy.py @@ -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 \ No newline at end of file diff --git a/skorohodovsa/task_2/source/models/base.py b/skorohodovsa/task_2/source/models/base.py index 2e8f890..863cb77 100644 --- a/skorohodovsa/task_2/source/models/base.py +++ b/skorohodovsa/task_2/source/models/base.py @@ -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 \ No newline at end of file + return '\n'.join( + ''.join(str(self._map[y][x]) for x in range(self._width)) + for y in range(self._height) + ) \ No newline at end of file