找往期文章包括但不限于本期文章中不懂的知識點:
個人主頁:我要學(xué)編程(?_?)-CSDN博客
所屬專欄:C語言
目錄
游戲前期準(zhǔn)備:
設(shè)置控制臺相關(guān)的信息?
GetStdHandle
GetConsoleCursorInfo?
SetConsoleCursorInfo
SetConsoleCursorPosition
GetAsyncKeyState
貪吃蛇游戲設(shè)計與分析?
本地化
地圖,食物和蛇身的設(shè)計?
GameStart()—— 游戲的初始化
打印歡迎界面?
繪制貪吃蛇地圖?
初始化貪吃蛇
初始化食物
GameRun()——游戲的運(yùn)行
打印右側(cè)的幫助信息
貪吃蛇的相關(guān)運(yùn)行信息?
GameOver()——游戲的結(jié)束(善后工作)?
貪吃蛇源碼?
使用C語言在Windows環(huán)境的控制臺中模擬實現(xiàn)經(jīng)典小游戲貪吃蛇。
游戲前期準(zhǔn)備:
?本次實現(xiàn)貪吃蛇會使用到的一些Win32 API知識,接下來我們就學(xué)習(xí)一下。
背景介紹:
Windows 這個多作業(yè)系統(tǒng)除了協(xié)調(diào)應(yīng)用程序的執(zhí)行、分配內(nèi)存、管理資源之外, 它同時也是一個很大的服務(wù)中心,調(diào)用這個服務(wù)中心的各種服務(wù)(每一種服務(wù)就是一個函數(shù)),可以幫應(yīng)用程序達(dá)到開啟視窗、描繪圖形、使用周邊設(shè)備等目的,由于這些函數(shù)服務(wù)的對象是應(yīng)用程序(Application), 所以便稱之為 Application Programming Interface,簡稱 API 函數(shù)。WIN32 API也就是Microsoft Windows 32位平臺的應(yīng)用程序編程接口。
設(shè)置控制臺相關(guān)的信息?
平常我們運(yùn)行起來的黑框程序其實就是控制臺程序(如下圖所示)。
我們可以使用cmd命令來設(shè)置控制臺窗口的長寬。例如:設(shè)置控制臺窗口的大小,30行,100列。?
//格式: 列 行
mode con cols=100 lines=30
注意:
1. 列和行在賦值時,不能帶有空格。例如:cols = 100,這就是不行的,沒有影響到控制臺的大小。
2. 使用這個命令之前,需要把這個控制臺改為讓W(xué)indows決定或者Windows 控制臺主機(jī)
演示:
改變VS編譯器的控制臺
3. 使用system函數(shù)所需要包含的頭文件既可以是stdlib.h,也可以是Windows.h(不分大小寫的,因此可以使用"windows.h"、"WINDOWS.H"或者"Windows.h"等形式來引用該頭文件。不過,一般約定使用"Windows.h"的形式來引用該頭文件,以保持代碼的一致性和可讀性。)?
下面就來使用這個來改變控制臺的大小。
從上面的結(jié)果來看:行列對應(yīng)不一致。沒錯,一行的寬度是一列的寬度的二倍。
也可以通過命令設(shè)置控制臺窗口的名字:?
//格式:
title 要修改的名字
注意:在更改之后要觀察到的話,就不能讓程序運(yùn)行結(jié)束,也就是說只能在程序運(yùn)行期間才能夠觀察的到。
?控制臺屏幕上的坐標(biāo)COORD
COORD 是Windows API中定義的一個結(jié)構(gòu)體,表示一個字符在控制臺屏幕緩沖區(qū)上的坐標(biāo),坐標(biāo)系 (0,0) 的原點位于緩沖區(qū)的頂部左側(cè)單元格。?
COORD類型的聲明:
typedef struct _COORD {
SHORT X;//短整型
SHORT Y;
} COORD, *PCOORD;
給坐標(biāo)賦值:
COORD pos = { 10, 15 };
GetStdHandle
GetStdHandle是一個Windows API函數(shù)。它用于從一個特定的標(biāo)準(zhǔn)設(shè)備(標(biāo)準(zhǔn)輸入、標(biāo)準(zhǔn)輸出或標(biāo)準(zhǔn)錯誤)中取得一個句柄(用來標(biāo)識不同設(shè)備的數(shù)值),使用這個句柄可以操作設(shè)備。
標(biāo)準(zhǔn)輸入是指鍵盤,標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯誤是指屏幕。
這個句柄就類似一個遙控器,可以通過句柄來操作標(biāo)準(zhǔn)設(shè)備。而我們想要操作標(biāo)準(zhǔn)設(shè)備也得通過GetStdHandle這個函數(shù)來獲得句柄。再通過句柄來操作。
HANDLE GetStdHandle(DWORD nStdHandle);
例如:
HANDLE hOutput = NULL;
//獲取標(biāo)準(zhǔn)輸出的句柄(?來標(biāo)識不同設(shè)備的數(shù)值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
GetConsoleCursorInfo?
檢索有關(guān)指定控制臺屏幕緩沖區(qū)的光標(biāo)大小和可見性的信息。?
BOOL WINAPI GetConsoleCursorInfo(
HANDLE hConsoleOutput,
PCONSOLE_CURSOR_INFO lpConsoleCursorInfo
);
PCONSOLE_CURSOR_INFO 是指向 CONSOLE_CURSOR_INFO 結(jié)構(gòu)的指針,該結(jié)構(gòu)接收有關(guān)主機(jī)游標(biāo)
CONSOLE_CURSOR_INFO 這個結(jié)構(gòu)體,包含有關(guān)控制臺光標(biāo)的信息。
typedef struct _CONSOLE_CURSOR_INFO {
DWORD dwSize;//光標(biāo)的寬度占比
BOOL bVisible;//光標(biāo)的可見性
} CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO;
舉例:
HANDLE hOutput = NULL;
//獲取標(biāo)準(zhǔn)輸出的句柄(用來標(biāo)識不同設(shè)備的數(shù)值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO CursorInfo = {0};
//獲取控制臺光標(biāo)信息
GetConsoleCursorInfo(hOutput, &CursorInfo);
SetConsoleCursorInfo
設(shè)置指定控制臺屏幕緩沖區(qū)的光標(biāo)的大小和可見性。
BOOL WINAPI SetConsoleCursorInfo(
HANDLE hConsoleOutput,
const CONSOLE_CURSOR_INFO *lpConsoleCursorInfo
);
舉例:
//獲取標(biāo)準(zhǔn)輸出的句柄
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
//定義一個存放光標(biāo)信息的結(jié)構(gòu)體
CONSOLE_CURSOR_INFO CursorInfo = {0};
//獲取控制臺光標(biāo)信息存放到這個結(jié)構(gòu)體中
GetConsoleCursorInfo(hOutput, &CursorInfo);
//隱藏控制臺光標(biāo)
CursorInfo.bVisible = false;
//把控制臺光標(biāo)大小調(diào)到最大
CursorInfo.dwSize = 100;
//設(shè)置控制臺光標(biāo)狀態(tài)(按照上面的設(shè)置調(diào))
SetConsoleCursorInfo(hOutput, &CursorInfo);
SetConsoleCursorPosition
設(shè)置控制臺屏幕緩沖區(qū)中的光標(biāo)位置,我們將想要設(shè)置的坐標(biāo)信息放在COORD類型的變量中,調(diào) 用SetConsoleCursorPosition函數(shù)將光標(biāo)位置設(shè)置到指定的位置。?
BOOL WINAPI SetConsoleCursorPosition(
HANDLE hConsoleOutput,
COORD pos
);
舉例:
//改變光標(biāo)的位置
COORD pos = { 10, 5};
HANDLE hOutput = NULL;
//獲取標(biāo)準(zhǔn)輸出的句柄
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
//設(shè)置標(biāo)準(zhǔn)輸出上光標(biāo)的位置為pos
SetConsoleCursorPosition(hOutput, pos);
注意:這個pos的位置設(shè)置有可能不一定會成功。?
GetAsyncKeyState
獲取按鍵情況,GetAsyncKeyState的函數(shù)原型如下:
SHORT GetAsyncKeyState(int vKey);
將鍵盤上每個鍵的虛擬鍵值傳遞給函數(shù),函數(shù)通過返回值來分辨按鍵的狀態(tài)。GetAsyncKeyState 的返回值是short類型,在上一次調(diào)用?GetAsyncKeyState 函數(shù)后,如果返回的16位的short數(shù)據(jù)中,最高位是1,說明按鍵的狀態(tài)是按下,如果最高是0,說明按鍵的狀態(tài)是抬起;如果最低位被置為1則說明,該按鍵被按過,否則為0。 如果我們要判斷一個鍵是否被按過,可以檢測GetAsyncKeyState返回值的最低位是否為1?。下面是虛擬鍵代碼虛擬鍵碼 (Winuser.h) - Win32 apps | Microsoft Learn虛擬鍵碼
舉例:檢測數(shù)字鍵是否被摁過。(字母鍵上面的數(shù)字鍵)
//判斷一個鍵是否被摁過
//如果被摁過結(jié)果就是1,否則就是0
#define KEY_PRESS(vk) ((GetAsyncKeyState(vk)&0x1)?1:0)
int main()
{
while (1)
{
if (KEY_PRESS(0x30))
{
printf("%d\n", 0);
}
else if (KEY_PRESS(0x31))
{
printf("%d\n", 1);
}
else if (KEY_PRESS(0x32))
{
printf("%d\n", 2);
}
else if (KEY_PRESS(0x33))
{
printf("%d\n", 3);
}
else if (KEY_PRESS(0x34))
{
printf("%d\n", 4);
}
else if (KEY_PRESS(0x35))
{
printf("%d\n", 5);
}
else if (KEY_PRESS(0x36))
{
printf("%d\n", 6);
}
else if (KEY_PRESS(0x37))
{
printf("%d\n", 7);
}
else if (KEY_PRESS(0x38))
{
printf("%d\n", 8);
}
else if (KEY_PRESS(0x39))
{
printf("%d\n", 9);
}
}
}
貪吃蛇游戲設(shè)計與分析?
實現(xiàn)基本的功能:
? 貪吃蛇地圖繪制
? 蛇吃食物的功能 (上、下、左、右方向鍵控制蛇的動作)
? 蛇撞墻死亡
? 蛇撞自身死亡
? 計算得分
? 蛇身加速、減速
? 暫停游戲
在游戲地圖上,我們打印墻體使用寬字符:□,打印蛇使用寬字符●,打印食物使用寬字符★
普通的字符是占一個字節(jié)的,這類寬字符是占用2個字節(jié)。?
加入了寬字符的類型 wchar_t 和寬字符的輸入和輸出函數(shù),加入了<locale.h>頭文件,其中提供了允許程序員針對特定地區(qū)(通常是國家或者說某種特定語言的地理區(qū)域)調(diào)整程序行為的函數(shù)。
<locale.h>本地化
setlocale函數(shù)原型:
char* setlocale (int category, const char* locale);
類項
通過修改地區(qū),程序可以改變它的行為來適應(yīng)世界的不同區(qū)域。但地區(qū)的改變可能會影響庫的許多部分,其中一部分可能是我們不希望修改的。所以C語言支持針對不同的類項進(jìn)行修改,下面的一個宏, 指定一個類項:
? LC_COLLATE:影響字符串比較函數(shù) strcoll() 和 strxfrm() 。
? LC_CTYPE:影響字符處理函數(shù)的行為。
? LC_MONETARY:影響貨幣格式。
? LC_NUMERIC:影響 printf() 的數(shù)字格式。
? LC_TIME:影響時間格式 strftime() 和 wcsftime() 。
? LC_ALL - 針對所有類項修改,將以上所有類別設(shè)置為給定的語言環(huán)境。?
setlocale 函數(shù)用于修改當(dāng)前地區(qū),可以針對一個類項修改,也可以針對所有類項。?setlocale 的第一個參數(shù)可以是前面說明的類項中的一個,那么每次只會影響一個類項,如果第一個參數(shù)是LC_ALL,就會影響所有的類項。 C標(biāo)準(zhǔn)給第二個參數(shù)僅定義了2種可能取值:"C"(正常模式)和" "(本地模式)。 在任意程序執(zhí)行開始,都會隱藏式執(zhí)行調(diào)用:
setlocale(LC_ALL, "C");
當(dāng)?shù)貐^(qū)設(shè)置為"C"時,庫函數(shù)按正常方式執(zhí)行,小數(shù)點是一個點。 當(dāng)程序運(yùn)行起來后想改變地區(qū),就只能顯示調(diào)用setlocale函數(shù)。用" "作為第2個參數(shù),調(diào)用setlocale 函數(shù)就可以切換到本地模式,這種模式下程序會適應(yīng)本地環(huán)境。比如:切換到我們的本地模式后就支持寬字符(漢字)的輸出等。?
setlocale(LC_ALL, "");//切換到本地環(huán)境(注意這里雙引號里不能有空格)
那如果想在屏幕上打印寬字符,怎么打印呢? 寬字符的字面量必須加上前綴“L”,否則 C 語言會把字面量當(dāng)作窄字符類型處理。前綴“L”在單引號前面,表示寬字符,對應(yīng) wprintf() 的占位符為 %lc ;在雙引號前面,表示寬字符串,對應(yīng) wprintf() 的占位符為 %ls 。
舉例:
#include <stdio.h>
#include <locale.h>
int main()
{
//切換到本地環(huán)境
setlocale(LC_ALL, "");
wprintf(L"%s\n", L"我要學(xué)編程");
wchar_t wc1 = L'我';
wchar_t wc2 = L'要';
wchar_t wc3 = L'學(xué)';
wchar_t wc4 = L'編';
wchar_t wc5 = L'程';
wprintf(L"%lc", wc1);
wprintf(L"%lc", wc2);
wprintf(L"%lc", wc3);
wprintf(L"%lc", wc4);
wprintf(L"%lc", wc5);
return 0;
}
地圖,食物和蛇身的設(shè)計?
我們假設(shè)實現(xiàn)一個棋盤27行,58列的棋盤,再圍繞地圖畫出墻。
由于1行的寬度是一列寬度的二倍,就可以按照上面的樣式繪制地圖。這些都用寬字符來打印。
初始化狀態(tài):假設(shè)蛇的長度是5,蛇身的每個節(jié)點是●,在固定的一個坐標(biāo)處,比如(24, 5)處開始出現(xiàn)蛇,連續(xù)5個節(jié)點。 注意:蛇的每個節(jié)點的x坐標(biāo)必須是2個倍數(shù),否則可能會出現(xiàn)蛇的一個節(jié)點有一半出現(xiàn)在墻體中, 另外一半在墻外的現(xiàn)象。 關(guān)于食物,就是在墻體內(nèi)隨機(jī)生成一個坐標(biāo)(x坐標(biāo)必須是2的倍數(shù)),坐標(biāo)不能和蛇的身體重合,然后打印★。食物的x坐標(biāo)也要是2的倍數(shù)。
在游戲運(yùn)行的過程中,蛇每次吃一個食物,蛇的身體就會變長一節(jié),如果我們使用鏈表存儲蛇的信 息,那么蛇的每一節(jié)其實就是鏈表的一個節(jié)點。每個節(jié)點只要記錄好蛇身節(jié)點在地圖上的坐標(biāo)就行, 所以蛇節(jié)點結(jié)構(gòu)如下:?
//蛇身的節(jié)點類型
typedef struct SnakeNode
{
//坐標(biāo)
int x;
int y;
//指向下一個節(jié)點的指針
struct SnakeNode* next;
}SnakeNode, * pSnakeNode;
還得創(chuàng)建一些變量:指向蛇頭的指針,初始時蛇的速度,蛇的方向,食物,食物分?jǐn)?shù),總分,貪吃蛇的狀態(tài)。但是這些都比較麻煩,我們就創(chuàng)建一個貪吃蛇的結(jié)構(gòu)體來管理這些變量。
//貪吃蛇
typedef struct Snake
{
pSnakeNode _pSnake;//指向蛇頭的指針
pSnakeNode _pFood;//指向食物的指針
DIRECTION _dir;//蛇的方向
GAME_STATE _state;//游戲的運(yùn)行狀態(tài)
int _FoodWeight;//一個食物的分?jǐn)?shù)
int _score;//總分?jǐn)?shù)
int _SleepTime;//休眠時間,時間越短,速度越快,時間越長,速度越慢
}Snake, * pSnake;
蛇的方向有四種:上,下,左,右。我們就可以枚舉出來。
//蛇的方向
typedef enum DIRECTION
{
UP = 1,//上
DOWN,//下
LEFT,//左
RIGHT//右
}DIRECTION;
游戲的運(yùn)行狀態(tài):正常運(yùn)行,正常退出,撞墻死亡,撞到自己死亡。
//游戲的狀態(tài)
typedef enum GAME_STATE
{
OK,//正常運(yùn)行
KILL_BY_WALL,//撞墻
KILL_BY_SELF,//撞到自己
END_NORMAL//正常退出
}GAME_STATE;
接下來就是正式的游戲設(shè)計。
首先分裝三個大的函數(shù)。
GameStart()—— 游戲的初始化
初始化的內(nèi)容:1,打印歡迎界面? 2,繪制貪吃蛇地圖? 3,初始化貪吃蛇和食物 。
打印歡迎界面?
首先得分裝一個函數(shù)用來定位坐標(biāo)。
//定位光標(biāo)
void SetPos(short x, short y)
{
HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);//獲取標(biāo)準(zhǔn)輸出的句柄
COORD pos = { x, y };
SetConsoleCursorPosition(houtput, pos);//設(shè)置光標(biāo)的位置
}
system("pause")是一個用于暫??刂婆_的函數(shù)。它會在控制臺輸出一個提示信息,等待用戶按下任意鍵后才會繼續(xù)執(zhí)行程序。
system("pause");
由上面的界面切換到下面這個界面,就需要用到一個清理控制臺界面的函數(shù)。?
system("cls");//用于清理當(dāng)前控制臺的界面所有信息
打印歡迎界面:
//打印歡迎界面
void WelcomToGame()
{
//首先得定位光標(biāo)
SetPos(39, 10);
printf("歡迎來到貪吃蛇小游戲\n");
SetPos(40, 15);
system("pause");//暫停
system("cls");//清理屏幕
SetPos(28, 10);
printf("用↑.↓.←.→ 來控制蛇的移動!摁F3加速!摁F4減速!\n");
SetPos(38, 11);
printf("游戲即將開始,請做好準(zhǔn)備!\n");
SetPos(40, 15);
system("pause");
system("cls");
}
繪制貪吃蛇地圖?
上面這個就是我們要繪制的地圖。值得一提的是:這個方塊是寬字符,而使用wprintf來打印寬字符就得先將C語言環(huán)境轉(zhuǎn)化到本地環(huán)境。至于后面的打印,就是通過定位來循環(huán)打印。
先打印上下兩行,再打印左右兩列。
上一行的坐標(biāo)是(2*i,0),i 的范圍是0~28。? 下一行的坐標(biāo)是(2*i,25),i 的范圍是0~28。?
左一列的坐標(biāo)是(0,i),i 的范圍是1~25。? ? ? 右一列的坐標(biāo)是(56,i),i 的范圍是1~25。
注意:
1. 一行的打印就相當(dāng)于是打印了一列中的一個,因此用總列數(shù)-2就是我們要打印的列坐標(biāo)。
2. 列在打印時,需要先定位好坐標(biāo)。因為打印的順序是默認(rèn)從左到右的;而我們是要實現(xiàn)從上到下的打印。
#define WALL L'□'
//繪制地圖
void CreatMap()
{
//打印上體墻
for (int i = 0; i <= 28; i++)
{
wprintf(L"%lc", WALL);
}
SetPos(0, 26);//定位到下體墻的位置
//打印下體墻
for (int i = 0; i <= 28; i++)
{
wprintf(L"%lc", WALL);
}
//打印左體墻
for (int i = 1; i <= 25; i++)
{
SetPos(0, i);
wprintf(L"%lc", WALL);//用換行不行,因此加不加換行無所謂
}
//打印右體墻
for (int i = 1; i <= 25; i++)
{
SetPos(56, i);
wprintf(L"%lc", WALL);//用換行不行,因此加不加換行無所謂
}
}
初始化貪吃蛇
既然要初始化蛇,首先就得有蛇,而蛇是用節(jié)點串起來的。因此我們只要創(chuàng)建5個節(jié)點,并且傳入我們想要的坐標(biāo)(我們想要蛇出現(xiàn)在哪個位置,這個位置最好是固定的,這里也是采用固定的位置。),最后把這些節(jié)點串起來就行了。
//創(chuàng)建一條蛇
pSnakeNode pcur = NULL;
pSnakeNode prev = NULL;
for (int i = 0; i < 5; i++)
{
pSnakeNode tmp = (pSnakeNode)malloc(sizeof(SnakeNode));
if (tmp == NULL)
{
perror("InitSnake():malloc:");
return;
}
pcur = tmp;
pcur->x = POS_X + 2 * i;
pcur->y = POS_Y;
pcur->next = NULL;
if (ps->_pSnake == NULL)
{
//直接插入即可
ps->_pSnake = pcur;
}
else
{
//尾插
if (prev != NULL)
prev->next = pcur;
}
prev = pcur;
}
打印蛇(打印整個鏈表)
#define BODY L'●'
#define HEAD L'◆'
//開始在控制臺上打印蛇
int count = 0;
while (pcur)
{
SetPos(pcur->x, pcur->y);//定位
if (count == 0)
{
count++;
wprintf(L"%lc", HEAD);//打印蛇頭
}
else
{
wprintf(L"%lc", BODY);//打印蛇身
}
pcur = pcur->next;
}
設(shè)置貪吃蛇的屬性。
//設(shè)置貪吃蛇的屬性
ps->_dir = LEFT;//初始時蛇的方向向左
ps->_FoodWeight = 50;//一個食物50分
ps->_score = 0;//總分為0
ps->_SleepTime = 200;//單位是毫秒
ps->_state = OK;//正常運(yùn)行
?因為這里的蛇頭是在最左邊,所以這個蛇的初始方向不能是向右走,除此之外都可以。
初始化食物
初始化食物其實就是創(chuàng)建一個食物并且打印出來。
這個食物為了能夠被蛇給吃掉,x坐標(biāo)也必須是2的倍數(shù),并且這個食物應(yīng)該是要隨機(jī)生成的,還要在這個墻體中。?
int x = 0;
int y = 0;
//隨機(jī)創(chuàng)建食物(食物的x坐標(biāo)必須是2的倍數(shù),因此要判斷)
again:
do
{
//為了食物出現(xiàn)在墻內(nèi)
x = rand() % 53 + 2;
y = rand() % 25 + 1;
} while (x % 2);
//食物的坐標(biāo)不能和蛇身沖突
pSnakeNode pcur = ps->_pSnake;
//開始尋找看看是否與蛇身沖突
while (pcur)
{
if (pcur->x == x && pcur->y == y)
{
goto again;//如果沖突了,就要回爐重造
}
pcur = pcur->next;
}
//開始創(chuàng)建食物的節(jié)點
pSnakeNode tmp = (pSnakeNode)malloc(sizeof(SnakeNode));
if (tmp == NULL)
{
perror("CreatFood():malloc:");
return;
}
tmp->x = x;
tmp->y = y;
tmp->next = NULL;
ps->_pFood = tmp;
打印食物
//打印食物
SetPos(x, y);
wprintf(L"%lc", FOOD);
GameRun()——游戲的運(yùn)行
1,打印幫助手冊? 2,貪吃蛇的相關(guān)運(yùn)行信息? 3,判斷貪吃蛇是否死亡
上圖就是游戲運(yùn)行時的界面。
打印右側(cè)的幫助信息
//打印幫助信息
void PrintHelpInfo()
{
SetPos(68, 10);
printf("小提示:");
SetPos(68, 13);
printf("不能穿墻,不能咬到自己!");
SetPos(68, 14);
printf("用↑.↓.←.→ 來控制蛇的移動!");
SetPos(68, 15);
printf("摁F3加速!摁F4減速!");
SetPos(68, 16);
printf("加速將增加單個食物的分?jǐn)?shù)!");
SetPos(68, 17);
printf("減速將減少單個食物的分?jǐn)?shù)!");
SetPos(68, 18);
printf("摁Esc退出游戲!摁空格暫停游戲!");
}
貪吃蛇的相關(guān)運(yùn)行信息?
貪吃蛇要運(yùn)行起來,就得需要我們摁鍵來實現(xiàn)貪吃蛇的走動。所以接下來就是判斷哪個鍵是否摁過來判斷蛇的走向。而只要是蛇的狀態(tài)不等于OK時,此時就不需要再走了。
#define KEY_PRESS(VK) ((GetAsyncKeyState(VK) & 0x1) ? 1 : 0)
//接下來就是通過按鍵來判斷貪吃蛇的運(yùn)行狀態(tài)
do
{
//打印分?jǐn)?shù)顯示
SetPos(68, 7);
printf("當(dāng)前總分?jǐn)?shù):%08d", ps->_score);
SetPos(68, 8);
printf("當(dāng)前食物分?jǐn)?shù):%02d", ps->_FoodWeight);
//判斷摁了什么鍵,根據(jù)鍵來判斷要執(zhí)行的命令
if (KEY_PRESS(VK_UP) && ps->_dir != DOWN)
{
ps->_dir = UP;//摁了↑,并且蛇不是往下走,就改變方向
}
else if (KEY_PRESS(VK_DOWN) && ps->_dir != UP)
{
ps->_dir = DOWN;
}
else if (KEY_PRESS(VK_LEFT) && ps->_dir != RIGHT)
{
ps->_dir = LEFT;
}
else if (KEY_PRESS(VK_RIGHT) && ps->_dir != LEFT)
{
ps->_dir = RIGHT;
}
else if (KEY_PRESS(VK_SPACE))
{
Pause();//暫停
}
else if (KEY_PRESS(VK_ESCAPE))
{
//退出游戲
ps->_state = END_NORMAL;
}
else if (KEY_PRESS(VK_F3))
{
//加速(減少休眠時間)
if (ps->_SleepTime > 80)//設(shè)置為4檔速度
{
ps->_SleepTime -= 30;
ps->_FoodWeight += 10;
}
}
else if (KEY_PRESS(VK_F4))
{
//減速
if (ps->_FoodWeight > 10)//設(shè)置為4檔速度
{
ps->_SleepTime += 20;
ps->_FoodWeight -= 10;
}
}
//蛇開始走
//走一步,就休息一下
SnakeMove(ps);//蛇走一步的過程
Sleep(ps->_SleepTime);
//檢測是否撞墻
KillByWall(ps);
//檢測是否撞到自己
KillBySelf(ps);
//通過總分?jǐn)?shù)來判斷游戲是否結(jié)束
if (ps->_score > 30000)
{
SetPos(20, 13);
printf("恭喜你!成功通關(guān)!");
}
} while (ps->_state == OK);//只有蛇的狀態(tài)正常才走
暫停的實現(xiàn)就只需要系統(tǒng)一直處于休眠狀態(tài)。?
void Pause()
{
while (1)
{
//休眠200毫秒
Sleep(200);
//這個只能放到休眠的后面
if (KEY_PRESS(VK_SPACE))
{
break;
}
}
}
當(dāng)進(jìn)入這個暫停函數(shù)就需要休眠,即使再次摁了空格鍵,也得先休眠一下。
蛇在走的時候,就是根據(jù)我們摁的鍵位來判斷蛇頭應(yīng)該出現(xiàn)在哪個地方。再通過鏈接蛇頭的下一個位置和蛇身以及釋放蛇身的尾節(jié)點。還有一個小細(xì)節(jié):如果蛇頭下一個位置是食物的話,就要吃掉食物,并且再創(chuàng)建一個食物,而如果不是食物的話,就只需要按照上面的步驟走就行了。因此就得先判斷是否為食物。
//蛇走一步的過程
void SnakeMove(pSnake ps)
{
//創(chuàng)建一個節(jié)點來存放蛇要走的下一個節(jié)點
pSnakeNode next = (pSnakeNode)malloc(sizeof(SnakeNode));
if (next == NULL)
{
perror("SnakeMove():malloc:");
return;
}
//根據(jù)方向來判斷蛇是怎么走的
switch (ps->_dir)
{
case UP:
next->x = ps->_pSnake->x;
next->y = ps->_pSnake->y - 1;
break;
case DOWN:
next->x = ps->_pSnake->x;
next->y = ps->_pSnake->y + 1;
break;
case LEFT:
next->x = ps->_pSnake->x - 2;
next->y = ps->_pSnake->y;
break;
case RIGHT:
next->x = ps->_pSnake->x + 2;
next->y = ps->_pSnake->y;
break;
}
//判斷蛇走的下一個節(jié)點是不是食物
if (NextIsFood(next, ps))
{
//是食物就吃掉食物
EatFood(next, ps);
}
else
{
//不是就不吃
NoFood(next, ps);
}
}
下一個位置是食物
//下一個位置是食物,就可以吃掉
void EatFood(pSnakeNode next, pSnake ps)
{
//把這個節(jié)點(就是食物節(jié)點)頭插到蛇身就行
ps->_pFood->next = ps->_pSnake;
ps->_pSnake = ps->_pFood;
//釋放掉這個節(jié)點(因為創(chuàng)建了兩個節(jié)點:一個食物節(jié)點,一個蛇頭的下一個節(jié)點)
free(next);
next = NULL;
//打印蛇身
pSnakeNode pcur = ps->_pSnake;
int count = 0;
while (pcur)
{
SetPos(pcur->x, pcur->y);//定位
if (count == 0)
{
count++;
wprintf(L"%lc", HEAD);//打印蛇頭
}
else
{
wprintf(L"%lc", BODY);//打印蛇身
}
pcur = pcur->next;
}
//分?jǐn)?shù)的增加
ps->_score += ps->_FoodWeight;
//重新創(chuàng)建食物
CreatFood(ps);
}
下一個位置不是食物
//下一個位置不是食物
void NoFood(pSnakeNode next, pSnake ps)
{
//把下一個位置的節(jié)點頭插到蛇身
next->next = ps->_pSnake;
ps->_pSnake = next;
//把蛇身最后一個節(jié)點的空間釋放掉,順便打印蛇身
pSnakeNode pcur = ps->_pSnake;
int count = 0;
while (pcur->next->next != NULL)
{
//遍歷時,可以直接打印
SetPos(pcur->x, pcur->y);
if (count == 0)
{
count++;
wprintf(L"%lc", HEAD);
}
else
{
wprintf(L"%lc", BODY);
}
pcur = pcur->next;
}
//把最后一個節(jié)點的位置打印成空格
SetPos(pcur->next->x, pcur->next->y);
printf(" ");
free(pcur->next);
pcur->next = NULL;
}
如果不把蛇身的尾節(jié)點的位置打印成空格,那么上一次的痕跡就不會被消除。?會導(dǎo)致蛇身一直變長。? 走一步,再休息兩百毫秒,可以讓我們有時間來判斷貪吃蛇下一步需怎么走,如果不休息就會直接撞墻。
檢測是否撞墻只需要檢測蛇頭是否撞墻,因為蛇身的每一個節(jié)點是重復(fù)執(zhí)行蛇頭的操作。蛇頭不撞墻那么蛇身就沒有機(jī)會撞墻,如果蛇頭撞墻,那么就說明這個蛇撞墻了。
//檢測是否撞墻
void KillByWall(pSnake ps)
{
//只要判斷蛇頭是否碰到墻就可以了
if (ps->_pSnake->x == 0 || ps->_pSnake->x == 56
|| ps->_pSnake->y == 0 || ps->_pSnake->y == 26)
{
ps->_state = KILL_BY_WALL;//改變蛇的狀態(tài)即可
}
}
檢測蛇是否撞到自身就只需要檢測蛇頭和蛇身的某一個節(jié)點是否重合就行了。
//檢測是否撞到自己
void KillBySelf(pSnake ps)
{
//只要判斷蛇頭是否碰到自己的蛇身
pSnakeNode pcur = ps->_pSnake->next;
while (pcur)
{
if (pcur->x == ps->_pSnake->x && pcur->y == ps->_pSnake->y)
{
ps->_state = KILL_BY_SELF;//改變蛇的狀態(tài)即可
break;
}
pcur = pcur->next;
}
}
GameOver()——游戲的結(jié)束(善后工作)?
善后也就是把蛇身的節(jié)點釋放掉,并告訴玩家游戲結(jié)束的原因。
//結(jié)束游戲(善后工作)
void GameOver(pSnake ps)
{
SetPos(20, 13);
switch (ps->_state)
{
case KILL_BY_WALL:
printf("很遺憾!撞墻死亡!");
break;
case KILL_BY_SELF:
printf("很遺憾!撞到自己死亡!");
break;
case END_NORMAL:
printf("玩家主動結(jié)束游戲!");
break;
}
//釋放蛇身鏈表
pSnakeNode prev = ps->_pSnake;
pSnakeNode pcur = ps->_pSnake;
while (pcur)
{
prev = pcur->next;
free(pcur);
pcur = prev;
}
}
貪吃蛇源碼?
?Snake.h
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
#include <locale.h>
#include <stdbool.h>
#include <time.h>
#define POS_X 24
#define POS_Y 5
#define WALL L'□'
#define BODY L'●'
#define FOOD L'★'
#define HEAD L'◆'
#define KEY_PRESS(VK) ((GetAsyncKeyState(VK) & 0x1) ? 1 : 0)
//游戲的狀態(tài)
typedef enum GAME_STATE
{
OK,//正常運(yùn)行
KILL_BY_WALL,//撞墻
KILL_BY_SELF,//撞到自己
END_NORMAL//正常退出
}GAME_STATE;
//蛇的方向
typedef enum DIRECTION
{
UP = 1,//上
DOWN,//下
LEFT,//左
RIGHT//右
}DIRECTION;
//蛇身的節(jié)點類型
typedef struct SnakeNode
{
//坐標(biāo)
int x;
int y;
//指向下一個節(jié)點的指針
struct SnakeNode* next;
}SnakeNode, * pSnakeNode;
//貪吃蛇
typedef struct Snake
{
pSnakeNode _pSnake;//指向蛇頭的指針
pSnakeNode _pFood;//指向食物的指針
DIRECTION _dir;//蛇的方向
GAME_STATE _state;//游戲的運(yùn)行狀態(tài)
int _FoodWeight;//一個食物的分?jǐn)?shù)
int _score;//總分?jǐn)?shù)
int _SleepTime;//休眠時間,時間越短,速度越快,時間越長,速度越慢
}Snake, * pSnake;
//定位光標(biāo)
void SetPos(short x, short y);
//初始化游戲
void GameStart(pSnake ps);
//打印歡迎界面
void WelcomToGame();
//繪制地圖
void CreatMap();
//初始化蛇
void InitSnake(pSnake ps);
//創(chuàng)建食物
void CreatFood(pSnake ps);
//游戲運(yùn)行
void GameRun(pSnake ps);
//蛇走一步的過程
void SnakeMove(pSnake ps);
//判斷蛇要走的下一個節(jié)點是否為食物
int NextIsFood(pSnakeNode next, pSnake ps);
//下一個位置是食物,就可以吃掉
void EatFood(pSnakeNode next, pSnake ps);
//下一個位置不是食物
void NoFood(pSnakeNode next, pSnake ps);
//檢測是否撞墻
void KillByWall(pSnake ps);
//檢測是否撞到自己
void KillBySelf(pSnake ps);
//結(jié)束游戲(善后工作)
void GameOver(pSnake ps);
Snake.c文章來源:http://www.zghlxwxcb.cn/news/detail-856283.html
#include "Snake.h"
//定位光標(biāo)
void SetPos(short x, short y)
{
HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);//獲取標(biāo)準(zhǔn)輸出的句柄
COORD pos = { x, y };
SetConsoleCursorPosition(houtput, pos);//設(shè)置光標(biāo)的位置
}
//打印歡迎界面
void WelcomToGame()
{
//首先得定位光標(biāo)
SetPos(39, 10);
printf("歡迎來到貪吃蛇小游戲\n");
SetPos(40, 15);
system("pause");//暫停
system("cls");//清理屏幕
SetPos(28, 10);
printf("用↑.↓.←.→ 來控制蛇的移動!摁F3加速!摁F4減速!\n");
SetPos(38, 11);
printf("游戲即將開始,請做好準(zhǔn)備!\n");
SetPos(40, 15);
system("pause");
system("cls");
}
//繪制地圖
void CreatMap()
{
//打印上體墻
for (int i = 0; i <= 28; i++)
{
wprintf(L"%lc", WALL);
}
SetPos(0, 26);//定位到下體墻的位置
//打印下體墻
for (int i = 0; i <= 28; i++)
{
wprintf(L"%lc", WALL);
}
//打印左體墻
for (int i = 1; i <= 25; i++)
{
SetPos(0, i);
wprintf(L"%lc", WALL);
}
//打印右體墻
for (int i = 1; i <= 25; i++)
{
SetPos(56, i);
wprintf(L"%lc", WALL);
}
}
//初始化蛇
void InitSnake(pSnake ps)
{
//創(chuàng)建一條蛇
pSnakeNode pcur = NULL;
pSnakeNode prev = NULL;
for (int i = 0; i < 5; i++)
{
pSnakeNode tmp = (pSnakeNode)malloc(sizeof(SnakeNode));
if (tmp == NULL)
{
perror("InitSnake():malloc:");
return;
}
pcur = tmp;
pcur->x = POS_X + 2 * i;
pcur->y = POS_Y;
pcur->next = NULL;
if (ps->_pSnake == NULL)
{
//直接插入即可
ps->_pSnake = pcur;
}
else
{
//尾插
if (prev != NULL)
prev->next = pcur;
}
prev = pcur;
}
pcur = ps->_pSnake;
//開始在控制臺上打印蛇
int count = 0;
while (pcur)
{
SetPos(pcur->x, pcur->y);//定位
if (count == 0)
{
count++;
wprintf(L"%lc", HEAD);//打印蛇頭
}
else
{
wprintf(L"%lc", BODY);//打印蛇身
}
pcur = pcur->next;
}
//設(shè)置貪吃蛇的屬性
ps->_dir = LEFT;//初始時蛇的方向向左
ps->_FoodWeight = 50;//一個食物50分
ps->_score = 0;//總分為0
ps->_SleepTime = 200;//單位是毫秒
ps->_state = OK;//正常運(yùn)行
}
//創(chuàng)建食物
void CreatFood(pSnake ps)
{
int x = 0;
int y = 0;
//隨機(jī)創(chuàng)建食物(食物的x坐標(biāo)必須是2的倍數(shù),因此要判斷)
again:
do
{
x = rand() % 53 + 2;
y = rand() % 25 + 1;
} while (x % 2);
//食物的坐標(biāo)不能和蛇身沖突
pSnakeNode pcur = ps->_pSnake;
//開始尋找看看是否與蛇身沖突
while (pcur)
{
if (pcur->x == x && pcur->y == y)
{
goto again;
}
pcur = pcur->next;
}
//開始創(chuàng)建食物的節(jié)點
pSnakeNode tmp = (pSnakeNode)malloc(sizeof(SnakeNode));
if (tmp == NULL)
{
perror("CreatFood():malloc:");
return;
}
tmp->x = x;
tmp->y = y;
tmp->next = NULL;
ps->_pFood = tmp;
//打印食物
SetPos(x, y);
wprintf(L"%lc", FOOD);
}
//初始化游戲
void GameStart(pSnake ps)
{
//設(shè)置窗口大小以及名字
system("mode con cols=100 lines=30");
system("title 貪吃蛇");
//隱藏光標(biāo)信息,為了后續(xù)打印
HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);//獲取標(biāo)準(zhǔn)輸出的句柄
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(houtput, &CursorInfo);//獲取光標(biāo)信息
CursorInfo.bVisible = false;//隱藏光標(biāo)
SetConsoleCursorInfo(houtput, &CursorInfo);//設(shè)置光標(biāo)狀態(tài)
//打印歡迎界面和功能介紹
WelcomToGame();
//繪制地圖
CreatMap();
//初始化蛇
InitSnake(ps);
//創(chuàng)建食物
CreatFood(ps);
}
//打印幫助信息
void PrintHelpInfo()
{
SetPos(68, 10);
printf("小提示:");
SetPos(68, 13);
printf("不能穿墻,不能咬到自己!");
SetPos(68, 14);
printf("用↑.↓.←.→ 來控制蛇的移動!");
SetPos(68, 15);
printf("摁F3加速!摁F4減速!");
SetPos(68, 16);
printf("加速將增加單個食物的分?jǐn)?shù)!");
SetPos(68, 17);
printf("減速將減少單個食物的分?jǐn)?shù)!");
SetPos(68, 18);
printf("摁Esc退出游戲!摁空格暫停游戲!");
}
void Pause()
{
while (1)
{
//休眠200毫秒
Sleep(200);
//這個只能放到休眠的后面
if (KEY_PRESS(VK_SPACE))
{
break;
}
}
}
//判斷蛇要走的下一個節(jié)點是否為食物
int NextIsFood(pSnakeNode next, pSnake ps)
{
return ((next->x == ps->_pFood->x) && (next->y == ps->_pFood->y));
}
//下一個位置是食物,就可以吃掉
void EatFood(pSnakeNode next, pSnake ps)
{
//把這個節(jié)點(就是食物節(jié)點)頭插到蛇身就行
ps->_pFood->next = ps->_pSnake;
ps->_pSnake = ps->_pFood;
//釋放掉這個節(jié)點(因為有兩個節(jié)點)
free(next);
next = NULL;
//打印蛇身
pSnakeNode pcur = ps->_pSnake;
int count = 0;
while (pcur)
{
SetPos(pcur->x, pcur->y);//定位
if (count == 0)
{
count++;
wprintf(L"%lc", HEAD);//打印蛇頭
}
else
{
wprintf(L"%lc", BODY);//打印蛇身
}
pcur = pcur->next;
}
ps->_score += ps->_FoodWeight;
//重新創(chuàng)建食物
CreatFood(ps);
}
//下一個位置不是食物
void NoFood(pSnakeNode next, pSnake ps)
{
//把下一個位置的節(jié)點頭插到蛇身
next->next = ps->_pSnake;
ps->_pSnake = next;
//把蛇身最后一個節(jié)點的空間釋放掉,順便打印蛇身
pSnakeNode pcur = ps->_pSnake;
int count = 0;
while (pcur->next->next != NULL)
{
//遍歷時,可以直接打印
SetPos(pcur->x, pcur->y);
if (count == 0)
{
count++;
wprintf(L"%lc", HEAD);
}
else
{
wprintf(L"%lc", BODY);
}
pcur = pcur->next;
}
//把最后一個節(jié)點的位置打印成空格
SetPos(pcur->next->x, pcur->next->y);
printf(" ");
free(pcur->next);
pcur->next = NULL;
}
//蛇走一步的過程
void SnakeMove(pSnake ps)
{
//創(chuàng)建一個節(jié)點來存放蛇要走的下一個節(jié)點
pSnakeNode next = (pSnakeNode)malloc(sizeof(SnakeNode));
if (next == NULL)
{
perror("SnakeMove():malloc:");
return;
}
//根據(jù)方向來判斷蛇是怎么走的
switch (ps->_dir)
{
case UP:
next->x = ps->_pSnake->x;
next->y = ps->_pSnake->y - 1;
break;
case DOWN:
next->x = ps->_pSnake->x;
next->y = ps->_pSnake->y + 1;
break;
case LEFT:
next->x = ps->_pSnake->x - 2;
next->y = ps->_pSnake->y;
break;
case RIGHT:
next->x = ps->_pSnake->x + 2;
next->y = ps->_pSnake->y;
break;
}
//判斷蛇走的下一個節(jié)點是不是食物
if (NextIsFood(next, ps))
{
EatFood(next, ps);
}
else
{
NoFood(next, ps);
}
}
//檢測是否撞墻
void KillByWall(pSnake ps)
{
//只要判斷蛇頭是否碰到墻就可以了
if (ps->_pSnake->x == 0 || ps->_pSnake->x == 56
|| ps->_pSnake->y == 0 || ps->_pSnake->y == 26)
{
ps->_state = KILL_BY_WALL;
}
}
//檢測是否撞到自己
void KillBySelf(pSnake ps)
{
//只要判斷蛇頭是否碰到自己的蛇身
pSnakeNode pcur = ps->_pSnake->next;
while (pcur)
{
if (pcur->x == ps->_pSnake->x && pcur->y == ps->_pSnake->y)
{
ps->_state = KILL_BY_SELF;
break;
}
pcur = pcur->next;
}
}
//游戲運(yùn)行
void GameRun(pSnake ps)
{
//先打印幫助信息
PrintHelpInfo();
//接下來就是通過按鍵來判斷貪吃蛇的運(yùn)行狀態(tài)
do
{
//打印分?jǐn)?shù)顯示
SetPos(68, 7);
printf("當(dāng)前總分?jǐn)?shù):%05d", ps->_score);
SetPos(68, 8);
printf("當(dāng)前食物分?jǐn)?shù):%02d", ps->_FoodWeight);
//判斷摁了什么鍵,根據(jù)鍵來判斷要執(zhí)行的命令
if (KEY_PRESS(VK_UP) && ps->_dir != DOWN)
{
ps->_dir = UP;//摁了↑,并且蛇不是往下走,就改變方向
}
else if (KEY_PRESS(VK_DOWN) && ps->_dir != UP)
{
ps->_dir = DOWN;
}
else if (KEY_PRESS(VK_LEFT) && ps->_dir != RIGHT)
{
ps->_dir = LEFT;
}
else if (KEY_PRESS(VK_RIGHT) && ps->_dir != LEFT)
{
ps->_dir = RIGHT;
}
else if (KEY_PRESS(VK_SPACE))
{
Pause();//暫停
}
else if (KEY_PRESS(VK_ESCAPE))
{
//退出游戲
ps->_state = END_NORMAL;
}
else if (KEY_PRESS(VK_F3))
{
//加速(減少休眠時間)
if (ps->_SleepTime > 80)//設(shè)置為4檔速度
{
ps->_SleepTime -= 30;
ps->_FoodWeight += 10;
}
}
else if (KEY_PRESS(VK_F4))
{
//減速
if (ps->_FoodWeight > 10)
{
ps->_SleepTime += 20;
ps->_FoodWeight -= 10;
}
}
//蛇開始走
//走一步,就休息一下
SnakeMove(ps);//蛇走一步的過程
Sleep(ps->_SleepTime);
//檢測是否撞墻
KillByWall(ps);
//檢測是否撞到自己
KillBySelf(ps);
if (ps->_score > 30000)
{
SetPos(20, 13);
printf("恭喜你!成功通關(guān)!");
break;
}
} while (ps->_state == OK);
}
//結(jié)束游戲(善后工作)
void GameOver(pSnake ps)
{
SetPos(20, 13);
switch (ps->_state)
{
case KILL_BY_WALL:
printf("很遺憾!撞墻死亡!");
break;
case KILL_BY_SELF:
printf("很遺憾!撞到自己死亡!");
break;
case END_NORMAL:
printf("玩家主動結(jié)束游戲!");
break;
}
//釋放蛇身鏈表
pSnakeNode prev = ps->_pSnake;
pSnakeNode pcur = ps->_pSnake;
while (pcur)
{
prev = pcur->next;
free(pcur);
pcur = prev;
}
}
?好啦!本期貪吃蛇游戲的學(xué)習(xí)之旅到此結(jié)束了!我們下一期再一起學(xué)習(xí)吧!文章來源地址http://www.zghlxwxcb.cn/news/detail-856283.html
到了這里,關(guān)于小游戲貪吃蛇的實現(xiàn)之C語言版的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!