Python 四大迷宫生成算法实现(2): 随机Prim算法

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/marble_xu/article/details/88285926

随机Prim算法简介

原始版本的随机Prim算法是维护一个墙的列表。
首先随机选择一个迷宫单元,设置为已访问,然后把它的所有邻墙放入列表。
当列表里还有墙时,重复下面循环

  • 从列表里随机选择一面墙,
    • 如果这面墙相邻的两个迷宫单元只有一个被访问过,先把这面墙设置为打通,并从列表里删除这面墙,然后把未访问的迷宫单元设为已访问,并把这个迷宫单元的邻墙添加到列表。
    • 如果这面墙相邻的两个单元单元都已经被访问过,那就从列表里删除这面墙

但是在实现时要同时记录墙和迷宫单元的信息,会比较复杂,所以使用改进版本,只维护一个迷宫单元的列表。地图信息和之前的递推回溯算法是一样的。

下图是算法使用的地图,地图最外围默认是一圈墙,其中白色单元是迷宫单元,黑色单元是墙,相邻白色单元之前的墙是可以被去掉的。可以看到这个地图中所有的迷宫单元在地图中的位置(X,Y),比如(1,1),(5,9)都是奇数,可以表示成(2 * x+1, 2 * y+1), x和y的取值范围从0到4。在迷宫生成算法中会用到这个表示方式。同时迷宫的长度和宽度必须为奇数。
地图示例
算法主循环,重复下面步骤2直到检查列表为空:
1 随机选择一个迷宫单元作为起点,加入检查列表并标记为已访问
2 当检查列表非空时,随机从列表中取出一个迷宫单元,进行循环

  • 如果当前迷宫单元有未被访问过的相邻迷宫单元
    • 随机选择一个未访问的相邻迷宫单元
    • 去掉当前迷宫单元与相邻迷宫单元之间的墙
    • 标记相邻迷宫单元为已访问,并将它加入检查列表
  • 否则,当前迷宫单元没有未访问的相邻迷宫单元
    • 则从检查列表删除当前迷宫单元

关键代码介绍

保存基本信息的地图类

这个和上一篇的递归回溯算法使用相同的地图类,这里就省略了。

算法主函数介绍

doRandomPrim 函数 先调用resetMap函数将地图都设置为墙,迷宫单元所在位置为墙表示未访问。有个注意点是地图的长宽和迷宫单元的位置取值范围的对应关系。
假如地图的宽度是31,长度是21,对应的迷宫单元的位置取值范围是 x(0,15), y(0,10), 因为迷宫单元(x,y)对应到地图上的位置是(2 * x+1, 2 * y+1)。
randomPrim 函数就是上面算法主循环的实现。

# random prim algorithm
def randomPrim(map, width, height):
	startX, startY = (randint(0, width-1), randint(0, height-1))
	print("start(%d, %d)" % (startX, startY))
	map.setMap(2*startX+1, 2*startY+1, MAP_ENTRY_TYPE.MAP_EMPTY)
	
	checklist = []
	checklist.append((startX, startY))
	while len(checklist):
		# select a random entry from checklist
		entry = choice(checklist)	
		if not checkAdjacentPos(map, entry[0], entry[1], width, height, checklist):
			# the entry has no unvisited adjacent entry, so remove it from checklist
			checklist.remove(entry)
			
def doRandomPrim(map):
	# set all entries of map to wall
	map.resetMap(MAP_ENTRY_TYPE.MAP_BLOCK)	
	randomPrim(map, (map.width-1)//2, (map.height-1)//2)

checkAdjacentPos 函数检查当前迷宫单元的是否有未访问的相邻单元,如果有,则随即选取一个相邻单元,标记未已访问,并去掉当前迷宫单元与相邻迷宫单元之间的墙。如果没有,则不做操作。这个和上一篇的递归回溯算法公用相同的函数,这里就省略了。

代码的初始化

可以调整地图的长度,宽度,注意长度和宽度必须为奇数。

def run():
	WIDTH = 31
	HEIGHT = 21
	map = Map(WIDTH, HEIGHT)
	doRandomPrim(map)
	map.showMap()	
	
if __name__ == "__main__":
	run()

执行的效果图如下,start 表示第一个随机选择的迷宫单元。迷宫中’#'表示墙,'0’表示通道。
在这里插入图片描述

完整代码

使用python3.7编译

from random import randint, choice
from enum import Enum

class MAP_ENTRY_TYPE(Enum):
	MAP_EMPTY = 0,
	MAP_BLOCK = 1,

class WALL_DIRECTION(Enum):
	WALL_LEFT = 0,
	WALL_UP = 1,
	WALL_RIGHT = 2,
	WALL_DOWN = 3,

class Map():
	def __init__(self, width, height):
		self.width = width
		self.height = height
		self.map = [[0 for x in range(self.width)] for y in range(self.height)]
	
	def resetMap(self, value):
		for y in range(self.height):
			for x in range(self.width):
				self.setMap(x, y, value)
	
	def setMap(self, x, y, value):
		if value == MAP_ENTRY_TYPE.MAP_EMPTY:
			self.map[y][x] = 0
		elif value == MAP_ENTRY_TYPE.MAP_BLOCK:
			self.map[y][x] = 1
	
	def isVisited(self, x, y):
		return self.map[y][x] != 1

	def showMap(self):
		for row in self.map:
			s = ''
			for entry in row:
				if entry == 0:
					s += ' 0'
				elif entry == 1:
					s += ' #'
				else:
					s += ' X'
			print(s)

# find unvisited adjacent entries of four possible entris
# then add random one of them to checklist and mark it as visited
def checkAdjacentPos(map, x, y, width, height, checklist):
	directions = []
	if x > 0:
		if not map.isVisited(2*(x-1)+1, 2*y+1):
			directions.append(WALL_DIRECTION.WALL_LEFT)
				
	if y > 0:
		if not map.isVisited(2*x+1, 2*(y-1)+1):
			directions.append(WALL_DIRECTION.WALL_UP)

	if x < width -1:
		if not map.isVisited(2*(x+1)+1, 2*y+1):
			directions.append(WALL_DIRECTION.WALL_RIGHT)
		
	if y < height -1:
		if not map.isVisited(2*x+1, 2*(y+1)+1):
			directions.append(WALL_DIRECTION.WALL_DOWN)
		
	if len(directions):
		direction = choice(directions)
		#print("(%d, %d) => %s" % (x, y, str(direction)))
		if direction == WALL_DIRECTION.WALL_LEFT:
				map.setMap(2*(x-1)+1, 2*y+1, MAP_ENTRY_TYPE.MAP_EMPTY)
				map.setMap(2*x, 2*y+1, MAP_ENTRY_TYPE.MAP_EMPTY)
				checklist.append((x-1, y))
		elif direction == WALL_DIRECTION.WALL_UP:
				map.setMap(2*x+1, 2*(y-1)+1, MAP_ENTRY_TYPE.MAP_EMPTY)
				map.setMap(2*x+1, 2*y, MAP_ENTRY_TYPE.MAP_EMPTY)
				checklist.append((x, y-1))
		elif direction == WALL_DIRECTION.WALL_RIGHT:
				map.setMap(2*(x+1)+1, 2*y+1, MAP_ENTRY_TYPE.MAP_EMPTY)
				map.setMap(2*x+2, 2*y+1, MAP_ENTRY_TYPE.MAP_EMPTY)
				checklist.append((x+1, y))
		elif direction == WALL_DIRECTION.WALL_DOWN:
			map.setMap(2*x+1, 2*(y+1)+1, MAP_ENTRY_TYPE.MAP_EMPTY)
			map.setMap(2*x+1, 2*y+2, MAP_ENTRY_TYPE.MAP_EMPTY)
			checklist.append((x, y+1))
		return True
	else:
		# if not find any unvisited adjacent entry
		return False
		
		
# random prim algorithm
def randomPrim(map, width, height):
	startX, startY = (randint(0, width-1), randint(0, height-1))
	print("start(%d, %d)" % (startX, startY))
	map.setMap(2*startX+1, 2*startY+1, MAP_ENTRY_TYPE.MAP_EMPTY)
	
	checklist = []
	checklist.append((startX, startY))
	while len(checklist):
		# select a random entry from checklist
		entry = choice(checklist)	
		if not checkAdjacentPos(map, entry[0], entry[1], width, height, checklist):
			# the entry has no unvisited adjacent entry, so remove it from checklist
			checklist.remove(entry)
			
def doRandomPrim(map):
	# set all entries of map to wall
	map.resetMap(MAP_ENTRY_TYPE.MAP_BLOCK)	
	randomPrim(map, (map.width-1)//2, (map.height-1)//2)

def run():
	WIDTH = 31
	HEIGHT = 21
	map = Map(WIDTH, HEIGHT)
	doRandomPrim(map)
	map.showMap()	
	
if __name__ == "__main__":
	run()

猜你喜欢

转载自blog.csdn.net/marble_xu/article/details/88285926
今日推荐