你真以爲貪喫蛇是個簡單的遊戲?

諾基亞手機經典遊戲,規則不多說~

先上一個多年前火了一段時間的 gif 動態圖以表達敬意:


聽說這是一個俄羅斯人用程序實現的。


所以這裏想談一下 貪喫蛇如何才能把地圖填滿

我們用把複雜問題簡單化的方法一步步分析用計算機求解的過程:

1)蛇的移動

蛇的移動是一個有趣的問題,在不碰牆的情況下,蛇頭可以有 3 個可移動的格子,蛇的任意一次移動都表示要把原蛇頭變成蛇身,把要移動到的格子變成蛇頭;如果要移動到的格子是食物,就不需要把蛇尾刪除,否則刪除蛇尾;這就完成了蛇的移動。所以用雙鏈表來儲存蛇的信息是很方便的。在邏輯設計的時候有一點是需要注意的:

                         

像這種情況(上左圖蛇頸是在蛇頭上面)如果蛇頭下一步是往下走,是合理的(我是這樣想的);所以這裏我是先把尾巴刪了後再移動蛇頭。如果反過來就會造成遊戲結束。這還有一個設計上的問題;像上面的左圖,如果我沒說出蛇頸在哪,你都分不出方向;所以蛇身之間是合着還是分開最好表現出來,以便觀察。


2)喫到食物

一般就看到哪出現食物就往哪跑,所以可以使用 A* 或單純的 BFS 來搜索最短路徑。這裏要注意的是:在搜索到路徑之後,不能直接按照路徑走;因爲蛇一移動,整個格局就改變了,即地圖是動態的。所以在搜索到路徑之後,只讓蛇走路徑的第一步,然後重新搜索,以此類推。

然而用這種方法喫食物會出現什麼問題?

1、有可能找不到食物:(紫色爲蛇頭,橘黃色爲食物)

                         

雖然找不到食物並不意味着死亡,但這是計算機在玩,在搜索失敗的時候必須給出個方案,這個後面再講。


2、吃了食物後必死:


這種情況毫無疑問無法補救了,所以程序是絕不允許出現這樣的情況的。


3)保持不死

沒錯,這個遊戲只要操作得當,是不會掛掉的(除非地圖被填滿)。隨着蛇的移動,尾巴走過的路線一定是空出來的;所以只要蛇頭跟着尾巴跑就絕對不會死。所以就有了一個很無恥的必勝法,非常無恥:


此法只對空白地圖有效,如果有障礙物很明顯就不適用,這種無腦操作不在討論範圍。。。


因此蛇要填滿地圖就必須保持不死的狀態喫食物。那如何做到這點?

從上面可以知道,在任意狀態下,只要蛇頭與蛇尾之間有路徑,蛇就不會死。所以我們搜索出蛇頭到食物的最短路後,先模擬走一下這條路線(這裏也是每走一步重新搜索一遍),不必繪圖;如果走完後蛇頭與蛇尾是連通的,就說明按這條路線喫食物是不會掛的;也就可以大膽過去喫。

這裏又出現了一個問題:

 (圖1)    (圖2)    (圖3)

上面 (圖1) 在搜索路徑時可能會出現 (圖2) 和 (圖3) 的狀況(兩者都是最短路);問題是如果是 (圖2) 這樣的話喫完食物後蛇頭與蛇尾是連通的,但 (圖3) 明顯就不行了。這是個棘手的問題(我覺得是),因爲我們無法判斷哪條路纔不會導致喫完食物後掛掉(除非把所有路徑找出然後逐條判斷),這裏我的態度是隨意(因爲還沒找到合適的方案):如果它搜出的是 (圖3) 的路線,就把它當作不合理路線;如果搜出 (圖2) 的路線那就最好了。


接着,如果找不到安全的路線去喫食物怎麼辦?比如這樣:


這樣很明顯就不要魯莽地去吃了,本來遇到這樣的情況就需要找一條蛇頭到蛇尾的最長路(首尾距離越大就越有可能騰出空間喫食物),然後取最長路的第一步走,之後又開始判斷能否安全地喫到食物;最長路可不好算,可以通過枚舉路徑長度得到,也可以用 A* 近似得到。這裏我想:反正就取一步,我直接從蛇頭下一步可以走的格子取一個離蛇尾最遠的走就行了,比如上圖就選 1號格子;實踐效果還是不錯的。


做到這裏就有不死之身了,然而會出現兩種情況:

(1)


這個例子裏喫完 39 個食物後出現死循環。主要還是上面提到的當出現多個最短路時沒有選對路線導致的,有點棘手。


(2)


沒錯!填滿了


如果仔細分析,由於食物出現的隨機性,是否一定可以填滿地圖呢(無腦那個例外~)?其實除了上面的由於設計不當導致死循環之外,還有一種可能就是,當地圖快要填滿時,食物出現在一個無論蛇身如何移動都喫不到的地方;由於蛇不會死,所以這也是個死循環,後面障礙物就是一個這樣的例子。


還有一個現象就是,上面那個填滿了地圖的動圖看似風光,其實是非常愚昧的;它採取的方案是:只要一出現安全路線,就跑過去喫。這樣沒有問題,只是會使地圖各處出現大大小小的空洞,也即食物會出現在各個空洞裏,這將導致貪喫蛇有時要移動很長一段距離才能喫到食物。因此,在後期蛇比較長的時候可以採取這樣的策略:以最大間距追着蛇尾跑,這個過程可以喫到食物就喫。這樣蛇也是不會掛的,而且可以得到優雅一點的路線。像下面那樣,我在蛇吃了 50 個食物(地圖的一半)後開始採取這樣的策略,可以儘可能地填補空格,可以感受一下;雖然無法跟那個俄羅斯人的相比。



沒有障礙物時可以隨意填滿,那有障礙物是什麼情況?實踐得到:複雜很多

反正我覺得在有障礙物的情況下要把地圖填滿需要一定的運氣(可能是我能力只有這麼點),所以也沒深究下去。直接用上面的搜索方案可以跑 2/3 左右(這個例子在喫完 68 個食物後出現無解死循環)。



心得:

這是一個非常適合練習搜索算法的題目,難度適中;我用 qt 開發,總共 500 多行代碼,界面與基本邏輯設計佔 3/5 ,AI 佔 2/5。整個思路如上面的內容,並不複雜,但是細節非常多,要成功地填滿地圖需要非常細心;這也是對自己思維嚴謹性的磨練。以 Ubuntu 爲平臺,gif 動圖錄制使用了 byzanz ,由於是命令行操作,可以配合 xdotool 獲取鼠標座標以定位錄製起點。不過我下載的 byzanz 好像沒有循環播放 gif 動圖的可選項,即 gif 只播放一次;可以使用這個在線圖片修改網修復:動態圖片循環播放修復工具-修復動態圖片只播放一次 (這也是個修圖的好東西,還是在線的)


qt 源代碼地址:

https://github.com/QYPan/qt-examples/tree/master/snake


參考文章:

貪喫蛇 AI 的實現 snake AI

如何用Python寫一個貪喫蛇AI

貪喫蛇 AI 人工智慧 C++ 實現  (這個要翻牆 or VPN)




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章