地牢房间迷宫走廊生成(二),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…

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

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

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…

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

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

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

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

Pull Up Field(字段上移)

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

DeepMind推出分布式深度强化学习架构IMPALA,让一个Agent学会多种技能

维金 编译自 DeepMind Blog量子位 出品 | 公众号 QbitAI目前&#xff0c;深度增强学习&#xff08;DeepRL&#xff09;技术在多种任务中都大获成功&#xff0c;无论是机器人的持续控制问题&#xff0c;还是掌握围棋和雅达利的电子游戏。不过&#xff0c;这些方面的进展仅限于孤…

AAAI2018正式落幕 13个世界顶尖AI教授都讲了啥?

来源&#xff1a;智东西概要&#xff1a;2月8日消息&#xff0c;第32届AAAI大会在美国新奥尔良正式闭幕。2月8日消息&#xff0c;第32届AAAI大会在美国新奥尔良正式闭幕。本次大会不仅颁发了最佳论文奖、最佳学生论文奖、经典论文将等一系列奖项。作为顶级学术会议之一&#xf…

Linux 中 VIM 的使用

Vim 官网&#xff1a;http://www.vim.org/ VIM 实用技巧&#xff1a;https://wenku.baidu.com/view/21c5f387d4d8d15abe234ecb.html vim 的一些小技巧&#xff1a;https://www.douban.com/group/topic/1815089 vim 简单实用的技巧总结&#xff1a;http://www.tuicool.com/arti…

Pull Up Method(函数上移)

有些函数&#xff0c;在各个子类中产生完全相同的结果 重构&#xff1a;将该函数移至超类

重磅 | 中国工程院提出新一代智能制造【附下载】

来源&#xff1a;走向智能论坛近日&#xff0c;中国工程院院刊《Engineering》推出最新观点性文章“走向新一代智能制造”&#xff0c;作者周济、李培根、周艳红等&#xff0c;文章指出智能制造是一个不断演进发展的大概念&#xff0c;可归纳为三个基本范式&#xff1a;数字化制…

Python 装饰器 函数

Python装饰器学习&#xff08;九步入门&#xff09;&#xff1a;http://www.cnblogs.com/rhcad/archive/2011/12/21/2295507.html 浅谈Python装饰器&#xff1a;https://blog.csdn.net/mdl13412/article/details/22608283 Python装饰器与面向切面编程&#xff1a;http://www.cn…

Homepod评测:一款音质超棒但低智商的音箱

来源&#xff1a;The Verge今天&#xff0c;让我们再近距离看看Homepod这款明星产品吧。Homepod拥有卓越的音质HomePod的电源线内置并包裹在面料中&#xff0c;顶部有LED背光音量按钮和一个“显示屏”。之所以打引号是因为它不是真正意义上的显示屏&#xff0c;而是一块LED。它…

Python 生成器 和 yield 关键字

Python 中 yield 的作用&#xff1a;http://youchen.me/2017/02/10/Python-What-does-yield-do/# Python 生成器详解&#xff1a;http://codingpy.com/article/python-generator-notes-by-kissg/#generator Python yield与实现&#xff1a;http://www.cnblogs.com/coder2012/p/…

Extract Subclass(提炼子类)

类中的某些特性只被某些实例用到 重构&#xff1a;新建一个子类&#xff0c;将一部分特性搬移到子类中

从上市公司数据读懂我国新兴产业发展态势

来源&#xff1a;新软件 ccidsaism在全球新一轮科技革命和产业变革中&#xff0c;大数据、物联网、区块链、量子通信、人工智能、机器人、可穿戴设备、新材料等新兴领域展现出巨大的发展潜力&#xff0c;极有可能对未来产业格局和市场产生决定性影响。这些领域的上市公司已成为…

JavaScript,等比例缩放图片的函数,很好用。

在Web上显示图片&#xff0c;通常都会有图片显示比例问题&#xff0c;如果不给<img />限制width和height&#xff0c;那么如果图片大了就会将整个页面挤乱&#xff0c;图片小了又会使图片失真。我的需求如下&#xff1a;1、预先定义好图片显示的标准宽度和高度。2、如果图…

深度学习综述:Hinton、Yann LeCun和Bengio经典重读

来源&#xff1a;人工智能头条翻译 | kevin&#xff0c;刘志远审校 | 李成华深度学习三巨头Geoffrey Hinton、Yann LeCun和Yoshua Bengio对AI领域的贡献无人不知、无人不晓。本文是《Nature》杂志为纪念人工智能60周年而专门推出的深度学习综述&#xff0c;也是Hinton、LeCun和…

Extract Superclass(提炼超类)

两个类有相识特性 重构&#xff1a;为这两个类建立一个超类&#xff0c;将相同特性搬移到超类