您的位置:首页 > 其它

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

2017-12-28 21:26 190 查看
在网上参考了各大神的代码后,了解了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 a 1

1 0 0 0 
0 0 0 1
1 1 0 0 

<2----->
a a 1 1 
1 a 0 0 
0 0 0 1
1 1 0 0 

<3----->
a a 1 1 
1 a a 0 
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步,我们就能从起点搜索到终点了.

然后,头文件:

//Bfs.h  

//贪吃蛇基本寻路算法.  

  

#ifndef BFS_H_H  

#define BFS_H_H  

  

#include <queue>  

using std::queue;  

  

struct XY  

{  

    int x;  

    int y;  

};  

  

class Bfs  

{  

public:  

    void InitBfs(bool **chess,XY size);//初始化图.  

    void CalcBfs(XY st,XY en);//计算Bfs路径.  

    void EetBfs(XY st,XY en);//得到Bfs路径.  

    void CalcQue(XY en);//计算队列.  

    queue<XY> m_que;  

private:  

    bool **m_chess;//用矩阵表示的图.  

    bool **m_visit;//节点是否被访问过.  

    XY **m_parent;//每个访问过的节点的父节点.  

    XY m_size;//图的大小.  

      

};  

  

#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

//Bfs.cpp  

#include "stdafx.h"  

#include "Bfs.h"  

int dir[4][2]={{0,1},{0,-1},{1,0},{-1,0}};  

  

void Bfs::InitBfs(bool **chess,XY size)  

{  

    m_size=size;  

    m_chess=new bool *[m_size.x];  

    m_visit=new bool *[m_size.x];  

    m_parent=new XY *[m_size.x];  

  

    for(int i=0;i<m_size.x;i++)  

    {  

        m_chess[i]=new bool [m_size.y];  

        m_visit[i]=new bool [m_size.y];  

        m_parent[i]=new XY [m_size.y];  

    }  

    for(int i=0;i<m_size.x;i++)  

    {  

        for(int j=0;j<m_size.y;j++)  

        {  

            m_chess[i][j]=*((bool*)chess+m_size.y*i+j);  

            m_visit[i][j]=false;  

            m_parent[i][j].x=-1;  

            m_parent[i][j].y=-1;  

        }  

    }  

    while(!m_que.empty())  

        m_que.pop();  

}  

  

void Bfs::CalcBfs(XY st,XY en)  

{  

    queue<XY> temque;  

    m_visit[st.x][st.y]=true;  

    temque.push(st);  

    XY head,next;  

    int quesize;  

  

    while(!temque.empty())  

    {  

        quesize=temque.size();  

        while(quesize--)  

        {  

            head=temque.front();  

            temque.pop();  

            if(head.x==en.x&&head.y==en.y)  

                return;//已经达到目的了.  

            for(int i=0;i<4;i++)  

            {  

                next.x=head.x+dir[i][0];   

                next.y=head.y+dir[i][1];  

                if(next.x<0||(next.x>(m_size.x-1))||  

                    next.y<0||(next.y>(m_size.y-1))||  

                    m_chess[next.x][next.y])  

                    continue;  

                if(!m_visit[next.x][next.y])  

                {  

                    m_visit[next.x][next.y]=1;  

                    temque.push(next);  

                    m_parent[next.x][next.y].x=head.x;  

                    m_parent[next.x][next.y].y=head.y;  

                }  

            }  

        }  

    }  

}  

  

void Bfs::CalcQue(XY en)  

{  

    if(en.x!=-1&&en.y!=-1)  

    {  

        CalcQue(m_parent[en.x][en.y]);  

        m_que.push(en);  

    }  

}  

  

void Bfs::EetBfs(XY st,XY en)  

{  

    CalcBfs(st,en);  

    CalcQue(en);  

    m_que.pop();//弹出没用的起始点..  

}  

需要说明的一点是Dir数组,该数组表示的是上下左右四个方向.

只要看过算法导论的BFS算法部分,其他的地方就非常好理解了,所以不多说了.下面我们就将该算法应用到我们的游戏中..

首先,我们可以设置一个变量,用来标记到底是人在玩还是电脑在玩,并在适当的地方更新这个值..

下一个问题是,怎样得到m_chess数组呢?

其实很简单,除了蛇的身体,剩下的部分都是通路,所以通过如下代码,我们就能得到m_chess了

[cpp] view
plain copy

bool maze[15][25];  

    memset(maze,0,sizeof(maze));  

    list<SnakeNode>::iterator iter=m_snake.m_snake.begin();  

    for(unsigned i=0;i<m_snake.m_snake.size();i++,iter++)  

    {  

        XY curpo;  

        curpo.y=iter->rc.left/m_po.x;  

        curpo.x=(iter->rc.top-50)/m_po.y;  

        maze[curpo.x][curpo.y]=1;  

    }  

至于起始点和终点就更简单了,起始点就是蛇头,终点就是食物..

所以我们可以编写这样一个函数,用来得到路径:

[cpp] view
plain copy

void CSnakeDlg::SetDire()  

{  

    XY size;  

    size.x=15;  

    size.y=25;  

    bool maze[15][25];  

    memset(maze,0,sizeof(maze));  

    list<SnakeNode>::iterator iter=m_snake.m_snake.begin();  

    for(unsigned i=0;i<m_snake.m_snake.size();i++,iter++)  

    {  

        XY curpo;  

        curpo.y=iter->rc.left/m_po.x;  

        curpo.x=(iter->rc.top-50)/m_po.y;  

        maze[curpo.x][curpo.y]=1;  

    }  

  

    RECT rect=m_snake.m_snake.back().rc;  

    XY st;  

    st.y=rect.left/m_po.x;  

    st.x=(rect.top-50)/m_po.y;  

    XY en;  

    en.y=m_food.left/m_po.x;  

    en.x=(m_food.top-50)/m_po.y;  

    m_bfs.InitBfs((bool**)maze,size);  

    m_bfs.EetBfs(st,en);  

}  

在每次吃到食物以后,我们就重新执行一下这个函数,用来得到新的路径.

得到路径之后,我们在OnTimer()函数中添加如下代码即可自动地改变贪吃蛇的移动方向了..

[cpp] view
plain copy

if(m_ispc)  

{  

    XY cur=m_bfs.m_que.front();  

    m_bfs.m_que.pop();  

  

    RECT rc=m_snake.m_snake.back().rc;  

    XY head;  

    head.y=rc.left/m_po.x;  

    head.x=(rc.top-50)/m_po.y;  

    if(cur.x==head.x)  

    {  

        if(cur.y-head.y==1)  

            m_dr=DR_RIGHT;  

        else if(cur.y-head.y==-1)  

            m_dr=DR_LEFT;  

    }  

    else if(cur.y==head.y)  

    {  

        if(cur.x-head.x==1)  

            m_dr=DR_DOWN;  

        else if(cur.x-head.x==-1)  

            m_dr=DR_UP;  

    }  

}  

这样,贪吃蛇就可以欢快的吃一段时间的食物了..

但是,过一段时间后,可爱的小蛇就自己撞墙死了,跟第一篇博文中贴出来的那只蛇相比,一点都不高大上..

我总结了一下,目前来说这只蛇还有两个很大的问题:

1.由于每次都是吃到食物之后才执行一下Bfs函数,所以,得到的路径就是那一瞬间的最短路径,但是可能在蛇移动的过程中,当前的格局发生了变化,可能会有更短的路径出现,虽然这个问题不是致命的,但是也浪费了蛇吃东西的时间.

2.随着蛇身的增长,蛇的身体很可能将地图分为互不相通的几个部分,所以,当食物和蛇的头部出现在不同的部分时,Bfs算法就没辙了,这个问题是致命的!



OK,智障蛇死了......
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: