C語言是全部編程語言的基礎,當咱們對C語言有足夠深刻的理解後,就能輕鬆入門其餘語言,好比如今流行的Python。如今,我將帶領你們看一個基於C語言經典算法,使用Python編寫的塔防小遊戲。python
在塔防遊戲中,有許多敵人向着同一目標前進。在不少塔防遊戲當中,有一條或幾條事先預約好的路徑。在一些中,好比經典的《Desktop Tower Defense》,你能夠將塔放在任何位置,它們充當障礙影響敵人選擇的路徑。試一試,點擊地圖來移動牆壁:c++
咱們如何來實現這種效果?算法
像A*這樣的圖搜索算法常常被用來尋找兩點之間的最短路徑。你能夠用這個來爲每個敵人找到前往目標的路徑。在這種類型的遊戲當中,咱們有不少不一樣的圖搜索算法來。這是一些經典方法編程
像《Desktop Tower Defense》這樣的遊戲會有不少個敵人(源)和一個共同的目的地。這使得它被歸爲多源單目標一類。咱們能夠執行一個算法,一次算出全部敵人的路徑,而不是爲每一個敵人執行一次A*算法。更好的是,咱們能夠計算出每一個位置的最短路徑,因此當敵人擠在一塊或者新敵人被建立時,他們的路徑已經被計算好了。數組
咱們先來看看有時也被稱做「洪水填充法」(FIFO變種)的廣度優先算法。雖然圖搜索算法是適用於任何由節點和邊構成的圖,可是我仍是使用方形網格來表示這些例子。網格是圖的一個特例。每一個網格瓦片是圖節點,網格瓷磚之間的邊界是圖的邊。我會在另外一篇文章當中探討非網格圖。編程語言
廣度優先搜索始於一個節點,並訪問鄰居節點。關鍵的概念是「邊界」,在已探索和未開發的區域之間的邊界。邊界從原始節點向外擴展,直到探索了整張圖。動畫
邊界隊列是一個圖節點(網格瓦片)是否須要被分析的列表/數組。它最開始僅僅包含一個元素,起始節點。每一個節點上的訪問標誌追蹤咱們是否採訪過該節點。開始的時候除了起始節點都標誌爲FALSE。使用滑塊來查看邊界是如何擴展的:spa
這個算法是如何工做的?在每一步,得到一個元素的邊界並把它命名爲current。而後尋找current的每一個鄰居,next。若是他們尚未被訪問過的話,將他們都添加到邊界隊列裏面。下面是一些python代碼:3d
Python frontier = Queue() frontier.put(start) visited = {} visited[start] = True while not frontier.empty(): current = frontier.get() for next in graph.neighbors(current): if next not in visited: frontier.put(next) visited[next] = True frontier = Queue() frontier.put(start) visited = {} visited[start] = True while not frontier.empty(): current = frontier.get() for next in graph.neighbors(current): if next not in visited: frontier.put(next) visited[next] = True
如今已經看見代碼了,試着步進上面的動畫。注意邊界隊列,關於current的代碼,還有next節點的集合。在每一步,有一個邊界元素成爲current節點,它的鄰居節點會被標註,而且未被拜訪過的鄰居節點會被添加到邊界隊列。有一些鄰居節點可能已經被訪問過,他們就不須要被添加到邊界隊列裏面了。指針
1.標識全部可達的點。這在你的圖不是徹底鏈接的,而且想知道哪些點是可達的時候是頗有用的。這就是我再上面用visited這部分所作的。
2.尋找從一個點到全部其餘點或者全部點到一個點的路徑。我在文章開始部分的動畫demo裏面使用了它。
3.測量從一個點到全部其餘點的距離。這在想知道一個移動中的怪物的距離時是頗有用的。
若是你正在生成路徑,你可能會想知道從每一個點移動的方向。當你訪問一個鄰居節點的時候,要記得你是從哪一個節點過來的。讓咱們把visited重命名爲came_from而且用它來保存以前位置的軌跡:
Python frontier = Queue() frontier.put(start) came_from = {} came_from[start] = None while not frontier.empty(): current = frontier.get() for next in graph.neighbors(current): if next not in came_from: frontier.put(next) came_from[next] = current frontier = Queue() frontier.put(start) came_from = {} came_from[start] = None while not frontier.empty(): current = frontier.get() for next in graph.neighbors(current): if next not in came_from: frontier.put(next) came_from[next] = current
咱們來看看它看起來是怎樣的:
若是你須要距離,你能夠在起始節點講一個計數器設置爲0,並在每次訪問鄰居節點的時候將它加一。讓咱們把visitd重命名爲distance,而且用它來存儲一個計數器:
Python frontier = Queue() frontier.put(start) distance = {} distance[start] = 0 while not frontier.empty(): current = frontier.get() for next in graph.neighbors(current): if next not in distance: frontier.put(next) distance[next] = 1 + distance[current] frontier = Queue() frontier.put(start) distance = {} distance[start] = 0 while not frontier.empty(): current = frontier.get() for next in graph.neighbors(current): if next not in distance: frontier.put(next) distance[next] = 1 + distance[current]
咱們來看看它看起來是怎樣的:
若是你想同時計算路徑和距離,你可使用兩個變量。
這就是廣度優先檢索算法。對於塔防風格的遊戲,我用它來計算全部位置到一個指定位置的路徑,而不是重複使用A*算法爲每一個敵人分開計算路徑。我用它來尋找一個怪物指定行動距離內全部的位置。我也是用它來進行程序化的地圖生成。Minecraft使用它來進行可見性提出。因而可知這是一個不錯的算法。
下一步
我有python和c++代碼的實現。 若是你想要找到從一個點出發而不是到達一個點的路徑,只須要在檢索路徑的時候翻轉came_from指針。
若是你想要知道一些點而不是一個點的路徑,你能夠在圖的邊緣爲你的每一個目標點添加一個額外的點。額外的點不會出如今網格中,可是它會表示在圖中的目標位置。 提早退出:若是你是在尋找一個到達某一點或從某一點出發,。我在A*算法的文章當中描述了這種狀況。
加權邊:若是你須要不一樣的移動成本,廣度優先搜索能夠替換爲爲Dijkstra算法。我在A*算法的文章當中描述了這種狀況。
啓發:若是你須要添加一種指導尋找目標的方法,廣度優先算法能夠替換爲最佳優先算法。我在A*算法的文章當中描述了這種狀況。
若是你從廣度優先算法,而且加上了提早退出,加權邊和啓發,你會獲得A*。如你所想,我在A*算法的文章當中描述了這種狀況。