您的位置:首页 > 大数据 > 人工智能

大一实训---贪吃蛇+走全图AI实现

2013-08-28 22:38 288 查看
              当时楼楼实训时大一下学期中期,学了类的一些特性。当时老师提示说没思路可以写XX管理系统雏形,当然按照我的想象应该很多人就会写这个了,毕竟上机课都有练过相关的代码实现,只要组织好逻辑再加上一些别出心裁的功能函数,基本上很快就搞定。所以当时楼楼没往这方面想。

             其实当也没想到写贪吃蛇,只是逛csdn的时候偶然看到某位大牛用Python写了个贪吃蛇ai(原文请看http://bbs.csdn.net/topics/390430805),所以当时就决定写c++版的贪吃蛇吧(事先声明:楼楼的项目是自己独立思考的,在实训完成之前就只是在逛csdn的时候看过一次,之后并无参照那位大牛,当然belive
it or not)。

             在开始写程序的时候,楼楼一般都会构思好主函数的逻辑,也就是说主函数一定是最先完成的,然后再添加功能函数。之后的思考过程就是:如何构造蛇->如何构造地图->如何判断键入->如何进行按键筛选->如何判断动作合法->如何让蛇"看起来动"。

              关于蛇的构造,楼楼采用的是建立结构体来储存蛇的坐标,然后用队列(先进先出的特点刚好符合蛇的行走规律)构造出一整条蛇(还有一种方法是采用双向链表来构造,当然这不是楼楼想的,而是楼楼的一基友用的方法,当时我本来是想和他合作的,而且明确了合作想法之后,我们各自都写了一个不同版本的可运行贪吃蛇成程序,但是当时楼楼有兼职,基友的ai开发进度远比我快,所以我放弃了合作,毕竟直接拿别人的成果也不好。题外话哈哈

)。而地图的构造就比较简单,开一个二维数组map[max][max],记录整条蛇位置,食物位置,就好了。至于食物的生成就是播撒随机数种子然后rand出坐标,对于非法坐标重新再rand就好。看下代码吧(注:以下代码可能出现未介绍变量以及函数,请忽略,下文会介绍,而且重点也不是这些未知变量和函数,下文不再提醒):

void foodpro()//生成食物函数
{
int i,j;
i=rand()%(n-1);
j=rand()%(m-1);
while(1)
{
if(map[i][j]=='*' || map[i][j]=='@' || map[i][j]=='-' || map[i][j]=='I')
{
i=rand()%(n-1);
j=rand()%(n-1);
}
else
{
map[i][j]='$';
vis[i][j]='$';
foodxy.x = i,foodxy.y = j;
Gotoxy(i,2*j);
cout<<"$";
return;
}
}
}

              然后就是键入判断使用kbhit()判断是否有按键落,有则记录否则return 0;继续代码:

char ifscanf()//按键判断
{
char ch;
if(kbhit())
{
ch=getch();
return ch;
}
else
{
return 0;
}
}


              接着就是按键的判断以及筛选,在楼楼的程序里默认wasd和pf是功能键,其他的一律无视。说到这里有些事就要说明一下,蛇本身有一个叫keepdire的变量,用于记录当前蛇的运动趋势(例如:(-1,0)代表的是向上走,(0,-1)代表的是向右走,以此类推),对于过滤方向按键有重要作用。然后上代码:

string move(char ch=0)//按键筛选并行走
{
int i,j;
if(ch=='p')
{
Gotoxy(n+1,0);
cout<<"暂停中 按任意键恢复"<<'\b';
getch();
Gotoxy(n+1,0);
cout<<"按p键暂停            "<<endl;

}
if(ch == 'f')
{
ju = 1;
return autofound();
}
if(keepdire.y)//向左向右走的状态
{
if(ch=='w')
{
i = -1, j = 0;
if(judge(i,j))
{
if(s.size()==(n-2)*(m-2)-1)
{
return "win";
}
}
else
{
return "loser";
}
}
else if(ch=='s')
{
i = 1,j = 0;
if(judge(i,j))
{
if(s.size()==(n-2)*(m-2)-1)
{
return "win";
}
}
else
{
return "loser";
}
}
else
{
if(judge(keepdire.x,keepdire.y))
{
if(s.size()==(n-2)*(m-2)-1)
{
return "win";
}

}
else
{
return "loser";
}
}
}
else//向上或下走的状态
{
if(ch=='a')
{
i = 0;j = -1;
if(judge(i,j))
{
if(s.size()==(n-2)*(n-2)-1)
{
return "win";
}
}
else
{
return "loser";
}
}
else if(ch=='d')
{
i = 0,j = 1;
if(judge(i,j))
{
if(s.size()==(n-2)*(m-2)-1)
{
return "win";
}
}
else
{
return "loser";
}
}
else
{
if(judge(keepdire.x,keepdire.y))
{
if(s.size()==(n-2)*(m-2)-1)
{
return "win";
}
}
else
{
return "loser";
}
}
}
return "go on";
}


              然后就是动作判断,无非就是对撞墙,撞自己,吃食物3个动作判断。撞墙,撞自己什么的直接判输就好,吃了食物就做一个q.push()和q.pop()的操作,然后重新rand食物,修改map[max][max]的信息就好,无特殊事件发生的就按照传入参数改变方向或继续走下一步就好,继续上代码:

bool judge(int i,int j)//行走判定函数
{
sanke b,f,tem;
int k;
b=s.back ();
f=s.front();
if(map[b.x+i][b.y+j]=='$')//若吃了食物
{
map[b.x][b.y]='*';
vis[b.x][b.y]='*';
Gotoxy(b.x,2*b.y);
cout<<'*';
map[b.x+i][b.y+j]='@';
vis[b.x+i][b.y+j]='@';
Gotoxy(b.x+i,2*(b.y+j));
cout<<'@';
b.x=b.x+i,b.y=b.y+j;
s.push(b);
foodpro();
keepdire.x=i,keepdire.y=j;
score++;
ti++;
Gotoxy(n,10);
for(k=10;k<=5;k++)
{
cout<<' ';
}
Gotoxy(n,10);
cout<<score;
if(ti==3)
{
ti=0;
second=second*0.95;
}
if(ju!=1 && second<100)
{
second = 100;
}
return true;
}
else if((map[b.x+i][b.y+j]=='*' || map[b.x+i][b.y+j]=='I' || map[b.x+i][b.y+j]=='-') && (!(b.x + i == f.x && b.y + j == f.y)))//若撞墙和撞到自己
{
//(b.x + i != f.x && b.y + j != f.y  对蛇尾预判忽略
return false;
}
else
{
map[f.x][f.y]=' ';
vis[f.x][f.x]=' ';
Gotoxy(f.x,2*f.y);
cout<<' ';
s.pop();
map[b.x][b.y]='*';
vis[b.x][b.y]='*';
Gotoxy(b.x,2*b.y);
cout<<'*';
map[b.x+i][b.y+j]='@';
vis[b.x+i][b.y+j]='@';
Gotoxy(b.x+i,2*(b.y+j));
cout<<'@';
b.x=b.x+i,b.y=b.y+j;
s.push(b);
keepdire.x=i,keepdire.y=j;
return true;
}
}


到了这里还有一个小问题,就是撞到蛇尾的判定,在发生动作的时候应该先把原先的蛇尾去掉,因为其实这个函数就相当一个预判,要对是蛇的下一个动作进行判定是否可以存在这样的情况,既然是预判之前就蛇尾就不能存在了。如果还是不太理解就想象一下蛇头与蛇尾无缝对接绕圈圈的情况好了。

              基本上到这里,一个可以跑的贪吃蛇游戏的框架就完成了,接下来就是如何让他"看起来跑"。楼楼一开始的方案是用system("cls")函数以一定频率来不断刷新屏幕,然后输出map[max][max]的信息,再用Sleep()函数来控制屏幕刷新的频率。事实证明这种方法实现了蛇"动起来"的动态效果,至于为什么,请baidu"帧"这个名词。但同时也出现的一个很不好的状况,就是越玩到后面"闪瞎眼"的很严重,真的很严重,真的"闪瞎眼"。为了这个问题,我特意请教了老湿,当时老湿给一个方法--光标定位,言下之意就是直接把光标定位在控制台的某个可控位置修改信息。其实我这种刷新屏幕的方法是很"浪费"的,一般来讲,map[max][max]要更新的最多就4个点,我却更新了max*max
遍,这也能说明为什么越玩到后面
4000
会"闪瞎眼"。至于那个定位函数,没错就是那个一直都存在的Gotoxy(),至于实现方法,楼楼看着那一大堆指令,没几句是楼楼懂的,老湿说那些还没学,其实那些也是软开的。既然如此,套就好。上代码:

void Gotoxy(int x, int y)//光标定位函数
{
COORD pos = {y,x};
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO cursor_info;
GetConsoleCursorInfo(hOut, &cursor_info);
cursor_info.bVisible = false;
cursor_info.dwSize = 20;
SetConsoleCursorPosition(hOut, pos);
SetConsoleCursorInfo(hOut, &cursor_info);
}


              那么一个贪吃蛇游戏的程序的重要组成就完成了,只要整理一下主函数逻辑,数据初始化一下,跑起来没问题。至于最重要的ai部分,留在下一篇,一下看着么多文字会晕的(而且下一篇会有完整源码)。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  实训 贪吃蛇