Python 四大迷宮生成算法實現(2): 隨機Prim算法

隨機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()
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章