2026-05-20 10:31:25 +00:00
|
|
|
|
from typing import Optional
|
|
|
|
|
|
|
2026-05-22 20:11:46 +00:00
|
|
|
|
from source.settings import cell_mapping
|
|
|
|
|
|
|
2026-05-20 10:31:25 +00:00
|
|
|
|
|
|
|
|
|
|
class Cell:
|
|
|
|
|
|
"""Класс отвечает за хранение характеристик поля лабиринта"""
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(
|
|
|
|
|
|
self,
|
|
|
|
|
|
x: int,
|
|
|
|
|
|
y: int,
|
|
|
|
|
|
is_wall: bool = False,
|
|
|
|
|
|
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
|
|
|
|
|
|
"""
|
|
|
|
|
|
self.x = x
|
|
|
|
|
|
self.y = y
|
2026-05-20 10:51:55 +00:00
|
|
|
|
self._is_wall = is_wall
|
|
|
|
|
|
self._is_start = is_start
|
|
|
|
|
|
self._is_exit = is_exit
|
2026-05-20 10:31:25 +00:00
|
|
|
|
|
2026-05-22 20:11:46 +00:00
|
|
|
|
def is_possible(self) -> bool:
|
2026-05-20 10:31:25 +00:00
|
|
|
|
"""Проверка возможности перемещения в это поле
|
|
|
|
|
|
|
|
|
|
|
|
:return: Если перемещение возможно, то `True`, иначе `False`
|
|
|
|
|
|
:rtype: bool
|
|
|
|
|
|
"""
|
|
|
|
|
|
return not self.is_wall
|
2026-05-20 10:51:55 +00:00
|
|
|
|
|
2026-05-21 21:05:59 +00:00
|
|
|
|
@property
|
|
|
|
|
|
def is_wall(self):
|
|
|
|
|
|
return self._is_wall
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
def is_start(self):
|
|
|
|
|
|
return self._is_start
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
def is_exit(self):
|
|
|
|
|
|
return self._is_exit
|
|
|
|
|
|
|
|
|
|
|
|
def _clear_flags(self) -> None:
|
|
|
|
|
|
"""Обнуляет все флаги поля"""
|
|
|
|
|
|
self._is_start = False
|
|
|
|
|
|
self._is_exit = False
|
|
|
|
|
|
self._is_wall = False
|
|
|
|
|
|
|
|
|
|
|
|
@is_wall.setter
|
|
|
|
|
|
def is_wall(self, value: bool) -> None:
|
2026-05-22 20:11:46 +00:00
|
|
|
|
if value:
|
|
|
|
|
|
self._clear_flags()
|
|
|
|
|
|
self._is_wall = value
|
2026-05-21 21:05:59 +00:00
|
|
|
|
|
|
|
|
|
|
@is_start.setter
|
|
|
|
|
|
def is_start(self, value: bool) -> None:
|
2026-05-22 20:11:46 +00:00
|
|
|
|
if value:
|
|
|
|
|
|
self._clear_flags()
|
|
|
|
|
|
self._is_start = value
|
2026-05-21 21:05:59 +00:00
|
|
|
|
|
|
|
|
|
|
@is_exit.setter
|
|
|
|
|
|
def is_exit(self, value: bool) -> None:
|
2026-05-22 20:11:46 +00:00
|
|
|
|
if value:
|
|
|
|
|
|
self._clear_flags()
|
|
|
|
|
|
self._is_exit = value
|
|
|
|
|
|
|
|
|
|
|
|
def _get_type_cell(self) -> str:
|
2026-05-20 10:51:55 +00:00
|
|
|
|
if self._is_wall:
|
2026-05-22 20:11:46 +00:00
|
|
|
|
type_cell = cell_mapping.get('wall')
|
2026-05-20 10:51:55 +00:00
|
|
|
|
elif self._is_start:
|
2026-05-22 20:11:46 +00:00
|
|
|
|
type_cell = cell_mapping.get('start')
|
2026-05-20 10:51:55 +00:00
|
|
|
|
elif self._is_exit:
|
2026-05-22 20:11:46 +00:00
|
|
|
|
type_cell = cell_mapping.get('exit')
|
2026-05-20 10:51:55 +00:00
|
|
|
|
else:
|
2026-05-22 20:11:46 +00:00
|
|
|
|
type_cell = cell_mapping.get('empty')
|
|
|
|
|
|
return type_cell
|
|
|
|
|
|
|
|
|
|
|
|
def __str__(self) -> str:
|
|
|
|
|
|
return self._get_type_cell()
|
2026-05-20 10:31:25 +00:00
|
|
|
|
|
2026-05-22 20:11:46 +00:00
|
|
|
|
def __repr__(self):
|
|
|
|
|
|
return f"Cell: (x={self.x}, y={self.y}), '{self._get_type_cell()}'"
|
2026-05-20 10:31:25 +00:00
|
|
|
|
|
|
|
|
|
|
class Maze:
|
|
|
|
|
|
def __init__(
|
|
|
|
|
|
self,
|
2026-05-20 10:51:55 +00:00
|
|
|
|
size: tuple[int, int] = (10, 10)
|
2026-05-20 10:31:25 +00:00
|
|
|
|
):
|
|
|
|
|
|
# Установка размеров лабиринта
|
|
|
|
|
|
self._width = size[0]
|
|
|
|
|
|
self._height = size[1]
|
|
|
|
|
|
|
|
|
|
|
|
# Создание двумерного списка лабиринта
|
|
|
|
|
|
self._map = [
|
|
|
|
|
|
[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:
|
2026-05-21 21:05:59 +00:00
|
|
|
|
"""Проверка нахождения точки в границах поля
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
x (int): Координата точки в оси X
|
|
|
|
|
|
y (int): Координата точки в оси Y
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
bool: True если поля в поле, иначе False
|
|
|
|
|
|
"""
|
2026-05-20 10:31:25 +00:00
|
|
|
|
return (0 <= x < self._width) and (0 <= y < self._height)
|
|
|
|
|
|
|
|
|
|
|
|
def get_cell(self, x: int, y: int) -> Optional[Cell]:
|
2026-05-21 21:05:59 +00:00
|
|
|
|
"""Получение значения поля по координате в лабиринте
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
x (int): Координата точки в оси X
|
|
|
|
|
|
y (int): Координата точки в оси Y
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
Optional[Cell]: Объект поля, при его наличии
|
|
|
|
|
|
"""
|
2026-05-20 10:31:25 +00:00
|
|
|
|
if not self._check_point_in_map(x, y):
|
2026-05-20 10:51:55 +00:00
|
|
|
|
return None
|
2026-05-20 10:31:25 +00:00
|
|
|
|
return self._map[y][x]
|
|
|
|
|
|
|
|
|
|
|
|
def get_neighbors(self, x: int, y: int) -> Optional[list[Cell]]:
|
2026-05-21 21:05:59 +00:00
|
|
|
|
"""Получение соседних полей относительно заданного поля
|
|
|
|
|
|
|
|
|
|
|
|
Под соседями поля в лабиринте имеется виду клетки сверху, справа,
|
|
|
|
|
|
снизу и слева относительно её. Если точка находится за границами,
|
|
|
|
|
|
то будет возвращено `None`
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
x (int): Координата точки в оси X
|
|
|
|
|
|
y (int): Координата точки в оси Y
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
Optional[list[Cell]]: Список соседних полей
|
|
|
|
|
|
"""
|
|
|
|
|
|
if not self._check_point_in_map(x, y):
|
|
|
|
|
|
return None
|
|
|
|
|
|
list()
|
2026-05-20 10:31:25 +00:00
|
|
|
|
vector_x = [0, 1, 0, -1]
|
|
|
|
|
|
vector_y = [1, 0, -1, 0]
|
|
|
|
|
|
|
|
|
|
|
|
neighbors = []
|
|
|
|
|
|
|
|
|
|
|
|
for vec_x, vec_y in zip(vector_x, vector_y):
|
|
|
|
|
|
temp_x, temp_y = x + vec_x, y + vec_y
|
2026-05-20 10:51:55 +00:00
|
|
|
|
value = self.get_cell(temp_x, temp_y)
|
2026-05-22 20:11:46 +00:00
|
|
|
|
if value is not None and value.is_possible():
|
2026-05-20 10:51:55 +00:00
|
|
|
|
neighbors.append(value)
|
2026-05-20 10:31:25 +00:00
|
|
|
|
|
|
|
|
|
|
return neighbors
|
2026-05-22 20:11:46 +00:00
|
|
|
|
|
|
|
|
|
|
def __getitem__(self, index: tuple[int, int]) -> Cell:
|
|
|
|
|
|
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 = index
|
|
|
|
|
|
if not self._check_point_in_map(col, row):
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
|
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:
|
|
|
|
|
|
result = ""
|
|
|
|
|
|
|
|
|
|
|
|
for y in range(self._height):
|
|
|
|
|
|
for x in range(self._width):
|
|
|
|
|
|
result += str(self[y, x])
|
|
|
|
|
|
result += '\n'
|
|
|
|
|
|
|
|
|
|
|
|
return result
|