地牢房间迷宫走廊生成(二),Python实现洪水法、完美迷宫

文章目录

  • 前言
  • 1 随机房间和房门
  • 2 生成走廊
    • 2.1生成迷宫
    • 2.4 使用循环改进
    • 2.3 走廊缩减
    • 2.3 走廊再简化
  • 总结


前言

  前面通过随机房间、房门,对房门寻路生成走廊。由于使用A星算法,寻到的是最短路径,这样生成的走廊过直和简单。如果需要生成弯曲的走廊(这样的走廊能增加探险的乐趣和战斗拉扯),需要做些什么呢?
  如果我们要在原基础上修改,可以随机选取几个走廊的点,将其扩大复杂化,那么有没有其他方法呢?通过在一块地图上生成一个弯曲的迷宫可以实现这个需求。在这里,无论这个地图上有无房间。

  首先考虑到数据结构的问题,使用什么样的数据表示地图数据?包括房间、墙壁、房门、走廊、空白点。在这里,我使用一个三维数组表示整个图,前两维的点是坐标,第三维一共有6个数据,前4个依次表示为左上右下是否连通,第5个表示为该点是否存在物体或被遍历,第6个表示物体的类别id,数据表示为map[rows,cols,6],当然第三维不一定全是数字,也可以使用二维的链表。


1 随机房间和房门

  在地图上随机寻找一定大小的房间,如果合适则放入房间。在这里需要限制循环的次数,避免无法放置房间时无限循环下去。随机房门时,在房间最外围随机寻找一个点当做房门(当然也可以使用另一个随机数来设定房门的数量)。为了简化代码,假定从房间的左上角出发,随机产生x和y方向的偏移量,使用预定义好的四个权重对x和y加权,将x和y映射到四个边上。此外,将数据可视化,使用matplotlib将地图画到图像上,在此需要一个数组表示图像。
如下所示

import random
import numpy as np
from matplotlib import pyplot as pltdef randomRoom(M):height, width, d = M.shapemin_size = 5max_size = 10room_num = 20count = 0try_count = 0room = []while count < room_num:if try_count > 1000:breakr = random.randint(min_size, max_size)c = random.randint(min_size, max_size)left = random.randint(3, width - 4)top = random.randint(3, height - 4)f = Truefor i in range(top-1,top+r+2):for j in range(left-1,left+c+2):if i >= height-3 or j >= width-3:f = Falsebreakif M[i,j,5] == 1:f = Falsebreakif f:room.append([left,top,c,r])for i in range(top, top + r + 1):for j in range(left, left + c + 1):if i >= height-3 or j >= width-3:continueM[i, j, :] = 1count += 1try_count += 1return roomdef randomDoor(map,room_list):for it in room_list:door_x,door_y = randomWall(it)for i in range(it[3]+1):map[it[1]+i, it[0], 5] = 3map[it[1]+i, it[0]+it[2], 5] = 3for i in range(it[2]+1):map[it[1], it[0]+i, 5] = 3map[it[1]+it[3], it[0]+i, 5] = 3map[door_x,door_y,5] = 2def randomWall(room):direction = random.randint(0,3)dir = [[0,1,-1,0],[1,0,0,-1],[1,0,0,room[3]],[0,1,room[2],0]]x_off = random.randint(1,room[2]-2)y_off = random.randint(1,room[3]-2)x = room[0] + x_off*dir[direction][0] #+ dir[direction][2]y = room[1] + y_off*dir[direction][1] #+ dir[direction][3]return y,xdef drawMap(M, image):rows, cols, deep = M.shapeblock = [[2,8,0,2],[0,2,2,8],[2,8,8,10],[8,10,2,8]]color = [0,0,230,140]for row in range(0, rows):for col in range(0, cols):unit = M[row, col]if unit[5] == 1:image[10 * row : 10 * row + 10, 10 * col:10 * col + 10] = 255else:image[10 * row + 2: 10 * row + 8, 10 * col + 2:10 * col + 8] = 255 - color[unit[5]]for i in range(4):if unit[i] == 1:x1 = 10 * row + block[i][0]x2 = 10 * row + block[i][1]y1 = 10 * col + block[i][2]y2 = 10 * col + block[i][3]image[x1:x2,y1:y2] = 255 - color[unit[5]]plt.imshow(image, interpolation='none', cmap ='gray')plt.show()if __name__ == '__main__':rows = int(input('输入房间行数:'))cols = int(input('输入房间列数:'))map = np.zeros((rows,cols,6),np.int16)image = np.zeros((rows * 10, cols * 10), dtype=np.uint8)room = randomRoom(map)randomDoor(map,room.copy())drawMap(map,image)

这样就生成了房间和房门,画出所示图
随机房间和房门

2 生成走廊

2.1生成迷宫

  现在有一个地图,地图上有带有房门的房间,接下来要做的是是未定区域产生走廊。在这里使用一个新的方法:洪水法(flood fill),随机选取一点,从这点出发将附近可到达的点填充。在迷宫中有一点不同的是前往下一个点时,需要将两点间的墙壁打通。同时,在迭代中,当发现走入死胡同时,回到上一次拐弯的地方选取另外一个方向。

  可以想到,通过递归来解决这个问题。在选取方向时,让电脑随机选取一个方向。当打通两个点间的墙壁时,显然,一个点向左必然是另一个点的向右,它们是对称的。假设用0123来表示三个方向,它们之间的关系很明显是在4的有限域内,将该点方向加上2模4就是另一点的方向。

def floodFill(M):h, w, d = M.shapedef findFree():x = random.randint(1,h-2)y = random.randint(1,w-2)for i1 in range(x,h-1):for j1 in range(y,w-1):if M[i1,j1,5] == 0:return [i1,j1]for i1 in range(1,h-1):for j1 in range(1,w-1):if M[i1,j1,5] == 0:return [i1,j1]return Nonedirection = [[0,-1],[-1,0],[0,1],[1,0]]def fill(point,dir=0):if point[0] < 1 or point[0] >= h-1 or point[1] < 1 or point[1] >= w-1:returnif M[point[0], point[1], 4] == 1:returnM[point[0], point[1], 4] = 1M[point[0], point[1], 5] = 4M[point[0], point[1], (dir+2)%4] = 1M[point[0]-direction[dir][0], point[1]-direction[dir][1], dir] = 1ind = random.randint(0,3)for i2 in range(4):index = (ind + i2) % 4fill([point[0]+direction[index][0],point[1]+direction[index][1]], index)while True:point = findFree()if point == None:breakfill(point)if __name__ == '__main__':rows = int(input('输入房间行数:'))cols = int(input('输入房间列数:'))map = np.zeros((rows,cols,6),np.int16)image = np.zeros((rows * 10, cols * 10), dtype=np.uint8)room = randomRoom(map)randomDoor(map,room.copy())floodFill(map)drawMap(map,image)

生成迷宫如下
在房间外生成迷宫述
  这样很简单地就生成了房间外的迷宫,由于洪水法的特点和所使用的数据结构,这样保证了房间是被迷宫的通道包围的,从房门打开,就进入了迷宫。

  此外,该迷宫是一个完美的迷宫。完美的迷宫是指:在迷宫任意两点间有且只有一条路径。如果不想让迷宫完美(也即有多种方法可以到达另一点,这往往是我们想要的),可以令房间产生多个房门,通过房间产生额外的路径,或者,可以在死胡同(周围三面是墙)上打洞。

2.4 使用循环改进

上述方法固然简单,也是常常想到的方法。但是随着地图增大,进行深递归时,往往会发生爆栈。一个不是解决方法的方法是使用循环去代替递归,改进如下

def floodFill2(M):h,w,d = M.shapels = []x = y = 0ls.append([x,y])dir = []direction = [[0,-1],[-1,0],[0,1],[1,0]]while ls:M[x, y, 4] = 1dir.clear()ind = 0for it in direction:if x+it[0] >= 0 and y+it[1] >= 0 and x+it[0] < h and y+it[1] < w:if M[x+it[0],y+it[1],4] == 0:dir.append(ind)ind += 1if len(dir) > 0:ls.append([x, y])next = random.choice(dir)M[x,y,5] = 4M[x,y,next] = 1x = x+direction[next][0]y = y+direction[next][1]M[x,y,(next+2)%4] = 1else:M[x, y, 5] = 4x, y = ls.pop()if __name__ == '__main__':rows = int(input('输入房间行数:'))cols = int(input('输入房间列数:'))map = np.zeros((rows,cols,6),np.int16)image = np.zeros((rows * 10, cols * 10), dtype=np.uint8)room = randomRoom(map)randomDoor(map,room.copy())floodFill2(map)drawMap(map,image)

这样即使地图增大,也能计算迷宫了,放下效果图
请添加图片描述

2.3 走廊缩减

  当主要的关注点是在房间,而不是迷宫上时,往往不需要迷宫占满整个地图,这很浪费时间。

  那么需要将迷宫缩减一下,入手点是死胡同。我希望这个走廊没有那么多死胡同,不至于走到浪费大量的时间在走迷宫上。寻找死胡同也很简单,周围三面是墙该点就是死胡同。

  首先遍历搜索当前所有的死胡同点加入链表,对链表内容循环直至循环超过一定次数/剩余走廊少于一定数量/没有死胡同。这个条件可以自行设置。代码如下

def deleteCorner(M,left_floor=500):direction = [[0, -1], [-1, 0], [0, 1], [1, 0]]try_num = 1000count = 0r, c, d = M.shapetotal = (r-1) * (c-1)corner = []for i in range(r):for j in range(c):if sum(M[i,j,:4]) == 1:corner.append([i,j])while len(corner):if count > try_num or len(corner) == 0 or total < left_floor:breakcor = random.choice(corner)if sum(M[cor[0],cor[1],:4]) != 1:corner.remove(cor)continueis_door = Falsefor it in direction:if M[cor[0]+it[0],cor[1]+it[1],5] == 2:corner.remove(cor)is_door = Trueif is_door:continuetemp = np.where(M[cor[0], cor[1], :4] == 1)front = int(temp[0])M[cor[0], cor[1], :] = 0M[cor[0] + direction[front][0], cor[1] + direction[front][1], (front + 2) % 4] = 0total -= 1corner.remove(cor)corner.append([cor[0] + direction[front][0], cor[1] + direction[front][1]])count += 1print(count)if __name__ == '__main__':rows = int(input('输入房间行数:'))cols = int(input('输入房间列数:'))map = np.zeros((rows,cols,6),np.int16)image = np.zeros((rows * 10, cols * 10), dtype=np.uint8)room = randomRoom(map)randomDoor(map,room.copy())floodFill2(map)deleteCorner(map)drawMap(map,image)

删除死胡同
可以看到一些死胡同被删除了,迷宫也不是占满整个地图。

2.3 走廊再简化

  上述方法可以生成一个完整的迷宫和房间了。那么当然了需求是满足不完的,尽管做了上述工作,还是觉得走廊过于复杂了,我不希望它是一条直线,也不希望它拐来拐去,该怎么办?

  在递归打通填充下一个点时,使用的是随机一个方向,那么要保持稳定,可以初始随机选定一个方向,在填充的过程中有概率拐弯,这样的小设定能保持一段直道走廊。

def floodFill3(M):h, w, d = M.shapearea = 4area_list = []def findFree():for i1 in range(1,h-1):for j1 in range(1,w-1):if M[i1,j1,5] == 0:return [i1,j1]return Nonedef outRect(p):return p[0] < 1 or p[0] >= h-1 or p[1] < 1 or p[1] >= w-1direction = [[0, -1], [-1, 0], [0, 1], [1, 0]]corner = []while True:new_point = point = findFree()if point == None:breakdir = random.randint(0, 3)while True:point = new_pointM[point[0], point[1], 5] = areaM[point[0], point[1], 4] = 1change = random.random()old_dir = dirif change > 0.9:tran = int((random.randint(-1,0)+0.5)*2)old_dir = dirdir = (dir + tran) % 4new_point = [point[0]+direction[dir][0], point[1]+direction[dir][1]]f = Falseif outRect(new_point):f = Trueelif M[new_point[0],new_point[1],4] == 1:f = Trueif f:for i in range(4):ind = (old_dir + i) % 4temp = [point[0]+direction[ind][0], point[1]+direction[ind][1]]if outRect(temp):continueelif M[temp[0],temp[1],4] == 1:continueelse:new_point = tempf = Falsedir = indif old_dir != dir and not f:corner.append(point)if not f:M[point[0],point[1],dir] = 1M[new_point[0],new_point[1],(dir+2)%4] = 1else:if len(corner):new_point = corner.pop()else:breakarea_list.append(area)area += 1return area_listif __name__ == '__main__':rows = int(input('输入房间行数:'))cols = int(input('输入房间列数:'))map = np.zeros((rows,cols,6),np.int16)image = np.zeros((rows * 10, cols * 10), dtype=np.uint8)room = randomRoom(map)randomDoor(map,room.copy())floodFill3(map)drawMap(map,image)

直走廊

  需要注意的是修改后的方法产生的不是完美迷宫了,而是一块一块不连通的迷宫,某些时候需要将两个不连通的迷宫打通,这需要额外的计算。对死胡同进行消除后看看结果。

#打通区域,ls存放area的列表
def breakArea(map,ls):h,w,d = map.shapeknown = []direction = [[0,-1],[-1,0],[0,1],[1,0]]while len(ls) > 1:f = Falsefor i in range(h):for j in range(w):if map[i,j,5] in ls and not map[i,j,5] in known:ind = 0for it in direction:if map[i+it[0],j+it[1],5] in ls and map[i+it[0],j+it[1],5] != map[i,j,5]:ls.remove(map[i+it[0],j+it[1],5])map[i, j, ind] = 1map[i+it[0], j+it[1], (ind + 2) % 4] = 1map[map==map[i+it[0],j+it[1],5]] = map[i, j, 5]known.append(map[i,j,5])known.append(map[i+it[0],j+it[1],5])f = Truebreakind += 1if f:breakif f:break

删除死角
那么这种方法就能生成需求中的迷宫和房间了。


总结

在看着自己随机生成的迷宫不断变化时一件有趣的事情,最后的最后,让我放上两种迷宫来奖赏自己。

弯曲大迷宫
直道大迷宫

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/497228.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Introduce Parameter Object(引入参数对象)

某些参数总是很自然地同时出现 重构&#xff1a;以一个对象取代这些参数

深度解析,马斯克最新发射的先进火箭

来源&#xff1a;环球时报概要&#xff1a;就在几个小时前&#xff0c;美国人成功发射了目前全世界运载能力最强的超级火箭——“猎鹰重型”。就在几个小时前&#xff0c;美国人成功发射了目前全世界运载能力最强的超级火箭——“猎鹰重型”。虽然中芯级火箭在回收过程中坠毁&a…

发现还是 True Image Server v8.1.941 比较好用

今天下了 True Image Server 9.1 并安装使用&#xff0c;发现9.1版本的用起来似乎更麻烦了&#xff0c;还是喜欢 8.1 的&#xff0e;于是卸载了 9.1 版本重新安装 8.1 版的 但是我发现 8.1 版本的安装使用时总是提示已经过期了&#xff0c;失效了&#xff0e;搞了半天&#xff…

C++学习之路 | PTA乙级—— 1011 A+B 和 C (15分)(精简)

1011 AB 和 C (15分) 给定区间 [−2 ​31 ​​ ,2 ​31 ​​ ] 内的 3 个整数 A、B 和 C&#xff0c;请判断 AB 是否大于 C。 输入格式&#xff1a; 输入第 1 行给出正整数 T (≤10)&#xff0c;是测试用例的个数。随后给出 T 组测试用例&#xff0c;每组占一行&#xff0c;顺…

Encapsulate Downcast(封装向下转型)

某个函数返回的对象&#xff0c;需要由函数调用者执行向下转型&#xff08;downcast&#xff09; public Object lastReading() {return readings.lastElement(); } 重构&#xff1a;将向下转型动作移动到函数中。 public Reading lastReading() {return (Reading) readings…

简明Python教程学习笔记_8_异常

菜鸟教程 之 Python 异常处理&#xff1a;http://www.runoob.com/python/python-exceptions.html Python 一篇搞定所有的异常处理&#xff1a;https://www.cnblogs.com/wj-1314/p/8707804.html Python 捕捉详细异常堆栈的方法 Python 中使用 try except 的方法捕获异常&#…

咖啡的味道

转自http://bbs.21our.com/main.asp一直就这样的静静的呆着&#xff0c;看着人来人往&#xff0c;听着笑语悲歌&#xff0c;却什么也不想说&#xff0c;什么也不想做。  下午有段时间&#xff0c;莫名其妙的烦躁起来&#xff0c;脑袋绷的紧紧的&#xff0c;甚至有种要窒息的感…

技术专栏 | 两万字深度长文!从原理到趋势 解剖风口上的区块链技术

来源&#xff1a;芯师爷概要&#xff1a;区块链不是一项新技术&#xff0c;而是一个新的技术组合。其关键技术包括P2P动态组网、基于密码学的共享账本、共识机制、智能合约等技术。区块链不是一项新技术&#xff0c;而是一个新的技术组合。其关键技术包括P2P动态组网、基于密码…

C++学习之路 | PTA乙级—— 1012 数字分类 (20分)(精简)

1012 数字分类 (20分) 给定一系列正整数&#xff0c;请按要求对数字进行分类&#xff0c;并输出以下 5 个数字&#xff1a; A ​1 ​​ 能被 5 整除的数字中所有偶数的和&#xff1b; A ​2 ​​ 将被 5 除后余 1 的数字按给出顺序进行交错求和&#xff0c;即计算 n ​1 ​​…

Replace Error Code with Exception(以异常取代错误码)

某个函数返回一个特定的代码&#xff0c;用来表示某种错误情况 public int withdraw(int amount) {if (amount > balance) {return -1;} else {balance - amount;return 0;} } 重构&#xff1a;改用异常 public int withdraw(int amount) {if (amount > balance) {thr…

Python 进阶

​Python 进阶&#xff1a;https://eastlakeside.gitbook.io/interpy-zh/ Python 经典教程 专题 系列&#xff1a;https://www.jb51.net/Special/520.htm Python 黑魔法指南&#xff1a;https://magic.iswbm.com/ Python 中文指南&#xff1a;https://python.iswbm.com/ Python…

太闷了,换个样子!

真的很闷&#xff0c;Blog换个样子&#xff0c;以前的黑色&#xff0c;太沉闷了&#xff01; 转载于:https://www.cnblogs.com/lovenets/archive/2006/11/18/564736.html

2018年中国65家机器人产业园布局与规划汇总盘点

来源&#xff1a;机器人创新生态概要&#xff1a;“机器人换人”大潮下&#xff0c;中国已连续两年坐上世界机器人最大消费国的宝座&#xff0c;根据国际机器人联合会&#xff08;IFR&#xff09;发布的数据&#xff0c;2016年中国工业机器人的销量为9万台&#xff0c;同比增长…

C++学习之路 | PTA乙级—— 1013 数素数 (20分)(精简)

1013 数素数 (20分) 令 P ​i ​​ 表示第 i 个素数。现任给两个正整数 M≤N≤10 ​4 ​​ &#xff0c;请输出 P ​M ​​ 到 P ​N ​​ 的所有素数。 输入格式&#xff1a; 输入在一行中给出 M 和 N&#xff0c;其间以空格分隔。 输出格式&#xff1a; 输出从 P ​M ​​…

Replace Exception with Test(以测试取代异常)

面对一个可以预先检查的条件&#xff0c;却抛出了一个异常 public double getValueForPeriod(int periodNumber) {try {return values[periodNumber];} catch (ArrayIndexOutOfBoundsException e) {return 0;} } 重构&#xff1a;调用函数之前先做检查 public double getVal…

(转)一段挺好的领导者应该记得的话

(转)一段挺好的领导者应该记得的话 在一个BLOG上看到的,一段挺好的话&#xff0c;适合于做团队领导者的人记得真心诚意&#xff0c;以情感人&#xff1b;推心置腹&#xff0c;以诚待人开诚布公&#xff0c;以理服人&#xff1b;言行一致&#xff0c;以信取人令行禁止&#xff0…

人工智能与经济学:关于近期文献的一个综述

来源&#xff1a;财新网概要&#xff1a;相比于之前的历次技术进步&#xff0c;“人工智能革命”所引发的冲击更为巨大&#xff0c;其对经济学造成的影响也将更为广泛和深远。人工智能技术的突飞猛进&#xff0c;对经济社会的各个领域都产生了重大影响&#xff0c;这种影响当然…

C++学习之路 | PTA乙级—— 1014 福尔摩斯的约会 (20分)(精简)

1014 福尔摩斯的约会 (20分) 大侦探福尔摩斯接到一张奇怪的字条&#xff1a;我们约会吧&#xff01; 3485djDkxh4hhGE 2984akDfkkkkggEdsb s&hgsfdk d&Hyscvnm。大侦探很快就明白了&#xff0c;字条上奇怪的乱码实际上就是约会的时间星期四 14:04&#xff0c;因为前面…

Pull Up Field(字段上移)

两个子类拥有形同的字段 重构&#xff1a;将该字段移至超类

gsdf

gsdfgsdgsdg转载于:https://www.cnblogs.com/lulei/archive/2006/12/01/579166.html