Генерация лабиринта Python с использованием PIL идет не так

Я работал над созданием специализированного кругового лабиринта, используя библиотеку PIL для рисования лабиринта на изображении.

В приведенном выше выходном изображении есть две проблемы.

  1. Линии стен лабиринта неполные, но присутствуют. Длина линии не такая, какой должна быть, что приводит к «полулабиринту».

  2. Поскольку круговой лабиринт расширяется, каждый слой (или «уровень», как я называю его в сценарии) определяет количество ячеек, помещаемых в этот слой, что позволяет получить более естественный вид лабиринта вместо лабиринта с продолжающимися коридорами. увеличиваться в размерах по мере удаления от центра. Это вызывает небольшие расхождения при соединении двух слоев. Это не на каждом уровне. Именно тогда, когда количество клеток должно увеличиться.

Как я могу их решить? Ниже приведена текущая реализация.

from PIL import Image, ImageDraw
import math
import random
from collections import deque

class CircularMaze:

    def __init__(self, levels, line_len, wall_width, corridor_size, canvas_size):
        assert line_len > 0, "Line length must be greater than 0"
        self.canvas_size = canvas_size
        self.num_levels = levels
        self.line_length = line_len
        self.wall_width = wall_width
        self.corridor_size = corridor_size
        self.num_cells_at_level = self.cell_count_by_level()
        self.total_cells = sum(self.num_cells_at_level)
        
    def cell_count_by_level(self):
        cells_by_level = [1]
        for level in range(1, self.num_levels): cells_by_level.append(int(math.pow(2, math.floor(math.log2(level + 1)) + 3)))
        return cells_by_level

    def index_1d_from_2d(self, level, cell):
        if level >= self.num_levels: raise Exception("level greater than maze levels")
        idx = 0
        for lvl in range(level): idx += self.num_cells_at_level[lvl]
        idx += cell
        return idx

    def index_2d_from_1d(self, idx):
        if idx >= self.total_cells: raise Exception("1D index greater than total number of cells")
        level = 0
        while idx - self.num_cells_at_level[level] >= 0:
            idx -= self.num_cells_at_level[level]
            level += 1
        return level, idx

    def parent_index_1d(self, level, cell):
        if level <= 0: raise Exception(f'Level {level} has no parent')
        elif level == 1: return 0
        parent = self.index_1d_from_2d(level - 1, cell)
        if self.num_cells_at_level[level - 1] < self.num_cells_at_level[level]: parent = self.index_1d_from_2d(level - 1, cell // 2)
        return parent

    def left_index_1d(self, level, cell):
        if level <= 0: raise Exception(f'Level {level} has no left cell')
        left = self.index_1d_from_2d(level, cell - 1)
        if cell == 0: left = self.index_1d_from_2d(level, self.num_cells_at_level[level] - 1)
        return left

    def right_index_1d(self, level, cell):
        if level <= 0: raise Exception(f'Level {level} has no left cell')
        right = self.index_1d_from_2d(level, cell + 1)
        if cell == self.num_cells_at_level[level] - 1: right = self.index_1d_from_2d(level, 0)
        return right

    def create_dfs_tree(self):
        print("Creating DFS tree...")
        graph = {node: [] for node in range(self.total_cells)}
        cell_1d = random.randint(1, self.total_cells - 1)
        visited = [cell_1d]
        stack = deque()
        stack.append(cell_1d)
        total_cells_visited = 1
        while len(visited) < self.total_cells:
            level, cell = self.index_2d_from_1d(cell_1d)
            connections = []
            if level == 0:
                for idx in range(1, self.num_cells_at_level[1] + 1): connections.append(idx)
            else:
                connections.append(self.parent_index_1d(level, cell))
                connections.append(self.left_index_1d(level, cell))
                connections.append(self.right_index_1d(level, cell))
                if level <= self.num_levels - 2:
                    if self.num_cells_at_level[level] < self.num_cells_at_level[level + 1]:
                        connections.append(self.index_1d_from_2d(level + 1, 2 * cell))
                        connections.append(self.index_1d_from_2d(level + 1, 2 * cell + 1))
                    else: connections.append(self.index_1d_from_2d(level + 1, cell))
            unvisited_connections = [conn for conn in connections if conn not in visited]
            if unvisited_connections:
                next_cell = random.choice(unvisited_connections)
                graph[cell_1d].append(next_cell)
                graph[next_cell].append(cell_1d)
                visited.append(next_cell)
                total_cells_visited += 1
                if next_cell != 0:
                    stack.append(next_cell)
                    cell_1d = next_cell
                else: cell_1d = stack.pop()
            else: cell_1d = stack.pop()
            if total_cells_visited % 100 == 0: print(f"Generating DFS Tree: {((total_cells_visited / self.total_cells) * 100):.2f}%")
        print("DFS tree created")
        return graph
    
    def draw_maze(self, graph):
        print("Drawing maze...")
        image = Image.new("RGB", (self.canvas_size, self.canvas_size), "white")
        draw = ImageDraw.Draw(image)

        for level in range(1, self.num_levels):
            radius = level * (self.line_length + self.corridor_size)
            arc_angle = 360 / self.num_cells_at_level[level]

            for cell in range(self.num_cells_at_level[level]):
                cell_1d = self.index_1d_from_2d(level, cell)
                parent_cell = self.parent_index_1d(level, cell)
                left_cell = self.left_index_1d(level, cell)

                if left_cell not in graph[cell_1d]:
                    start_angle = (cell - 0.5) * arc_angle
                    end_angle = cell * arc_angle
                    draw.arc(
                        [self.canvas_size // 2 - radius, self.canvas_size // 2 - radius,
                        self.canvas_size // 2 + radius, self.canvas_size // 2 + radius],
                        start=start_angle, end=end_angle, fill = "black", width=self.wall_width
                    )

                if parent_cell not in graph[cell_1d]:
                    angle = cell * arc_angle + arc_angle / 2
                    x1 = self.canvas_size // 2 + radius * math.cos(math.radians(angle))
                    y1 = self.canvas_size // 2 + radius * math.sin(math.radians(angle))
                    x2 = self.canvas_size // 2 + (radius - self.line_length) * math.cos(math.radians(angle))
                    y2 = self.canvas_size // 2 + (radius - self.line_length) * math.sin(math.radians(angle))
                    draw.line([x1, y1, x2, y2], fill = "black", width=self.wall_width)

        image.save("maze.png")
        image.show()

levels = 15 # end goal 70 & 100
line_len = 15 # end goal 70 & 100
wall_width = 8 # end goal 8 & 12
corridor_size = 25 # end goal 25 & 50
canvas_size = 2048 # end goal 16384

maze = CircularMaze(levels, line_len, wall_width, corridor_size, canvas_size)
graph = maze.create_dfs_tree()
maze.draw_maze(graph)

Можете ли вы добавить сгенерированное изображение с дефектами, которые хотите исправить?

jsbueno 25.06.2024 20:57

он должен быть прямо над блоком кода. ссылка i.sstatic.net/12LFrz3L.png

asdf 25.06.2024 21:01

Ваши дуги имеют угловую протяженность arc_angle/2 (от (cell - 0.5) * arc_angle до cell * arc_angle), но расположены на расстоянии arc_angle градусов друг от друга, поэтому очевидно, что они покрывают только половину необходимого расстояния. Аналогично, ваши радиальные линии имеют длину self.line_length, но уровни вашего лабиринта расположены на расстоянии self.line_length + self.corridor_size друг от друга.

jasonharper 25.06.2024 21:05

@jasonharper приложил все усилия, чтобы внести исправления в предложенные области, и получил лабиринт получше, но все равно сломан. start_angle = cell * arc_angle end_angle = (cell + 1) * arc_angle ... x2 = self.canvas_size // 2 + (радиус - self.line_length - self.corridor_size) * math.cos(math.radians(angle)) y2 = self.canvas_size // 2 + (радиус - self.line_length - self.corridor_size) * math.sin(math.radians(angle))

asdf 25.06.2024 21:17
ibb.co/48vfWnz я не понимаю, что происходит, но проблема 2 все еще кажется распространенной
asdf 25.06.2024 21:18

Возможно, сначала используйте print()print(type(...)), print(len(...)) и т. д.), чтобы увидеть, какая часть кода выполняется и что на самом деле у вас есть в переменных. Он называется "print debugging" и помогает увидеть, что на самом деле делает код.

furas 26.06.2024 03:04

Не могли бы вы задавать по одному вопросу в каждом посте?

Mad Physicist 28.06.2024 03:10
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
7
63
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вы неправильно рассчитали координаты ячеек и перепутали условия проверки соседних ячеек.

Вам необходимо четко определить геометрию ячеек, как показано в следующем примере.

  1. Точка в лабиринте будет описываться как (x, y) в декартовой системе координат и (r, a) в полярной системе координат. Начало координат — центр круга, ось x ориентирована слева направо, а ось y — сверху вниз. Из определения x = r cos(a) и y = r sin(a).

  2. Пусть d — длина радиальных стенок ячейки. И пусть i-й уровень представляет собой область, удовлетворяющую ri < r < ri+1, где ri = i d.

  3. Пусть ai — угол, под которым смыкаются дуги ячейки i-го уровня. И пусть j-я ячейка i-го уровня представляет собой область, удовлетворяющую j ai < a < (j+1) ai

Тогда код рисования должен выглядеть следующим образом.

    def draw_maze(self, graph):
        ...
        x0 = y0 = self.canvas_size // 2
        d = self.line_length + self.corridor_size
        for level in range(1, self.num_levels):
            r_i = (level - 1) * d
            a_i = 2*math.pi / self.num_cells_at_level[level]
            for cell in range(self.num_cells_at_level[level]):
                cell_1d = self.index_1d_from_2d(level, cell)
                parent_cell = self.parent_index_1d(level, cell)
                left_cell = self.left_index_1d(level, cell)

                if parent_cell not in graph[cell_1d] and level > 1:
                    draw.arc(
                        [x0 - r_i, y0 - r_i, x0 + r_i, y0 + r_i],
                        start=180/math.pi * cell*a_i, end=180/math.pi * (cell+1)*a_i,
                        fill='black', width=self.wall_width
                    )

                if left_cell not in graph[cell_1d]:
                    c, s = math.cos(cell*a_i), math.sin(cell*a_i)
                    draw.line(
                        [x0 + r_i*c, y0 + r_i*s, x0 + (r_i+d)*c, y0 + (r_i+d)*s],
                        fill='black', width=self.wall_width
                    )

о боже мой, ты просто спасатель жизни, спасибо тебе огромное, это сработало как чудо. ты лучший

asdf 29.06.2024 15:50

Другие вопросы по теме