贪吃蛇的纯C语言实现过程
花了點時間,用C語言實現了一個貪吃蛇小游戲,開發工具是VS2010。
以下是代碼代碼鏈接,我放在了github上,需要的可自行下載:
https://github.com/zhengzebin525/basic_algorithm
本文會著重于思路講解,最后會放上完整代碼。
先來看看運行后的效果圖:
總體來說,貪吃蛇小游戲大體需要實現以下幾個最主要的函數功能:
1、游戲區域展示;
2、運用鏈表操作初始化小蛇
3、在游戲區域內隨機出現方塊食物
4、利用API函數獲取用戶的按鍵狀態
5、根據按鍵狀態控制小蛇的不同移動方向,小蛇移動中一旦吃到食物,分數增加;
6、游戲的錯誤函數,比如小蛇吃到自己,撞墻等;
那么接下來,就是對以上六大部分進行詳細說明。
第一部分:游戲區域展示
//游戲區域展示 void Area_Show() {int num;for(num=0;num<70;num+=2) //如果改成num++的話,上下 邊框會出現錯誤{Goto_Coord(num,0);printf("■");Goto_Coord(num,26);printf("■");}for(num=0;num<=26;num++){Goto_Coord(0,num);printf("■");Goto_Coord(70,num);printf("■");} }這是個比較簡單的功能,利用方塊“■”來劃定游戲的區域,而Goto_Coord(num,0)用于配置文本顯示的坐標,這是個自定義函數,用到了GetStdHandle()和SetConsoleCursorPosition()這兩個window自帶的API函數,前一個用來獲取標準輸入/標準輸出/標準錯誤中句柄,后一個用來定位要顯示的坐標。
句柄分別有:標準輸入:STD_INPUT_HANDLE、標準輸出:STD_OUTPUT_HANDLE、標準錯誤:STD_ERROR_HANDLE。
很明顯,因為是要在屏幕上顯示,所以是獲取標準輸出STD_OUTPUT_HANDLE的句柄。
| //定位輸出坐標 void Goto_Coord(int x,int y) ? ? ? ? { ?? ?COORD pos; ?? ?pos.X = x; ?? ?pos.Y = y; ?? ?HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); ?? ?SetConsoleCursorPosition(handle,pos); } |
第二部分:運用鏈表操作初始化小蛇
void Snake_Init() {snake *newnode = (snake *)malloc(sizeof(struct node));newnode->x = 10; //賦予初始坐標newnode->y = 10; //賦予初始坐標newnode->next = NULL;for(int i=1;i<snake_len_init;i++){head = (snake*)malloc(sizeof(struct node));head->x = 10 + 2*i;head->y = 10;head->next = newnode;newnode = head;}while(newnode!= NULL){Goto_Coord(newnode->x,newnode->y);printf("■");newnode = newnode->next;} }初始化小蛇是用若干個方塊組成的,所以需要存儲每個方塊的數據比如內存信息,顯示坐標,并用鏈表將方塊聯系起來,最后一并打印出來,從而呈現方塊小蛇的樣子。
為了跟游戲區域對應,因此橫坐標是偶數單位增加,縱坐標是奇數單位增加。
因此肯定會有一個結構體,來存儲小蛇每個方塊的數據信息:
| typedef struct node { ?? ?int x;? ? ? ? ? //橫坐標 ?? ?int y;? ? ? ? ? //縱坐標 ?? ?int color;? ? //方塊顏色 ?? ?struct node *next;? ?? }snake; ? |
?
第三部分:在游戲區域內隨機出現方塊食物
int Food_Show() {int color_num;snake *temp1 = NULL,*temp2 = NULL;food1 = (snake *)malloc(sizeof(struct node));food2 = (snake *)malloc(sizeof(struct node));if(food1 == NULL || food2 == NULL){return FALSE;}srand((unsigned)time(NULL)); //產生隨機數必須要的種子//產生第一個食物while((food1->x%2)!= 0 || food1->x == 70) //食物x坐標不是偶數,跟邊界沒有對齊{ food1->x = rand()%70 + 2;}do{food1->y = rand()%26 + 1;}while(food1->y == 26);temp1 = head;while(temp1 != NULL){if(food1->x == temp1->x && food1->y == temp1->y){free(food1);Food_Show();}temp1 = temp1->next;}Goto_Coord(food1->x,food1->y);color_num = rand()%6;food1->color = color_table[color_num];color(food1->color);printf("■");//產生第二個食物while((food2->x%2)!= 0 || food2->x == 70) //食物x坐標不是偶數,跟邊界沒有對齊{ food2->x = rand()%70 + 2;}do{food2->y = rand()%26 + 1;}while(food2->y == 26);temp2 = head;while(temp2 != NULL){if(food2->x == temp2->x && food2->y == temp2->y){free(food2);Food_Show();}temp2 = temp2->next;}Goto_Coord(food2->x,food2->y);color_num = rand()%6;food2->color = color_table[color_num];color(food2->color);printf("■");}每個食物的出現,都是一小塊內存,因此malloc()申請內存是必須的步驟,此處需要注意的是隨機的坐標必須位于游戲區域坐標內,并且不能跟區域邊界方塊重疊,也不能跟小蛇身上的每一塊“蛇身”方塊重疊,而且我給每個食物隨機分配了不同的顏色,使得小蛇吃到不同的顏色就會有不一樣的分數加成。
第四部分:利用API函數獲取用戶的按鍵狀態
//游戲控制 void Game_Control() {Goto_Coord(80,18);color(0x07);printf("當前獲得分數:%d\n",total_score);//判斷按下了哪一個按鍵,并且避免上一次的按鍵狀態與其方向相反if(GetAsyncKeyState(VK_UP) && Key_Status != Key_Down) Key_Status = Key_Up;else if(GetAsyncKeyState(VK_DOWN) && Key_Status != Key_Up)Key_Status = Key_Down;else if(GetAsyncKeyState(VK_LEFT) && Key_Status != Key_Right)Key_Status = Key_Left;else if(GetAsyncKeyState(VK_RIGHT) && Key_Status != Key_Left)Key_Status = Key_Right;else if(GetAsyncKeyState(VK_SPACE)) {Save_Status = Key_Status; //當按下了暫停健的時候,暫停前的運動方向狀態需要保存起來Key_Status = Key_Space;}else if(GetAsyncKeyState(VK_ESCAPE ))Key_Status = Key_Esc; }這一部分點主要在GetAsyncKeyState(int vkey)這個window的API函數,用于判斷某個物理按鍵是處于按下狀態(非0)還是沒按下狀態(0),而vkey 是256個虛擬按鍵中的一個,返回值是一個非0的數值。
常用的大概是以下幾個:
| VK_SHIFT Shift鍵 VK_LSHIFT 左Shift鍵 VK_RSHIFT 右Shift鍵 VK_CONTROL Ctrl鍵 VK_LCONTROL 左Ctrl鍵 VK_RCONTROL 右Ctril鍵 VK_MENU Alt鍵 VK_LMENU 左Alt鍵 VK_RMENU 右Alt鍵 VK_LBUTTON 鼠標左鍵 VK_RBUTTON 鼠標右鍵 VK_ESCAPE ESC鍵 VK_RETURN回車鍵 VK_TABTAB鍵 VK_SPACE空格鍵 VK_UP↑鍵 VK_DOWN↓鍵 VK_LEFT←鍵 VK_RIGHT→鍵 |
比如if(GetAsyncKeyState(VK_UP) && Key_Status != Key_Down) ,前一個條件判斷用戶是否按下了↑鍵,而后一個條件則判斷上一次的按鍵狀態是不是↓鍵,這么做是為了避免小蛇由向上移動突變為向下移動的情況出現。
第五部分:根據按鍵狀態控制小蛇的不同移動方向,小蛇移動中一旦吃到食物,分數增加
//蛇的移動 void Snake_Move() {snake *nextnode = (snake *)malloc(sizeof(struct node));snake *temp = NULL;if(Key_Status == Key_Up){nextnode->x = head->x;nextnode->y = (head->y) - 1;//Goto_Coord(80,28);//printf("蛇頭head的坐標 %d,%d",head->x,head->y);if((nextnode->x == food1->x && nextnode->y == food1->y) ||(nextnode->x == food2->x && nextnode->y == food2->y)) //移動過程中吃到了食物{nextnode->next = head;head = nextnode;temp = head;while(temp != NULL){Goto_Coord(temp->x,temp->y);printf("■");temp = temp->next;}Food_Score(food1->color);free(food1);Food_Show();}else //移動過程沒有遇到食物{nextnode->next = head;head = nextnode;temp = head;while(temp->next->next != NULL){Goto_Coord(temp->x,temp->y);printf("■");temp = temp->next;}Goto_Coord(temp->next->x,temp->next->y);printf(" ");free(temp->next);temp->next = NULL;}}此處只截取了小蛇向上移動的狀態代碼,小蛇一旦運動起來,第一步就需要配置下一步要顯示的方塊的坐標,然后判斷此方塊有沒有與食物的坐標重疊,重疊了就說明吃到了食物,獲得分數加成;沒重疊的話,就把新顯示的方塊作為新的蛇頭添加進小蛇鏈表,蛇尾最后的方塊從鏈表上釋放消除掉,然后重新打印小蛇鏈表,這樣就出現了小蛇向上移動一步的現象。
利用Food_Score(food1->color)函數進行分數加成,這是個自定義函數,根據吃到的食物方塊顏色進行判斷,不同的顏色獲取不同的分數加成。
| void Food_Score(int food_color) { ?? ?switch (food_color) ?? ?{ ?? ??? ?case red: ?? ??? ??? ?total_score += 50;break; ?? ??? ?case orange: ?? ??? ??? ?total_score += 40;break; ?? ??? ?case green: ?? ??? ??? ?total_score += 30;break; ?? ??? ?case blue: ?? ??? ??? ?total_score += 20;break; ?? ??? ?case purple: ?? ??? ??? ?total_score += 10;break; ?? ?} } |
第六部分:游戲的錯誤函數,比如小蛇吃到自己,撞墻等
//穿墻錯誤函數 void Through_Walls() {if(head->x == 0 || head->x == 70 ||head->y == 0 || head->y == 26){system("cls");Goto_Coord(30,10);printf("抱歉,事故判斷,小蛇死亡撞墻\n");Goto_Coord(30,12);color(0x07);printf("當前獲得分數:%d\n",total_score);exit(0);} }只要判斷蛇頭方塊的坐標與游戲區域邊界方塊坐標重疊,那就是撞墻的,撞得死死的。
//咬到自己錯誤函數 void Bit_Oneself() {snake *temp;temp = head->next;while(temp!=NULL){if(head->x == temp->x && head->y == temp->y){system("cls");Goto_Coord(30,10);printf("抱歉,事故判斷,小蛇咬死了自己\n");Goto_Coord(30,12);color(0x07);printf("當前獲得分數:%d\n",total_score);exit(0);}temp = temp->next;} }只要判斷蛇頭方塊的坐標與蛇身任何一個方塊坐標重疊,那小蛇就是咬到自己了,咬得死死的。
以下是完整代碼,直接copy能用的。
Gluttonous_Snake.h
#ifndef __GLUTTONOUS_SNAKE_H #define __GLUTTONOUS_SNAKE_H#include <stdio.h> #include <stdlib.h> #include <string.h> #include <windows.h> #include <time.h>#define FALSE 0 #define TRUE 1#define snake_len_init 3 //蛇的初始化長度為3#define Key_Up 1 //↑按鍵狀態 #define Key_Down 2 //↓按鍵狀態 #define Key_Left 3 //←按鍵狀態 #define Key_Right 4 //→按鍵狀態 #define Key_Space 5 #define Key_Esc 6#define red FOREGROUND_RED #define orange FOREGROUND_RED|FOREGROUND_RED|FOREGROUND_GREEN #define green FOREGROUND_GREEN #define blue FOREGROUND_BLUE #define purple FOREGROUND_RED|FOREGROUND_BLUEtypedef struct node {int x;int y;int color;struct node *next; }snake;void Goto_Coord(int x,int y); void color(int color); void Area_Show(); void Side_Show(); void Snake_Init(); int Food_Show(); void Game_Init(); void Food_Score(int food_color); void Game_Control(); void Snake_Move(); void Pause(); void Through_Walls(); void Bit_Oneself();#endifGluttonous_Snake.cpp
#include "Gluttonous_Snake.h"int snake_x,snake_y; int food_score = 10; //每個食物的分數 int total_score = 0; //獲得的總分數 int highest_score = 0; //最高分 int Key_Status = Key_Right; //蛇一開始是向右行進 int Save_Status; snake *head; snake *newnode; //中間變量,放在while(1)中會內存溢出 snake *food1,*food2; //顏色表 int color_table[6] = {red, //紅 orange, //橙green, //綠blue, //藍purple //紫};//定位輸出坐標 void Goto_Coord(int x,int y) {COORD pos;pos.X = x;pos.Y = y;HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);SetConsoleCursorPosition(handle,pos); }//設置文本顏色 void color(int color) {HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);SetConsoleTextAttribute(handle,color); }//游戲區域展示 void Area_Show() {int num;for(num=0;num<70;num+=2) //如果改成num++的話,上下 邊框會出現錯誤{Goto_Coord(num,0);printf("■");Goto_Coord(num,26);printf("■");}for(num=0;num<=26;num++){Goto_Coord(0,num);printf("■");Goto_Coord(70,num);printf("■");} }//側面說明展示 void Side_Show() {Goto_Coord(80,5); printf("歡迎來到貪吃蛇游戲\n");printf("\n");Goto_Coord(80,7);printf("↑ ↓ ← → 是上下左右移動\n");printf("\n");Goto_Coord(80,9); printf("規則1:不能穿墻,不能吃自己\n");printf("\n");Goto_Coord(80,11); printf("規則2:ESC退出,SPACE是暫停\n");printf("\n");Goto_Coord(80,13); printf("規則2:吃的食物越多,分數越高\n");printf("\n");Goto_Coord(80,15); printf("規則3:其中會出現吃到了也沒分數的假食物\n");printf("\n"); }void Snake_Init() {snake *newnode = (snake *)malloc(sizeof(struct node));newnode->x = 10; //賦予初始坐標newnode->y = 10; //賦予初始坐標newnode->next = NULL;for(int i=1;i<snake_len_init;i++){head = (snake*)malloc(sizeof(struct node));head->x = 10 + 2*i;head->y = 10;head->next = newnode;newnode = head;}while(newnode!= NULL){Goto_Coord(newnode->x,newnode->y);printf("■");newnode = newnode->next;} }int Food_Show() {int color_num;snake *temp1 = NULL,*temp2 = NULL;food1 = (snake *)malloc(sizeof(struct node));food2 = (snake *)malloc(sizeof(struct node));if(food1 == NULL || food2 == NULL){return FALSE;}srand((unsigned)time(NULL)); //產生隨機數必須要的種子//產生第一個食物while((food1->x%2)!= 0 || food1->x == 70) //食物x坐標不是偶數,跟邊界沒有對齊{ food1->x = rand()%70 + 2;}do{food1->y = rand()%26 + 1;}while(food1->y == 26);temp1 = head;while(temp1 != NULL){if(food1->x == temp1->x && food1->y == temp1->y){free(food1);Food_Show();}temp1 = temp1->next;}Goto_Coord(food1->x,food1->y);color_num = rand()%6;food1->color = color_table[color_num];color(food1->color);printf("■");//Goto_Coord(80,21);//printf("食物1坐標 %d,%d",food1->x,food1->y);//產生第二個食物while((food2->x%2)!= 0 || food2->x == 70) //食物x坐標不是偶數,跟邊界沒有對齊{ food2->x = rand()%70 + 2;}do{food2->y = rand()%26 + 1;}while(food2->y == 26);temp2 = head;while(temp2 != NULL){if(food2->x == temp2->x && food2->y == temp2->y){free(food2);Food_Show();}temp2 = temp2->next;}Goto_Coord(food2->x,food2->y);color_num = rand()%6;food2->color = color_table[color_num];color(food2->color);printf("■");//Goto_Coord(80,23);//printf("食物2坐標 %d,%d",food2->x,food2->y);}//游戲界面初始化 void Game_Init() {system("title 貪吃蛇");Goto_Coord(40,10);printf("歡迎來到貪吃蛇小游戲\n");system("pause");system("cls");Goto_Coord(40,10);printf("希望能帶給你良好的游戲體驗\n");system("pause");system("cls");Area_Show(); //游戲區域初始化Side_Show(); //側面說明初始化Snake_Init(); //蛇初始化Food_Show(); //產生方塊食物 }void Food_Score(int food_color) {switch (food_color){case red:total_score += 50;break;case orange:total_score += 40;break;case green:total_score += 30;break;case blue:total_score += 20;break;case purple:total_score += 10;break;} }//游戲控制 void Game_Control() {Goto_Coord(80,18);color(0x07);printf("當前獲得分數:%d\n",total_score);//判斷按下了哪一個按鍵,并且避免上一次的按鍵狀態與其方向相反if(GetAsyncKeyState(VK_UP) && Key_Status != Key_Down) Key_Status = Key_Up;else if(GetAsyncKeyState(VK_DOWN) && Key_Status != Key_Up)Key_Status = Key_Down;else if(GetAsyncKeyState(VK_LEFT) && Key_Status != Key_Right)Key_Status = Key_Left;else if(GetAsyncKeyState(VK_RIGHT) && Key_Status != Key_Left)Key_Status = Key_Right;else if(GetAsyncKeyState(VK_SPACE)) {Save_Status = Key_Status; //當按下了暫停健的時候,暫停前的運動方向狀態需要保存起來Key_Status = Key_Space;}else if(GetAsyncKeyState(VK_ESCAPE ))Key_Status = Key_Esc; }//蛇的移動 void Snake_Move() {snake *nextnode = (snake *)malloc(sizeof(struct node));snake *temp = NULL;if(Key_Status == Key_Up){nextnode->x = head->x;nextnode->y = (head->y) - 1;//Goto_Coord(80,28);//printf("蛇頭head的坐標 %d,%d",head->x,head->y);if((nextnode->x == food1->x && nextnode->y == food1->y) ||(nextnode->x == food2->x && nextnode->y == food2->y)) //移動過程中吃到了食物{nextnode->next = head;head = nextnode;temp = head;while(temp != NULL){Goto_Coord(temp->x,temp->y);printf("■");temp = temp->next;}Food_Score(food1->color);free(food1);Food_Show();}else //移動過程沒有遇到食物{nextnode->next = head;head = nextnode;temp = head;while(temp->next->next != NULL){Goto_Coord(temp->x,temp->y);printf("■");temp = temp->next;}Goto_Coord(temp->next->x,temp->next->y);printf(" ");free(temp->next);temp->next = NULL;}}if(Key_Status == Key_Down){nextnode->x = head->x;nextnode->y = (head->y) + 1;//Goto_Coord(80,28);//printf("蛇頭head的坐標 %d,%d",head->x,head->y);if((nextnode->x == food1->x && nextnode->y == food1->y) ||(nextnode->x == food2->x && nextnode->y == food2->y)) //移動過程中吃到了食物{nextnode->next = head;head = nextnode;temp = head;while(temp != NULL){Goto_Coord(temp->x,temp->y);printf("■");temp = temp->next;}Food_Score(food1->color);free(food1);Food_Show();}else //移動過程沒有遇到食物{nextnode->next = head;head = nextnode;temp = head;while(temp->next->next != NULL){Goto_Coord(temp->x,temp->y);printf("■");temp = temp->next;}Goto_Coord(temp->next->x,temp->next->y);printf(" ");free(temp->next);temp->next = NULL;}}if(Key_Status == Key_Left){nextnode->x = (head->x) - 2;nextnode->y = head->y;//Goto_Coord(80,28);//printf("蛇頭head的坐標 %d,%d",head->x,head->y);if((nextnode->x == food1->x && nextnode->y == food1->y) ||(nextnode->x == food2->x && nextnode->y == food2->y)) //移動過程中吃到了食物{nextnode->next = head;head = nextnode;temp = head;while(temp != NULL){Goto_Coord(temp->x,temp->y);printf("■");temp = temp->next;}Food_Score(food1->color);free(food1);Food_Show();}else //移動過程沒有遇到食物{nextnode->next = head;head = nextnode;temp = head;while(temp->next->next != NULL){Goto_Coord(temp->x,temp->y);printf("■");temp = temp->next;}Goto_Coord(temp->next->x,temp->next->y);printf(" ");free(temp->next);temp->next = NULL;}}if(Key_Status == Key_Right){nextnode->x = (head->x) + 2;nextnode->y = head->y;//Goto_Coord(80,28);//printf("蛇頭head的坐標 %d,%d",head->x,head->y);if((nextnode->x == food1->x && nextnode->y == food1->y) ||(nextnode->x == food2->x && nextnode->y == food2->y)) //移動過程中吃到了食物{nextnode->next = head;head = nextnode;temp = head;while(temp != NULL){Goto_Coord(temp->x,temp->y);printf("■");temp = temp->next;}Food_Score(food1->color);free(food1);Food_Show();}else //移動過程沒有遇到食物{nextnode->next = head;head = nextnode;temp = head;while(temp->next->next != NULL){Goto_Coord(temp->x,temp->y);printf("■");temp = temp->next;}Goto_Coord(temp->next->x,temp->next->y);printf(" ");free(temp->next);temp->next = NULL;}}while(Key_Status == Key_Space) //按空格健暫停{Sleep(300);if(!(GetAsyncKeyState(VK_SPACE))){Pause(); //暫停函數break;} }if(Key_Status == Key_Esc){system("cls");Goto_Coord(40,10);printf("您已結束游戲!\n");}Through_Walls(); //判斷是否撞墻Bit_Oneself(); //判斷是否咬到了自己 }void Pause() {while(1){ system("title 暫停中····");Sleep(300);if(GetAsyncKeyState(VK_SPACE)) //第二次按空格的時候,解除暫停狀態{Sleep(200);Key_Status = Save_Status;break;}} }//穿墻錯誤函數 void Through_Walls() {if(head->x == 0 || head->x == 70 ||head->y == 0 || head->y == 26){system("cls");Goto_Coord(30,10);printf("抱歉,事故判斷,小蛇死亡撞墻\n");Goto_Coord(30,12);color(0x07);printf("當前獲得分數:%d\n",total_score);exit(0);} }//咬到自己錯誤函數 void Bit_Oneself() {snake *temp;temp = head->next;while(temp!=NULL){if(head->x == temp->x && head->y == temp->y){system("cls");Goto_Coord(30,10);printf("抱歉,事故判斷,小蛇咬死了自己\n");Goto_Coord(30,12);color(0x07);printf("當前獲得分數:%d\n",total_score);exit(0);}temp = temp->next;} }main.cpp
#include "Gluttonous_Snake.h" int sleep_time = 200;int main() {Game_Init(); //游戲界面初始化while(1){system("title 游戲中····");Game_Control(); //游戲控制,判斷按下的是哪一個按鍵Sleep(sleep_time); //延時200msSnake_Move();} }當然,這個小游戲個人覺得還有很多自己的設想沒有實現,以后會抽出時間來實現它。
總結:1、在初始化過程中,如果有某一參數只初始化依次,該參數直接使用常量,如果使用變量,還需要占用一定的存儲空間,浪費內存;
? ? ? ? ? ? 2、在鏈表操作中,用最少的步驟完成操作,不要花里胡哨,鏈表邏輯要清晰,多看幾遍,確保無誤;
? ? ? ? ? ? ? ? ? ? ? ? ?3、遞歸函數越少用越好,在一個函數中最好不要超過一個遞歸函數;
? ? ? ? ? ? ? ? ? ? ? ? ?4、如果需要在界面上顯示肉眼可見的運動,延時操作必不可少,畢竟系統的運行速度太快了;
總結
以上是生活随笔為你收集整理的贪吃蛇的纯C语言实现过程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 时空恋旅人 豆瓣影评
- 下一篇: 微信官宣:一大波 2022 新年红包封面