字符游戏-智能蛇

之前看到下面这样一幅gif动图的时候,都会忍不住花上个几分钟把它看完,看看这条蛇如何骚操作跑完全图
AI蛇
看别人跑还不够过瘾,所以得自己来写代码让自己的智能蛇也来跑一跑,看看是否也能够做到这么智能,跑完全图。

于是,在多次观察这条蛇的路径以及上网查阅一些资料之后,得到以下的初步分析:
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_h2fstep_h2tstep_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+的长度,运气好一点的情况则可以跑完全图
AI_snake
最终效果如图(跑完全图),由于录制设备的问题,颜色显示不出来,设置连头部也跟背景颜色融为一体了….
AI_snake
其实目前仍然存在的一个最大问题就是在智能蛇去吃食物的时候,难以避免出现一些单独的洞,而且有时很难去修复它,所有到后期这些洞很可能就成为智能蛇的葬身之处。所以,后来修改为,到了后期(长度超过一半时)就强行搜索最远的路径,无论是否能找到安全到达食物的路径,以尽可能减少空洞。

源代码:

#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;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值