上次写了个简单的字符贪吃蛇,这次让电脑自己跑贪吃蛇,也就是简单的智能蛇。
上次的蛇还有一些问题,比如输入方向键后要输入回车才会运动,老师提供了一种在linux上的程序,可以不用输回车就能读入键盘输入,代码如下:
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <termios.h>
#include <unistd.h>
static struct termios ori_attr, cur_attr;
static __inline
int tty_reset(void)
{
if (tcsetattr(STDIN_FILENO, TCSANOW, &ori_attr) != 0)
return -1;
return 0;
}
static __inline
int tty_set(void)
{
if ( tcgetattr(STDIN_FILENO, &ori_attr) )
return -1;
memcpy(&cur_attr, &ori_attr, sizeof(cur_attr) );
cur_attr.c_lflag &= ~ICANON;
// cur_attr.c_lflag |= ECHO;
cur_attr.c_lflag &= ~ECHO;
cur_attr.c_cc[VMIN] = 1;
cur_attr.c_cc[VTIME] = 0;
if (tcsetattr(STDIN_FILENO, TCSANOW, &cur_attr) != 0)
return -1;
return 0;
}
static __inline
int kbhit(void)
{
fd_set rfds;
struct timeval tv;
int retval;
/* Watch stdin (fd 0) to see when it has input. */
FD_ZERO(&rfds);
FD_SET(0, &rfds);
/* Wait up to five seconds. */
tv.tv_sec = 0;
tv.tv_usec = 0;
retval = select(1, &rfds, NULL, NULL, &tv);
/* Don't rely on the value of tv now! */
if (retval == -1) {
perror("select()");
return 0;
} else if (retval)
return 1;
/* FD_ISSET(0, &rfds) will be true. */
else
return 0;
return 0;
}
//将你的 snake 代码放在这里
int main()
{
//设置终端进入非缓冲状态
int tty_set_flag;
tty_set_flag = tty_set();
//将你的 snake 代码放在这里
printf("pressed `q` to quit!\n");
while(1) {
if( kbhit() ) {
const int key = getchar();
printf("%c pressed\n", key);
if(key == 'q')
break;
} else {
;// fprintf(stderr, "<no key detected>\n");
}
}
//恢复终端设置
if(tty_set_flag == 0)
tty_reset();
return 0;
}
代码太复杂(反正我看不懂)不过好在只要把贪吃蛇代码加到指定位置就可以了。本人试了一下确实可以,不过随机产生食物还要在最前面加上引用time.h和stdlib.h的库文件。
接下来就是智能蛇的算法了,老师提供的算法为走曼哈顿距离:
分别计算蛇头到食物的距离,算出最小距离,最小距离决定了头往哪走,即返回下一个操作。具体代码如下:
char WhereGoNext(int Hx, int Hy, int Fx, int Fy)
{
char moveable[4] = { 'a','d','w','s' };
int distance[4] = { 0 };
distance[0] = abs(Fy - (Hy - 1)) + abs(Fx - Hx);
if (map[Hx][Hy - 1] != BLANK_CELL&&map[Hx][Hy - 1] != SNAKE_FOOD)
distance[0] = 9999;
distance[1] = abs(Fy - (Hy + 1)) + abs(Fx - Hx);
if (map[Hx][Hy + 1] != BLANK_CELL&&map[Hx][Hy + 1] != SNAKE_FOOD)
distance[1] = 9999;
distance[2] = abs(Fy - Hy) + abs(Fx - (Hx - 1));
if (map[Hx - 1][Hy] != BLANK_CELL&&map[Hx - 1][Hy] != SNAKE_FOOD)
distance[2] = 9999;
distance[3] = abs(Fy - Hy) + abs(Fx - (Hx + 1));
if (map[Hx + 1][Hy] != BLANK_CELL&&map[Hx + 1][Hy]!=SNAKE_FOOD)
distance[3] = 9999;
int min=0;
for (int i = 0; i <= 3; i++)
{
if (distance[i] < distance[min] && distance[i] != 9999)
min = i;
}
return moveable[min];
}
用这个代码替换原来贪吃蛇的scanf就可以实现自动寻路。但是计算机运算的速度非常快,可能你还没看清楚蛇就已经死了。这时候要加入一个操作,让生成下一步的方向时停一下。这要引入windows.h里面的Sleep(注意,S是大写)Sleep(i)可以让进行下一个操作之前暂停i的时间,单位为毫秒,所以我让蛇每0.1.秒运动一次。做出来的效果如图:
可见,蛇已经可以自己跑了,但是缺点很显然:容易死。因为这种算法不会考虑走完后下一步该怎么走,按最短路线的话非常容易把自己困死。我在网上找了一下进阶的算法,主要的思想就是蛇头要追着蛇尾走,而且走的不是最近路线,而是最远路线。蛇必须判断走完这次后是否还能找到尾巴的路线才去吃,否则就要往别的方向走一格,而这个走法是走最远的距离。
算法伪代码:
1 if 可以吃食物
2 if 虚拟蛇沿规则最短路吃食物后能找到尾巴
3 真实蛇移动一步
4 重新判断
5 else if 虚拟蛇沿不规则最短路去吃食物能找到尾巴
6 真实蛇移动一步
7 重新判断
8 else if 可知到达自己的尾巴并且移动一步已让可以到达自己尾巴
9 选择离食物最远的位置移动
10 重新判断
11 else
12 向最深的路径移动一步
据说这样可以让蛇铺满整个地图(不过我还不会搞)不过自己做出来了第一个类似人工智能的作品还是很高兴的(可把我厉害坏了,插会腰.jpg)另外感谢让我搞死无数次的小蛇: