之前看到下面这样一幅gif动图的时候,都会忍不住花上个几分钟把它看完,看看这条蛇如何骚操作跑完全图
看别人跑还不够过瘾,所以得自己来写代码让自己的智能蛇也来跑一跑,看看是否也能够做到这么智能,跑完全图。
于是,在多次观察这条蛇的路径以及上网查阅一些资料之后,得到以下的初步分析:
1. 蛇的身体以及地图的边界是不可接触的格子,相当于障碍物;
2. 因为贪吃蛇移动是一个动态的过程,所以每走一步,要重新进行寻路;
3. 吃完食物蛇的长度变长,蛇尾巴位置不变,因此食物跟尾巴之间要留有空隙;
4. 如果一直跟着尾巴走,就永远不会没有路;
5. 随机出现的食物位置可能会堵住蛇头部到尾巴的路径,使得头部没有办法找到尾巴,但只要食物与尾巴之间有空隙,吃掉食物是安全的,而且可以重新找到从头部到尾巴的路径。
然后,慢慢摸索出实现智能蛇的算法:
1 获取当前地图智能蛇头部(head)的位置(snakex[len], snakey[len])
、尾部(tail)的位置(snakex[1], snakey[1])
,以及食物(food)的位置(foodx, foody)
;
2 对head to food、head to tail、food to tail各自进行一次BFS搜索,并将搜索结果存到step_h2f
、step_h2t
和step_f2t
数组中
int step_h2f[MAP_SIZE][MAP_SIZE]; // head to food
int step_h2t[MAP_SIZE][MAP_SIZE]; // head to tail
int step_f2t[MAP_SIZE][MAP_SIZE]; // food to tail
bfs(foodx, foody, snakex[len], snakey[len], step_h2f);
bfs(snakex[1], snakey[1], snakex[len], snakey[len], step_h2t);
bfs(snakex[1], snakey[1], foodx, foody, step_f2t);
3 判断是否能够找到从头部到尾巴路径A
1) 若找到路径A,寻找从头部到食物的路径B,
a. 如果找到,则向着食物的方向跟着尾巴走一步;
b. 如果不能找到路径B,就先跟着尾巴后面绕几步,直到出现安全路径B;
2) 若找不到路径A,则先吃食物,再继续跟尾巴走;
#define inf 999
...
if (step_h2t[snakex[len]][snakey[len]] < inf){
// 寻找安全的吃食物的路径
int x, y, minb = 4, t = 4;
int i = fdir;
int b[5] = { inf, inf, inf, inf, inf }; // min
int c[4] = { 0, 0, 0, 0 };
while (t--){
while (c[i]) i = rand() % 4;
c[i] = 1;
x = snakex[len] + dx[i];
y = snakey[len] + dy[i];
if (is_space(x, y) && step_h2t[x][y] < inf)
b[i] = step_h2f[x][y];
if (is_food(x, y) && step_f2t[x][y] < inf && step_f2t[x][y] > 1)
b[i] = 0;
if (b[i] < b[minb]) minb = i;
}
if (b[minb] == inf)
return find_farthest_way(); // 找不到安全的吃食物路径,先跟尾巴走一步
else
return fdir = minb % 4; // 已找到最短安全路径,返回方向
}
else return get_food(); // 从头部到尾巴的路被食物堵住时,先吃食物
这样基本就完成了智能蛇的大致算法,补充完find_farthest_way()
和get_food()
两个函数就能跑起来了,总的来说,算法还算是比较满意,全图最大长度100,一般都能够跑到90+的长度,运气好一点的情况则可以跑完全图
最终效果如图(跑完全图),由于录制设备的问题,颜色显示不出来,设置连头部也跟背景颜色融为一体了….
其实目前仍然存在的一个最大问题就是在智能蛇去吃食物的时候,难以避免出现一些单独的洞,而且有时很难去修复它,所有到后期这些洞很可能就成为智能蛇的葬身之处。所以,后来修改为,到了后期(长度超过一半时)就强行搜索最远的路径,无论是否能找到安全到达食物的路径,以尽可能减少空洞。
源代码:
#include <stdlib.h>
#include <time.h>
#include <Windows.h>
#define SNAKE_MAX_LENGTH 100
#define SNAKE_HEAD 'O'
#define SNAKE_BODY 'o'
#define SNAKE_FOOD '$'
#define SNAKE_TAIL '*'
#define MAP_SIZE 12
#define inf 999
void print();
void HideCursor();
void put_food();
int snake_move(int);
int is_over(int, int);
int is_food(int, int);
int is_space(int, int);
int get_direction();
int find_farthest_way();
int get_food();
void bfs(int, int, int, int, int *(*)[MAP_SIZE]);
void initial();
int foodx, foody, fdir = 1, len = 5;
int dx[] = { -1, 0, 1, 0 }; // up, down, left, right
int dy[] = { 0, 1, 0, -1 };
int snakex[SNAKE_MAX_LENGTH] = { 0, 1, 1, 1, 1, 1 };
int snakey[SNAKE_MAX_LENGTH] = { 0, 1, 2, 3, 4, 5 };
struct note{
int x;
int y;
int f;
int s;
};
int step_h2f[MAP_SIZE][MAP_SIZE]; // head to food
int step_h2t[MAP_SIZE][MAP_SIZE]; // head to tail
int step_f2t[MAP_SIZE][MAP_SIZE]; // food to tail
char map[MAP_SIZE][MAP_SIZE] = {
" __________ ",
"|ooooO |",
"| |",
"| |",
"| |",
"| |",
"| |",
"| |",
"| |",
"| |",
"| |",
" ~~~~~~~~~~ "
};
void main()
{
srand((unsigned int)time(NULL));
HideCursor();
while (1){
initial();
// start
while (!snake_move(get_direction()));
// end
if (len == 100) printf("Win!\n");
else printf("Game over!\nThe length of the snake is %d\n", len);
printf("Enter \'q\' to quit or any other keys to restart.\n");
char c = getchar();
if (c == 'q') break;
}
return;
}
void initial(){
fdir = 1, len = 5;
int i, j;
for (i = 1; i <= len; i++){
snakex[i] = 1;
snakey[i] = i;
map[1][i] = SNAKE_BODY;
}
for (i = len + 1; i <= MAP_SIZE - 2; i++) map[1][i] = ' ';
for (i = 2; i < MAP_SIZE - 1; i++)
for (j = 1; j < MAP_SIZE - 1; j++)
map[i][j] = ' ';
put_food();
print();
}
void color(const unsigned short color1)
{
if (color1 >= 0 && color1 <= 15)
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), color1);
else
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 7);
}
void HideCursor()
{
CONSOLE_CURSOR_INFO cursor_info = { 1, 0 };
SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cursor_info);
}
void print(){
system("cls");
for (int i = 0; i < MAP_SIZE; i++){
for (int j = 0; j < MAP_SIZE; j++){
if (is_food(i, j)){
color(14); printf("%c", map[i][j]); color(15);
}
else if (i == 0 || i == 11 || j == 0 || j == 11){
color(11); printf("%c", map[i][j]); color(15);
}
else if (map[i][j] == SNAKE_HEAD){
color(11); printf("%c", SNAKE_HEAD); color(15);
}
else if (i == snakex[1] && j == snakey[1]){
color(11); printf("%c", SNAKE_TAIL); color(15);
}
else printf("%c", map[i][j]);
}
printf("\n");
}
}
void put_food(){
int x, y;
while (map[x = rand() % 10 + 1][y = rand() % 10 + 1] != ' ');
map[x][y] = SNAKE_FOOD;
foodx = x;
foody = y;
}
int snake_move(int dir){
int x = snakex[len] + dx[dir];
int y = snakey[len] + dy[dir];
// grow
if (map[x][y] == SNAKE_FOOD){
map[snakex[len]][snakey[len]] = SNAKE_BODY;
len++;
snakex[len] = x;
snakey[len] = y;
map[x][y] = SNAKE_HEAD;
put_food();
print();
return 0;
}
// game over
if (map[x][y] != ' ') return 1;
// move
map[snakex[1]][snakey[1]] = ' ';
for (int i = 1; i < len; i++){
snakex[i] = snakex[i + 1];
snakey[i] = snakey[i + 1];
}
map[snakex[len - 1]][snakey[len - 1]] = SNAKE_BODY;
snakex[len] += dx[dir];
snakey[len] += dy[dir];
map[snakex[len]][snakey[len]] = SNAKE_HEAD;
print();
return 0;
}
int is_over(int x, int y){
int i, over = 0;
char c;
for (i = 0; i < 4; i++){
c = map[x + dx[i]][y + dy[i]];
if (c != ' ' && c != SNAKE_FOOD) over++;
}
return over;
}
int is_food(int x, int y){
return (x == foodx && y == foody);
}
int is_space(int x, int y){
return map[x][y] == ' ';
}
int get_direction(){
if (!is_over(snakex[len], snakey[len])) return 1;
bfs(foodx, foody, snakex[len], snakey[len], step_h2f);
bfs(snakex[1], snakey[1], snakex[len], snakey[len], step_h2t);
bfs(snakex[1], snakey[1], foodx, foody, step_f2t);
if (step_h2t[snakex[len]][snakey[len]] == inf) return get_food(); // 找不到尾巴找食物
else{
if (len > 50) return find_farthest_way(); // 长度大于50采取保守跑法
int x, y, minb = 4, t = 4;
int i = fdir;
int b[5] = { inf, inf, inf, inf, inf }; // min
int c[4] = { 0, 0, 0, 0 };
while (t--){
while (c[i]){ i = rand() % 4; }
c[i] = 1;
x = snakex[len] + dx[i];
y = snakey[len] + dy[i];
if (is_space(x, y) && step_h2t[x][y] < inf) b[i] = step_h2f[x][y];
if (is_food(x, y) && step_f2t[x][y] < inf && step_f2t[x][y] > 1) b[i] = 0;
if (b[i] < b[minb]) minb = i;
}
if (b[minb] == inf) return find_farthest_way(); // 找不到安全的吃食物路径,先跟尾巴
else return fdir = minb % 4; // 已找到最短安全路径
}
}
int find_farthest_way(){
int i = fdir;
int x, y, maxb = 4, t = 4;
int d[5] = { -1, -1, -1, -1, -1 };
while (t--){
x = snakex[len] + dx[i];
y = snakey[len] + dy[i];
if (is_space(x, y)) d[i] = step_h2t[x][y];
if (is_food(x, y) && step_f2t[x][y] < inf && step_f2t[x][y] > 1) d[i] = step_f2t[x][y];
if ((is_food(x, y) || step_h2f[x][y] < inf) && step_f2t[x][y] < inf) d[i] = step_f2t[x][y];
if (d[i] > d[maxb] && d[i] < inf) maxb = i;
i = (i + 1) % 4;
}
return fdir = maxb % 4;
}
int get_food(){
int i = fdir;
int x, y, maxb = 4, t = 4;
int d[5] = { -1, -1, -1, -1, -1 };
while (t--){
x = snakex[len] + dx[i];
y = snakey[len] + dy[i];
if (is_food(x, y) && len == 99) return i;
if (is_space(x, y) || (is_food(x, y) && step_f2t[x][y] > 1)) d[i] = step_h2f[x][y];
if (d[i] > d[maxb] && d[i] < inf) maxb = i;
i = (i + 1) % 4;
}
return fdir = maxb % 4;
}
void bfs(int x, int y, int tx, int ty, int *(*step)[MAP_SIZE]){
struct note que[102];
int xx, yy, head, tail;
int book[MAP_SIZE][MAP_SIZE] = { 0 };
for (int i = 0; i < MAP_SIZE; i++)
for (int j = 0; j < MAP_SIZE; j++)
*(*(step + i) + j) = inf;
*(*(step + x) + y) = 0;
book[x][y] = 1;
head = 1; tail = 1;
que[tail].x = x;
que[tail].y = y;
que[tail].f = 0;
que[tail].s = 0;
tail++;
int done = 0;
while (head < tail){
for (int k = 0; k < 4; k++){
xx = que[head].x + dx[k];
yy = que[head].y + dy[k];
if (xx == 0 || xx == 11 || yy == 0 || yy == 11) continue;
if (book[xx][yy] == 0 && is_space(xx, yy)){
book[xx][yy] = 1;
que[tail].x = xx;
que[tail].y = yy;
que[tail].f = head;
que[tail].s = que[head].s + 1;
*(*(step + xx) + yy) = que[tail].s;
tail++;
}
if (xx == tx && yy == ty) done++;
if (done == 4) return;
}
if (done == 4) return;
head++;
}
int i = 4;
while (i--){
xx = tx + dx[i];
yy = ty + dy[i];
if (*(*(step + xx) + yy) < *(*(step + tx) + ty) - 1) *(*(step + tx) + ty) = *(*(step + xx) + yy) + 1;
}
}