继上周写完贪吃蛇后,我们很快又有了新的作业,写一个智能蛇,虽然这条蛇可能有点智障。。
这里先写一下上一次的总结。对于上次的贪吃蛇,虽然成功写了出来,但是感觉写的时候有点磕磕绊绊,我反思了一下,也学习了一下别人的代码,有几点感悟。
1.要有图层的概念,background,snake都是可以分离的,糅在一起,虽然代码可能短,但增加了思维复杂度,当然也不能分得太多,money就可以揉在background里。
2.对于子程序的功能要有很清楚的了解。
3.全局变量的含义的维护。
下面才是这次的主角——智能蛇
这是老师给的智能蛇的程序框架
输出字符矩阵
WHILE not 游戏结束 DO
wait(time)
ch=whereGoNext(Hx,Hy,Fx,Fy)
CASE ch DO
‘A’:左前进一步,break
‘D’:右前进一步,break
‘W’:上前进一步,break
‘S’:下前进一步,break
END CASE
输出字符矩阵
END WHILE
输出 Game Over!!!
很容易看出,智能蛇的智能体现在whereGoNext(Hx,Hy,Fx,Fy)这个函数上,下面是老师给出的这个函数的伪代码。
// Hx,Hy: 头的位置
// Fx,Fy:食物的位置
function whereGoNext(Hx,Hy,Fx,Fy) {
// 用数组movable[3]={“a”,”d”,”w”,”s”} 记录可走的方向
// 用数组distance[3]={0,0,0,0} 记录离食物的距离
// 分别计算蛇头周边四个位置到食物的距离。H头的位置,F食物位置
// 例如:假设输入”a” 则distance[0] = |Fx – (Hx-1)| + |Fy – Hy|
// 如果 Hx-1,Hy 位置不是Blank,则 distance[0] = 9999
// 选择distance中存最小距离的下标p,注意最小距离不能是9999
// 返回 movable[p]
}
因为蛇是沿着线走的,所以这里采用的是曼哈顿距离,找出曼哈顿距离最小,便是我们的最短路,有一种贪心的思想,虽然很快,但是走到后面,还是会被蛇身困住或走进死胡同里,而这两个问题便是贪吃蛇AI最棘手的问题。
当然,下面先来实现这个简单的智能方法。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <math.h>
#define SNAKE_MAX_LENGTH 100
#define SNAKE_HEAD 'H'
#define SNAKE_BODY 'X'
#define BLANK_CELL ' '
#define SNAKE_FOOD '$'
#define WALL_CELL '*'
#define INF 0x3f3f3f3f
//snake stepping: dy = -1(up),1(down); dx = -1(left),1(right),0(no move)
void snakeMove(int,int);
//put a food randomized on a blank cell
void put_money(void);
//out cells of the grid
void output(void);
//outs when gameover
void gameover(void);
//find next
char whereGoNext(int ,int ,int ,int );
char map[13][13]=
{"************",
"*XXXXH *",
"* *",
"* *",
"* *",
"* *",
"* *",
"* *",
"* *",
"* *",
"* *",
"************"};
// define vars for snake , notice name of vars in C
int snakeX[SNAKE_MAX_LENGTH]={1,2,3,4,5};
int snakeY[SNAKE_MAX_LENGTH]={1,1,1,1,1};
int snakelength=5;
//记录分数
int score=0;
//判断游戏是否结束
int ok=1;
int money_x,money_y;
int step;
int main(){
srand(time(NULL));
put_money();
output();
step=-1;
while(ok){
step++;
char ch=whereGoNext(snakeX[snakelength-1],snakeY[snakelength-1],money_x,money_y);
if (ch!='A'&ch!='D'&ch!='W'&ch!='S') continue;
switch (ch){
case 'A':
snakeMove(-1,0);
break;
case 'D':
snakeMove(1,0);
break;
case 'W':
snakeMove(0,-1);
break;
case 'S':
snakeMove(0,1);
break;
}
output();
}
gameover();
return 0;
}
void snakeMove(int dx,int dy){
int x=snakeX[snakelength-1]+dx;
int y=snakeY[snakelength-1]+dy;
snakeX[snakelength]=x;
snakeY[snakelength]=y;
//如果碰到边界
if (map[y][x]=='*') {
ok=0;
return ;
}
//如果碰到蛇身
else if (map[y][x]=='X'){
ok=0;
return ;
}
//如果吃到钱,不用处理蛇尾,处理蛇头,长度+1,放钱,分数+1
else if (map[y][x]=='$') {
snakelength++;
//蛇头处理
map[snakeY[snakelength-2]][snakeX[snakelength-2]]='X';
map[y][x]='H';//蛇头处理
score++;
put_money();
}
//如果吃不到钱,处理蛇头,蛇尾位置变空格,身体移一位
else if (map[y][x]==' ') {
map[snakeY[0]][snakeX[0]]=' ';
for(int i=0;i<snakelength;i++){
snakeX[i]=snakeX[i+1];
snakeY[i]=snakeY[i+1];
}
//蛇头处理
map[snakeY[snakelength-2]][snakeX[snakelength-2]]='X';
map[y][x]='H';
}
}
void put_money(void){
//找到合法位置
do{
money_x=rand()%10+1;
money_y=rand()%10+1;
} while (map[money_y][money_x]!=' ');
map[money_y][money_x]='$';
}
void output(void){
system("cls");
printf("score:%d\n",score);
for(int i=0;i<12;i++){
for(int j=0;j<12;j++){
printf("%c",map[i][j]);
}
printf("\n");
}
}
void gameover(void){
printf("Game Over!!!");
}
char whereGoNext(int Hx,int Hy,int Fx,int Fy){
char moveable[4]={'A','D','W','S'};
int distance[4]={INF,INF,INF,INF};
int dx[4]={-1,1,0,0},
dy[4]={0,0,-1,1};
for(int i=0;i<4;i++){
int X=Hx+dx[i];
int Y=Hy+dy[i];
if (map[Y][X]==' '||map[Y][X]=='$'){
distance[i]=abs(Fx-(Hx+dx[i]))+abs(Fy-(Hy+dy[i]));
}
}
int k=-1,tmp=INF;
for(int i=0;i<4;i++){
if (distance[i]<tmp){
k=i;
tmp=distance[i];
}
}
if (k!=-1) return moveable[k];
else {
ok=0;
return moveable[0];
}
}
上面是通过计算曼哈顿距离来寻路的gif,不出所料,蛇被自己蛇身困住。
我将在下面一篇博客,介绍一些可能更加智能或者更加有效的寻路算法。