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、F4 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_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、F4 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_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; }