小編給大家分享一下C語言如何實現(xiàn)貪吃蛇AI,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
創(chuàng)新互聯(lián)專業(yè)為企業(yè)提供長海網(wǎng)站建設、長海做網(wǎng)站、長海網(wǎng)站設計、長海網(wǎng)站制作等企業(yè)網(wǎng)站建設、網(wǎng)頁設計與制作、長海企業(yè)網(wǎng)站模板建站服務,10余年長海做網(wǎng)站經(jīng)驗,不只是建網(wǎng)站,更提供有價值的思路和整體網(wǎng)絡服務。
1. 目標
編寫一個貪吃蛇AI,也就是自動繞過障礙,去尋找最優(yōu)路徑吃食物。
2. 問題分析
為了達到這一目的,其實很容易,總共只需要兩步,第一步抓一條蛇,第二步給蛇裝一個腦子。具體來說就是,首先我們需要有一條普通的貪吃蛇,也就是我們常玩兒的,手動控制去吃食物的貪吃蛇;然后給這條蛇加入AI,也就是通過算法控制,告訴蛇怎么最方便的繞開障礙去吃食物。為了講清楚這個問題,文章將分為三部分:上,寫一個貪吃蛇程序;中,算法基礎(需要運用到什么算法);下,運用算法基礎中的算法編寫一個貪吃蛇AI。
在動手寫貪吃蛇之前,我們需要想清楚以下幾個問題,就非常容易了:
a. 蛇身。由于蛇在吃食物的過程中會不斷的長大,所以很適合用單鏈表表示,并且吃食物的過程就是用頭插法插入元素的過程
b. 食物。食物直接用隨機生成函數(shù),隨機生成食物,但是需要檢查,所生成的食物的位置不可以和蛇身重合
c. 顯示。我們需要實時的顯示出蛇身的移動,但事實上,我們不用每次都打印整個蛇身,因為蛇身每走一步,僅僅是蛇頭和蛇尾的位置移動一格,其他的地方都沒有變化,所以只需要打印一個新的蛇頭,并把蛇尾的位置抹掉,那么視覺效果就是蛇身先前移動了一格,這個過程中,我們需要用到SetConsoleCursorPosition(),將光標移到到指定的位置(比如蛇尾),完成相應的操作(比如打印空格抹掉蛇尾)
d.控制。我們需要用鍵盤來控制蛇身的移動,這個程序中是利用上下左右方向鍵來實現(xiàn)的,這里需要用到GetAsyncKeyState(),來實時監(jiān)測按鍵的狀態(tài)
3. 運行效果
4. 源代碼
總共由三個文件組成gluttonous.h,source.c & main.cpp。由于這個貪吃蛇是用于后面加AI,所以并沒有加入一些錯誤檢測,比如是否撞到邊界,是否撞到蛇身等。
需要注意的是,這個程序中用到了比較特殊的字符('■')來表示游戲空間的邊界,在VS2013中可以正常編譯,但是在codeblock中會亂碼。
另外還有一點容易混淆的是,我們通常都是用(x,y)坐標表示第x行,第y列,但是在SetConsoleCursorPosition(x,y)中,表示把光標移動到第y行,第x列
4.1 gluttonous.h
#ifndef SNAKE_H_ #define SNAKE_H_ #include<stdio.h> #include<Windows.h> //SetConsoleCursorPosition, sleep函數(shù)的頭函數(shù) #include<time.h> //time()的頭函數(shù) #include<malloc.h> //malloc()的頭函數(shù) #define N 32 //地圖大小 #define snake_mark '#'//表示蛇身 #define food_mark '$' #define sleeptime 500 /*表示蛇身坐標的結構體*/ typedef struct SNAKE{ int x; //行坐標 int y; //列坐標 struct SNAKE* next; }snake_body, *psnake; extern psnake food; typedef enum Direction{ U,D,L,R} direction;//蛇頭的朝向 extern direction snake_direction; void set_cursor_position(int x, int y); void initial_map(); psnake initial_snake(); void create_food(psnake snake,psnake food); void printe_map(psnake snake, psnake food); int is_food(psnake snake_head, psnake food); int is_boundary(psnake snake_head, psnake food); int is_snakebody(psnake snake_head, psnake food); psnake snake_move(psnake sanke, psnake food); void control_snake(); #endif
4.2 source.cpp
#include"gluttonous.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("■"); } } /*初始化蛇身*/ /*蛇身初始化坐標為(5,8),(4,8), (3,8) */ psnake initial_snake() { int i=5;//列 int j = N / 4;//行 psnake snake = NULL, tsnake = NULL, temp = NULL; snake = (psnake)malloc(sizeof(snake_body)); (snake)->x = i; (snake)->y = j; (snake)->next = NULL; tsnake = snake; for (i = 4; i >2; i--) { temp = (psnake)malloc(sizeof(snake_body)); (temp)->x = i; (temp)->y = j; (temp)->next = NULL; (tsnake)->next = (temp); (tsnake) = (tsnake)->next; } return snake; } void create_food(psnake snake, psnake food) { static int i=1; psnake head = snake; srand((unsigned)time(NULL)); food->x = rand() % N + 2; food->y = rand() % (N/2) + 1; //檢查食物是否和蛇身重回 while (head) { if (head->x == food->x && head->y == food->y) { free(food); food = NULL; create_food(snake,food); } else { head = head->next; } } } void printe_map(psnake snake, psnake food) { psnake temp=snake; while (temp) { set_cursor_position(temp->x, temp->y); printf("%c",snake_mark); temp = temp->next; } if (food) set_cursor_position(food->x,food->y ); printf("%c",food_mark); set_cursor_position(0, N/2+2); } //判斷是否吃到食物,吃到食物返回 1,否則返回 0; int is_food(psnake snake_head, psnake food) { if (snake_head->x == food->x && snake_head->y == food->y) return 1; return 0; } //判斷是否撞到墻,撞到墻返回 1,否則返回 0; int is_boundary(psnake snake_head) { if (snake_head->y <= 0 || snake_head->y >= N / 2 + 1 || snake_head->x <= 1 || snake_head->x >= N + 1) return 1; return 0; } //判斷是否撞到自己,撞到自己返回 1,否則返回 0; int is_snakebody(psnake snake_head) { psnake temp=snake_head->next; while (temp) { if (snake_head->x == temp->x && snake_head->y == temp->y) return 1; else temp = temp->next; } return 0; } //將蛇身移動到合適的位置,并打印出來 psnake snake_move(psnake snake, psnake food) { psnake snake_head = (psnake)malloc(sizeof(snake_body)); if (snake_direction == U) { snake_head->y = snake->y-1; snake_head->x = snake->x; snake_head->next = snake; } else if (snake_direction == D) { snake_head->y = snake->y + 1; snake_head->x = snake->x; snake_head->next = snake; } else if (snake_direction == L) { snake_head->y = snake->y; snake_head->x = snake->x - 1; snake_head->next = snake; } else if (snake_direction == R) { snake_head->y = snake->y; snake_head->x = snake->x + 1; snake_head->next = snake; } if (is_food(snake_head, food))//如果是食物 { create_food(snake_head, food); printe_map(snake_head, food); } else if (is_boundary(snake_head) == 0 && is_snakebody(snake_head) == 0)//不是食物,不是邊界,也不是蛇身 { psnake temp = snake_head; while (temp->next->next)//尋找蛇尾 { temp = temp->next; } set_cursor_position(temp->next->x, temp->next->y); printf(" ");//把蛇尾用空格消掉 free(temp->next);//釋放蛇尾的內(nèi)存空間 temp->next = NULL;//將temp的next置成NULL printe_map(snake_head, food); } else { free(snake_head); snake_head = NULL; } return snake_head; } void control_snake() { if (GetAsyncKeyState(VK_UP) && snake_direction != D) { snake_direction = U; } else if (GetAsyncKeyState(VK_DOWN) && snake_direction != U) { snake_direction = D; } else if (GetAsyncKeyState(VK_LEFT) && snake_direction != R) { snake_direction = L; } else if (GetAsyncKeyState(VK_RIGHT) && snake_direction != L) { snake_direction = R; } }
4.3 main.cpp
#include"gluttonous.h" direction snake_direction; psnake food; int main(void) { psnake snake; initial_map(); snake=initial_snake(); food = (psnake)malloc(sizeof(snake_body)); food->next = NULL; create_food(snake, food); printe_map(snake, food); snake_direction = R; while (1) { Sleep(sleeptime); control_snake(); snake=snake_move(snake, food); } return 0; }
1. 目標
這一部分的目標是把之前寫的貪吃蛇加入AI功能,即自動的去尋找食物并吃掉。
2. 控制策略
為了保證蛇不會走入“死地”,所以蛇每前進一步都需要檢查,移動到新的位置后,能否找到走到蛇尾的路徑,如果可以,才可以走到新的位置;否則在當前的位置尋找走到蛇尾的路徑,并按照路徑向前走一步,開始循環(huán)之前的操作,如下圖所示。這個策略可以工作,但是并不高效,也可以嘗試其他的控制策略,比如易水寒的貪吃蛇AI
運行效果如下:
3. 源代碼
需要注意的是,由于mapnode的數(shù)據(jù)量比較大,這里需要把棧的大小設置大一點,如下圖所示,否則會出現(xiàn)棧溢出的情況。
整個項目由以下三個文件組成:
a. snake AI.h
#ifndef SNAKE_H_ #define SNAKE_H_ #include<stdio.h> #include<Windows.h> //SetConsoleCursorPosition, sleep函數(shù)的頭函數(shù) #include<time.h> //time()的頭函數(shù) #include<malloc.h> //malloc()的頭函數(shù) #define N 32 //地圖大小 #define snake_mark '#'//表示蛇身 #define food_mark '$'//表示食物 #define sleeptime 50//間隔時間 #define W 10//權重 typedef struct STARNODE{ int x;//節(jié)點的x,y坐標 int y; int G;//該節(jié)點的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;//該節(jié)點的父節(jié)點 } 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
b. 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(22 * i, 0); printf("■"); set_cursor_position(22 * 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("■"); } } //初始化mapnode void 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; } } //初始化mapnode void update_mapnode() { psnake temp = snake; int x, y; initial_mapnode();//初始化mapnode while (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() { int i = 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; } //根據(jù)指針所指向的節(jié)點的F值,按大頂堆進行調(diào)整 void heapadjust(pstarnode a[], int m, int n) { int i; pstarnode temp = a[m]; for (i = 22 * 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)//如果已經(jīng)在opentable中 { if (mapnode[x1][y1].G > pcurtnode->G + W)//但是不是最優(yōu)路徑 { mapnode[x1][y1].G = pcurtnode->G + W;//把G值更新(變小) mapnode[x1][y1].ParentNode = pcurtnode;//把該鄰點的雙親節(jié)點更新 //由于改變了opentable中一個點的F值,需要對opentable中的點的順序進行調(diào)整,以滿足有序 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個節(jié)點加入closetable中 if (!opennode_count)//如果opentable已經(jīng)為空,即沒有找到路徑 { //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);//釋放蛇尾的內(nèi)存空間 temp->next = NULL;//將temp的next置成NULL printe_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);//走到下一個節(jié)點后,能否找到更新后的蛇尾 if (t==1)//如果按照路徑走到下一個位置,可以找到蛇尾,就把蛇頭移動到下一個位置 { nextnode->x = x; nextnode->y = y; Sleep(sleeptime); snake_move(); } else//否則,從該點出發(fā)去找蛇尾 { mapnode[x][y].is_snakebody = 0; search_snaketail(snake); Sleep(sleeptime); snake_move(); } free(tsnake); } else//如果找不到食物 { search_snaketail(snake); Sleep(sleeptime); snake_move(); } } }
c. 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; }
以上是“C語言如何實現(xiàn)貪吃蛇AI”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學習更多知識,歡迎關注創(chuàng)新互聯(lián)行業(yè)資訊頻道!
當前文章:C語言如何實現(xiàn)貪吃蛇AI
文章位置:http://jinyejixie.com/article26/gdphjg.html
成都網(wǎng)站建設公司_創(chuàng)新互聯(lián),為您提供靜態(tài)網(wǎng)站、域名注冊、手機網(wǎng)站建設、品牌網(wǎng)站設計、App開發(fā)、外貿(mào)網(wǎng)站建設
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉載內(nèi)容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉載,或轉載時需注明來源: 創(chuàng)新互聯(lián)