文章目錄
- 3. 貪吃蛇的具體實現
3. 貪吃蛇的具體實現
- 首先,我們要讓整個程序適應本地化
int main()
{//修改適配本地中文環境setlocale(LC_ALL, "");return 0;
}
- 蛇身節點的創建
//蛇身結點的定義
typedef struct SnakeNode
{int x; int y;struct SnakeNode* next;
}SnakeNode, * pSnakeNode;//typedef struct SnakeNode* pSnakeNode;//上面的寫法和這個寫法是一個效果
- 再定義一個結構體來維護整個貪吃蛇游戲
//游戲的狀態
enum GAME_STATUS
{OK = 1,//正常運行ESC,//按了ESC鍵退出,正常退出KILL_BY_WALL,//撞墻KILL_BY_SELF//撞到自身
};//蛇行走的方向
enum DIRECTION
{UP = 1,DOWN,LEFT,RIGHT
};//貪吃蛇
typedef struct Snake
{pSnakeNode pSnake;//維護整條蛇的指針,是指向蛇頭pSnakeNode pFood;//指向食物的指針int Score;//當前累積的分數int FoodWeight;//一個食物的分數int SleepTime;//蛇休眠的時間,休眠的時間越短,蛇的速度越快,休眠的時間越長,蛇的速度越慢enum GAME_STATUS status;//當前的狀態enum DIRECTION dir;//蛇當前走的方向
}Snake, * pSnake;
- 游戲開始的函數
GameStart:
void GameStart(pSnake ps)
{//設置控制臺的信息,窗口大小,窗口名system("mode con cols=100 lines=30");system("title 貪吃蛇");//隱藏光標HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);CONSOLE_CURSOR_INFO CursorInfo;GetConsoleCursorInfo(handle, &CursorInfo);//獲取控制臺光標信息CursorInfo.bVisible = false;SetConsoleCursorInfo(handle, &CursorInfo);//打印歡迎信息WelcomeToGame();//繪制地圖CreateMap();//初始化蛇InitSnake(ps);//創建食物CreateFood(ps);
}
打印歡迎信息:
void SetPos(int x, int y)
{//獲得設備句柄HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);//根據句柄設置光標的位置COORD pos = { x, y };SetConsoleCursorPosition(handle, pos);
}void WelcomeToGame()
{//歡迎信息SetPos(35, 10);printf("歡迎來到貪吃蛇小游戲\n");SetPos(38, 20);system("pause");system("cls");//功能介紹信息SetPos(15, 10);printf("用 ↑ . ↓ . ← . → 來控制蛇的移動,F3是加速,F4是減速\n");SetPos(15, 11);printf("加速能得到更高的分數");SetPos(38, 20);system("pause");system("cls");
}
繪制地圖:
#define WALL L'□'
void CreateMap()
{int i = 0;//上SetPos(0, 0);for (i = 0; i <= 56; i += 2){wprintf(L"%lc", WALL);}//下SetPos(0, 26);for (i = 0; i <= 56; i += 2){wprintf(L"%lc", WALL);}//左for (i = 1; i <= 25; i++){SetPos(0, i);wprintf(L"%lc", WALL);}//右for (i = 1; i <= 25; i++){SetPos(56, i);wprintf(L"%lc", WALL);}
}
初始化蛇:
注意: 蛇的每個節點的x坐標必須是2的倍數,否則可能會出現蛇的?個節點有一半出現在墻體中,另外?半在墻外的現象,坐標不好對齊。
#define BODY L'●'//蛇默認的起始坐標
#define POS_X 24
#define POS_Y 5
void InitSnake(pSnake ps)
{//創建5個蛇身的結點pSnakeNode cur = NULL;int i = 0;for (i = 0; i < 5; i++){cur = (pSnakeNode)malloc(sizeof(SnakeNode));if (NULL == cur){perror("InitSnake():malloc()");return;}cur->x = POS_X + 2 * i;cur->y = POS_Y;cur->next = NULL;//頭插法if (NULL == ps->pSnake){ps->pSnake = cur;}else{cur->next = ps->pSnake;ps->pSnake = cur;}}//打印蛇身while (cur){SetPos(cur->x, cur->y);wprintf(L"%lc", BODY);cur = cur->next;}//貪吃蛇的其他信息初始化ps->dir = RIGHT;ps->FoodWeight = 10;ps->pFood = NULL;ps->Score = 0;ps->SleepTime = 200;ps->status = OK;
}
創建食物:
關于食物,就是在墻體內隨機生成?個坐標(x坐標必須是2的倍數),坐標不能和蛇的身體重合,然后打印★。
#define FOOD L'★'
void CreateFood(pSnake ps)
{int x = 0;int y = 0;again:do{x = rand() % 53 + 2;y = rand() % 24 + 1;} while (x % 2 != 0);//坐標和蛇的身體的每個節點的坐標比較pSnakeNode cur = ps->pSnake;while (cur){if (x == cur->x && y == cur->y){goto again;}cur = cur->next;}//創建食物pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode));if (NULL == pFood){perror("CreatFood():malloc()");return;}pFood->x = x;pFood->y = y;pFood->next = NULL;ps->pFood = pFood;SetPos(x, y);wprintf(L"%lc", FOOD);
}
int main()
{srand((unsigned int)time(NULL));return 0;
}
- 游戲運行的函數
打印幫助信息:
void PrintHelpInfo()
{SetPos(62, 15);printf("1.不能穿墻,不能咬到自己");SetPos(62, 16);printf("2.用 ↑.↓.←.→ 來控制蛇的移動");SetPos(62, 17);printf("3.F3是加速,F4是減速");SetPos(62, 18);printf("4.ESC:退出游戲 space:暫停游戲");
}
GameRun:
#define KEY_PRESS(VK) ((GetAsyncKeyState(VK) & 0x1) ? 1 : 0)
void GameRun(pSnake ps)
{//打印幫助信息PrintHelpInfo();if (KEY_PRESS(VK_UP) || KEY_PRESS(VK_DOWN) ||KEY_PRESS(VK_LEFT) || KEY_PRESS(VK_RIGHT) ||KEY_PRESS(VK_ESCAPE) || KEY_PRESS(VK_SPACE) ||KEY_PRESS(VK_F3) || KEY_PRESS(VK_F4)){;//消除之前 按任意鍵繼續 的影響,比如我按了空格然后進入了游戲,這里不檢測,那么下面的代碼會檢測到我按過空格,游戲一開始就會暫停}do{//當前的分數情況SetPos(62, 10);printf("總分:%5d\n", ps->Score);SetPos(62, 11);printf("食物的分值:%02d\n", ps->FoodWeight);//檢測按鍵//上、下、左、右、ESC、空格、F3、F4if (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_ESCAPE)){ps->status = ESC;break;}else if (KEY_PRESS(VK_SPACE)){//游戲要暫停pause();//暫停和恢復暫停}else if (KEY_PRESS(VK_F3)){if (ps->SleepTime > 80){ps->SleepTime -= 30;ps->FoodWeight += 2;}}else if (KEY_PRESS(VK_F4)){if (ps->FoodWeight > 2){ps->SleepTime += 30;ps->FoodWeight -= 2;}}//走一步SnakeMove(ps);//睡眠一下Sleep(ps->SleepTime);} while (OK == ps->status);
}
pause:
void pause()
{while (1){Sleep(100);if (KEY_PRESS(VK_SPACE)){break;}}
}
SnakeMove:
void SnakeMove(pSnake ps)
{pSnakeNode pNext = (pSnakeNode)malloc(sizeof(SnakeNode));if (NULL == pNext){perror("SnakeMove()::malloc()");return;}pNext->next = NULL;switch (ps->dir){case UP:pNext->x = ps->pSnake->x;pNext->y = ps->pSnake->y - 1;break;case DOWN:pNext->x = ps->pSnake->x;pNext->y = ps->pSnake->y + 1;break;case LEFT:pNext->x = ps->pSnake->x - 2;pNext->y = ps->pSnake->y;break;case RIGHT:pNext->x = ps->pSnake->x + 2;pNext->y = ps->pSnake->y;break;}//下一個坐標處是否是食物if (NextIsFood(ps, pNext)){//是食物就吃掉EatFood(ps, pNext);}else{//不是食物就正常走一步NotEatFood(ps, pNext);}//檢測撞墻KillByWall(ps);//檢測撞到自己KillBySelf(ps);
}
NextIsFood:
int NextIsFood(pSnake ps, pSnakeNode pNext)
{if (ps->pFood->x == pNext->x && ps->pFood->y == pNext->y){return 1;//下一個坐標處是食物}else{return 0;}
}
EatFood:
void EatFood(pSnake ps, pSnakeNode pNext)
{pNext->next = ps->pSnake;ps->pSnake = pNext;//打印蛇pSnakeNode cur = ps->pSnake;while (cur){SetPos(cur->x, cur->y);wprintf(L"%lc", BODY);cur = cur->next;}ps->Score += ps->FoodWeight;//釋放舊的食物free(ps->pFood);ps->pFood = NULL;//新建食物CreateFood(ps);
}
NotEatFood:
void NotEatFood(pSnake ps, pSnakeNode pNext)
{//頭插法pNext->next = ps->pSnake;ps->pSnake = pNext;//釋放尾結點pSnakeNode cur = ps->pSnake;while (cur->next->next){SetPos(cur->x, cur->y);wprintf(L"%lc", BODY);cur = cur->next;}SetPos(cur->x, cur->y);wprintf(L"%lc", BODY);//將尾節點的位置打印成空白字符SetPos(cur->next->x, cur->next->y);printf(" ");free(cur->next);cur->next = NULL;
}
KillByWall:
void KillByWall(pSnake ps)
{if (0 == ps->pSnake->x ||56 == ps->pSnake->x ||0 == ps->pSnake->y ||26 == ps->pSnake->y){ps->status = KILL_BY_WALL;}
}
KillBySelf:
void KillBySelf(pSnake ps)
{pSnakeNode cur = ps->pSnake->next;//從第二個節點開始while (cur){if (cur->x == ps->pSnake->x && cur->y == ps->pSnake->y){ps->status = KILL_BY_SELF;return;}cur = cur->next;}
}
- 游戲的善后工作
void GameEnd(pSnake ps)
{SetPos(15, 12);switch (ps->status){case ESC:printf("主動退出游戲,正常退出\n");break;case KILL_BY_WALL:printf("很遺憾,撞墻了,游戲結束\n");break;case KILL_BY_SELF:printf("很遺憾,咬到自己了,游戲結束\n");break;}//釋放貪吃蛇的鏈表資源pSnakeNode cur = ps->pSnake;pSnakeNode del = NULL;while (cur){del = cur;cur = cur->next;free(del);del = NULL;}free(ps->pFood);ps->pFood = NULL;ps = NULL;
}
int main()
{SetPos(0, 26);//讓整個程序結束后的提示放到界面的最下面return 0;
}
- 讓游戲循環起來
void test()
{int ch = 0;do{//創建貪吃蛇Snake snake = { 0 };GameStart(&snake);//游戲開始前的初始化GameRun(&snake);//玩游戲的過程GameEnd(&snake);//善后的工作SetPos(20, 15);printf("再來一局嗎?(Y/N):");ch = getchar();getchar();//清理\n} while ('Y' == ch || 'y' == ch);
}
完整代碼:
//snake.h#include <locale.h>
#include <stdlib.h>
#include <windows.h>
#include <stdbool.h>
#include <stdio.h>#define WALL L'□'
#define BODY L'●'
#define FOOD L'★'//蛇默認的起始坐標
#define POS_X 24
#define POS_Y 5#define KEY_PRESS(VK) ((GetAsyncKeyState(VK) & 0x1) ? 1 : 0)//游戲的狀態
enum GAME_STATUS
{OK = 1,//正常運行ESC,//按了ESC鍵退出,正常退出KILL_BY_WALL,//撞墻KILL_BY_SELF//撞到自身
};//蛇行走的方向
enum DIRECTION
{UP = 1,DOWN,LEFT,RIGHT
};//蛇身結點的定義
typedef struct SnakeNode
{int x; int y;struct SnakeNode* next;
}SnakeNode, * pSnakeNode;//typedef struct SnakeNode* pSnakeNode;//上面的寫法和這個寫法是一個效果//貪吃蛇
typedef struct Snake
{pSnakeNode pSnake;//維護整條蛇的指針,是指向蛇頭pSnakeNode pFood;//指向食物的指針int Score;//當前累積的分數int FoodWeight;//一個食物的分數int SleepTime;//蛇休眠的時間,休眠的時間越短,蛇的速度越快,休眠的時間越長,蛇的速度越慢enum GAME_STATUS status;//當前的狀態enum DIRECTION dir;//蛇當前走的方向
}Snake, * pSnake;//游戲開始前的準備
void GameStart(pSnake ps);//游戲運行的整個邏輯
void GameRun(pSnake ps);//游戲結束的資源釋放
void GameEnd(pSnake ps);//定位控制臺光標位置
void SetPos(int x, int y);
//snake.c#include "snake.h"void SetPos(int x, int y)
{//獲得設備句柄HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);//根據句柄設置光標的位置COORD pos = { x, y };SetConsoleCursorPosition(handle, pos);
}void WelcomeToGame()
{//歡迎信息SetPos(35, 10);printf("歡迎來到貪吃蛇小游戲\n");SetPos(38, 20);system("pause");system("cls");//功能介紹信息SetPos(15, 10);printf("用 ↑ . ↓ . ← . → 來控制蛇的移動,F3是加速,F4是減速\n");SetPos(15, 11);printf("加速能得到更高的分數");SetPos(38, 20);system("pause");system("cls");
}void CreateMap()
{int i = 0;//上SetPos(0, 0);for (i = 0; i <= 56; i += 2){wprintf(L"%lc", WALL);}//下SetPos(0, 26);for (i = 0; i <= 56; i += 2){wprintf(L"%lc", WALL);}//左for (i = 1; i <= 25; i++){SetPos(0, i);wprintf(L"%lc", WALL);}//右for (i = 1; i <= 25; i++){SetPos(56, i);wprintf(L"%lc", WALL);}
}void InitSnake(pSnake ps)
{//創建5個蛇身的結點pSnakeNode cur = NULL;int i = 0;for (i = 0; i < 5; i++){cur = (pSnakeNode)malloc(sizeof(SnakeNode));if (NULL == cur){perror("InitSnake():malloc()");return;}cur->x = POS_X + 2 * i;cur->y = POS_Y;cur->next = NULL;//頭插法if (NULL == ps->pSnake){ps->pSnake = cur;}else{cur->next = ps->pSnake;ps->pSnake = cur;}}//打印蛇身while (cur){SetPos(cur->x, cur->y);wprintf(L"%lc", BODY);cur = cur->next;}//貪吃蛇的其他信息初始化ps->dir = RIGHT;ps->FoodWeight = 10;ps->pFood = NULL;ps->Score = 0;ps->SleepTime = 200;ps->status = OK;
}void CreateFood(pSnake ps)
{int x = 0;int y = 0;again:do{x = rand() % 53 + 2;y = rand() % 24 + 1;} while (x % 2 != 0);//坐標和蛇的身體的每個節點的坐標比較pSnakeNode cur = ps->pSnake;while (cur){if (x == cur->x && y == cur->y){goto again;}cur = cur->next;}//創建食物pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode));if (NULL == pFood){perror("CreatFood():malloc()");return;}pFood->x = x;pFood->y = y;pFood->next = NULL;ps->pFood = pFood;SetPos(x, y);wprintf(L"%lc", FOOD);
}void GameStart(pSnake ps)
{//設置控制臺的信息,窗口大小,窗口名system("mode con cols=100 lines=30");system("title 貪吃蛇");//隱藏光標HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);CONSOLE_CURSOR_INFO CursorInfo;GetConsoleCursorInfo(handle, &CursorInfo);//獲取控制臺光標信息CursorInfo.bVisible = false;SetConsoleCursorInfo(handle, &CursorInfo);//打印歡迎信息WelcomeToGame();//繪制地圖CreateMap();//初始化蛇InitSnake(ps);//創建食物CreateFood(ps);
}void PrintHelpInfo()
{SetPos(62, 15);printf("1.不能穿墻,不能咬到自己");SetPos(62, 16);printf("2.用 ↑.↓.←.→ 來控制蛇的移動");SetPos(62, 17);printf("3.F3是加速,F4是減速");SetPos(62, 18);printf("4.ESC:退出游戲 space:暫停游戲");
}void pause()
{while (1){Sleep(100);if (KEY_PRESS(VK_SPACE)){break;}}
}int NextIsFood(pSnake ps, pSnakeNode pNext)
{if (ps->pFood->x == pNext->x && ps->pFood->y == pNext->y){return 1;//下一個坐標處是食物}else{return 0;}
}void EatFood(pSnake ps, pSnakeNode pNext)
{pNext->next = ps->pSnake;ps->pSnake = pNext;//打印蛇pSnakeNode cur = ps->pSnake;while (cur){SetPos(cur->x, cur->y);wprintf(L"%lc", BODY);cur = cur->next;}ps->Score += ps->FoodWeight;//釋放舊的食物free(ps->pFood);ps->pFood = NULL;//新建食物CreateFood(ps);
}void NotEatFood(pSnake ps, pSnakeNode pNext)
{//頭插法pNext->next = ps->pSnake;ps->pSnake = pNext;//釋放尾結點pSnakeNode cur = ps->pSnake;while (cur->next->next){SetPos(cur->x, cur->y);wprintf(L"%lc", BODY);cur = cur->next;}SetPos(cur->x, cur->y);wprintf(L"%lc", BODY);//將尾節點的位置打印成空白字符SetPos(cur->next->x, cur->next->y);printf(" ");free(cur->next);cur->next = NULL;
}void KillByWall(pSnake ps)
{if (0 == ps->pSnake->x ||56 == ps->pSnake->x ||0 == ps->pSnake->y ||26 == ps->pSnake->y){ps->status = KILL_BY_WALL;}
}void KillBySelf(pSnake ps)
{pSnakeNode cur = ps->pSnake->next;//從第二個節點開始while (cur){if (cur->x == ps->pSnake->x && cur->y == ps->pSnake->y){ps->status = KILL_BY_SELF;return;}cur = cur->next;}
}void SnakeMove(pSnake ps)
{pSnakeNode pNext = (pSnakeNode)malloc(sizeof(SnakeNode));if (NULL == pNext){perror("SnakeMove()::malloc()");return;}pNext->next = NULL;switch (ps->dir){case UP:pNext->x = ps->pSnake->x;pNext->y = ps->pSnake->y - 1;break;case DOWN:pNext->x = ps->pSnake->x;pNext->y = ps->pSnake->y + 1;break;case LEFT:pNext->x = ps->pSnake->x - 2;pNext->y = ps->pSnake->y;break;case RIGHT:pNext->x = ps->pSnake->x + 2;pNext->y = ps->pSnake->y;break;}//下一個坐標處是否是食物if (NextIsFood(ps, pNext)){//是食物就吃掉EatFood(ps, pNext);}else{//不是食物就正常走一步NotEatFood(ps, pNext);}//檢測撞墻KillByWall(ps);//檢測撞到自己KillBySelf(ps);
}void GameRun(pSnake ps)
{//打印幫助信息PrintHelpInfo();if (KEY_PRESS(VK_UP) || KEY_PRESS(VK_DOWN) ||KEY_PRESS(VK_LEFT) || KEY_PRESS(VK_RIGHT) ||KEY_PRESS(VK_ESCAPE) || KEY_PRESS(VK_SPACE) ||KEY_PRESS(VK_F3) || KEY_PRESS(VK_F4)){;//消除之前 按任意鍵繼續 的影響,比如我按了空格然后進入了游戲,這里不檢測,那么下面的代碼會檢測到我按過空格,游戲一開始就會暫停}do{//當前的分數情況SetPos(62, 10);printf("總分:%5d\n", ps->Score);SetPos(62, 11);printf("食物的分值:%02d\n", ps->FoodWeight);//檢測按鍵//上、下、左、右、ESC、空格、F3、F4if (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_ESCAPE)){ps->status = ESC;break;}else if (KEY_PRESS(VK_SPACE)){//游戲要暫停pause();//暫停和恢復暫停}else if (KEY_PRESS(VK_F3)){if (ps->SleepTime > 80){ps->SleepTime -= 30;ps->FoodWeight += 2;}}else if (KEY_PRESS(VK_F4)){if (ps->FoodWeight > 2){ps->SleepTime += 30;ps->FoodWeight -= 2;}}//走一步SnakeMove(ps);//睡眠一下Sleep(ps->SleepTime);} while (OK == ps->status);
}void GameEnd(pSnake ps)
{SetPos(15, 12);switch (ps->status){case ESC:printf("主動退出游戲,正常退出\n");break;case KILL_BY_WALL:printf("很遺憾,撞墻了,游戲結束\n");break;case KILL_BY_SELF:printf("很遺憾,咬到自己了,游戲結束\n");break;}//釋放貪吃蛇的鏈表資源pSnakeNode cur = ps->pSnake;pSnakeNode del = NULL;while (cur){del = cur;cur = cur->next;free(del);del = NULL;}free(ps->pFood);ps->pFood = NULL;ps = NULL;
}
//test.c#include "snake.h"void test()
{int ch = 0;do{//創建貪吃蛇Snake snake = { 0 };GameStart(&snake);//游戲開始前的初始化GameRun(&snake);//玩游戲的過程GameEnd(&snake);//善后的工作SetPos(20, 15);printf("再來一局嗎?(Y/N):");ch = getchar();getchar();//清理\n} while ('Y' == ch || 'y' == ch);
}int main()
{//修改適配本地中文環境setlocale(LC_ALL, "");srand((unsigned int)time(NULL));test();//貪吃蛇游戲的測試SetPos(0, 26);return 0;
}