一、源碼簡介
這是一個可以進行貪吃蛇游戲的小程序,采用C語言進行編寫。
上下左右控制運動方向,吃到食物得分,如果撞墻或者咬到自身,游戲結束。
編譯環(huán)境:VC6.0(采取純C語言寫法)
第三方庫:無
二、運行截圖
游戲結束界面
三、源碼解析
先看整個程序的邏輯:
開始界面
進行游戲
初始化
以下循環(huán):
{
根據(jù)輸入按鍵的不同,做出不同的反應。
每經(jīng)過一段時間,蛇進行移動。
}
結束游戲
主函數(shù)以及游戲運行的Gamerun函數(shù)如下。
int main() { Gamestart(); Gamerun(); Gameend(); return 0; } void Gamerun() { Initsnake();//初始化蛇 Createfood();//創(chuàng)建食物 while(1) { Pos(64,10); printf("得分:%d ",score); if(GetAsyncKeyState(VK_UP)&&status!=D)status=U;//根據(jù)之前是否有按下某種按鍵,改變前進方向或者暫停 else if(GetAsyncKeyState(VK_DOWN)&&status!=U)status=D; else if(GetAsyncKeyState(VK_LEFT)&&status!=R)status=L; else if(GetAsyncKeyState(VK_RIGHT)&&status!=L)status=R; else if(GetAsyncKeyState(VK_SPACE))Pause(); else if(GetAsyncKeyState(VK_ESCAPE)) { exit(0); break; } else; Sleep(sleeptime);//經(jīng)過一段時間繼續(xù)前進 if(Snakemove());//如果行動成功(沒有死) else break;//否則跳出循環(huán) } }
其中,接收輸入按鍵可以用<Windows.h>中的GetAsyncKeyState函數(shù),它可以判斷之前的一段時間內是否輸入了某按鍵。
我們用U,D,L,R來表示蛇頭朝向上下左右。經(jīng)過定義之后,可以用這四個字母為status賦值。
#define U 1 #define D 2 #define L 3 #define R 4 int status;
蛇并不能180?轉彎,所以要注意判定按鍵方向是否與目前前進方向相反。
另外我們在這里還用了一個Pos函數(shù)。這個函數(shù)的目的是將光標定位到(x,y)處,并且能在此進行寫入。
void Pos(int x,int y)//定義一個設置光標位置的函數(shù) { COORD pos; HANDLE hOutput; pos.X=x; pos.Y=y; hOutput=GetStdHandle(STD_OUTPUT_HANDLE); SetConsoleCursorPosition(hOutput,pos); }
用到的幾個函數(shù)都可以在<windows.h>當中找到。
COORD是windows API中定義的一種結構,表示一個字符在控制臺屏幕上的坐標;
GetStdHandle用于返回一個句柄,表明是輸入,輸出還是錯誤(參數(shù)分別是STD_INPUT_HANDLE,STD_OUTPUT_HANDLE,STD_ERROR_HANDLE),HANDLE是對應的句柄的結構體;
SetConsoleCursorPosition可在指定的控制臺屏幕緩沖區(qū)中設置光標位置。
更多內容可以在控制臺文檔 - Windows Console | Microsoft Docs中搜索找到。
Pause是另外一個我們定義的函數(shù)。如果不按下空格,程序就會循環(huán)Sleep指令。
void Pause()//定義暫停函數(shù) { while(1) { Sleep(100); if(GetAsyncKeyState(VK_ESCAPE))break; else; } }
在進行具體游戲函數(shù)的編寫之前,我們還需要確定游戲數(shù)據(jù)是如何存儲及調用的,也就是如何保存蛇身的坐標。像蛇身這樣的線性結構,非常適合用一個鏈表進行存儲。將蛇身的一個點的坐標放入一個結構體當中,結構體中還有指向下一個結構體的指針。
struct SNAKE//定義蛇身上的一個點 { int x,y; SNAKE *next;//定義一個指針,指向蛇下一個點的地址 };
準備工作做完之后,接下來的是游戲主體部分。
接下來我們要寫的函數(shù)包括:
void Creatmap()//定義創(chuàng)建地圖的函數(shù) void Initsnake()//初始化函數(shù) int Hitwall()//檢測是否撞墻 int Eatitself()//檢測是否碰到自身 void Createfood()//定義創(chuàng)建食物的函數(shù) int Snakemove()//定義蛇身行動的函數(shù) void Gamestart()//開始頁面以及初始化 void Gameend()//游戲結束
其中Snakemove會用到Hitwall,Eatitself,Createfood。
1. 創(chuàng)建地圖
void Creatmap()//定義創(chuàng)建地圖的函數(shù) { int i; for(i=0;i<58;i+=2)//打印上下邊框 { Pos(i,0); printf("■"); Pos(i,26); printf("■"); } for(i=1;i<26;i++)//打印左右邊框 { Pos(0,i); printf("■"); Pos(56,i); printf("■"); } }
利用好之前定義的Pos函數(shù)就可以了。
2. 初始化蛇身以及游戲參數(shù)
void Initsnake()//初始化函數(shù) { sleeptime=300; score=0; status=D; SNAKE *tail; int i; tail=(SNAKE*)malloc(sizeof(SNAKE));//從蛇尾開始,頭插法,以x,y設定開始的位置// tail->x=24; tail->y=5; tail->next=NULL; for(i=1;i<=4;i++)//設定整個蛇身體各點的位置 { head=(SNAKE*)malloc(sizeof(SNAKE)); head->next=tail; head->x=24+2*i; head->y=5; tail=head; } while(tail!=NULL)//從頭到尾,輸出蛇身 { Pos(tail->x,tail->y); printf("■"); tail=tail->next; } }
malloc函數(shù)用于動態(tài)為指針分配內存空間。其參數(shù)為分配空間的大小,返回值為該空間地址。注意這個地址對應void類型的指針,所以一定要用(SNAKE*)這樣方式進行強制類型轉化。
malloc函數(shù)的意義是初始化指針。如果指針不進行初始化,那么就會指向一個沒有意義的地址,成為一個野指針,在對指針指向的位置進行讀或寫操作時,程序就會報錯。類似的函數(shù)還有new,有興趣的同學可以查查它們的區(qū)別。
3. 檢測撞墻行為
int Hitwall()//檢測是否撞墻 { if(head->x==0||head->x==56||head->y==0||head->y==26)return 1; else return 0; }
4. 檢測是否碰到自身
int Eatitself()//檢測是否碰到自身 { SNAKE *s; s=head->next; while(s->next!=NULL)//利用循環(huán)對每個點進行判定 { if(s->x==head->x&&s->y==head->y)return 1; else s=s->next; } return 0; }
5. 創(chuàng)建食物
void Createfood()//定義創(chuàng)建食物的函數(shù) { SNAKE *food_1; SNAKE *q; srand((unsigned)time(NULL));//利用時間獲取隨機種子 food_1=(SNAKE*)malloc(sizeof(SNAKE)); food_1->x=2*(rand()%26)+2;//利用隨機種子獲取坐標 food_1->y=rand()%24+1; q=head; while(q->next!=NULL)//利用循環(huán)對每個點進行判定 { if(q->x==food_1->x&&q->y==food_1->y) //判斷蛇身是否與食物重合 { free(food_1); food_1=NULL;//不將地址置為NULL,也會讓指針變?yōu)橐爸羔? Createfood(); } q=q->next; } Pos(food_1->x,food_1->y); food=food_1; printf("■");//打印食物 }
對于食物位置的數(shù)據(jù),我們同樣用SNAKE結構體進行存儲,這樣可以便于與蛇身進行連接。
因為食物的位置要隨機生成,所以我們需要一個隨機種子來表征這種隨機性。
srand函數(shù)在<stdlib. h>當中,是隨機數(shù)發(fā)生器的初始化函數(shù)。其參數(shù)只有一個,為隨機數(shù)產(chǎn)生器的初始值(種子值)。為了防止隨機數(shù)出現(xiàn)重復,我們常用(unsigned)time(&t)來生成隨機種子,原理是使用 time函數(shù)來獲得系統(tǒng)時間,它的返回值為從 00:00:00 GMT, January 1, 1970 到現(xiàn)在所持續(xù)的秒數(shù),然后將time_t型數(shù)據(jù)轉化為(unsigned)型再傳給srand函數(shù)。當然這還有另外一個用法,不用定義time_t型t變量,即: srand((unsigned) time(NULL)),直接傳入一個空指針,因為程序中往往并不需要經(jīng)過參數(shù)獲得的數(shù)據(jù)。
6. 蛇身行動
int Snakemove()//定義蛇身行動的函數(shù) { SNAKE *nexthead;//蛇將要走到的位置 nexthead=(SNAKE*)malloc(sizeof(SNAKE)); SNAKE *n; if(status==U)//根據(jù)移動方向,計算下一個點的坐標 { nexthead->x=head->x; nexthead->y=head->y-1; nexthead->next=head; } else if(status==D) { nexthead->x=head->x; nexthead->y=head->y+1; nexthead->next=head; } else if(status==L) { nexthead->x=head->x-2; nexthead->y=head->y; nexthead->next=head; } else { nexthead->x=head->x+2; nexthead->y=head->y; nexthead->next=head; } head=nexthead; if(Hitwall())//是否撞墻 { typelose=1; return 0; } else; if(Eatitself())//是否咬到自身 { typelose=2; return 0; } else; if(head->x==food->x&&head->y==food->y)//如果前方是食物 { score++; Createfood(); } else { n=head; while(n->next->next!=NULL)n=n->next;//將移動到的下一個點打印,同時去掉尾部的點 Pos(n->next->x,n->next->y); printf(" "); Pos(head->x,head->y); printf("■"); free(n->next); n->next=NULL; } return 1; }
7. 游戲開始及終止
void Gamestart()//開始頁面以及初始化 { system("mode con cols=100 lines=30"); Startpage();//開始頁面 Creatmap();//繪制地圖 } void Gameend()//游戲結束 { system("cls");//清除屏幕 Pos(24,12); if(typelose==1) { printf("對不起,您撞到墻了。游戲結束."); } else if(typelose==2) { printf("對不起,您咬到自己了。游戲結束."); } Pos(24,13); printf("您的得分是%d\n",score); }
四、完整源碼
C語言網(wǎng)提供由在職研發(fā)工程師或ACM藍橋杯競賽優(yōu)秀選手錄制的視頻教程,并配有習題和答疑,點擊了解:
一點編程也不會寫的:零基礎C語言學練課程
解決困擾你多年的C語言疑難雜癥特性的C語言進階課程
從零到寫出一個爬蟲的Python編程課程
只會語法寫不出代碼?手把手帶你寫100個編程真題的編程百練課程
信息學奧賽或C++選手的 必學C++課程
藍橋杯ACM、信息學奧賽的必學課程:算法競賽課入門課程
手把手講解近五年真題的藍橋杯輔導課程