文章目录

  • 前言
  • 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


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


总结

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


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

  1. Python 制作迷宫游戏(二)——游戏窗口

    Python 制作迷宫游戏(二)--游戏窗口 上一节我们使用prime做了迷宫的底层数组,它的形式是一个二维数组. 这一节我们着手开始制作游戏窗口 使用模块 pygame 在这项迷宫游戏的制作当中需要 ...

  2. [Qt] 迷宫随机生成和自动寻路算法、布局管理器、动态效果、界面切换、播放音频【迷宫、魔塔、超级玛丽】 -C++课程设计:Qt实现的迷宫与地牢游戏

    基于QT的迷宫与地牢小游戏开发 首先贴出链接: 完整Qt源码:点击进入https://download.csdn.net/download/qq_43365825/11852112 发布可执行程序:点 ...

  3. 在Ubuntu 16.04.5 LTS上使用python第三方库QRCode 6.0生成二维码实录

    简介 二维码简称 QR Code(Quick Response Code),学名为快速响应矩阵码,是二维条码的一种,由日本的 Denso Wave 公司于 1994 年发明.现随着智能手机的普及,已广 ...

  4. 使用Python的库qrcode生成二维码

    现在有很多二维码的生成工具,在线的,或者安装的软件,都可以进行生成二维码.今天我用Python的qrcode库生成二维码.需要预先安装  Image 库 安装 用pip安装 # pip install ...

  5. python生成二维码、动态二维码 和 而二维码解析

    python生成二维码.动态二维码 和 而二维码解析(8-20190129) 文章目录: 一.二维码介绍 二. 就是为了好玩所以想搞一下二维码,"好玩",少年醒醒,不要骗自己啦,起 ...

  6. Py之qrcode:调用python的qrcode库两种方式生成二维码、带logo的二维码

    Py之qrcode:调用python的qrcode库两种方式生成二维码.带logo的二维码 目录 python编程实现生成二维码 1.第一种方式-纯文本 2.第二种方式-带logo

  7. 互联网 4 大发明之二维码,你如何使用 Python 生成二维码?

    阅读文本大概需要 8 分钟. 新时代,人们有人信新的追求,自然而然会有新发明的诞生.去年,在"一带一路"国际合作高峰论坛举行期间, 20 国青年投票选出中国的"新四大发明 ...

  8. python利用myqr库生成二维码

    1.今天,来学习一下利用myqr库来生成二维码,首先,先执行命令安装myqr这个库,输入以下命令: pip install myqr 2.安装成功后,在本地的磁盘底下建一个目录,这里我存放的目录为:E ...

  9. python生成二维码_python生成二维码的实例详解

    python生成二维码的实例详解 版本相关 操作系统:Mac OS X EI Caption Python版本:2.7 IDE:Sublime Text 3 依赖库 Python生成二维码需要的依赖库 ...

最新文章

  1. linux qemu 源码编译
  2. malloc()/free()的实现
  3. python图形编程基础-Python从基础到入门系列教程
  4. 收集的一些android偏方
  5. 【信号】信号集、sigprocmask、sigpending
  6. 多个left join 产生多个结果
  7. 从R-CNN到Faster R-CNN漫谈
  8. 李笑来登GitHub趋势榜第一,教你自学编程,含37%“硬核鸡汤”
  9. - 动规讲解基础讲解一——01背包(模板)
  10. Linux导出未越狱Iphone10.3-QQ聊天记录
  11. U盘 未知USB设备 设定地址失败 由于该设备有问题Windows 已将其停止(代码 43) 终极解决方案(做过系统装机盘而无法解决的必看)
  12. 唐僧日记[作者:殷靖海] (一)
  13. 通用能力-数量关系专项练习(2)
  14. 发烧友labviEW随手练小试牛刀,精美奖品轻松拿,期待你的参与
  15. CFgym:Outer space invaders(区间dp)
  16. echarts 中国地图标注所在点
  17. jdbc(2)——之Class.forName(com.mysql.cj.jdbc.Driver)理解
  18. 仪表放大器的PCB布局-运放
  19. 印第安纳大学校庆超算,使用下一代 N 卡,速度达 6 千万亿次/秒
  20. WordPress从入门到精通建站教程

热门文章

  1. python中字符串添加r的作用,原始字符串不发生转义
  2. 【数据挖掘】挖掘建模-回归分析(1)
  3. 设计模式三大类及六大设计原则
  4. .Net Core 学习资料
  5. 关于字节对齐以及内存占用
  6. Android学习笔记----ArcGIS在线地图服务(Android API)坐标纠偏
  7. vb6 枚举对象属性
  8. php jquery 源码,最新版jQuery 2.1.0完整
  9. eclipse启动提示java,Eclipse启动时报错-JSP教程,Java技巧及代码
  10. java lookandfeel nimbus_动态改变LookAndFeel