貪吃蛇AI
作者:CodeNoob 轉載請標明作者和出處
序言
前幾天在網上看到一張讓人漲姿勢的圖片,這張圖片我很早以前看過,當時就覺得肯定是程序實現的,只是當時還比較渣,不會算法。這次學了java也正在學算法,便打算開始實現它,說做就做,let’s do it
語言選擇
Java,雖然好久不用Swing
最初版本
Make it work
首先肯定是先讓程序能跑,再去想算法,開始肯定是在一個矩形里不斷的隨機出現食物,然后讓蛇在不走出矩形的情況下去吃,蛇每吃一個食物就會變長。這時問題基本就是,給你一個起點(蛇頭)和一個終點(食物),從起點找到一條可行路到達終點。這個路怎么走呢,最簡單的就是走曼哈頓距離了.
如下圖,綠色是蛇頭藍色是蛇尾,蛇頭在去吃食物的時候是直接穿過身體的,這是最簡單的方法,先運行起來。
BFS
搜索路徑的算法其實有多種,分為盲目式搜索和啟發式搜索。其中盲目式搜索有DFS和BFS,啟發式搜索有A*和有序搜索(或者最佳優先搜索),
這里我用的BFS,首先將蛇頭節點放入隊列,然后循環彈出隊列直到隊列為空,為空返回-1,沒找到路徑,每彈出一個隊列里的節點,判斷該節點是不是食物,如果不是食物,把該節點添加到vis表說明已經走過,並且把該節點相鄰的上下左右四個節點添加到隊列里並記錄下節點的父節點(我用的HashMap),添加時如果節點在vis表或者出了牆或者是身體節點,就不添加到隊列里。如果是食物,返回去食物的第一步的方向,因為我是線程每刷新一次,蛇走一步。這樣每走一步就BFS找最優路徑。
調了下速度所以比較快,當蛇吃完食物后發現自己沒路去吃另外出現的食物時就會GG了
所以不能出現食物就立馬去吃,得先判斷吃完后能否吃下一個,這里其實可以在隨機食物的位置時把下一個食物的位置提前隨機出來,不過這樣就有點算作弊的感覺。。
高級版
make it right
當我們不知道下一個食物的位置時就只能模擬一條蛇去吃了,我們派一條虛擬蛇(不畫在屏幕上)去吃,虛擬蛇吃后生成的食物也是虛擬的所以我們不知道真實食物吃完又會在哪出現,吃完怎么判斷是否能去吃下一個呢?
我們可以看最上面的吃完全圖的可以發現,當它吃完后能跟着尾巴走就表明是安全的,於是策略是
if(能吃到食物)
派虛擬蛇去吃,
if(吃完能跟着蛇尾走) 真蛇去吃
if(吃完不能跟着蛇尾) 真蛇跟着蛇尾走
else
真蛇跟着蛇尾
if(不能吃食物也不能跟着蛇尾)隨便逛逛,
高級進階版
解決辦法還是得從原圖來,(原圖不知道被我看了多少遍。。)發現蛇頭在追蛇尾時我總是走的最短路徑,其實應該還可以不那么快走到蛇尾可以到繞下彎去,這就不能用BFS了。於是本不願意寫A*算法的,只好用A*算比較遠的路徑了。(為什么A*不是最遠路徑因為這個怎么判斷多遠我是用曼哈頓距離的大小判斷遠近,實際是可以不停繞彎走才是最遠的,不過這樣就不好寫算法了。)
A*其實本質就是BFS加貪心,怎么貪就是給當前節點周圍的四個節點先估計下哪個離目標(食物)遠一點或近一點(我們要找最遠路肯定想遠一點),權值表示這個(遠或近的)程度,權值越大越遠,越小越近,不用像BFS那樣四個都走一遍,我們只走權值最大的那個,就可能離遠一點。
具體A*算法步驟如下
//將開始節點放入open表
while(opne表不為空){
0.在open表找F值最大的(說明離目標最遠),如果有相同我們選的排在后面的也就是最新添加的。
1.把當前節點從開放列表刪除, 加入到封閉列表;
2.遍歷四個方向的相鄰節點
(0)如果該相鄰節點不可通行或者該相鄰節點已經在封閉列表中,則什么操作也不執行,繼續檢驗下一個節點;
(1)如果該相鄰節點不在開放列表中,則將該節點添加到開放列表中,並將該相鄰節點的父節點設為當前節點,同時保存該相鄰節點的G和H值
[0]當終點節點被加入到開放列表作為待檢驗節點時, 表示路徑被找到,此時終止循環,返回方向;
(2)如果該相鄰節點在開放列表中,則判斷若經由當前節點到達該相鄰節點的G值是否大於或小於(這里找最遠用大於)原來保存的G值,若大於或小於,則將該相鄰節點的父節點設為當前節點,並重新設置該相鄰節點的G和H值
}
//當開放列表為空,表明已無可以添加的新節點,而已檢驗的節點中沒有終點節點則意味着路徑無法被找到,此時也結束循環返回-1;
然后我們換個策略
吃食物時走最近路徑
追尾巴時走最遠路徑
通過最后一個結果可以看出,由於是隨機產生的食物,還是有吃不到的時候,這時就只能優化食物的出現算法或調整尋路算法了。(想要吃滿全圖,可以一直走S路就可以了,不過這樣沒意思了)。
最后
如果對源代碼感興趣請戳我
如果有需要優化的地方的話,我想可能BFS太慢了,還是直接全用A*或許能減少點CPU的壓力,哈哈。
