貪吃蛇(智能蛇的一些算法)

在網上參考了各大神的代碼後,瞭解了BTS算法:
要實現一定的智能,肯定就要用到相應的尋路算法.我採用的是最簡單的寬度優先搜索的方式 (BFS算法)
所以在具體的實現遊戲之前,我們先來看一下BFS算法.
該算法在<算法導論>中有詳細解說,並給出了可行的僞代碼,本系列的博文的重點不在於此,所以只是簡單一說,然後給出代碼.
下面就給出一個例子來說明該算法的尋路過程
(說明:我們將路徑抽象化爲一個二維數組,在二維數組中,我們用0表示未探索過的通路,用a表示探索過的通路,用1表示不通)
具體到例子,比如說下面一個地圖
0 0 1 1
1 0 0 0 
0 0 0 1
1 1 0 0 
假設起始點爲(0,0),終止點爲(3,3),即從左下角到右下角..
我們通過觀察法得,最短的路徑爲:

(0,0)->(0,1)->(1,1)->(2,1)->(2,2)->(3,2)->(3,3)
下面我們就通過Bfs將該路徑求出來,Bfs算法尋路過程如下所示(標藍的字母是當前步驟搜索的節點):
<0----->
a 0 1 1 
1 0 0 0 
0 0 0 1
1 1 0 0 

<1----->

a 1 1 
1 0 0 0 
0 0 0 1
1 1 0 0 

<2----->

a a 1 1 
a 0 0 
0 0 0 1
1 1 0 0 

<3----->

a a 1 1 
1 a a 0 
a 0 1
1 1 0 0 

<4----->
a a 1 1 
1 a a a 
a a a 1
1 1 0 0 
<5----->
a a 1 1 
1 a a a 
a a a 1
1 1 a 0 

<6----->
a a 1 1 
1 a a a 
a a a 1
1 1 a a 



這樣,經過7步,我們就能從起點搜索到終點了.
然後,頭文件:
  1. //Bfs.h  
  2. //貪吃蛇基本尋路算法.  
  3.   
  4. #ifndef BFS_H_H  
  5. #define BFS_H_H  
  6.   
  7. #include <queue>  
  8. using std::queue;  
  9.   
  10. struct XY  
  11. {  
  12.     int x;  
  13.     int y;  
  14. };  
  15.   
  16. class Bfs  
  17. {  
  18. public:  
  19.     void InitBfs(bool **chess,XY size);//初始化圖.  
  20.     void CalcBfs(XY st,XY en);//計算Bfs路徑.  
  21.     void EetBfs(XY st,XY en);//得到Bfs路徑.  
  22.     void CalcQue(XY en);//計算隊列.  
  23.     queue<XY> m_que;  
  24. private:  
  25.     bool **m_chess;//用矩陣表示的圖.  
  26.     bool **m_visit;//節點是否被訪問過.  
  27.     XY **m_parent;//每個訪問過的節點的父節點.  
  28.     XY m_size;//圖的大小.  
  29.       
  30. };  
  31.   
  32. #endif //BFS_H_H 
下面就對Bfs類中的成員變量和函數做一下說明:
m_chess是一個二維數組,其中false表示通路,true表示不通,也就是我們要求最短路徑的"地圖"(跟前面的例子同理).
m_visit是一個跟m_chess等大的數組,用來表示每個節點的訪問情況.
m_parent用來表示每個節點的父節點,我們最終得到的路徑就是通過該數組得出的.
m_size就是上面三個數組的尺寸了.
還有一個公用隊列m_que用來存儲最終求得的路徑.
InitBfs()函數用來初始化各個數組.
ClacBfs是核心算法,通過該函數得到m_parent數組.
下面就來看一下Bfs.cpp源文件:
[cpp] view plain copy
  1. //Bfs.cpp  
  2. #include "stdafx.h"  
  3. #include "Bfs.h"  
  4. int dir[4][2]={{0,1},{0,-1},{1,0},{-1,0}};  
  5.   
  6. void Bfs::InitBfs(bool **chess,XY size)  
  7. {  
  8.     m_size=size;  
  9.     m_chess=new bool *[m_size.x];  
  10.     m_visit=new bool *[m_size.x];  
  11.     m_parent=new XY *[m_size.x];  
  12.   
  13.     for(int i=0;i<m_size.x;i++)  
  14.     {  
  15.         m_chess[i]=new bool [m_size.y];  
  16.         m_visit[i]=new bool [m_size.y];  
  17.         m_parent[i]=new XY [m_size.y];  
  18.     }  
  19.     for(int i=0;i<m_size.x;i++)  
  20.     {  
  21.         for(int j=0;j<m_size.y;j++)  
  22.         {  
  23.             m_chess[i][j]=*((bool*)chess+m_size.y*i+j);  
  24.             m_visit[i][j]=false;  
  25.             m_parent[i][j].x=-1;  
  26.             m_parent[i][j].y=-1;  
  27.         }  
  28.     }  
  29.     while(!m_que.empty())  
  30.         m_que.pop();  
  31. }  
  32.   
  33. void Bfs::CalcBfs(XY st,XY en)  
  34. {  
  35.     queue<XY> temque;  
  36.     m_visit[st.x][st.y]=true;  
  37.     temque.push(st);  
  38.     XY head,next;  
  39.     int quesize;  
  40.   
  41.     while(!temque.empty())  
  42.     {  
  43.         quesize=temque.size();  
  44.         while(quesize--)  
  45.         {  
  46.             head=temque.front();  
  47.             temque.pop();  
  48.             if(head.x==en.x&&head.y==en.y)  
  49.                 return;//已經達到目的了.  
  50.             for(int i=0;i<4;i++)  
  51.             {  
  52.                 next.x=head.x+dir[i][0];   
  53.                 next.y=head.y+dir[i][1];  
  54.                 if(next.x<0||(next.x>(m_size.x-1))||  
  55.                     next.y<0||(next.y>(m_size.y-1))||  
  56.                     m_chess[next.x][next.y])  
  57.                     continue;  
  58.                 if(!m_visit[next.x][next.y])  
  59.                 {  
  60.                     m_visit[next.x][next.y]=1;  
  61.                     temque.push(next);  
  62.                     m_parent[next.x][next.y].x=head.x;  
  63.                     m_parent[next.x][next.y].y=head.y;  
  64.                 }  
  65.             }  
  66.         }  
  67.     }  
  68. }  
  69.   
  70. void Bfs::CalcQue(XY en)  
  71. {  
  72.     if(en.x!=-1&&en.y!=-1)  
  73.     {  
  74.         CalcQue(m_parent[en.x][en.y]);  
  75.         m_que.push(en);  
  76.     }  
  77. }  
  78.   
  79. void Bfs::EetBfs(XY st,XY en)  
  80. {  
  81.     CalcBfs(st,en);  
  82.     CalcQue(en);  
  83.     m_que.pop();//彈出沒用的起始點..  
  84. }  

需要說明的一點是Dir數組,該數組表示的是上下左右四個方向.
只要看過算法導論的BFS算法部分,其他的地方就非常好理解了,所以不多說了.下面我們就將該算法應用到我們的遊戲中..

首先,我們可以設置一個變量,用來標記到底是人在玩還是電腦在玩,並在適當的地方更新這個值..

下一個問題是,怎樣得到m_chess數組呢?
其實很簡單,除了蛇的身體,剩下的部分都是通路,所以通過如下代碼,我們就能得到m_chess了
[cpp] view plain copy
  1. bool maze[15][25];  
  2.     memset(maze,0,sizeof(maze));  
  3.     list<SnakeNode>::iterator iter=m_snake.m_snake.begin();  
  4.     for(unsigned i=0;i<m_snake.m_snake.size();i++,iter++)  
  5.     {  
  6.         XY curpo;  
  7.         curpo.y=iter->rc.left/m_po.x;  
  8.         curpo.x=(iter->rc.top-50)/m_po.y;  
  9.         maze[curpo.x][curpo.y]=1;  
  10.     }  

至於起始點和終點就更簡單了,起始點就是蛇頭,終點就是食物..
所以我們可以編寫這樣一個函數,用來得到路徑:
[cpp] view plain copy
  1. void CSnakeDlg::SetDire()  
  2. {  
  3.     XY size;  
  4.     size.x=15;  
  5.     size.y=25;  
  6.     bool maze[15][25];  
  7.     memset(maze,0,sizeof(maze));  
  8.     list<SnakeNode>::iterator iter=m_snake.m_snake.begin();  
  9.     for(unsigned i=0;i<m_snake.m_snake.size();i++,iter++)  
  10.     {  
  11.         XY curpo;  
  12.         curpo.y=iter->rc.left/m_po.x;  
  13.         curpo.x=(iter->rc.top-50)/m_po.y;  
  14.         maze[curpo.x][curpo.y]=1;  
  15.     }  
  16.   
  17.     RECT rect=m_snake.m_snake.back().rc;  
  18.     XY st;  
  19.     st.y=rect.left/m_po.x;  
  20.     st.x=(rect.top-50)/m_po.y;  
  21.     XY en;  
  22.     en.y=m_food.left/m_po.x;  
  23.     en.x=(m_food.top-50)/m_po.y;  
  24.     m_bfs.InitBfs((bool**)maze,size);  
  25.     m_bfs.EetBfs(st,en);  
  26. }  
在每次吃到食物以後,我們就重新執行一下這個函數,用來得到新的路徑.
得到路徑之後,我們在OnTimer()函數中添加如下代碼即可自動地改變貪吃蛇的移動方向了..
[cpp] view plain copy
  1. if(m_ispc)  
  2. {  
  3.     XY cur=m_bfs.m_que.front();  
  4.     m_bfs.m_que.pop();  
  5.   
  6.     RECT rc=m_snake.m_snake.back().rc;  
  7.     XY head;  
  8.     head.y=rc.left/m_po.x;  
  9.     head.x=(rc.top-50)/m_po.y;  
  10.     if(cur.x==head.x)  
  11.     {  
  12.         if(cur.y-head.y==1)  
  13.             m_dr=DR_RIGHT;  
  14.         else if(cur.y-head.y==-1)  
  15.             m_dr=DR_LEFT;  
  16.     }  
  17.     else if(cur.y==head.y)  
  18.     {  
  19.         if(cur.x-head.x==1)  
  20.             m_dr=DR_DOWN;  
  21.         else if(cur.x-head.x==-1)  
  22.             m_dr=DR_UP;  
  23.     }  
  24. }  

這樣,貪吃蛇就可以歡快的吃一段時間的食物了..
但是,過一段時間後,可愛的小蛇就自己撞牆死了,跟第一篇博文中貼出來的那隻蛇相比,一點都不高大上..
我總結了一下,目前來說這隻蛇還有兩個很大的問題:

1.由於每次都是吃到食物之後才執行一下Bfs函數,所以,得到的路徑就是那一瞬間的最短路徑,但是可能在蛇移動的過程中,當前的格局發生了變化,可能會有更短的路徑出現,雖然這個問題不是致命的,但是也浪費了蛇吃東西的時間.
2.隨着蛇身的增長,蛇的身體很可能將地圖分爲互不相通的幾個部分,所以,當食物和蛇的頭部出現在不同的部分時,Bfs算法就沒轍了,這個問題是致命的!


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