python 生成九宫格
Welcome to “Fun with Python”, part 1. In this part, we will automate maze creation, utilizing Prim’s randomized algorithm.
欢迎使用第1部分“使用Python进行乐趣”。在这一部分中,我们将利用Prim的随机算法自动创建迷宫。
理论与基础 (Theory and Foundations)
Everyone, at some point at his life, has tried to solve a maze. As defined in Wikipedia, a maze is a path, or a collection of paths, typically from an entrance to a goal. In other words, there is an entrance and an exit and starting from the entrance, you need to navigate through the complex paths and walls and find a path towards the exit.
每个人在生命中的某个时刻都试图解决迷宫问题。 如Wikipedia中所定义,迷宫是一条路径或路径的集合,通常从入口到目标。 换句话说,有一个入口和一个出口,从入口开始,您需要浏览复杂的路径和墙壁,并找到通往出口的路径。
Of course, there are variations of this simple game. For example you can have multiple entrances or exits, you may have multiple paths that lead you to the exit and many more. In this article we are going to examine the simplest form. One entrance and one exit.
当然,这个简单的游戏也有变化。 例如,您可以有多个入口或出口,您可以有多个通往出口的路径等等。 在本文中,我们将研究最简单的形式。 一进一出。
Even if a very big portion of everyone reading this has solved (or at least tried to solve) a maze, only a small percentage has created a maze out of thin air. Even less has wondered how can one create a “good” maze. There is a handful of algorithms out there that can be used to create mazes. Here we will examine the Randomized Prim’s Algorithm.
即使每个阅读此书的人中有很大一部分已经解决(或至少试图解决)迷宫,也只有一小部分人凭空创造了一个迷宫。 很少有人想知道如何创建一个“好”迷宫。 那里有几种算法可用于创建迷宫。 在这里,我们将研究随机基数算法。
Randomized Prim’s Algorithm consists of the following steps:
随机Prim的算法包括以下步骤:
- Start with a grid full of walls 从充满墙壁的网格开始
- Pick a cell, mark it as part of the maze. Add the walls of the cell to the walls of the list 选择一个单元格,将其标记为迷宫的一部分。 将单元格的墙添加到列表的墙
While there are walls in the list:
虽然列表中有墙:
1. Pick a random wall from the list. If only one of the two cells that the wall divides is visited, then:
1.从列表中选择一面随机墙。 如果仅访问墙划分的两个单元之一,则:
a) Make the wall a passage and mark the unvisited cell as part of the maze
a)使墙壁通过并标记未访问的单元作为迷宫的一部分
b) Add the neighboring walls of the cell to the wall list.
b)将单元格的相邻墙添加到墙列表中。
2. Remove the wall from the list
2.从列表中删除墙
Let’s take a look and see how we can automate this.
让我们来看一下如何使它自动化。
实作 (Implementation)
First, we need a maze. At first our maze will be empty. We have not decided if any block of the maze will be a cell or a wall. We will denote walls with ‘w’, cells with ‘c’ and unvisited blocks with ‘u’. So:
首先,我们需要一个迷宫。 起初,我们的迷宫将是空的。 我们尚未确定迷宫的任何障碍物是牢房还是墙。 我们将以“ w”表示墙,以“ c”表示单元格,以“ u”表示未访问的图块。 所以:
cell = 'c'
wall = 'w'
For a fixed height and width, we will create a function that creates an empty maze.
对于固定的高度和宽度,我们将创建一个创建空迷宫的函数。
def init_maze(width, height):
maze = []
for i in range(0, height):
line = []
for j in range(0, width):
line.append('u')
maze.append(line) return maze
For debugging purposes, we will also create a function that prints the maze in a user friendly format. In order to be able to easily distinguish walls, cells and unvisited blocks, we will paint each letter with a different color, depending on the letter. To do so, we will use colorama
.
为了调试,我们还将创建一个函数,以用户友好的格式打印迷宫。 为了能够轻松地区分墙壁,单元格和未访问的图块,我们将根据字母为每个字母涂上不同的颜色。 为此,我们将使用colorama
。
from colorama import init, Fore# colorama needs to be initialized in order to be used
init()def print_maze(maze):
for i in range(0, len(maze)):
for j in range(0, len(maze[0])):
if maze[i][j] == 'u':
print(Fore.WHITE, f'{maze[i][j]}', end="")
elif maze[i][j] == 'c':
print(Fore.GREEN, f'{maze[i][j]}', end="")
else:
print(Fore.RED, f'{maze[i][j]}', end="")
print('\n')
Setting height=11
and width=27
we get this output:
设置height=11
和width=27
我们得到以下输出:
u u u u u u u u u u u u u u u u u u u u u u u u u u uu u u u u u u u u u u u u u u u u u u u u u u u u u uu u u u u u u u u u u u u u u u u u u u u u u u u u uu u u u u u u u u u u u u u u u u u u u u u u u u u uu u u u u u u u u u u u u u u u u u u u u u u u u u uu u u u u u u u u u u u u u u u u u u u u u u u u u uu u u u u u u u u u u u u u u u u u u u u u u u u u uu u u u u u u u u u u u u u u u u u u u u u u u u u uu u u u u u u u u u u u u u u u u u u u u u u u u u uu u u u u u u u u u u u u u u u u u u u u u u u u u uu u u u u u u u u u u u u u u u u u u u u u u u u u u
Of course, you cannot see the color here, but if you run it, you will see that all the blocks are white. And this sums up the first step.
当然,您在这里看不到颜色,但是如果运行它,您将看到所有块都是白色的。 这就是第一步的总结。
Step two instructs us to pick a spot in the maze and set it as a free spot. And then add the walls of it in a list. So first, let’s pick our starting points:
第二步指示我们在迷宫中选择一个地点并将其设置为自由地点。 然后将其墙添加到列表中。 因此,首先,让我们选择出发点:
starting_height = int(random.random()*height)
starting_width = int(random.random()*width)
We need to make sure that we do not start on a block that is on the edge of the maze:
我们需要确保我们不要从迷宫边缘的方块开始:
if starting_height == 0:
starting_height += 1
if starting_height == height-1:
starting_height -= 1if starting_width == 0:
starting_width += 1
if starting_width == width-1:
starting_width -= 1
So now we will mark this block as a path and add the surrounding walls to the walls list:
因此,现在我们将此块标记为路径,并将周围的墙壁添加到墙壁列表中:
maze[starting_height][starting_width] = cell
walls = []
walls.append([starting_height-1, starting_width])
walls.append([starting_height, starting_width-1])
walls.append([starting_height, starting_width+1])
walls.append([starting_height+1, starting_width])
In order to complete step two, we need to denote the blocks round the starting cell as walls:
为了完成第二步,我们需要将起始单元周围的块表示为墙:
maze[starting_height-1][starting_width] = wall
maze[starting_height][starting_width-1] = wall
maze[starting_height][starting_width+1] = wall
maze[starting_height+1][starting_width] = wall
Step three is the complex one in this algorithm. Let’s break it down.
第三步是该算法中的复杂步骤。 让我们分解一下。
While there are walls in the list pick a random wall from the list
虽然列表中有墙,但从列表中随机选择墙
This is simple enough to start and build on this as we progress:
这很简单,可以随着我们的进步而开始并以此为基础:
while walls:
rand_wall = walls[int(random.random()*len(walls))-1]
The next instruction is:
下一条指令是:
If only one of the two cells that the wall divides is visited
如果仅访问墙划分的两个单元之一
Actually this is a condition. We need to check the surrounding blocks of the wall we are currently processing. Remember that we picked a wall at random in the previous step. We need to check if the two cells that wall divides. But how does a wall divide two cells?
实际上这是一个条件。 我们需要检查当前正在处理的墙的周围块。 请记住,在上一步中我们随机选择了墙。 我们需要检查壁的两个单元是否分裂。 但是,墙壁如何划分两个单元呢?
We have two possibilities here. First we need to check the blocks to the left and right of the wall we are processing and then we need to check the blocks above and below the wall. Let’s visualize it in order to understand it:
这里有两种可能性。 首先,我们需要检查正在处理的墙壁左右两侧的砖块,然后检查墙壁上方和下方的砖块。 让我们对其进行可视化以了解它:
Case 1 (we check the blocks at left and right of the selected wall): u
u w c
uCase 2 (we check the blocks above and below the selected wall):
u
u w u
c
This are 2 cases that our condition will hold. Of course, it can be mirrored, so we have 4 cases in general. Let’s add this check to our code:
这是我们的情况将成立的两种情况。 当然,它可以被镜像,所以我们一般有4种情况。 让我们将此检查添加到我们的代码中:
while walls:
rand_wall = walls[int(random.random()*len(walls))-1] if maze[rand_wall[0]][rand_wall[1]-1] == 'u' and maze[rand_wall[0]][rand_wall[1]+1] == 'c': if maze[rand_wall[0]-1][rand_wall[1]] == 'u' and maze[rand_wall[0]+1][rand_wall[1]+1] == 'c': if maze[rand_wall[0]+1][rand_wall[1]] == 'u' and maze[rand_wall[0]-1][rand_wall[1]] == 'c': if maze[rand_wall[0]][rand_wall[1]+1] == 'u' and maze[rand_wall[0]][rand_wall[1]-1] == 'c':
Here, we need to be extra careful. We are accessing data inside a list using indexes. We need to make sure we are always accessing a correct index. Meaning that if the selected wall is the one on the first line of the maze, we cannot go and check the cell above it, since it would create an IndexError
in Python. So let’s add these checks:
在这里,我们需要格外小心。 我们正在使用索引访问列表内的数据。 我们需要确保我们始终在访问正确的索引。 这意味着如果选定的墙是迷宫的第一行上的墙,我们将无法去检查它上方的单元格,因为它将在Python中创建IndexError
。 因此,让我们添加以下检查:
while walls:
rand_wall = walls[int(random.random()*len(walls))-1] if rand_wall[1] != 0:
if maze[rand_wall[0]][rand_wall[1]-1] == 'u' and maze[rand_wall[0]][rand_wall[1]+1] == 'c': if rand_wall[0] != 0:
if maze[rand_wall[0]-1][rand_wall[1]] == 'u' and maze[rand_wall[0]+1][rand_wall[1]+1] == 'c': if rand_wall[0] != height-1:
if maze[rand_wall[0]+1][rand_wall[1]] == 'u' and maze[rand_wall[0]-1][rand_wall[1]] == 'c': if rand_wall[1] != width-1:
if maze[rand_wall[0]][rand_wall[1]+1] == 'u' and maze[rand_wall[0]][rand_wall[1]-1] == 'c':
Now that we added this logic, we can continue. If any of the four conditions holds, then:
现在,我们添加了此逻辑,我们可以继续。 如果四个条件中的任何一个成立,则:
Make the wall a passage and mark the unvisited cell as part of the maze
使墙壁通过并标记未访问的单元格作为迷宫的一部分
This part is a little bit tricky. We need to make sure that every wall that we are going to turn into passage, does not have more than one cell around it. If we do not make this kind of check now, we will end up with a maze that has clusters of passages and the maze will not be good. So we will add an extra check if the surrounding cells are less than two. But first we need to create a function that checks that:
这部分有些棘手。 我们需要确保将要通过的每一堵墙周围都没有一个以上的单元。 如果我们现在不做这种检查,我们将最终得到一个迷宫,迷宫中有许多通道,迷宫将是不好的。 因此,我们将额外检查周围的单元格是否少于两个。 但是首先我们需要创建一个函数来检查以下内容:
def surroundingCells(rand_wall):
s_cells = 0
if (maze[rand_wall[0]-1][rand_wall[1]] == 'c'):
s_cells += 1
if (maze[rand_wall[0]+1][rand_wall[1]] == 'c'):
s_cells += 1
if (maze[rand_wall[0]][rand_wall[1]-1] == 'c'):
s_cells +=1
if (maze[rand_wall[0]][rand_wall[1]+1] == 'c'):
s_cells += 1 return s_cells
So now we can incorporate that code in the main function:
因此,现在我们可以将该代码合并到主要功能中:
while walls:
rand_wall = walls[int(random.random()*len(walls))-1] if rand_wall[1] != 0:
if maze[rand_wall[0]][rand_wall[1]-1] == 'u' and maze[rand_wall[0]][rand_wall[1]+1] == 'c':
s_cells = surroundingCells(rand_wall)
if s_cells < 2:
For simplicity and readability purposes I am adding the extra code needed only in the first case. But the same applies for the rest.
为了简单起见,我只在第一种情况下添加所需的额外代码。 但是其余部分也一样。
After all those conditions hold, we can finally turn this wall into a passage and turn the surrounding walls to maze:
在所有这些条件都满足之后,我们最终可以将这堵墙变成一条通道,并将周围的墙变成迷宫:
while walls:
rand_wall = walls[int(random.random()*len(walls))-1]if rand_wall[1] != 0:
if maze[rand_wall[0]][rand_wall[1]-1] == 'u' and maze[rand_wall[0]][rand_wall[1]+1] == 'c':
s_cells = surroundingCells(rand_wall)
if s_cells < 2:
maze[rand_wall[0]][rand_wall[1]] = 'c'
if (rand_wall[0] != 0):
if (maze[rand_wall[0]-1][rand_wall[1]] != 'c'):
maze[rand_wall[0]-1][rand_wall[1]] = 'w'
if ([rand_wall[0]-1, rand_wall[1]] not in walls):
walls.append([rand_wall[0]-1, rand_wall[1]])
Again for simplicity purposes, I am adding only one of the surrounding walls. Of course, we also need to check if we are trying to accessing an invalid index and that we are not adding already existing walls in our list. The code above also completes the next step that is:
再次为简单起见,我仅添加一堵围墙。 当然,我们还需要检查是否尝试访问无效索引,以及是否没有在列表中添加现有的墙。 上面的代码还完成了下一步,即:
Add the neighboring walls of the cell to the wall list
将单元格的相邻墙添加到墙列表中
Finally, we need to remove this processed block from the walls list. Then we need to continue with the next iteration. Let’s create a function for that:
最后,我们需要从墙列表中删除此已处理的块。 然后,我们需要继续下一个迭代。 让我们为此创建一个函数:
def delete_wall(rand_wall):
for wall in walls:
if (wall[0] == rand_wall[0] and wall[1] == rand_wall[1]):
walls.remove(wall)
Add this function to our code. If the wall we are processing does not fall in any of the 4 cases we had earlier, we also need to delete it from the list:
将此功能添加到我们的代码中。 如果我们要处理的墙不属于我们先前遇到的4种情况中的任何一种,我们还需要从列表中删除它:
while walls:
rand_wall = walls[int(random.random()*len(walls))-1]if rand_wall[1] != 0:
if maze[rand_wall[0]][rand_wall[1]-1] == 'u' and maze[rand_wall[0]][rand_wall[1]+1] == 'c':
s_cells = surroundingCells(rand_wall)
if s_cells < 2:
maze[rand_wall[0]][rand_wall[1]] = 'c'
if (rand_wall[0] != 0):
if (maze[rand_wall[0]-1][rand_wall[1]] != 'c'):
maze[rand_wall[0]-1][rand_wall[1]] = 'w'
if ([rand_wall[0]-1, rand_wall[1]] not in walls):
walls.append([rand_wall[0]-1, rand_wall[1]])
delete_wall(rand_wall)
continue
continue
If you print the maze when the list of walls is empty, you will notice that there are some cells that will be unvisited. You need to make them walls:
如果在墙壁列表为空时打印迷宫,您会注意到有些单元格是不可见的。 您需要使它们成为墙:
def make_walls(width, height):
for i in range(0, height):
for j in range(0, width):
if (maze[i][j] == 'u'):
maze[i][j] = 'w'
Finally we need to create an entrance and an exit for the maze:
最后,我们需要为迷宫创建一个入口和一个出口:
def create_entrance_exit(width, height):
for i in range(0, width):
if (maze[1][i] == 'c'):
maze[0][i] = 'c'
break
for i in range(width-1, 0, -1):
if (maze[height-2][i] == 'c'):
maze[height-1][i] = 'c'
break
全部放在一起 (Putting it all together)
If we define the while loop as a function (let’s call it create_maze
), then we can put this all together and test our script:
如果我们将while循环定义为一个函数(我们将其称为create_maze
),那么我们可以将所有内容放在一起并测试脚本:
cell = 'c'
wall = 'w'
unvisited = 'u'
height = 11
width = 27maze = init_maze(width, height)
print_maze(maze)starting_height = int(random.random()*height)
starting_width = int(random.random()*width)starting_height == 0:
starting_height += 1
if starting_height == height-1:
starting_height -= 1if starting_width == 0:
starting_width += 1
if starting_width == width-1:
starting_width -= 1maze[starting_height][starting_width] = cell
walls = []
walls.append([starting_height-1, starting_width])
walls.append([starting_height, starting_width-1])
walls.append([starting_height, starting_width+1])
walls.append([starting_height+1, starting_width])maze[starting_height-1][starting_width] = wall
maze[starting_height][starting_width-1] = wall
maze[starting_height][starting_width+1] = wall
maze[starting_height+1][starting_width] = wallcreate_maze()
make_walls(width, height)
create_entrance_exit(width, height)print_maze(maze)
And this is pretty much it! Let’s run it and see what comes up!
这差不多! 让我们运行它,看看会发生什么!
The final maze 最后的迷宫As you can see, we have a maze! Of course, you can change the width and the height to create larger or smaller mazes. Also, by changing the order we are checking the 4 cases may change the structure of our maze. Feel free to try different variations and let me know about the results you get.
如您所见,我们有一个迷宫! 当然,您可以更改宽度和高度以创建更大或更小的迷宫。 另外,通过更改顺序,我们正在检查4种情况可能会更改迷宫的结构。 随时尝试不同的变体,让我知道您获得的结果。
The full functional code can be found here.
完整的功能代码可以在这里找到。
翻译自: https://medium.com/swlh/fun-with-python-1-maze-generator-931639b4fb7e
python 生成九宫格