计算机软件实习项目三 —— 超级玛丽闯迷宫 (代码实现) 12-21
代碼實(shí)現(xiàn)(超級(jí)瑪麗闖迷宮)
??上一篇博客對(duì)這個(gè)游戲的整體框架以及算法的流程進(jìn)行了比較詳細(xì)的設(shè)計(jì)及分析;對(duì)必要的類和類的成員變量、類的方法進(jìn)行了聲明以及聲明。這一篇博客主要來呈現(xiàn)相關(guān)代碼。
目錄
- 代碼實(shí)現(xiàn)(超級(jí)瑪麗闖迷宮)
- 一、迷宮定義(Map類)
- 1. 定義枚舉類
- 2. 成員變量
- 3. 成員方法
- 二、隨機(jī)生成迷宮(Prim算法)
- 1. prim算法生成迷宮流程
- 三、迷宮自動(dòng)尋路(A*算法)
- 1. 定義結(jié)點(diǎn)
- 2. 子方法
- 3. 算法流程
- 四、游戲界面(PyQt5)
- 1. 成員變量
- 2. 構(gòu)造函數(shù)
- 3. 子方法
一、迷宮定義(Map類)
??要實(shí)現(xiàn)隨機(jī)生成迷宮第一步當(dāng)然是定義迷宮,定義迷宮的大小以及對(duì)迷宮進(jìn)行獲取、設(shè)置、重置、判斷、打印等操作的方法。
# 定義地圖類 class Map():1. 定義枚舉類
??用于規(guī)范結(jié)點(diǎn)類型和墻的方位。
(1)NODE_TYPE(Enum): 結(jié)點(diǎn)的類型。
class NODE_TYPE(Enum):NODE_EMPTY = 0,NODE_BLOCK = 1,NODE_START = 2,NODE_END = 3,(2)WALL_DIRECTION(Enum): 墻壁在當(dāng)前結(jié)點(diǎn)的方位。
class WALL_DIRECTION(Enum):WALL_LEFT = 0,WALL_UP = 1,WALL_RIGHT = 2,WALL_DOWN = 3,2. 成員變量
(1)width,height:定義迷宮的行數(shù)和列數(shù)。
(2)map:二維列表用來存放迷宮地圖,坐標(biāo)定義與數(shù)組索引相似,x為縱向,y為橫向(與一般坐標(biāo)系不同)。
二維列表中每一個(gè)元素的值代表不同的含義,分別如下:
① map[i][j] == 0:當(dāng)前格為空格。
② map[i][j] == 1:當(dāng)前格為墻壁。
③ map[i][j] == 2:當(dāng)前格為起點(diǎn)。
④ map[i][j] == 3:當(dāng)前格為終點(diǎn)。
⑤ map[i][j] == 4:當(dāng)前格為自動(dòng)尋路路徑上的結(jié)點(diǎn)。
def __init__(self, width, height):self.width = widthself.height = height# 嵌套使用列表解析快速生成地圖self.map = [[0 for x in range(self.width)] for y in range(self.height)]3. 成員方法
(1)getMap(self):返回當(dāng)前對(duì)象的map[n][m]二位迷宮列表。
def getMap(self):return self.map(2)resetNode(self, value):設(shè)置map[n][m]二位迷宮列表的每一個(gè)元素為value值。
def resetNode(self, value):for x in range(self.height):for y in range(self.width):self.setNode(x, y, value)(3)setNode(self, x, y, value):設(shè)置map[x][y] = value。
def setNode(self, x, y, value):if value == NODE_TYPE.NODE_EMPTY:self.map[x][y] = 0elif value == NODE_TYPE.NODE_BLOCK:self.map[x][y] = 1elif value == NODE_TYPE.NODE_START:self.map[x][y] = 2elif value == NODE_TYPE.NODE_END:self.map[x][y] = 3(4)isWall(self, x, y):判斷map[x][y] = 1 ?也就是判斷是否是墻壁。
def isWall(self, x, y):return self.map[x][y] == 1(5)showMap(self):打印map[n][m]整個(gè)迷宮。
def showMap(self):for row in self.map:s = ''for col in row:if col == 0:s += ' 'elif col == 1:s += ' #'elif col == 2:s += ' S'elif col == 3:s += ' E'print(s)print(self.map)二、隨機(jī)生成迷宮(Prim算法)
1. prim算法生成迷宮流程
def randomPrim(map, width, height):預(yù)處理部分:
(1)定義一個(gè)檢查列表checklist存放為劃分頂點(diǎn)集。
checklist = [](2)把初始起點(diǎn)加入checklist列表中。
checklist.append((startX, startY)) # 把初始結(jié)點(diǎn)加入隊(duì)列主循環(huán)部分:
(3)重復(fù)下面幾個(gè)步驟直到檢查列表checklist為空:
# 如果列表不為空while len(checklist):??① 隨機(jī)取出checklist列表中的一個(gè)結(jié)點(diǎn)node1。
# 每次隨機(jī)選擇一個(gè)空結(jié)點(diǎn)entry = choice(checklist)??② 檢查這個(gè)結(jié)點(diǎn)node1周圍有沒有墻wall。
if checkAdjacentPos(map, entry[0], entry[1], width, height, checklist):????③ 有墻:從這個(gè)結(jié)點(diǎn)node1周圍的墻中隨機(jī)選一個(gè),假設(shè)是wall_left,把這個(gè)墻置為空;并且把這個(gè)墻與node1之間的結(jié)點(diǎn)也置為空;最后把這個(gè)墻的位置坐標(biāo)加入checklist中。
directions = []if x > 0:if map.isWall(2 * (x - 1) + 1, 2 * y + 1):directions.append(WALL_DIRECTION.WALL_UP)if y > 0:if map.isWall(2 * x + 1, 2 * (y - 1) + 1):directions.append(WALL_DIRECTION.WALL_LEFT)if x < height - 1:if map.isWall(2 * (x + 1) + 1, 2 * y + 1):directions.append(WALL_DIRECTION.WALL_DOWN)if y < width - 1:if map.isWall(2 * x + 1, 2 * (y + 1) + 1):directions.append(WALL_DIRECTION.WALL_RIGHT)# 如果當(dāng)前結(jié)點(diǎn)四周的相鄰結(jié)點(diǎn)有墻if len(directions):# 隨機(jī)選一個(gè)墻direction = choice(directions)if direction == WALL_DIRECTION.WALL_LEFT:map.setNode(2 * x + 1, 2 * (y - 1) + 1, NODE_TYPE.NODE_EMPTY)map.setNode(2 * x + 1, 2 * y, NODE_TYPE.NODE_EMPTY)checklist.append((x, y - 1))elif direction == WALL_DIRECTION.WALL_UP:map.setNode(2 * (x - 1) + 1, 2 * y + 1, NODE_TYPE.NODE_EMPTY)map.setNode(2 * x, 2 * y + 1, NODE_TYPE.NODE_EMPTY)checklist.append((x - 1, y))elif direction == WALL_DIRECTION.WALL_RIGHT:map.setNode(2 * x + 1, 2 * (y + 1) + 1, NODE_TYPE.NODE_EMPTY)map.setNode(2 * x + 1, 2 * (y + 1), NODE_TYPE.NODE_EMPTY)checklist.append((x, y + 1))elif direction == WALL_DIRECTION.WALL_DOWN:map.setNode(2 * (x + 1) + 1, 2 * y + 1, NODE_TYPE.NODE_EMPTY)map.setNode(2 * (x + 1), 2 * y + 1, NODE_TYPE.NODE_EMPTY)checklist.append((x + 1, y))????④ 無墻:把node1從checklist中刪除。
checklist.remove(entry)(4)通過上述步驟后,迷宮即可隨機(jī)生成。
三、迷宮自動(dòng)尋路(A*算法)
1. 定義結(jié)點(diǎn)
??為什么要定義結(jié)點(diǎn):因?yàn)樽詈筝敵雎窂降臅r(shí)候需要從最后一個(gè)目標(biāo)結(jié)點(diǎn)不斷訪問父節(jié)點(diǎn)來進(jìn)行路徑追溯,最后才能輸出從源節(jié)點(diǎn)到目標(biāo)結(jié)點(diǎn)的一條完整路徑。
主要變量有:
- x、y坐標(biāo)
- 沿當(dāng)前路徑到map[x][y]的路徑長(zhǎng)度(G值)
- 當(dāng)前路徑的F值(F = G + H)
- 父節(jié)點(diǎn)(用于路徑回溯)
2. 子方法
(1)pathEvaluate(val, fx, fy, ex, ey) :
??計(jì)算當(dāng)前路徑代價(jià)(F = G + H)。
# 當(dāng)前路徑代價(jià) = 當(dāng)前路徑長(zhǎng)度 + 曼哈頓距離 def pathEvaluate(val, fx, fy, ex, ey):return val + abs(ey - fy) + abs(ex - fx)(2)findMin(open) :
??返回open表中代價(jià)最小的結(jié)點(diǎn)。
# 返回open表中代價(jià)最小的結(jié)點(diǎn) def findMin(open):min_val = open[0]for temp in open:if temp.score < min_val.score:min_val = tempreturn min_val(3)find_node(node, list):
??判斷某個(gè)結(jié)點(diǎn)在不在某個(gè)表中。
# 判斷某個(gè)結(jié)點(diǎn)在不在某個(gè)表中 def find_node(node, list):for i in range(0,list.__len__()):if list[i].x == node.x and list[i].y == node.y:return ireturn -1(4)AStar(map, row, col, sx, sy, ex, ey)方法:
??完成A* 算法的主邏輯,執(zhí)行過程中調(diào)用前面三個(gè)子方法。
# A*算法 def AStar(map, row, col, sx, sy, ex, ey):3. 算法流程
定義與預(yù)處理部分:
(1)定義一個(gè)open開放列表,這里面的結(jié)點(diǎn)可能更新可能移除。
(2)定義一個(gè)close封閉列表 ,這里面的結(jié)點(diǎn)不需要處理。
(3)往open表中加入起點(diǎn)。
主循環(huán)部分:
(4)重復(fù)下面幾個(gè)步驟直到檢查列表checklist為空:
# 只要open表不為空就繼續(xù)搜索while len(open):??① 找到open表中代價(jià)最小的結(jié)點(diǎn),把它從open表中取出并放入close表中。
# 找到open表中代價(jià)最小的結(jié)點(diǎn)node = findMin(open)# 刪除open表中剛才找到的最小結(jié)點(diǎn)open.remove(node)# 在close表中加入剛才找到的最小結(jié)點(diǎn)close.append(node)??② 判斷這個(gè)結(jié)點(diǎn)是否是目標(biāo)結(jié)點(diǎn)
# 如果目標(biāo)結(jié)點(diǎn)是終點(diǎn),返回pathif node.x == ex and node.y == ey:??????③ 是:通過這個(gè)結(jié)點(diǎn)不斷回溯到源節(jié)點(diǎn)生成一條路徑,返回這個(gè)路徑并退出整個(gè)AStar函數(shù)。
path = []while node.parent != -1:path.append((node.x, node.y))node = node.parent# path.append([node.x, node.y])del(path[0])path.reverse()return path??????④ 否:往相鄰結(jié)點(diǎn)nx拓展搜索。
# 往相鄰結(jié)點(diǎn)拓展搜索for add in turn:dx = node.x + add[0] # 目的結(jié)點(diǎn)橫坐標(biāo)dy = node.y + add[1] # 目的結(jié)點(diǎn)縱坐標(biāo)dl = node.long + 1 # 目的路徑長(zhǎng)度ds = pathEvaluate(dl, dx, dy, ex, ey) # 計(jì)算目標(biāo)結(jié)點(diǎn)路徑代價(jià)d_node = Node(dx, dy, dl, ds, node)??????????⑤ 如果相鄰結(jié)點(diǎn)nx:1.在close表里。2.是墻壁。3. 超出地圖邊界。就跳過這個(gè)相鄰結(jié)點(diǎn)nx,直到這個(gè)結(jié)點(diǎn)的4個(gè)相鄰結(jié)(n1、n2、n3、n4)點(diǎn)都判斷過。
# 如果相鄰結(jié)點(diǎn)1.在close表里。2.是墻壁。3. 超出地圖邊界。就不拓展。if find_node(d_node, close) != -1 or map[dx][dy] == 1 or dx < 0 or dy < 0 or dx >= row or dy >= col:continue??????????⑥ 如果相鄰結(jié)點(diǎn)nx在open表中(注意只要 (x,y) 坐標(biāo)相同就算相同),比較相鄰結(jié)點(diǎn)nx和open表中結(jié)點(diǎn)的G移動(dòng)代價(jià),如果相鄰結(jié)點(diǎn)n的G值比open表中相同結(jié)點(diǎn)的G值小,則更新open表中的相同結(jié)點(diǎn)的G值和F值,并且把open表中的相同結(jié)點(diǎn)的父節(jié)點(diǎn)設(shè)置為當(dāng)前結(jié)點(diǎn)。
# 如果目標(biāo)結(jié)點(diǎn)在open表中pos = find_node(d_node, open)if pos != -1:t_node = open[pos]if d_node.long < t_node.long:open[pos].long = d_nodeopen[pos].score = pathEvaluate(open[pos].long, open[pos].x, open[pos].y, ex, ey)open[pos].parent = node??????????⑦ 如果相鄰結(jié)點(diǎn)nx不在open表中,直接把相鄰結(jié)點(diǎn)nx加入open表中。
# 如果不在open表中,將當(dāng)前結(jié)點(diǎn)加入open表else:open.append(d_node)(4)通過上述步驟后,從源結(jié)點(diǎn)到目標(biāo)結(jié)點(diǎn)的路徑就生成了。
四、游戲界面(PyQt5)
1. 成員變量
??① 窗口變量:設(shè)置窗口大小
# 窗口變量width = 900height = 900??② 地圖變量:設(shè)置地圖行列數(shù)、起點(diǎn)、終點(diǎn)、地圖列表
# 地圖變量row = 7col = 7start_x = 1start_y = 0end_x = row - 2end_y = col - 1map = [] # 隨機(jī)生成的迷宮??③ 玩家變量:定義玩家坐標(biāo)
# 玩家變量player_x = 1player_y = 0??④ 游戲標(biāo)志:定義各種游戲標(biāo)志,如到第幾關(guān)、是否通關(guān)、人物左右。
# 游戲標(biāo)志player_direction = 1 # 0代表人朝左,1代表人朝右key_flag = 1 # 標(biāo)志是否可以按鍵level = 1 # 游戲關(guān)卡paint_label = 1 # 畫關(guān)卡win_flag = 0 # 標(biāo)記勝利??⑤ 音樂播放器:音樂播放器的初始化以及音樂的導(dǎo)入。
# 音樂播放器pygame.mixer.init()pygame.mixer.music.load(r"Music/Level 1.mp3") # 背景音樂sound_grow = pygame.mixer.Sound(r"Music/grow.mp3") # 升級(jí)音效sound_pass = pygame.mixer.Sound(r"Music/Pass.mp3") # 過關(guān)音效sound_gold = pygame.mixer.Sound(r"Music/gold.mp3") # 金幣音效sound_gold.set_volume(0.2)2. 構(gòu)造函數(shù)
3. 子方法
??從理解Prim算法和A* 算法到自己實(shí)現(xiàn)這個(gè)算法需要不斷地嘗試、調(diào)試,在這個(gè)過程中又會(huì)進(jìn)一步加深對(duì)這些算法的理解。想法往往是簡(jiǎn)單的,而實(shí)現(xiàn)是復(fù)雜的,想得到的不一定實(shí)現(xiàn)的了。
———2020.12.21(羅涵)
THE END
總結(jié)
以上是生活随笔為你收集整理的计算机软件实习项目三 —— 超级玛丽闯迷宫 (代码实现) 12-21的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Redis实战:第五章-使用Redis构
- 下一篇: 动手学数据分析(五)- 模型建立和评估