PHP前端开发

Python如何实现迷宫生成器

百变鹏仔 3个月前 (01-21) #Python
文章标签 生成器

首先展示一下效果图:

我们先分析一下所需的库:

既然是生成器,每次生成的迷宫一模一样显然是说不过去的。因此,我们不可避免地要使用随机数(Random库)。迷宫一定是要绘制的,所以需要有一个GUI库或绘图库,这里我使用Pygame(Tkinter或Turtle其实都可以做到,但毕竟Pygame比较顺手)。在使用Pygame时似乎也需要Sys(用于退出程序),不过即使没有使用,也不会有太大影响。下面是对原句的重写:接下来我们有Tkinter.filedialog,它主要用于询问迷宫生成后的保存路径。毕竟,生成好的迷宫需要保存下来。当然,用Time加一个计时器似乎是锦上添花。

于是,就有:

#coding:utf-8import contextlibwith contextlib.redirect_stdout(None):    import pygameimport randomimport sysimport timefrom tkinter.filedialog import *

这里要说明的是,由于导入Pygame时会输出版本信息等很多内容(这很影响美感),我们需要使用Contextlib阻止它输出。

立即学习“Python免费学习笔记(深入)”;

接下来,我们需要询问一些参数:

a=int(input("列数:"))b=int(input("行数:"))l=int(input("大小:"))saveit=input("是否保存:")

然后,就要运行生成迷宫的程序了。同时,我们有必要计录一下时间(相当于开启计时器):

print("生成中...")e = time.time()

然后就是正式生成迷宫。在介绍这部分代码之前,我们需要了解一下算法:

第一步,生成一个由迷宫单元(白格)和墙(黑格)组成的网格。迷宫单元数量在行中的数目等于迷宫的列数,而在列中的数目则等于迷宫的行数。让左上角的迷宫单元作为起点,右下角的迷宫单元作为终点,并且拆除起点左侧和终点右侧的墙壁,如图所示:

第二步,访问各迷宫单元。将起点标记为当前迷宫单元,当存在未被访问的迷宫单元(凡是曾经成为过当前迷宫单元的迷宫单元,都视为已访问)时,重复执行:

  • 将周围的未被访问的迷宫单元加入表格;

  •  如果表格中有迷宫单元:

    • 将当前迷宫单元入栈(可以理解为将其加入一个叫做栈的表格);

    • 从表格中随机选择一个迷宫单元;

    • 打破当前迷宫单元与选择的迷宫单元之间的墙;

    • 将选择的迷宫单元标记为当前迷宫单元;

  • 如果表格中没有迷宫单元:

    • 栈顶迷宫单元出栈(可以理解为将栈中的最后一个元素获取并删除);

    • 将出栈的迷宫单元设为当前迷宫单元;

在循环结束以后,就会出现像文章开头效果图一样的结果。

接下来,我们就要将文字化的算法转化为Python的代码。

首先,程序是不认识图片的,它认识的是数据。为了用一串数据表示当前的图像,我们需要创建一个二维列表。当然,我们可以顺便将第一步的设置一起完成:

alist = []aa=0need=[]for j in range(2*a+1):    if aa==0:        aa = 1        alistone = []        for i in range(2*b+1):            alistone.append(1)        alist.append(alistone)    else:        aa=0        alistone = []        bb=0        for i in range(2*b+1):            if bb==0:                bb=1                alistone.append(1)            else:                bb = 0                need.append((j,i))                alistone.append(0)        alist.append(alistone)alist[0][1]=0alist[-1][-2]=0

我们不仅建立了一个存储了所有迷宫单元的列表need,而且还可以看到。它的作用就是判断迷宫单元是否被访问,每次访问都会将迷宫单元从表格中删除,当表格中没有迷宫单元时,就说明所有迷宫单元都被访问了。

x=1y=1need.remove((1, 1))listing=[]while len(need)>0:    aroundit=[]    try:        if x-20:        listing.append((x,y))        exec(random.choice(aroundit))        need.remove((x, y))    else:        x,y=listing[-1]        listing.pop()

而这些内容,就是第二步。其算法我已经解释过,唯一一个微小的不同是,在此处我们并没有在列表中加入相邻迷宫单元的坐标,而是将其对应的破墙和标记为当前迷宫单元的代码以字符串的形式存储在表格中,并在随机选择出某个迷宫单元所对应的字符串后,使用exec将其转换为代码并运行(这可以节省一些代码)。

print("完成!用时{}秒".format(time.time()-e))

打印完生成迷宫的用时后,我们需要将表格中的数据转化为图像了。当然,在此之前,我们要先确定图片保存的位置。

if saveit=="1":    ccc = askdirectory()    h=""    bbbbb=1    while True:        try:            open("{}/{}×{}迷宫{}.png".format(ccc,a,b,h),"r")            h="({})".format(bbbbb)        except:            break        bbbbb+=1

在进行选择之前需要先确定您是要保存还是不保存图片,因为有可能会选择不保存。这里字符“1”表示保存(输入其他,自然就是不保存了)。接下来,您需要选择保存路径 (使用askdirectory()方法选择文件路径,无需选择文件名称)。接下来,我们需要确认文件名为“a×b迷宫.png”。如果该路径下已存在该文件名,则需要在文件名后加上序号。总而言之,通过这串代码,我们已经将迷宫的路径+文件名确定了。

pygame.init()icon=pygame.image.load("迷宫.png")pygame.display.set_icon(icon)screen=pygame.display.Info()screen = pygame.display.set_mode((l*(2*a+1),l*(2*b+1)))pygame.display.set_caption('迷宫')screen.fill("white")c = pygame.Surface((l, l), flags=pygame.HWSURFACE)c.fill(color='white')d = pygame.Surface((l, l), flags=pygame.HWSURFACE)d.fill(color='black')for i in range(2*a+1):    for j in range(2*b+1):        if alist[i][j]==0:            screen.blit(c, (i*l, j*l))        elif alist[i][j]==1:            screen.blit(d, (i*l, j*l))pygame.display.flip()if saveit=="1":    pygame.image.save(screen, "{}/{}×{}迷宫{}.png".format(ccc, a, b, h))while True:    for event in pygame.event.get():        if event.type == pygame.QUIT:            pygame.quit()            sys.exit()

代码中使用的图片“迷宫.png”(名称不太对,下载以后要重新命名一下):

这里主要是Pygame的基本设置,并将表格内容图像化。方格中每个数字代表一个方块,数字的大小则决定了方块的颜色,数值在表格中的位置决定了方块放置的位置。就这样,我们呢将表格完全转化成了图像。当然,我们还需要使用pygame.image.save()函数将图像另存为图片文件。

这样,这个生成器似乎完成了。

它运行良好,但当迷宫比较复杂时,暴露出一个问题(下图是100×100的迷宫):

由于正确路径过于曲折,在复杂度较高时,这个迷宫的难度会变得极高!

难度高,在某方面上讲,的确是好事。如果你自己无法找到正确的路线,那么当你展示这个迷宫给朋友看时,感觉会很失落吧?

因此,一个寻路算法变得非常有必要。

寻路算法的大体思路:

在生成的迷宫中,白格为路,黑格为墙。将起点设置为当前位置,重复执行直到终点成为当前位置:

  • 将当前位置标记为正确路径;

  • 将周围未标记的路加入一个表格;

  • 如果表格不空:

    • 将当前位置入栈;

    • 从表格中随机选择一条路,并将其设为当前位置;

  • 如果表格是空的:

    • 栈顶的路出栈;

    • 将其设为当前位置;

通过这个算法,我们可以试出正确的路径(如图):

代码的实现:

x2=0y2=1listing2=[]while not(alist[-1][-2]==2):    alist[x2][y2]=3    around2=[]    try:        if x2-10:        listing2.append((x2,y2))        exec(random.choice(around2))    else:        alist[x2][y2]=2        x2,y2=listing2[-1]        listing2.pop()alist[-1][-2]=3for i in range(len(alist)):    for j in range(len(alist[0])):        if alist[i][j]==2:            alist[i][j]=0

同时,图像绘制的过程也要作出一些改动,以显示正确路径:

if saveit=="1":    ccc = askdirectory()    h=""    bbbbb=1    while True:        try:            open("{}/{}×{}迷宫{}.png".format(ccc,a,b,h),"r")            open("{}/{}×{}迷宫(正确线路){}.png".format(ccc,a,b,h),"r")            h="({})".format(bbbbb)        except:            break        bbbbb+=1pygame.init()icon=pygame.image.load("迷宫.png")pygame.display.set_icon(icon)screen=pygame.display.Info()screen = pygame.display.set_mode((l*(2*a+1),l*(2*b+1)))pygame.display.set_caption('迷宫')screen.fill("white")if saveit=="1":    c = pygame.Surface((l, l), flags=pygame.HWSURFACE)    c.fill(color='white')    d = pygame.Surface((l, l), flags=pygame.HWSURFACE)    d.fill(color='black')    f = pygame.Surface((l, l), flags=pygame.HWSURFACE)    f.fill(color='white')    for i in range(2 * a + 1):        for j in range(2 * b + 1):            if alist[i][j] == 0:                screen.blit(c, (i * l, j * l))            elif alist[i][j] == 1:                screen.blit(d, (i * l, j * l))            else:                screen.blit(f, (i * l, j * l))    pygame.image.save(screen, "{}/{}×{}迷宫{}.png".format(ccc, a, b, h))    c = pygame.Surface((l, l), flags=pygame.HWSURFACE)    c.fill(color='white')    d = pygame.Surface((l, l), flags=pygame.HWSURFACE)    d.fill(color='black')    f = pygame.Surface((l, l), flags=pygame.HWSURFACE)    f.fill(color='red')    for i in range(2 * a + 1):        for j in range(2 * b + 1):            if alist[i][j] == 0:                screen.blit(c, (i * l, j * l))            elif alist[i][j] == 1:                screen.blit(d, (i * l, j * l))            else:                screen.blit(f, (i * l, j * l))    pygame.image.save(screen, "{}/{}×{}迷宫(正确线路){}.png".format(ccc, a, b, h))c = pygame.Surface((l, l), flags=pygame.HWSURFACE)c.fill(color='white')d = pygame.Surface((l, l), flags=pygame.HWSURFACE)d.fill(color='black')f = pygame.Surface((l, l), flags=pygame.HWSURFACE)f.fill(color='white')for i in range(2*a+1):    for j in range(2*b+1):        if alist[i][j]==0:            screen.blit(c, (i*l, j*l))        elif alist[i][j]==1:            screen.blit(d, (i*l, j*l))        else:            screen.blit(f,(i*l, j*l))pygame.display.flip()aaaaaaa = 0while True:    for event in pygame.event.get():        if event.type == pygame.QUIT:            pygame.quit()            sys.exit()        if event.type == pygame.MOUSEBUTTONDOWN:            if aaaaaaa == 1:                aaaaaaa = 0                c = pygame.Surface((l, l), flags=pygame.HWSURFACE)                c.fill(color='white')                d = pygame.Surface((l, l), flags=pygame.HWSURFACE)                d.fill(color='black')                f = pygame.Surface((l, l), flags=pygame.HWSURFACE)                f.fill(color='white')                for i in range(2 * a + 1):                    for j in range(2 * b + 1):                        if alist[i][j] == 0:                            screen.blit(c, (i * l, j * l))                        elif alist[i][j] == 1:                            screen.blit(d, (i * l, j * l))                        else:                            screen.blit(f, (i * l, j * l))                pygame.display.flip()            else:                aaaaaaa = 1                c = pygame.Surface((l, l), flags=pygame.HWSURFACE)                c.fill(color='white')                d = pygame.Surface((l, l), flags=pygame.HWSURFACE)                d.fill(color='black')                f = pygame.Surface((l, l), flags=pygame.HWSURFACE)                f.fill(color='red')                for i in range(2 * a + 1):                    for j in range(2 * b + 1):                        if alist[i][j] == 0:                            screen.blit(c, (i * l, j * l))                        elif alist[i][j] == 1:                            screen.blit(d, (i * l, j * l))                        else:                            screen.blit(f, (i * l, j * l))                pygame.display.flip()

通过这些改动,显示正确路径的效果就实现了。当生成迷宫完成后,窗口显示的是没有正确路径的迷宫。但是,当单击窗口时,正确的路径便会显示出来,再次单击即可隐藏。

刚刚那张100×100的迷宫,其正确路径是:

完整的代码:

#coding:utf-8import contextlibwith contextlib.redirect_stdout(None):    import pygameimport randomimport sysimport timefrom tkinter.filedialog import *a=int(input("列数:"))b=int(input("行数:"))l=int(input("大小:"))saveit=input("是否保存:")print("生成中...")e = time.time()alist = []aa=0need=[]for j in range(2*a+1):    if aa==0:        aa = 1        alistone = []        for i in range(2*b+1):            alistone.append(1)        alist.append(alistone)    else:        aa=0        alistone = []        bb=0        for i in range(2*b+1):            if bb==0:                bb=1                alistone.append(1)            else:                bb = 0                need.append((j,i))                alistone.append(0)        alist.append(alistone)alist[0][1]=0alist[-1][-2]=0x=1y=1need.remove((1, 1))listing=[]while len(need)>0:    aroundit=[]    try:        if x-20:        listing.append((x,y))        exec(random.choice(aroundit))        need.remove((x, y))    else:        x,y=listing[-1]        listing.pop()x2=0y2=1listing2=[]while not(alist[-1][-2]==2):    alist[x2][y2]=3    around2=[]    try:        if x2-10:        listing2.append((x2,y2))        exec(random.choice(around2))    else:        alist[x2][y2]=2        x2,y2=listing2[-1]        listing2.pop()alist[-1][-2]=3for i in range(len(alist)):    for j in range(len(alist[0])):        if alist[i][j]==2:            alist[i][j]=0print("完成!用时{}秒".format(time.time()-e))if saveit=="1":    ccc = askdirectory()    h=""    bbbbb=1    while True:        try:            open("{}/{}×{}迷宫{}.png".format(ccc,a,b,h),"r")            open("{}/{}×{}迷宫(正确线路){}.png".format(ccc,a,b,h),"r")            h="({})".format(bbbbb)        except:            break        bbbbb+=1pygame.init()icon=pygame.image.load("迷宫.png")pygame.display.set_icon(icon)screen=pygame.display.Info()screen = pygame.display.set_mode((l*(2*a+1),l*(2*b+1)))pygame.display.set_caption('迷宫')screen.fill("white")if saveit=="1":    c = pygame.Surface((l, l), flags=pygame.HWSURFACE)    c.fill(color='white')    d = pygame.Surface((l, l), flags=pygame.HWSURFACE)    d.fill(color='black')    f = pygame.Surface((l, l), flags=pygame.HWSURFACE)    f.fill(color='white')    for i in range(2 * a + 1):        for j in range(2 * b + 1):            if alist[i][j] == 0:                screen.blit(c, (i * l, j * l))            elif alist[i][j] == 1:                screen.blit(d, (i * l, j * l))            else:                screen.blit(f, (i * l, j * l))    pygame.image.save(screen, "{}/{}×{}迷宫{}.png".format(ccc, a, b, h))    c = pygame.Surface((l, l), flags=pygame.HWSURFACE)    c.fill(color='white')    d = pygame.Surface((l, l), flags=pygame.HWSURFACE)    d.fill(color='black')    f = pygame.Surface((l, l), flags=pygame.HWSURFACE)    f.fill(color='red')    for i in range(2 * a + 1):        for j in range(2 * b + 1):            if alist[i][j] == 0:                screen.blit(c, (i * l, j * l))            elif alist[i][j] == 1:                screen.blit(d, (i * l, j * l))            else:                screen.blit(f, (i * l, j * l))    pygame.image.save(screen, "{}/{}×{}迷宫(正确线路){}.png".format(ccc, a, b, h))c = pygame.Surface((l, l), flags=pygame.HWSURFACE)c.fill(color='white')d = pygame.Surface((l, l), flags=pygame.HWSURFACE)d.fill(color='black')f = pygame.Surface((l, l), flags=pygame.HWSURFACE)f.fill(color='white')for i in range(2*a+1):    for j in range(2*b+1):        if alist[i][j]==0:            screen.blit(c, (i*l, j*l))        elif alist[i][j]==1:            screen.blit(d, (i*l, j*l))        else:            screen.blit(f,(i*l, j*l))pygame.display.flip()aaaaaaa = 0while True:    for event in pygame.event.get():        if event.type == pygame.QUIT:            pygame.quit()            sys.exit()        if event.type == pygame.MOUSEBUTTONDOWN:            if aaaaaaa == 1:                aaaaaaa = 0                c = pygame.Surface((l, l), flags=pygame.HWSURFACE)                c.fill(color='white')                d = pygame.Surface((l, l), flags=pygame.HWSURFACE)                d.fill(color='black')                f = pygame.Surface((l, l), flags=pygame.HWSURFACE)                f.fill(color='white')                for i in range(2 * a + 1):                    for j in range(2 * b + 1):                        if alist[i][j] == 0:                            screen.blit(c, (i * l, j * l))                        elif alist[i][j] == 1:                            screen.blit(d, (i * l, j * l))                        else:                            screen.blit(f, (i * l, j * l))                pygame.display.flip()            else:                aaaaaaa = 1                c = pygame.Surface((l, l), flags=pygame.HWSURFACE)                c.fill(color='white')                d = pygame.Surface((l, l), flags=pygame.HWSURFACE)                d.fill(color='black')                f = pygame.Surface((l, l), flags=pygame.HWSURFACE)                f.fill(color='red')                for i in range(2 * a + 1):                    for j in range(2 * b + 1):                        if alist[i][j] == 0:                            screen.blit(c, (i * l, j * l))                        elif alist[i][j] == 1:                            screen.blit(d, (i * l, j * l))                        else:                            screen.blit(f, (i * l, j * l))                pygame.display.flip()