如何用Python寫乙個貪吃蛇AI

2021-09-11 09:18:42 字數 4686 閱讀 2083

這兩天在網上看到一張讓人漲姿勢的,中展示的是貪吃蛇遊戲, 估計大部分人都玩過。但如果僅僅是貪吃蛇遊戲,那麼它就沒有什麼讓人漲姿勢的地方了。 問題的關鍵在於,中的貪吃蛇真的很貪吃xd,它把矩形**現的食物吃了個遍, 然後華麗麗地把整個矩形填滿,真心是看得賞心悅目。作為乙個cser, 第乙個想到的是,這東西是寫程式實現的(因為,一般人幹不出這事。 果斷是要讓程式來幹的)第二個想到的是,寫程式該如何實現,該用什麼演算法? 既然開始想了,就開始做。因為talk is cheap,要show me the code才行。 (從耗子叔那學來的)

snake_ai

life is short, use python! 所以,根本就沒多想,直接上python。

先讓你的程式跑起來

首先,我們第一件要做的就是先不要去分析這個問題。 你好歹先寫個能執行起來的貪吃蛇遊戲,然後再去想ai部分。這個應該很簡單, c\c++也就百來行**(如果我沒記錯的話。不弄複雜介面,直接在控制台下跑), python就更簡單了,去掉注釋和空行,5、60行**就搞定了。而且,最最關鍵的, 這個東西網上肯定寫濫了,你沒有必要重複造輪子, 去弄乙份來按照你的意願改造一下就行了。

簡單版本

我覺得直接寫perfect版本不是什麼好路子。因為perfect版本往往要考慮很多東西, 直接上來就寫這個一般是bug百出的。所以, 一開始我的目標僅僅是讓程式去控制貪吃蛇運動,讓它去吃食物,僅此而已。 現在讓我們來陳述一下最初的問題:

在乙個矩形中,每一時刻有乙個食物,貪吃蛇要在不撞到自己的條件下, 找到一條路(未必要最優),然後沿著這條路執行,去享用它的美食

我們先不去想蛇會越來越長這個事實,問題基本就是,給你乙個起點(蛇頭)和乙個終點( 食物),要避開障礙物(蛇身),從起點找到一條可行路到達終點。 我們可以用的方法有:

只要有選擇,就先選擇最簡單的方案,我們現在的目標是要讓程式先跑起來, 優化是後話。so,從bfs開始。我們最初將蛇頭位置放入佇列,然後只要佇列非空, 就將隊頭位置出隊,然後把它四領域內的4個點放入佇列,不斷地迴圈操作, 直到到達食物的位置。這個過程中,我們需要注意幾點:1.訪問過的點不再訪問。 2.儲存每個點的父結點(即每個位置是從哪個位置走到它的, 這樣我們才能把可行路徑找出來)。3.蛇身所在位置和四面牆不可訪問。

通過bfs找到食物後,只需要讓蛇沿著可行路徑運動即可。這個簡單版本寫完後, 貪吃蛇就可以很歡快地執行一段時間了。看圖吧:(不流暢的感覺來自錄屏軟體@_@)

fb7ffd6eea395b62d80b3dff42745df5

為了盡量保持簡單,我用的是curses模組,直接在終端進行繪圖。 從上面的動態可以看出,每次都單純地使用bfs,最終有一天, 貪吃蛇會因為這種不顧後果的短視行為而陷入困境。 而且,即使到了那個時候,它也只會bfs一種策略, 導致因為當前看不到目標(食物),認為自己這輩子就這樣了,破罐子破摔, 最終停在它人生中的某乙個點,不再前進。(我好愛講哲理xd)

上一節的簡單版本跑起來後,我們認識到,只教貪吃蛇一種策略是不行的。 它這麼笨一條蛇,你不多教它一點,它分分鐘就會掛掉的。 所以,我寫了個wander函式,顧名思義,當貪吃蛇陷入困境後, 就別讓它再bfs了,而是讓它隨便四處走走,散散心,思考一下人生什麼的。 這個就好比你困惑迷茫的時候還去工作,效率不佳不說,還可能阻礙你走出困境; 相反,這時候你如果放下手中的工作,停下來,出去旅個遊什麼的。回來時, 說不定就豁然開朗,土地平曠,屋舍儼然了。

wander函式怎麼寫都行,但是肯定有優劣之分。我寫了兩個版本,乙個是在可行的範圍內, 朝隨機方向走隨機步。也就是說,蛇每次運動的方向是隨機出來的, 總共運動的步數也是隨機的。wander完之後,再去bfs一下,看能否吃到食物, 如果可以那就皆大歡喜了。如果不行,說明思考人生的時間還不夠,再wander一下。 這樣過程不斷地迴圈進行。可是就像「隨機過程隨機過」一樣,你「隨機wander就隨機掛」。 會wander的蛇確實能多走好多步。可是有一天,它就會把自己給隨機到一條死路上了。 陷入困境還可以wander,進入死胡同,那可沒有回滾機制。所以, 第二個版本的wander函式,我就讓貪吃蛇貪到底。在bfs無解後, 告訴蛇乙個步數step(隨機產生step),讓它在空白區域以s形運動step步。 這回運動方向就不隨機了,而是有組織有紀律地運動。先看圖,然後再說說它的問題:

751cdec011158be8665b554188c4a959

沒錯,最終還是掛掉了。s形運動也是無法讓貪吃蛇避免死亡的命運。 貪吃蛇可以靠s形運動多存活一段時間,可是由於它的策略是:

沒有按下esc鍵: if 蛇與食物間有路徑: 走起,吃食物去 else: wander一段時間

問題就出在蛇發現它自己和食物間有路徑,就二話不說跑去吃食物了。 它沒有考慮到,你這一去把食物給吃了後形成的局勢(蛇身布局), 完全就可能讓你掛掉。(比如進入了乙個自己蛇身圍起來的封閉小空間)

so,為了能讓蛇活得久一些,它還要更**遠矚才行。

我們現在已經有了乙個比較低端的版本,而且對問題的認識也稍微深入了一些。 現在可以進行一些比較慎密和嚴謹的分析了。首先,讓我們羅列一些問題: (像頭腦風暴那樣,想到什麼就寫下來即可)

只要去想,問題還挺多的。這時讓我們以面向過程的思想,帶著上面的問題, 把思路理一理。一開始,蛇很短(初始化長度為1),它看到了乙個食物, 使用bfs得到矩形中每個位置到達食物的最短路徑長度。在沒有蛇身阻擋下, 就是曼哈頓距離。然後,我要先判斷一下,貪吃蛇這一去是否安全。 所以我需要一條虛擬的蛇,它每次負責去探路。如果安全,才讓真正的蛇去跑。 當然,虛擬的蛇是不會繪製出來的,它只負責模擬探路。那麼, 怎麼定義乙個布局是安全的呢? 如果你把文章開頭那張動態中蛇的銷魂走位好好的看一下, 會發現即使到最後蛇身已經很長了,它仍然沒事一般地走出了一條路。而且, 是跟著蛇尾走的!嗯,這個其實不難解釋,蛇在運動的過程中,消耗蛇身, 蛇尾後面總是不斷地出現新的空間。蛇短的時候還無所謂,當蛇一長, 就會發現,要想活下來,基本就只能追著蛇尾跑了。在追著蛇尾跑的過程中, 再去考慮能否安全地吃到食物。(下圖是某次bfs後,得到的乙個布局, 0代表食物,數字代表該位置到達食物的距離,+號代表蛇頭,*號代表蛇身, -號代表蛇尾,#號代表空格,外面的一圈#號代表圍牆)

經過上面的分析,我們可以將布局是否安全定義為蛇是否可以跟著蛇尾運動, 也就是蛇吃完食物後,蛇頭和蛇尾間是否存在路徑,如果存在,我就認為是安全的。

ok,繼續。真蛇派出虛擬蛇去探路後,發現吃完食物後的布局是安全的。那麼, 真蛇就直奔食物了。等等,這樣的策略好嗎?未必。因為蛇每運動一步, 布局就變化一次。布局一變就意味著可能存在更優解。比如因為蛇尾的消耗, 原本需要繞路才能吃到的食物,突然就出現在蛇眼前了。所以,真蛇走一步後, 更好的做法是,重新做bfs。然後和上面一樣進行安全判斷,然後再走。

接下來我們來考慮一下,如果蛇和食物之間不存在路徑怎麼辦? 上文其實已經提到了做法了,跟著蛇尾走。只要蛇和食物間不存在路徑, 蛇就一直跟著蛇尾走。同樣的,由於每走一步布局就會改變, 所以每走一步就重新做bfs得到最新布局。

好了,問題又來了。如果蛇和食物間不存在路徑且蛇和蛇尾間也不存在路徑, 怎麼辦?這個我是沒辦法了,選一步可行的路徑來走就是了。還是乙個道理, 每次只走一步,更新布局,然後再判斷蛇和食物間是否有安全路徑; 沒有的話,蛇頭和蛇尾間是否存在路徑;還沒有,再挑一步可行的來走。

上面列的好幾個問題裡都涉及到蛇的行走策略,一般而言, 我們會讓蛇每次都走最短路徑。這是針對蛇去吃食物的時候, 可是蛇在追自己的尾巴的時候就不能這麼考慮了。我們希望的是蛇頭在追蛇尾的過程中, 盡可能地慢。這樣蛇頭和蛇尾間才能騰出更多的空間,空間多才有得發展。 所以蛇的行走策略主要分為兩種:

1. 目標是食物時,走最短路徑 2. 目標是蛇尾時,走最長路徑

那第三種情況呢?與食物和蛇尾都沒路徑存在的情況下, 這個時候本來就只是挑一步可行的步子來走,最短最長關係都不大了。 至於人為地讓蛇走s形,我覺得這不是什麼好策略,最初版本中已經分析過它的問題了。 (當然,除非你想使用最最無懈可擊的那個版本,就是完全不管食物, 讓蛇一直走s,然後在牆邊留下一條過道即可。這樣一來, 蛇總是可以完美地把所有食物吃完,然後佔滿整個空間,可是就很boring了。 沒有任何的意思)

上面還提到乙個問題:因為食物是隨機出現的,有沒可能出現無解的局面? 答案是:有。我執行了程式,然後把每一次布局都輸出到log,發現會有這樣的情況:

其中,+號是蛇頭,-號是蛇尾,*號是蛇身,0是食物,#號代表空格,外面一圈# 號代表牆。這個布局上,食物已經在蛇頭面前了,可是它能吃嗎?不能! 因為它吃完食物後,長度加1,蛇頭就會把0的位置填上,布局就變成:

此時,由於蛇的長度加1,蛇尾沒有動,而蛇頭被自己圍著,掛掉了。可是, 我們卻還有乙個空白的格仔#沒有填充。按照我們之前教給蛇的策略, 面對這種情況,蛇頭就只會一直追著蛇尾跑,每當它和食物有路徑時, 它讓虛擬的蛇跑一遍發現,得到的新布局是不安全的,所以不會去吃食物, 而是選擇繼續追著蛇尾跑。然後它就這樣一直跑,一直跑。死迴圈, 直到你按esc鍵為止。

由於食物是隨機出現的,所以有可能出現上面這種無解的布局。當然了, 你也可以得到完滿的結局,貪吃蛇把整個矩形都填充滿。

上面的最後乙個問題,暴力法是否能得到最優序列。從上面的分析看來, 可以得到,但不能保證一定得到。

最後,看看**遠矚的蛇是怎麼跑的吧:

66007afbeee85e3087dd487d8be503a4

矩形大小1020,除去外面的邊框,也就是818。linux下錄完屏再轉成gif格式的, 優化前40多m,真心是沒法和windows的比。用下面的命令優化時, 有一種系統在用生命做優化的感覺:

以上的**仍然可以繼續改進(現在加注釋不到300行,優化一下可以更少), 也可用pygame或是pyglet庫把介面做得更加漂亮,enjoy!

歡迎和我愉快學習和開發, 終端研發部是乙個以技術為主的學習交流技術號,談的是技術,是產品,更是我們的人生。做東半球最會思考,最有味道的網際網路開發者

canvas寫乙個貪吃蛇小遊戲

截圖 開始 js snake.js function snake canvas this.ctx canvas.getcontext 2d this.state score 0,分數 this.getpoint this.init this.state.startx,this.state.start...

用 Python 製作乙個 貪吃蛇

今天呢,將分享乙個關於 遊戲製作的小案例 僅用不到 200 行 實現乙個貪吃蛇遊戲,作為 python遊戲 系列的第一篇文章,先看一下程式效果 關於程式具體實現部分,請看下文 工具庫程式中用到的 python 庫有 sys pygame time collection time random 其中核...

c語言寫了乙個貪吃蛇

很簡單的 可能存在一些bug 大牛不要嘲笑 include stdafx.h include include head.h include include int x 1,y 1,m 0 m為蛇的長度 n為食物個數 int nx 1,ny 1,n 0 int chengji 0 void 遊戲開始 ...