C语言-手把手教你写贪吃蛇AI(下)

来源:互联网 发布:java 泛型使用 编辑:程序博客网 时间:2024/04/29 19:34

1. 目标

        这一部分的目标是把之前写的贪吃蛇加入AI功能,即自动的去寻找食物并吃掉。


2. 控制策略

        为了保证蛇不会走入“死地”,所以蛇每前进一步都需要检查,移动到新的位置后,能否找到走到蛇尾的路径,如果可以,才可以走到新的位置;否则在当前的位置寻找走到蛇尾的路径,并按照路径向前走一步,开始循环之前的操作,如下图所示。这个策略可以工作,但是并不高效,也可以尝试其他的控制策略,比如易水寒的贪吃蛇AI

         

        运行效果如下:

          


3. 源代码

需要注意的是,由于mapnode的数据量比较大,这里需要把栈的大小设置大一点,如下图所示,否则会出现栈溢出的情况。



整个项目由以下三个文件组成:

a. snake AI.h

#ifndef SNAKE_H_#define SNAKE_H_#include<stdio.h>#include<Windows.h> //SetConsoleCursorPosition, sleep函数的头函数#include<time.h>//time()的头函数#include<malloc.h>//malloc()的头函数#define N 32 //地图大小#define snake_mark '#'//表示蛇身#define food_mark '$'//表示食物#define sleeptime 50//间隔时间#define W 10//权重typedef struct STARNODE{int x;//节点的x,y坐标int y;int G;//该节点的G, H值int H;int is_snakebody;//是否为蛇身,是为1,否则为0;int in_open_table;//是否在open_table中,是为1,否则为0;int in_close_table;//是否在close_table中,是为1,否则为0;struct STARNODE* ParentNode;//该节点的父节点} starnode, *pstarnode;extern starnode (*mapnode)[N + 4];extern pstarnode opentable[N*N / 2];extern pstarnode closetable[N*N / 2];extern int opennode_count;extern int closenode_count;/*表示蛇身坐标的结构体*/typedef struct SNAKE{int x; //行坐标int y; //列坐标struct SNAKE* next;}snake_body, *psnake;extern psnake snake;extern psnake food;extern psnake snaketail;extern psnake nextnode;void set_cursor_position(int x, int y);void initial_map();void initial_mapnode();void update_mapnode();void printe_map();void initial_snake();void create_food();int is_food();void heapadjust(pstarnode a[], int m, int n);void swap(pstarnode a[], int m, int n);void crtheap(pstarnode a[], int n);void heapsort(pstarnode a[], int n);void insert_opentable(int x1, int y1, pstarnode pcurtnode, psnake endnode);void find_neighbor(pstarnode pcurtnode, psnake endnode);int search_short_road(psnake snakehead, psnake endnode);int search_snaketail(psnake snakehead);void update_snaketail(psnake snakehead);void snake_move();psnake create_tsnake();void snake_control();#endif

2. source.cpp

#include"Snake AI.h"/*控制光标的坐标*/void set_cursor_position(int x, int y){COORD coord = { x, y };//x表示列,y表示行。SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);}/*初始化后的地图为 N列 N/2行*//*游戏的空间为2至N+1列,1至N/2行*/void initial_map(){int i = 0;//打印上下边框(每个■占用一行两列)for (i = 0; i<N / 2 + 2; i++){set_cursor_position(2 * i, 0);printf("■");set_cursor_position(2 * i, N / 2 + 1);printf("■");}for (i = 0; i<N / 2 + 2; i++)   //打印左右边框  {set_cursor_position(0, i);printf("■");set_cursor_position(N + 2, i);printf("■");}}//初始化mapnodevoid initial_mapnode(){int i = 0, j = 0;for (i = 0; i < N / 2 + 2; i++)for (j = 0; j < N + 4; j++){mapnode[i][j].G = 0;mapnode[i][j].H = 0;mapnode[i][j].in_close_table = 0;mapnode[i][j].in_open_table = 0;mapnode[i][j].is_snakebody = 0;mapnode[i][j].ParentNode = NULL;mapnode[i][j].x = i;mapnode[i][j].y = j;}}//初始化mapnodevoid update_mapnode(){psnake temp = snake;int x, y;initial_mapnode();//初始化mapnodewhile (temp){x = temp->x;y = temp->y;mapnode[x][y].is_snakebody = 1;temp = temp->next;}}void printe_map(){psnake temp = snake;while (temp){set_cursor_position(temp->y, temp->x);printf("%c", snake_mark);temp = temp->next;}if (food)set_cursor_position(food->y, food->x);printf("%c", food_mark);set_cursor_position(0, N / 2 + 2);}/*初始化蛇身*//*蛇身初始化坐标为(8,5),(8,4), (8,3) */void initial_snake(){inti = 5;//列int j = N / 4;//行psnake tsnake = NULL, temp = NULL;snake = (psnake)malloc(sizeof(snake_body));(snake)->x = j;(snake)->y = i;(snake)->next = NULL;tsnake = snake;for (i = 4; i >2; i--){temp = (psnake)malloc(sizeof(snake_body));(temp)->x = j;(temp)->y = i;(temp)->next = NULL;(tsnake)->next = (temp);(tsnake) = (tsnake)->next;}snaketail = tsnake;}//生成食物void create_food(){srand((unsigned)time(NULL));food->y = rand() % N + 2;//列food->x = rand() % (N / 2) + 1;//行//检查食物是否和蛇身重回update_mapnode();if (mapnode[food->x][food->y].is_snakebody){create_food();}}//判断是否吃到食物,吃到食物返回 1,否则返回 0;int is_food(){if (snake->x == food->x && snake->y == food->y)return 1;return 0;}//根据指针所指向的节点的F值,按大顶堆进行调整void heapadjust(pstarnode a[], int m, int n){int i;pstarnode temp = a[m];for (i = 2 * m; i <= n; i *= 2){if (i + 1 <= n && (a[i + 1]->G + a[i + 1]->H)>(a[i]->G + a[i]->H)){i++;}if ((temp->G + temp->H)>(a[i]->G + a[i]->H)){break;}a[m] = a[i];m = i;}a[m] = temp;}void swap(pstarnode a[], int m, int n){pstarnode temp;temp = a[m];a[m] = a[n];a[n] = temp;}void crtheap(pstarnode a[], int n){int i;for (i = n / 2; i>0; i--){heapadjust(a, i, n);}}void heapsort(pstarnode a[], int n){int i;crtheap(a, n);for (i = n; i>1; i--){swap(a, 1, i);heapadjust(a, 1, i - 1);}}//x1, y1是邻域点坐标//curtnode是当前点坐标//endnode是目标点坐标void insert_opentable(int x1, int y1, pstarnode pcurtnode, psnake endnode){int i = 1;if (!mapnode[x1][y1].is_snakebody && !mapnode[x1][y1].in_close_table)//如果不是蛇身也不在closetable中{if (mapnode[x1][y1].in_open_table)//如果已经在opentable中{if (mapnode[x1][y1].G > pcurtnode->G + W)//但是不是最优路径{mapnode[x1][y1].G = pcurtnode->G + W;//把G值更新(变小)mapnode[x1][y1].ParentNode = pcurtnode;//把该邻点的双亲节点更新//由于改变了opentable中一个点的F值,需要对opentable中的点的顺序进行调整,以满足有序for (i = 1; i <= opennode_count; i++){if (opentable[i]->x == x1 && opentable[i]->y == y1){break;}}heapsort(opentable, i);}}else//如果不在opentable中,把该点加入opentable中{opentable[++opennode_count] = &mapnode[x1][y1];mapnode[x1][y1].G = pcurtnode->G + W;mapnode[x1][y1].H = (abs(endnode->x - x1) + abs(endnode->y - y1))*W;mapnode[x1][y1].in_open_table = 1;mapnode[x1][y1].ParentNode = pcurtnode;heapsort(opentable, opennode_count);}}}//寻找当前点的四邻域点,把符合条件的点加入opentable中void find_neighbor(pstarnode pcurtnode, psnake endnode){int x;int y;x = pcurtnode->x;y = pcurtnode->y;if (x + 1 <= N / 2){insert_opentable(x + 1, y, pcurtnode, endnode);}if (x - 1 >= 1){insert_opentable(x - 1, y, pcurtnode, endnode);}if (y + 1 <= N + 1){insert_opentable(x, y + 1, pcurtnode, endnode);}if (y - 1 >= 2){insert_opentable(x, y - 1, pcurtnode, endnode);}}int search_short_road(psnake snakehead, psnake endnode){int is_search_short_road = 0;opennode_count = 0;closenode_count = 0;pstarnode pcurtnode;pstarnode temp;pstarnode startnode = &mapnode[snakehead->x][snakehead->y];//startnode指向蛇头所对应的结点opentable[++opennode_count] = startnode;//起始点加入opentable中startnode->in_open_table = 1;startnode->ParentNode = NULL;startnode->G = 0;startnode->H = (abs(endnode->x - startnode->x) + abs(endnode->y - startnode->y))*W;while (1){//取出opentable中第1个节点加入closetable中if (!opennode_count)//如果opentable已经为空,即没有找到路径{//printf("No way");return is_search_short_road;}pcurtnode = opentable[1];opentable[1] = opentable[opennode_count--];closetable[++closenode_count] = pcurtnode;pcurtnode->in_open_table = 0;pcurtnode->in_close_table = 1;if (pcurtnode->x == endnode->x && pcurtnode->y == endnode->y){is_search_short_road = 1;break;}find_neighbor(pcurtnode, endnode);}if (is_search_short_road)//如果找到,则用nextnode记录蛇头下一步应该移动的位置{temp = closetable[closenode_count];while (temp->ParentNode->ParentNode){temp = temp->ParentNode;}nextnode->x = temp->x;nextnode->y = temp->y;nextnode->next = NULL;}return is_search_short_road;}int search_snaketail(psnake snakehead){int t = 0;update_mapnode();mapnode[snaketail->x][snaketail->y].is_snakebody = 0;t = search_short_road(snakehead, snaketail);mapnode[snaketail->x][snaketail->y].is_snakebody = 1;return t;}//蛇尾向前移动一格,并把原来的蛇尾注销void update_snaketail(psnake snakehead){psnake temp;temp = snakehead;while (temp->next->next){temp = temp->next;}snaketail = temp;temp = temp->next;mapnode[temp->x][temp->y].is_snakebody = 0;//将蛇尾注销掉}//将蛇身移动到指定的位置(nextnode),并打印出来void snake_move(){psnake snake_head = (psnake)malloc(sizeof(snake_body));snake_head->x = nextnode->x;snake_head->y = nextnode->y;snake_head->next = snake;snake = snake_head;if (is_food())//如果是食物{create_food();printe_map();}else//不是食物{psnake temp = snake_head;while (temp->next->next)//寻找蛇尾{temp = temp->next;}snaketail = temp;//更新snaketail的位置set_cursor_position(temp->next->y, temp->next->x);printf(" ");//把蛇尾用空格消掉free(temp->next);//释放蛇尾的内存空间temp->next = NULL;//将temp的next置成NULLprinte_map();}snake=snake_head;}psnake create_tsnake(){psnake tsnake = (psnake)malloc(sizeof(snake_body));tsnake->x = nextnode->x;tsnake->y = nextnode->y;tsnake->next = NULL;psnake temp1 = snake;psnake temp2 = tsnake;while (temp1!=snaketail){temp2->next = (psnake)malloc(sizeof(snake_body));temp2->next->x = temp1->x;temp2->next->y = temp1->y;temp2->next->next = NULL;temp1 = temp1->next;temp2 = temp2->next;}return tsnake;}void snake_control(){int r, t, x, y;psnake tsnake = NULL;;while (1){r = 0;t = 0;x = 0;y = 0;update_mapnode();r = search_short_road(snake, food);if (r == 1)//如果能找到到达食物的路径{x = nextnode->x;y = nextnode->y;tsnake=create_tsnake();mapnode[x][y].is_snakebody = 1;t = search_snaketail(tsnake);//走到下一个节点后,能否找到更新后的蛇尾if (t==1)//如果按照路径走到下一个位置,可以找到蛇尾,就把蛇头移动到下一个位置{nextnode->x = x;nextnode->y = y;Sleep(sleeptime);snake_move();}else//否则,从该点出发去找蛇尾{mapnode[x][y].is_snakebody = 0;search_snaketail(snake);Sleep(sleeptime);snake_move();}free(tsnake);}else//如果找不到食物{search_snaketail(snake);Sleep(sleeptime);snake_move();}}}

3. main.cpp

#include"Snake AI.h"psnake snake = NULL;psnake food = NULL;psnake snaketail = NULL;psnake nextnode = NULL;//蛇头下一步该走的结点starnode (*mapnode)[N+4]=(starnode(*)[N+4])malloc(sizeof(starnode)*(N/2+2)*(N+4));pstarnode opentable[N*N / 2];pstarnode closetable[N*N / 2];int opennode_count = 0;int closenode_count = 0;int main(void){initial_map();initial_snake();food = (psnake)malloc(sizeof(snake_body));nextnode = (psnake)malloc(sizeof(snake_body));food->next = NULL;create_food();food->x = 1;food->y = 3;printe_map();snake_control();free(food);free(snake);free(mapnode);return 0;}



0 0