<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          如何用Python寫一個貪吃蛇?

          共 9174字,需瀏覽 19分鐘

           ·

          2020-09-26 02:12

          點擊上方Python知識大全設為星標

          號外:

          本號免費提供 CSDN 資源下載,需要的伙伴公眾號后臺回復【CSDN】

          閱讀文本大概需要 5 分鐘

          作者:Hawstein
          http://hawstein.com/2013/04/15/snake-ai/

          前言

          這兩天在網(wǎng)上看到一張讓人漲姿勢的圖片,圖片中展示的是貪吃蛇游戲, 估計大部分人都玩過。但如果僅僅是貪吃蛇游戲,那么它就沒有什么讓人漲姿勢的地方了。問題的關鍵在于,圖片中的貪吃蛇真的很貪吃XD,它把矩形中出現(xiàn)的食物吃了個遍, 然后華麗麗地把整個矩形填滿,真心是看得賞心悅目。作為一個CSer, 第一個想到的是,這東西是寫程序實現(xiàn)的(因為,一般人干不出這事。果斷是要讓程序來干的)第二個想到的是,寫程序該如何實現(xiàn),該用什么算法?既然開始想了,就開始做。Talk is cheap,show me the code。
          開始之前,讓我們再欣賞一下那只讓人漲姿勢的貪吃蛇吧:( 如果下面的動態(tài)圖片瀏覽效果不佳的話,可以右鍵保存下來查看)

          語言選擇

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

          最初版本

          先讓你的程序跑起來
          首先,我們第一件要做的就是先不要去分析這個問題。你好歹先寫個能運行起來的貪吃蛇游戲,然后再去想AI部分。這個應該很簡單, c\c++也就百來行代碼(如果我沒記錯的話。不弄復雜界面,直接在控制臺下跑), python就更簡單了,去掉注釋和空行,5、60行代碼就搞定了。而且,最最關鍵的, 這個東西網(wǎng)上肯定寫濫了,你沒有必要重復造輪子, 去弄一份來按照你的意愿改造一下就行了。
          簡單版本
          我覺得直接寫perfect版本不是什么好路子。因為perfect版本往往要考慮很多東西, 直接上來就寫這個一般是bug百出的。所以, 一開始我的目標僅僅是讓程序去控制貪吃蛇運動,讓它去吃食物,僅此而已。現(xiàn)在讓我們來陳述一下最初的問題:
          
              
                   
          1
          2
                   
          在一個矩形中,每一時刻有一個食物,貪吃蛇要在不撞到自己的條件下,
          找到一條路(未必要最優(yōu)),然后沿著這條路運行,去享用它的美食
          我們先不去想蛇會越來越長這個事實,問題基本就是,給你一個起點(蛇頭)和一個終點( 食物),要避開障礙物(蛇身),從起點找到一條可行路到達終點。我們可以用的方法有:
          • BFS

          • DFS

          • A*

          只要有選擇,就先選擇最簡單的方案,我們現(xiàn)在的目標是要讓程序先跑起來, 優(yōu)化是后話。so,從BFS開始。我們最初將蛇頭位置放入隊列,然后只要隊列非空, 就將隊頭位置出隊,然后把它四領域內的4個點放入隊列,不斷地循環(huán)操作, 直到到達食物的位置。這個過程中,我們需要注意幾點:1.訪問過的點不再訪問。2.保存每個點的父結點(即每個位置是從哪個位置走到它的, 這樣我們才能把可行路徑找出來)。3.蛇身所在位置和四面墻不可訪問。
          通過BFS找到食物后,只需要讓蛇沿著可行路徑運動即可。這個簡單版本寫完后, 貪吃蛇就可以很歡快地運行一段時間了。看圖吧:(不流暢的感覺來自錄屏軟件@_@)
          為了盡量保持簡單,我用的是curses模塊,直接在終端進行繪圖。從上面的動態(tài)圖片可以看出,每次都單純地使用BFS,最終有一天, 貪吃蛇會因為這種不顧后果的短視行為而陷入困境。而且,即使到了那個時候,它也只會BFS一種策略, 導致因為當前看不到目標(食物),認為自己這輩子就這樣了,破罐子破摔, 最終停在它人生中的某一個點,不再前進。(我好愛講哲理XD)

          BFS+Wander

          上一節(jié)的簡單版本跑起來后,我們認識到,只教貪吃蛇一種策略是不行的。它這么笨一條蛇,你不多教它一點,它分分鐘就會掛掉的。所以,我寫了個Wander函數(shù),顧名思義,當貪吃蛇陷入困境后, 就別讓它再BFS了,而是讓它隨便四處走走,散散心,思考一下人生什么的。這個就好比你困惑迷茫的時候還去工作,效率不佳不說,還可能阻礙你走出困境;相反,這時候你如果放下手中的工作,停下來,出去旅個游什么的。回來時, 說不定就豁然開朗,土地平曠,屋舍儼然了。
          Wander函數(shù)怎么寫都行,但是肯定有優(yōu)劣之分。我寫了兩個版本,一個是在可行的范圍內, 朝隨機方向走隨機步。也就是說,蛇每次運動的方向是隨機出來的, 總共運動的步數(shù)也是隨機的。Wander完之后,再去BFS一下,看能否吃到食物, 如果可以那就皆大歡喜了。如果不行,說明思考人生的時間還不夠,再Wander一下。這樣過程不斷地循環(huán)進行。可是就像“隨機過程隨機過”一樣,你“隨機Wander就隨機掛”。會Wander的蛇確實能多走好多步。可是有一天,它就會把自己給隨機到一條死路上了。陷入困境還可以Wander,進入死胡同,那可沒有回滾機制。所以, 第二個版本的Wander函數(shù),我就讓貪吃蛇貪到底。在BFS無解后, 告訴蛇一個步數(shù)step(隨機產(chǎn)生step),讓它在空白區(qū)域以S形運動step步。這回運動方向就不隨機了,而是有組織有紀律地運動。先看圖,然后再說說它的問題:
          沒錯,最終還是掛掉了。S形運動也是無法讓貪吃蛇避免死亡的命運。貪吃蛇可以靠S形運動多存活一段時間,可是由于它的策略是:
          
              
                   
          1
          2
          3
          4
          5
                   
          while 沒有按下ESC鍵:
          if 蛇與食物間有路徑:
          走起,吃食物去
          else:
          Wander一段時間
          問題就出在蛇發(fā)現(xiàn)它自己和食物間有路徑,就二話不說跑去吃食物了。它沒有考慮到,你這一去把食物給吃了后形成的局勢(蛇身布局), 完全就可能讓你掛掉。(比如進入了一個自己蛇身圍起來的封閉小空間)
          so,為了能讓蛇活得久一些,它還要更高瞻遠矚才行。

          高瞻遠矚版本

          我們現(xiàn)在已經(jīng)有了一個比較低端的版本,而且對問題的認識也稍微深入了一些。現(xiàn)在可以進行一些比較慎密和嚴謹?shù)姆治隽恕J紫龋屛覀兞_列一些問題:(像頭腦風暴那樣,想到什么就寫下來即可)
          • 蛇和食物間有路徑直接就去吃,不可取。那該怎么辦?

          • 如果蛇去吃食物后,布局是安全的,是否就直接去吃?(這樣最優(yōu)嗎?)

          • 怎樣定義布局是否安全?

          • 蛇和食物之間如果沒有路徑,怎么辦?

          • 最短路徑是否最優(yōu)?(這個明顯不是了)

          • 那么,如果布局安全的情況下,最短路徑是否最優(yōu)?

          • 除了最短路徑,我們還可以怎么走?S形?最長?

          • 怎么應對蛇身越來越長這個問題?

          • 食物是隨機出現(xiàn)的,有沒可能出現(xiàn)無解的布局?

          • 暴力法(brute force)能否得到最優(yōu)序列?(讓貪吃蛇盡可能地多吃食物)

          只要去想,問題還挺多的。這時讓我們以面向過程的思想,帶著上面的問題, 把思路理一理。一開始,蛇很短(初始化長度為1),它看到了一個食物, 使用BFS得到矩形中每個位置到達食物的最短路徑長度。在沒有蛇身阻擋下, 就是曼哈頓距離。然后,我要先判斷一下,貪吃蛇這一去是否安全。所以我需要一條虛擬的蛇,它每次負責去探路。如果安全,才讓真正的蛇去跑。當然,虛擬的蛇是不會繪制出來的,它只負責模擬探路。那么, 怎么定義一個布局是安全的呢?如果你把文章開頭那張動態(tài)圖片中蛇的銷魂走位好好的看一下, 會發(fā)現(xiàn)即使到最后蛇身已經(jīng)很長了,它仍然沒事一般地走出了一條路。而且, 是跟著蛇尾走的!嗯,這個其實不難解釋,蛇在運動的過程中,消耗蛇身, 蛇尾后面總是不斷地出現(xiàn)新的空間。蛇短的時候還無所謂,當蛇一長, 就會發(fā)現(xiàn),要想活下來,基本就只能追著蛇尾跑了。在追著蛇尾跑的過程中, 再去考慮能否安全地吃到食物。(下圖是某次BFS后,得到的一個布局, 0代表食物,數(shù)字代表該位置到達食物的距離,+號代表蛇頭,*號代表蛇身, -號代表蛇尾,#號代表空格,外面的一圈#號代表圍墻)
          
              
                   
          1
          2
          3
          4
          5
          6
          7
                   
          # # # # # # #
          # 0 1 2 3 4 #
          # 1 2 3 # 5 #
          # 2 3 4 - 6 #
          # 3 + * * 7 #
          # 4 5 6 7 8 #
          # # # # # # #
          經(jīng)過上面的分析,我們可以將布局是否安全定義為蛇是否可以跟著蛇尾運動, 也就是蛇吃完食物后,蛇頭和蛇尾間是否存在路徑,如果存在,我就認為是安全的。
          OK,繼續(xù)。真蛇派出虛擬蛇去探路后,發(fā)現(xiàn)吃完食物后的布局是安全的。那么, 真蛇就直奔食物了。等等,這樣的策略好嗎?未必。因為蛇每運動一步, 布局就變化一次。布局一變就意味著可能存在更優(yōu)解。比如因為蛇尾的消耗, 原本需要繞路才能吃到的食物,突然就出現(xiàn)在蛇眼前了。所以,真蛇走一步后, 更好的做法是,重新做BFS。然后和上面一樣進行安全判斷,然后再走。
          接下來我們來考慮一下,如果蛇和食物之間不存在路徑怎么辦?上文其實已經(jīng)提到了做法了,跟著蛇尾走。只要蛇和食物間不存在路徑, 蛇就一直跟著蛇尾走。同樣的,由于每走一步布局就會改變, 所以每走一步就重新做BFS得到最新布局。
          好了,問題又來了。如果蛇和食物間不存在路徑且蛇和蛇尾間也不存在路徑, 怎么辦?這個我是沒辦法了,選一步可行的路徑來走就是了。還是一個道理, 每次只走一步,更新布局,然后再判斷蛇和食物間是否有安全路徑;沒有的話,蛇頭和蛇尾間是否存在路徑;還沒有,再挑一步可行的來走。
          上面列的好幾個問題里都涉及到蛇的行走策略,一般而言, 我們會讓蛇每次都走最短路徑。這是針對蛇去吃食物的時候, 可是蛇在追自己的尾巴的時候就不能這么考慮了。我們希望的是蛇頭在追蛇尾的過程中, 盡可能地慢。這樣蛇頭和蛇尾間才能騰出更多的空間,空間多才有得發(fā)展。所以蛇的行走策略主要分為兩種:
          
              
                   
          1
          2
                   
          1. 目標是食物時,走最短路徑
          2. 目標是蛇尾時,走最長路徑
          那第三種情況呢?與食物和蛇尾都沒路徑存在的情況下, 這個時候本來就只是挑一步可行的步子來走,最短最長關系都不大了。至于人為地讓蛇走S形,我覺得這不是什么好策略,最初版本中已經(jīng)分析過它的問題了。(當然,除非你想使用最最無懈可擊的那個版本,就是完全不管食物, 讓蛇一直走S,然后在墻邊留下一條過道即可。這樣一來, 蛇總是可以完美地把所有食物吃完,然后占滿整個空間,可是就很boring了。沒有任何的意思)
          上面還提到一個問題:因為食物是隨機出現(xiàn)的,有沒可能出現(xiàn)無解的局面?答案是:有。我運行了程序,然后把每一次布局都輸出到log,發(fā)現(xiàn)會有這樣的情況:
          
              
                   
          1
          2
          3
          4
          5
          6
          7
                   
          # # # # # # #
          # * * * * * #
          # * * - 0 * #
          # * * # + * #
          # * * * * * #
          # * * * * * #
          # # # # # # #
          其中,+號是蛇頭,-號是蛇尾,*號是蛇身,0是食物,#號代表空格,外面一圈# 號代表墻。這個布局上,食物已經(jīng)在蛇頭面前了,可是它能吃嗎?不能!因為它吃完食物后,長度加1,蛇頭就會把0的位置填上,布局就變成:
          
              
                   
          1
          2
          3
          4
          5
          6
          7
                   
          # # # # # # #
          # * * * * * #
          # * * - + * #
          # * * # * * #
          # * * * * * #
          # * * * * * #
          # # # # # # #
          此時,由于蛇的長度加1,蛇尾沒有動,而蛇頭被自己圍著,掛掉了。可是, 我們卻還有一個空白的格子#沒有填充。按照我們之前教給蛇的策略, 面對這種情況,蛇頭就只會一直追著蛇尾跑,每當它和食物有路徑時, 它讓虛擬的蛇跑一遍發(fā)現(xiàn),得到的新布局是不安全的,所以不會去吃食物, 而是選擇繼續(xù)追著蛇尾跑。然后它就這樣一直跑,一直跑。死循環(huán), 直到你按ESC鍵為止。
          由于食物是隨機出現(xiàn)的,所以有可能出現(xiàn)上面這種無解的布局。當然了, 你也可以得到完滿的結局,貪吃蛇把整個矩形都填充滿。
          上面的最后一個問題,暴力法是否能得到最優(yōu)序列。從上面的分析看來, 可以得到,但不能保證一定得到。
          最后,看看高瞻遠矚的蛇是怎么跑的吧:
          矩形大小10*20,除去外面的邊框,也就是8*18。Linux下錄完屏再轉成GIF格式的圖片, 優(yōu)化前40多M,真心是沒法和Windows的比。用下面的命令優(yōu)化時, 有一種系統(tǒng)在用生命做優(yōu)化的感覺:
          
              
                   
          1
                   
          convert output.gif -fuzz 10% -layers Optimize optimised.gif
          最后還是拿到Windows下用AE,三下五除二用圖片序列合成的動態(tài)圖片 (記得要在format options里選looping,不然圖片是不會循環(huán)播放的)

          Last but not least

          如果對源代碼感興趣,請戳鏈接:https://github.com/Hawstein/snake-ai
          另外,本文的貪吃蛇程序使用了curses模塊, 類Unix系統(tǒng)都默認安裝的,使用Windows的童鞋需要安裝一下這個模塊。
          以上的代碼仍然可以繼續(xù)改進(現(xiàn)在加注釋不到300行,優(yōu)化一下可以更少), 也可用pygame或是pyglet庫把界面做得更加漂亮,Enjoy!
             
          推薦閱讀
          關于 Python 3.9,那些你不知道的事
          總結了 90 條寫 Python 程序的建議
                

                    
          關注「Python 知識大全」,做全棧開發(fā)工程師
          歲月有你 惜惜相處


          瀏覽 38
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  www.俺取夜 | 激情久久成人 | 狂野欧美性猛交xxxx巴西 | 男人天堂av网 | 无码人妻一区二区三区综合另类 |