[2] добавлены модель лабиринта и паттерн Builder

This commit is contained in:
pogodinda 2026-05-23 21:33:40 +03:00
parent 1b7ad33278
commit 4db9491cec
5 changed files with 260 additions and 0 deletions

View File

@ -0,0 +1,10 @@
###############
#S ##
# ####### # # #
# # # # # #
# # ### # # #
# # ##### #
##### # #
# # ##### #
# ##### E
###############

View File

@ -0,0 +1,84 @@
from typing import List, Optional
class Cell:
"""Клетка лабиринта."""
def __init__(self, x: int, y: int, is_wall: bool = False,
is_start: bool = False, is_exit: bool = False, weight: int = 1):
self.x = x
self.y = y
self.is_wall = is_wall
self.is_start = is_start
self.is_exit = is_exit
self.weight = weight # для взвешенных лабиринтов
def is_passable(self) -> bool:
return not self.is_wall
def __repr__(self):
if self.is_start:
return 'S'
elif self.is_exit:
return 'E'
elif self.is_wall:
return '#'
else:
return ' '
def __eq__(self, other):
if isinstance(other, Cell):
return self.x == other.x and self.y == other.y
return False
def __hash__(self):
return hash((self.x, self.y))
class Maze:
"""Лабиринт — сетка клеток."""
def __init__(self, width: int, height: int):
self.width = width
self.height = height
self.cells: List[List[Cell]] = []
self.start: Optional[Cell] = None
self.exit: Optional[Cell] = None
for y in range(height):
row = []
for x in range(width):
row.append(Cell(x, y))
self.cells.append(row)
def get_cell(self, x: int, y: int) -> Optional[Cell]:
if 0 <= x < self.width and 0 <= y < self.height:
return self.cells[y][x]
return None
def set_cell(self, x: int, y: int, cell: Cell):
if 0 <= x < self.width and 0 <= y < self.height:
self.cells[y][x] = cell
if cell.is_start:
self.start = cell
if cell.is_exit:
self.exit = cell
def get_neighbors(self, cell: Cell) -> List[Cell]:
"""Соседи сверху/снизу/слева/справа, если проходимы."""
neighbors = []
directions = [(0, -1), (0, 1), (-1, 0), (1, 0)]
for dx, dy in directions:
nx, ny = cell.x + dx, cell.y + dy
neighbor = self.get_cell(nx, ny)
if neighbor and neighbor.is_passable():
neighbors.append(neighbor)
return neighbors
def __repr__(self):
lines = []
for row in self.cells:
lines.append(''.join(str(cell) for cell in row))
return '\n'.join(lines)

View File

@ -0,0 +1,121 @@
# maze_builder.py
from abc import ABC, abstractmethod
from maze import Maze, Cell
class MazeBuilder(ABC):
"""Интерфейс строителя лабиринта (Builder pattern)."""
@abstractmethod
def build_from_file(self, filename: str) -> Maze:
"""Принимает путь к файлу, возвращает готовый Maze."""
pass
class TextFileMazeBuilder(MazeBuilder):
"""
Строитель лабиринта из текстового файла.
Формат файла:
# — стена
. или пробел проход
S старт
E выход
"""
def build_from_file(self, filename: str) -> Maze:
with open(filename, 'r', encoding='utf-8') as f:
lines = [line.rstrip('\n') for line in f.readlines()]
if not lines:
raise ValueError("Файл лабиринта пуст")
height = len(lines)
width = max(len(line) for line in lines)
maze = Maze(width, height)
start_found = False
exit_found = False
for y, line in enumerate(lines):
for x, char in enumerate(line):
is_wall = (char == '#')
is_start = (char == 'S')
is_exit = (char == 'E')
if is_start:
start_found = True
if is_exit:
exit_found = True
cell = Cell(x, y, is_wall=is_wall, is_start=is_start, is_exit=is_exit)
maze.set_cell(x, y, cell)
if not start_found or not exit_found:
raise ValueError("Лабиринт должен содержать старт (S) и выход (E)")
return maze
class RandomMazeBuilder(MazeBuilder):
"""
Строитель случайного лабиринта.
Алгоритм: рекурсивный бэктрекинг.
"""
def __init__(self, width: int, height: int):
self.width = width
self.height = height
def build_from_file(self, filename: str = None) -> Maze:
"""
filename игнорируется лабиринт генерируется случайно.
Название метода общее для всех Builder'ов.
"""
import random
maze = Maze(self.width, self.height)
# Шаг 1: Заполняем всё стенами
for y in range(self.height):
for x in range(self.width):
maze.set_cell(x, y, Cell(x, y, is_wall=True))
# Шаг 2: Рекурсивно прокладываем пути
visited = set()
def carve(x, y):
"""Прокладывает проход из точки (x, y)."""
visited.add((x, y))
maze.set_cell(x, y, Cell(x, y, is_wall=False))
# 4 направления, перемешанные случайно
directions = [(0, -2), (0, 2), (-2, 0), (2, 0)]
random.shuffle(directions)
for dx, dy in directions:
nx, ny = x + dx, y + dy
# Проверяем границы и что ещё не посещали
if (0 <= nx < self.width and 0 <= ny < self.height
and (nx, ny) not in visited):
# Убираем стену между текущей и новой клеткой
wall_x = x + dx // 2
wall_y = y + dy // 2
maze.set_cell(wall_x, wall_y, Cell(wall_x, wall_y, is_wall=False))
# Рекурсия в новую клетку
carve(nx, ny)
# Начинаем с (1, 1) — нечётные координаты для коридоров
carve(1, 1)
# Шаг 3: Устанавливаем старт и выход в углах лабиринта
maze.set_cell(1, 1, Cell(1, 1, is_wall=False, is_start=True))
maze.set_cell(
self.width - 2, self.height - 2,
Cell(self.width - 2, self.height - 2, is_wall=False, is_exit=True)
)
return maze

View File

@ -0,0 +1,26 @@
from src.maze_builder import TextFileMazeBuilder, RandomMazeBuilder
# Тест 1: Загрузка из файла
print("=" * 50)
print("ТЕСТ 1: Загрузка из файла")
print("=" * 50)
builder = TextFileMazeBuilder()
maze = builder.build_from_file("data/demo_maze.txt")
print(maze)
print(f"\nРазмер: {maze.width}x{maze.height}")
print(f"Старт: ({maze.start.x}, {maze.start.y})")
print(f"Выход: ({maze.exit.x}, {maze.exit.y})")
# Тест 2: Случайный лабиринт
print("\n" + "=" * 50)
print("ТЕСТ 2: Случайный лабиринт 21x11")
print("=" * 50)
random_builder = RandomMazeBuilder(21, 11)
random_maze = random_builder.build_from_file()
print(random_maze)
print(f"\nРазмер: {random_maze.width}x{random_maze.height}")
print(f"Старт: ({random_maze.start.x}, {random_maze.start.y})")
print(f"Выход: ({random_maze.exit.x}, {random_maze.exit.y})")

View File

@ -0,0 +1,19 @@
from src.maze import Maze, Cell
maze = Maze(5, 5)
# Заполняем границы стенами
for y in range(5):
for x in range(5):
if x == 0 or x == 4 or y == 0 or y == 4:
maze.set_cell(x, y, Cell(x, y, is_wall=True))
# Старт, выход, одна стена внутри
maze.set_cell(1, 1, Cell(1, 1, is_start=True))
maze.set_cell(3, 3, Cell(3, 3, is_exit=True))
maze.set_cell(2, 2, Cell(2, 2, is_wall=True))
print(maze)
print("Старт:", maze.start)
print("Выход:", maze.exit)
print("Соседи старта:", maze.get_neighbors(maze.start))