接下来就是ai部分,老实说,楼楼的ai其实写得并不是很好。楼楼随意测试N遍,目测成功几率最高10%,失败原因是策略性死循环。但还是献丑一下,若有什么好的意见,也请各位指导一下我。
总体上的ai策略是分别先对3个方向的下一步进行bfs寻找蛇尾,对于能找到蛇尾的方向进行求蛇头与食物的最短距离,然后进行动作,策略完成了。
在这里的策略中,bfs蛇尾是第一步,原因是无论到未来的局面有多么的复杂,能从蛇头bfs到蛇尾就说明蛇不管怎么样都不会死。接下来就是考虑吃的问题,这里的求距离并没有使用bfs去求,而是直接采用两点间的距离来求,原因就是每走一步,局势都会有变化,3,4步下来就变得完全不一样,而且还有就是到了最后蛇身很长的时候,很多时候都并不能bfs求解了(想象一下那个揉成一团的蛇身的情况你就知道了)。接下来上代码:
string autofound()//ai实现
{
int i,j,t,i1,vi[4],pan;
double fo,keepfo[4];
sanke ke,hd,keep[4];
hd = s.back();
for(i=0;i<=3;i++)
{
keep[i]=ke;
keepfo[i]=0;
}
pan = 0;//是否可以搜蛇尾判定
for(i = 0;i <= 3;i++)
{
here:
j = rand()%4;
for(i1 = 0;i1 < i;i1++)//生成随机方向 {
if(vi[i1]==j)
{
goto here;
}
}
vi[i] = j;
ke.x = dire[j][0],ke.y = dire[j][1];//记录方向状态
if(bfs(ke,s.front()))//此方向可以搜到蛇尾就求次方向和食物的距离
{
keep[j] = ke;
t = j;
keepfo[j] =sqrt(double((hd.x+ke.x - foodxy.x)*(hd.x+ke.x - foodxy.x) + (hd.y+ke.y - foodxy.y)*(hd.y+ke.y - foodxy.y)));
fo = keepfo[j];
pan = 1;
}
else
{
keepfo[j] = 100000;
}
}
if(pan)
{
pan = 0;
for(i = 0;i <= 3 ;i++)//寻找离食物最近的方向走
{
if(fo>keepfo[i])
{
t = i;
fo = keepfo[i];
}
}
if(judge(keep[t].x,keep[t].y))//行走
{
if(s.size() == ((n-2)*(m-2)-1))
{
return "win";
}
else
{
return "go on";
}
}
}
}
还有就是bfs的:
bool bfs(sanke a,sanke b)//bfs寻找如今蛇尾
{
queue q;
int i,j;
sanke tem,hd,ke;
tem = s.front();
hd = s.front();
vis[tem.x][tem.y]=' ';
tem = s.back();
vis[tem.x][tem.y]='*';
tem.x = tem.x + a.x,tem.y = tem.y + a.y;
if(vis[tem.x][tem.y] == '*' || vis[tem.x][tem.y] == 'I' || vis[tem.x][tem.y] == '-' || (tem.x == hd.x && tem.y == hd.y))
{
if((tem.x == hd.x && tem.y == hd.y))
{
for(i=0;i
至于上面的代码,有一个叫"随机生成方向"的东西,其实楼楼当时是按照一定顺序去bfs的,即这个数组
int dire[4][2]={{0,1},{1,0},{0,-1},{-1,0}};//右下左上
但写完后运行发现出现的策略性的死循环,即明明食物就在蛇头面前且吃了食物还能bfs到蛇尾,但就是不去吃,一直死磕着蛇尾,并无限走同一个轨迹。这种情况让我很脑疼,我觉得应该是有同多个方向到食物的最短距离是一样的,于是就出现的rand编号这么一个东西来增加他的可变性,但尽管已经做到这样了,通关率也只是上升了一点。接着就上程序源码了:
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
//#墙 $食物 *蛇身 @头
int dire[4][2]={{0,1},{1,0},{0,-1},{-1,0}};//右下左上
const int MAX = 30;
char map[MAX][MAX],vis[MAX][MAX];//map为当前状态地图 vis为map的复制在寻路时作模拟行走
int n,m;
int score,ti,ju,to;//分数,控制变速变量,自动寻路判定
double second;//时间控制
bool judge(int i,int j);
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);
}
struct sanke//用结构体储存蛇身坐标
{
int x,y;
sanke(int i1=0,int j1=0)//构造函数
{
x=i1;
y=j1;
}
}keepdire,foodxy;//保存当前方向 记录食物坐标
queue s; //用队列特点储存蛇身
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;
}
}
}
void printf()
{
Gotoxy(0,0);
int i,j;
for(i=0;i>ke;
if(strlen(ke)>1 ||(strlen(ke)==1&& (ke[0]-'0'!=1) && (ke[0]-'0'!=2) && (ke[0]-'0'!=3)))
{
system("cls");
cout<<"输入错误 请重新输入"< q;
int i,j;
sanke tem,hd,ke;
tem = s.front();
hd = s.front();
vis[tem.x][tem.y]=' ';
tem = s.back();
vis[tem.x][tem.y]='*';
tem.x = tem.x + a.x,tem.y = tem.y + a.y;
if(vis[tem.x][tem.y] == '*' || vis[tem.x][tem.y] == 'I' || vis[tem.x][tem.y] == '-' || (tem.x == hd.x && tem.y == hd.y))
{
if((tem.x == hd.x && tem.y == hd.y))
{
for(i=0;ikeepfo[i])
{
t = i;
fo = keepfo[i];
}
}
if(judge(keep[t].x,keep[t].y))//行走
{
if(s.size() == ((n-2)*(m-2)-1))
{
return "win";
}
else
{
return "go on";
}
}
}
}
char ifscanf()//按键判断
{
char ch;
if(kbhit())
{
ch=getch();
return ch;
}
else
{
return 0;
}
}
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<>again;
system("cls");
q1=1;
}
else if(pan=="loser")
{
system("cls");
cout<<"你输了"<>again;
system("cls");
q1=1;
}
if(q1)
{
while(1)
{
if(again=='n' || again=='N') exit(0);
else if(again=='y' || again=='Y')
{
q=1;
break;
}
else
{
cout<<"你耍我??"<>again;
}
}
}
if(q)break;
Sleep(second);
}
}
return 0;
}
如果看官们只是看一下ai的话,看到这句话就直接可以alt+f4了,下面我要废话一下。
关于这次的实训,收获当然不仅仅是写出了贪吃蛇,对于楼楼的代码风格,变量命名习惯,代码的调试方式都有的一定程度上的纠正。风格自然不用说,篇幅较长的代码变量名的命名就显得尤为重要,随便就出来了一个a,b什么的就会让人看着蛋疼(相信平时刷惯了题习惯了写短小精悍的程序的acmer应该是多少有点体会)。至于调试(大牛教主请无视),楼楼学到一招在你认为有问题代码处用getch()来停顿程序,然后输出"关键信息",当然关键信息看具体程序而定,在这里就是蛇头坐标,蛇尾坐标,现在的运动趋势,将来的运动趋势等等,对比一下信息就知道哪里出了问题。
还有就是跟我做同一项目的基友,他的通过关率就比我高多了,起码在80%以上(惭愧啊),我跟他交流了一下思想,本质上差不多,唯一的明显不同就是他有一个"推空格"操作,简单的说就是他的蛇尾与蛇头起码保持了一个空格的距离,至于为什么,他也说不清楚。还有就是一些其他的项目,有一个是手机上比较流行的"pop star",只是在这里游戏方式就变成了手动输入坐标,看着个坐标的图案能否消除,本质上就是dfs+图案移动,看着貌似还挺不错的样子,反正之后就是他在也没玩过这个游戏,因为测bug的时候玩到他想吐(其实楼楼玩贪吃蛇也玩到想吐了)。还有就是有人写五子棋之类的。
好吧废话完毕。