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

在网上参考了各大神的代码后,了解了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   
  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 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
  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
  20.     {  
  21.         for(int j=0;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 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::iterator iter=m_snake.m_snake.begin();  
  4.     for(unsigned i=0;i
  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::iterator iter=m_snake.m_snake.begin();  
  9.     for(unsigned i=0;i
  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,智障蛇死了......

你可能感兴趣的:(贪吃蛇)