> 作者简介:დ旧言~,目前大二,现在学习Java,c,c++,Python等
> 座右铭:松树千年终是朽,槿花一日自为荣。> 目标:实现贪吃蛇
> 毒鸡汤:时间并不可真的帮我们去解决哪些问题,它只不过是会把原来怎么也想不通的问题,变得不再重要了。
> 望小伙伴们点赞👍收藏✨加关注哟💕💕
🌟前言
作为零零后的我们想必贪吃蛇都玩过吧,博主记得当时还在小霸王里面玩的这款小游戏,十分怀念,记忆深刻,每次都是偷偷躲在被窝里面玩,为了防止别抓,基本上是躲在厕所里面玩,上个厕所上个十年,丑事不必再提。作为一个程序员捏,当然要简单的实现它啦,也是为曾经的自己画上一个圆满的句号吧。那咱们闲话少谈,直接手撕贪吃蛇。
⭐游戏背景
我们采用的是VS2019编译环境,所以家人们准备好编译环境,当然咱们上手不能直接上代码,可能一开始上代码就是从入门到放弃,我懂大家的捏。咱们先介绍游戏,到时候反手就说,这个博主教我们怎么玩游戏,取关,必须取关,家人们不先知道怎么玩你怎么知道游戏的逻辑,给这些人拖出去斩了。
不知道大家还记得在C语言中我们实现了两款游戏,一个是三子棋,另一个是扫雷,这些游戏和贪吃蛇是久负盛名的游戏,它也和俄罗斯⽅块,扫雷等游戏位列经典游戏的⾏列。
博主后面这些经典小游戏都会一一实现,刺激
⭐游戏演示
看的是不是很神奇,一点也不神奇,不信你继续向下看。
贪吃蛇
⭐实现目标
使⽤C语⾔在Windows环境的控制台中模拟实现经典⼩游戏贪吃蛇
实现基本的功能:
• 贪吃蛇地图绘制
• 蛇吃⻝物的功能 (上、下、左、右⽅向键控制蛇的动作)
• 蛇撞墙死亡
• 蛇撞⾃⾝死亡
• 计算得分
• 蛇⾝加速、减速
• 暂停游戏
⭐技术要点

⭐win32 API介绍
使用Win32 API,应用程序可以充分挖掘Windows的32位操作系统的潜力。 Microsoft的所有32位平台都支持统一的API,包括函数、结构、消息、宏及接口。使用 Win32 API不但可以开发出在各种平台上都能成功运行的应用程序,而且也可以充分利用每个平台特有的功能和属性。
在具体编程时,程序实现方式的差异依赖于相应平台的底层功能的不同。最显著的差异是某些函数只能在更强大的平台上实现其功能。例如,安全函数只能在Windows NT操作系统下使用。另外一些主要差别就是系统限制,比如值的范围约束,或函数可管理的项目个数等等。
当然啦,咱们不用学习这里面全部知识,不然又要被黑子喷咯。
🌙Win32 API
有一些函数,并不用于交互,比如管理当前系统正在运行的进程、硬件系统状态的监视等等……这些函数只有一套,但是可以被所有的Windows程序调用(只要这个程序的权限足够高),简而言之,API是为程序所共享的。
为了达到所有程序能共享一套API的目的,Windows采用了“动态链接库”的办法。之所以叫“动态链接库”,是因为这样的函数库的调用方式是“随用随取”而不是像静态链接库那样“用不用都要带上”。
🌙控制台程序
像我们运行一个代码,就有一个黑框框,没错它就是控制台程序,Win32 API我们是可以控制它滴,终于有人可以管得住它了,不然它还是一个野孩子。
我们可以使⽤cmd命令来设置控制台窗⼝的⻓宽:设置控制台窗⼝的⼤⼩,30⾏,100列
1 mode con cols= 100 lines= 30
1 title 贪吃蛇![]()
🌙控制台屏幕上的坐标COORD
typedef struct _COORD {SHORT X;SHORT Y;
} COORD, * PCOORD;
COORD pos = { 10 , 15 };
🌙GetStdHandle
HANDLE GetStdHandle(DWORD nStdHandle);HANDLE hOutput = NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值)
hOutput = GetStdHandle(STD _OUTPUT_HANDLE);
🌙GetConsoleCursorInfo
检索有关指定控制台屏幕缓冲区的光标大小和可见性的信息
BOOL WINAPI GetConsoleCursorInfo(
HANDLE hConsoleOutput,
PCONSOLE_CURSOR_INFO lpConsoleCursorInfo
);
HANDLE hOutput = NULL ;// 获取标准输出的句柄 ( ⽤来标识不同设备的数值 )hOutput = GetStdHandle (STD_OUTPUT_HANDLE);CONSOLE_CURSOR_INFO CursorInfo;GetConsoleCursorInfo (hOutput, &CursorInfo); // 获取控制台光标信息
🌙SetConsoleCursorInfo
BOOL WINAPI SetConsoleCursorInfo (HANDLE hConsoleOutput,const CONSOLE_CURSOR_INFO *lpConsoleCursorInfo);
HANDLE hOutput = GetStdHandle (STD_OUTPUT_HANDLE);// 影藏光标操作CONSOLE_CURSOR_INFO CursorInfo;GetConsoleCursorInfo (hOutput, &CursorInfo); // 获取控制台光标信息CursorInfo.bVisible = false ; // 隐藏控制台光标SetConsoleCursorInfo (hOutput, &CursorInfo); // 设置控制台光标状态
🌙SetConsoleCursorPosition
BOOL WINAPI SetConsoleCursorPosition (HANDLE hConsoleOutput,COORD pos);
COORD pos = { 10 , 5 };HANDLE hOutput = NULL ;// 获取标准输出的句柄 ( ⽤来标识不同设备的数值 )hOutput = GetStdHandle (STD_OUTPUT_HANDLE);// 设置标准输出上光标的位置为 posSetConsoleCursorPosition (hOutput,pos);
// 设置光标的坐标void SetPos ( short x, short y){COORD pos = { x, y };HANDLE hOutput = NULL ;// 获取标准输出的句柄 ( ⽤来标识不同设备的数值 )hOutput = GetStdHandle (STD_OUTPUT_HANDLE);// 设置标准输出上光标的位置为 posSetConsoleCursorPosition (hOutput, pos);}
🌙GetAsyncKeyState
SHORT GetAsyncKeyState ( int vKey );
# define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )
⭐贪吃蛇游戏设计与分析
这里我们得了解游戏的逻辑和框架,别慌,一步一步慢慢来!!!
🌙地图设计
咱们看看地图是如何设计的:
既然要设计地图,必然需要在VS2019下控制台的坐标问题。

💫setlocale函数
char* setlocale (int category, const char* locale);
- setlocale 函数用于修改当前地区,可以针对⼀个类项修改,也可以针对所有类项。
- setlocale 的第一个参数可以是前面说明的类项中的一个,那么每次只会影响一个类项,如果第一个参数是LC_ALL,就会影响所有的类项。
C标准给第⼆个参数仅定义了2种可能取值:"C"和" "。在任意程序执⾏开始,都会隐藏式执⾏调⽤:
setlocale(LC_ALL, "C");
setlocale (LC_ALL, " " ); // 切换到本地环境
💫地图坐标

🌙蛇身和食物

🌙游戏流程设计
⭐核心逻辑实现分析(重点)
咱们从三个方面来讲解贪吃蛇的核心逻辑分析:test.c主函数,snake.h包含头文件,snake.c主函数的实现,跟我们玩链表差不多,都是老套路了那贪吃蛇的实现正式上路咯,启程。
🌙snake.h头文件的实现
像头文件这个东西必然是要包含一些头文件的,当然这只是它的一部分作用,像我们定义的链表,为了在各个源文件都能使用,我们就在头文件实现链表。如果函数很多,就把函数头包函数在头文件中,方便调用,那咱们看看在snake.h中有啥?
//包含头文件
#include <locale.h>
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<windows.h>
#include<time.h>
#include<stdbool.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 DIRECTION
{UP = 1,//上DOWN,//下LEFT,//左RIGHT//右
};//枚举游戏状态
enum GAME_STATUS
{OK,//正常运行END_NORMAL,//按ESC退出KILL_BY_WALL,KILL_BY_SELF
};//贪吃蛇每个节点的描述
typedef struct SnakeNode
{int x;int y;struct SnakeNode* next;//下个节点
}SnakeNode, * pSnakeNode;//贪吃蛇组成
typedef struct Snake
{pSnakeNode _pSnake;//指向贪吃蛇头结点的指针pSnakeNode _pFood;//指向食物结点的指针int _Score;//贪吃蛇累计的总分int _FoodWeight;//一个食物的分数int _SleepTime;//每走一步休息的时间,时间越短,速度越快,时间越长,速度越慢enum DIRECTION _Dir;//描述蛇的方向enum GAME_STATUS _Status;//游戏的状态:正常、退出、撞墙、吃到自己
}Snake, * pSnake;//游戏开始 - 完成游戏的初始化动作
void GameStart(pSnake ps);//定位坐标
void SetPos(short x, short y);//游戏开始的欢迎界面
void WelComeToGame();//打印地图
void CreateMap();//初始化贪吃蛇
void InitSnake(pSnake ps);//创建食物
void CreateFood(pSnake ps);//游戏的正常运行
void GameRun(pSnake ps);//打印帮助信息
void PrintHelpInfo();//游戏暂定和恢复
void Pause();//蛇的移动
void SnakeMove(pSnake ps);//判断蛇头到达的坐标处是否是食物
int NextIsFood(pSnake ps, pSnakeNode pnext);//吃掉食物
void EatFood(pSnake ps, pSnakeNode pnext);//不吃食物
void NoFood(pSnake ps, pSnakeNode pnext);//蛇是否撞墙
void KillByWall(pSnake ps);//蛇是否自杀
void KillBySelf(pSnake ps);//游戏结束后的善后处理
void GameEnd(pSnake ps);
🌙snake.c源文件的实现
在这个文件中咱们实现贪吃蛇的函数:实现时当然需要包含snake.h的文件啦,需要用头文件里面的链表等.....
🌠游戏开始 - 初始化游戏
//1. 游戏开始 - 初始化游戏
void GameStart(pSnake ps)
{//设置控制台的大小system("mode con cols=100 lines=30");system("title 贪吃蛇");//光标影藏掉HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//影藏光标操作CONSOLE_CURSOR_INFO CursorInfo;GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息CursorInfo.bVisible = false; //隐藏控制台光标SetConsoleCursorInfo(hOutput, &CursorInfo);//设置控制台光标状态//打印欢迎界面WelComeToGame();//创建地图CreateMap();//初始化贪食蛇InitSnake(ps);//创建食物CreateFood(ps);
}
💫设置光标的坐标
光标的移动需要多次实现,为了我们方便使用移动光标位置,所以我们直接实现这个函数。不会有人跟我说光标是啥吧,看我打不死你,看到没:
设置光标位置在win32 API已经介绍咯。
//设置光标的坐标
void SetPos(short x, short y)
{COORD pos = { x, y };HANDLE hOutput = NULL;//获取标准输出的句柄(用来标识不同设备的数值)hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//设置标准输出上光标的位置为posSetConsoleCursorPosition(hOutput, pos);
}
💫打印欢迎界面
其实这个函数大家可以自己设置,把欢迎界面整的好看些,像博主就比较懒就简单设计一下咯。
//打印欢迎界面
void WelComeToGame()
{//定位光标SetPos(40, 14);printf("欢迎来到贪吃蛇小游戏");SetPos(40, 25);system("pause");//pause是暂停system("cls");SetPos(20, 14);printf("使用 ↑ . ↓ . ← . → . 分别控制蛇的移动, F3是加速,F4是减速");SetPos(40, 25);system("pause");system("cls");
}
💫创建地图/初始化贪食蛇
大家眼睛擦亮点,计算坐标时不要计算错了,可以根据自己设计自己喜欢的图:
切记一定要把蛇初始状态需要是偶数倍:初始化状态,假设蛇的长度是5,蛇身的每个节点是●,在固定的一个坐标处,比如(24, 5)处开始出现蛇,连续5个节点。注意:蛇的每个节点的x坐标必须是2个倍数,否则可能会出现蛇的⼀个节点有一半儿出现在墙体中,另外⼀般在墙外的现象,坐标不好对齐。
//创建地图
void CreateMap()
{//上SetPos(0, 0);int i = 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)
{//初始化蛇身 0 0 0 0 0 pSnakeNode cur = NULL;//循环5个节点for (int i = 0; i < 5; i++){//开辟空间cur = (pSnakeNode)malloc(sizeof(SnakeNode));//判断是否开辟成功if (cur == NULL){perror("InitSnake:malloc");exit(-1);}//初始化蛇的位置在(24,5)cur->x = POS_X + i * 2;cur->y = POS_Y;cur->next = NULL;//开始链接//头插if (ps->_pSnake == NULL){ps->_pSnake = cur;}else{cur->next = ps->_pSnake;ps->_pSnake = cur;}}//打印蛇身cur = ps->_pSnake;//指向贪吃蛇头结点的指针while (cur){//定位坐标SetPos(cur->x, cur->y);//打印wprintf(L"%lc", BODY);//指向下一个节点cur = cur->next;}//对贪吃蛇的每个组成都初始化ps->_Status = OK;//游戏的状态ps->_Score = 0;//贪吃蛇累计的总分ps->_pFood = NULL;//指向食物结点的指针ps->_SleepTime = 200;//每走一步休息的时间ps->_FoodWeight = 10;//一个食物的分数//(我们默认蛇开始向右走)ps->_Dir = RIGHT;//描述蛇的方向
}
💫创建食物
关于食物,就是在墙体内随机生成⼀个坐标(x坐标必须是2的倍数),坐标不能和蛇的身体重合,然后打印★。
在创建食物时我们需要注意一些事项:小本本拿好咯
- 食物可能与创建的蛇相同:就必须从新创建食物。(这里采用again)
- 食物需要开辟空间的:方便和蛇链接嘛,毕竟是单链表
- 食物的一些初始化
//创建食物
void CreateFood(pSnake ps)
{//初始化int x = 0;int y = 0;
again:do{x = rand() % 53 + 2;y = rand() % 25 + 1;} while (x % 2 != 0);//x坐标必须是2的倍数//坐标不能和蛇的身体冲突pSnakeNode cur = ps->_pSnake;while (cur){//比较坐标if (cur->x == x && cur->y == y){goto again;}cur = cur->next;}//给食物开辟空间pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode));//判断if (pFood == NULL){perror("CreateFood()::malloc()");return;}//初始化pFood->x = x;pFood->y = y;ps->_pFood = pFood;//打印食物SetPos(x, y);wprintf(L"%lc", FOOD);}
🌠游戏运行 - 游戏的正常运行过程
//2. 游戏运行 - 游戏的正常运行过程
void GameRun(pSnake ps)
{//打印帮助信息PrintHelpInfo();//循环方向,直到方向正确do{//打印得分SetPos(64, 10);printf("得分:%05d", ps->_Score);SetPos(64, 11);printf("每个食物的分数:%2d", ps->_FoodWeight);//判断蛇方向,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 = END_NORMAL;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->_SleepTime < 320){ps->_SleepTime += 30;ps->_FoodWeight -= 2;}}//蛇动的时间Sleep(ps->_SleepTime);//蛇的移动SnakeMove(ps);} while (ps->_Status == OK);
}
💫打印帮助信息
这个函数根据自己的需要而写,这个就是看大家的创意咯
//打印帮助信息
void PrintHelpInfo()
{SetPos(64, 15);printf("1.不能撞墙,不能咬到自己");SetPos(64, 16);printf("2.使用 ↑.↓.←.→ 分别控制蛇的移动");SetPos(64, 17);printf("3.F3加速,F4减速");SetPos(64, 18);printf("4.ESC-退出, 空格-暂停游戏");SetPos(64, 20);printf("旧言专用版权");}
💫判断蛇头到达的坐标处是否是食物
这个函数其实很挫,判断一下x和y坐标就行。
//判断蛇头到达的坐标处是否是食物
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;}//释放食物空间free(ps->_pFood);//加分数ps->_Score += ps->_FoodWeight;//创建食物CreateFood(ps);//新创建食物
}
💫不吃食物
运转到下一个坐标,开始覆盖,用头插,把前面的蛇尾置为空。
//不吃食物
void NoFood(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->next->x, cur->next->y);printf(" ");//释放空间free(cur->next);cur->next = NULL;
}
💫蛇是否撞墙
这个函数还是比较简单的,判断墙体的坐标是否和蛇移动到的坐标相同
//蛇是否自杀
void KillBySelf(pSnake ps)
{//遍历一遍蛇的坐标就行pSnakeNode cur = ps->_pSnake->next;while (cur){if (ps->_pSnake->x == cur->x && ps->_pSnake->y == cur->y){//游戏状态为自杀ps->_Status = KILL_BY_SELF;}//找下一个节点cur = cur->next;}
}
💫蛇的移动
蛇既然要移动到下一个坐标,所以需要开辟下一个位置的空间,再判断蛇是否到食物。
等到蛇移动到下一个坐标再判断蛇是否撞墙自杀。
//蛇的移动
void SnakeMove(pSnake ps)
{//开辟蛇移动到下一个格子的空间pSnakeNode pNext = (pSnakeNode)malloc(sizeof(SnakeNode));//判断if (pNext == NULL){perror("SnakeMove()::malloc()");exit(-1);}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{//不吃食物NoFood(ps, pNext);}//蛇是否撞墙KillByWall(ps);//蛇是否自杀KillBySelf(ps);}
💫暂停蛇移动
让蛇可以有视觉效果,打印时,休息一会儿。
//暂停蛇移动
void Pause()
{while (1){Sleep(100);if (KEY_PRESS(VK_SPACE)){break;}}
}
🌠游戏结束 - 游戏善后(释放资源)
等于释放内存,这个表示我们熟的来:
//3. 游戏结束 - 游戏善后(释放资源)
void GameEnd(pSnake ps)
{//判断游戏状态SetPos(20, 12);switch (ps->_Status){case END_NORMAL:printf("您主动退出游戏\n");break;case KILL_BY_SELF:printf("自杀了,游戏结束\n");break;case KILL_BY_WALL:printf("撞墙了,游戏结束\n");break;}//释放蛇身的结点pSnakeNode cur = ps->_pSnake;while (cur){pSnakeNode del = cur;cur = cur->next;free(del);}ps->_pSnake = NULL;}
🌙test.c源文件的实现
直接上代码好叭
//包含头文件
#include"snake.h"void test()
{int ch = 0;do{Snake snake = { 0 };//创建了贪吃蛇//1. 游戏开始 - 初始化游戏GameStart(&snake);//2. 游戏运行 - 游戏的正常运行过程GameRun(&snake);//3. 游戏结束 - 游戏善后(释放资源)GameEnd(&snake);SetPos(20, 18);printf("再来一局吗?(Y/N):");ch = getchar();getchar();// 清理掉\n} while (ch == 'Y' || ch == 'y');SetPos(0, 27);
}int main()
{//设置程序适应本地环境setlocale(LC_ALL, "");//设置语言环境srand((unsigned int)time(NULL));//游戏睡眠状态//调用函数test();return 0;
}
🌞总代码
💧snake.c
#define _CRT_SECURE_NO_WARNINGS 1//包含头文件
#include"snake.h"//设置光标的坐标
void SetPos(short x, short y)
{COORD pos = { x, y };HANDLE hOutput = NULL;//获取标准输出的句柄(用来标识不同设备的数值)hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//设置标准输出上光标的位置为posSetConsoleCursorPosition(hOutput, pos);
}//打印欢迎界面
void WelComeToGame()
{//定位光标SetPos(40, 14);printf("欢迎来到贪吃蛇小游戏");SetPos(40, 25);system("pause");//pause是暂停system("cls");SetPos(20, 14);printf("使用 ↑ . ↓ . ← . → . 分别控制蛇的移动, F3是加速,F4是减速");SetPos(40, 25);system("pause");system("cls");
}//创建地图
void CreateMap()
{//上SetPos(0, 0);int i = 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)
{//初始化蛇身 0 0 0 0 0 pSnakeNode cur = NULL;//循环5个节点for (int i = 0; i < 5; i++){//开辟空间cur = (pSnakeNode)malloc(sizeof(SnakeNode));//判断是否开辟成功if (cur == NULL){perror("InitSnake:malloc");exit(-1);}//初始化蛇的位置在(24,5)cur->x = POS_X + i * 2;cur->y = POS_Y;cur->next = NULL;//开始链接//头插if (ps->_pSnake == NULL){ps->_pSnake = cur;}else{cur->next = ps->_pSnake;ps->_pSnake = cur;}}//打印蛇身cur = ps->_pSnake;//指向贪吃蛇头结点的指针while (cur){//定位坐标SetPos(cur->x, cur->y);//打印wprintf(L"%lc", BODY);//指向下一个节点cur = cur->next;}//对贪吃蛇的每个组成都初始化ps->_Status = OK;//游戏的状态ps->_Score = 0;//贪吃蛇累计的总分ps->_pFood = NULL;//指向食物结点的指针ps->_SleepTime = 200;//每走一步休息的时间ps->_FoodWeight = 10;//一个食物的分数//(我们默认蛇开始向右走)ps->_Dir = RIGHT;//描述蛇的方向
}//创建食物
void CreateFood(pSnake ps)
{//初始化int x = 0;int y = 0;
again:do{x = rand() % 53 + 2;y = rand() % 25 + 1;} while (x % 2 != 0);//x坐标必须是2的倍数//坐标不能和蛇的身体冲突pSnakeNode cur = ps->_pSnake;while (cur){//比较坐标if (cur->x == x && cur->y == y){goto again;}cur = cur->next;}//给食物开辟空间pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode));//判断if (pFood == NULL){perror("CreateFood()::malloc()");return;}//初始化pFood->x = x;pFood->y = y;ps->_pFood = pFood;//打印食物SetPos(x, y);wprintf(L"%lc", FOOD);}//1. 游戏开始 - 初始化游戏
void GameStart(pSnake ps)
{//设置控制台的大小system("mode con cols=100 lines=30");system("title 贪吃蛇");//光标影藏掉HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//影藏光标操作CONSOLE_CURSOR_INFO CursorInfo;GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息CursorInfo.bVisible = false; //隐藏控制台光标SetConsoleCursorInfo(hOutput, &CursorInfo);//设置控制台光标状态//打印欢迎界面WelComeToGame();//创建地图CreateMap();//初始化贪食蛇InitSnake(ps);//创建食物CreateFood(ps);
}//打印帮助信息
void PrintHelpInfo()
{SetPos(64, 15);printf("1.不能撞墙,不能咬到自己");SetPos(64, 16);printf("2.使用 ↑.↓.←.→ 分别控制蛇的移动");SetPos(64, 17);printf("3.F3加速,F4减速");SetPos(64, 18);printf("4.ESC-退出, 空格-暂停游戏");SetPos(64, 20);printf("旧言专用版权");//getchar();
}//判断蛇头到达的坐标处是否是食物
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;}//释放食物空间free(ps->_pFood);//加分数ps->_Score += ps->_FoodWeight;//创建食物CreateFood(ps);//新创建食物
}//不吃食物
void NoFood(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->next->x, cur->next->y);printf(" ");//释放空间free(cur->next);cur->next = NULL;
}//蛇是否撞墙
void KillByWall(pSnake ps)
{if (ps->_pSnake->x == 0 ||ps->_pSnake->x == 56 ||ps->_pSnake->y == 0 ||ps->_pSnake->y == 26)ps->_Status = KILL_BY_WALL;
}//蛇是否自杀
void KillBySelf(pSnake ps)
{//遍历一遍蛇的坐标就行pSnakeNode cur = ps->_pSnake->next;while (cur){if (ps->_pSnake->x == cur->x && ps->_pSnake->y == cur->y){//游戏状态为自杀ps->_Status = KILL_BY_SELF;}//找下一个节点cur = cur->next;}
}//蛇的移动
void SnakeMove(pSnake ps)
{//开辟蛇移动到下一个格子的空间pSnakeNode pNext = (pSnakeNode)malloc(sizeof(SnakeNode));//判断if (pNext == NULL){perror("SnakeMove()::malloc()");exit(-1);}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{//不吃食物NoFood(ps, pNext);}//蛇是否撞墙KillByWall(ps);//蛇是否自杀KillBySelf(ps);}//暂停蛇移动
void Pause()
{while (1){Sleep(100);if (KEY_PRESS(VK_SPACE)){break;}}
}//2. 游戏运行 - 游戏的正常运行过程
void GameRun(pSnake ps)
{//打印帮助信息PrintHelpInfo();//循环方向,直到方向正确do{//打印得分SetPos(64, 10);printf("得分:%05d", ps->_Score);SetPos(64, 11);printf("每个食物的分数:%2d", ps->_FoodWeight);//判断蛇方向,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 = END_NORMAL;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->_SleepTime < 320){ps->_SleepTime += 30;ps->_FoodWeight -= 2;}}//蛇动的时间Sleep(ps->_SleepTime);//蛇的移动SnakeMove(ps);} while (ps->_Status == OK);
}//3. 游戏结束 - 游戏善后(释放资源)
void GameEnd(pSnake ps)
{//判断游戏状态SetPos(20, 12);switch (ps->_Status){case END_NORMAL:printf("您主动退出游戏\n");break;case KILL_BY_SELF:printf("自杀了,游戏结束\n");break;case KILL_BY_WALL:printf("撞墙了,游戏结束\n");break;}//释放蛇身的结点pSnakeNode cur = ps->_pSnake;while (cur){pSnakeNode del = cur;cur = cur->next;free(del);}ps->_pSnake = NULL;}
💧snake.h
#pragma once//包含头文件
#include <locale.h>
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<windows.h>
#include<time.h>
#include<stdbool.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 DIRECTION
{UP = 1,//上DOWN,//下LEFT,//左RIGHT//右
};//枚举游戏状态
enum GAME_STATUS
{OK,//正常运行END_NORMAL,//按ESC退出KILL_BY_WALL,KILL_BY_SELF
};//贪吃蛇每个节点的描述
typedef struct SnakeNode
{int x;int y;struct SnakeNode* next;//下个节点
}SnakeNode, * pSnakeNode;//贪吃蛇组成
typedef struct Snake
{pSnakeNode _pSnake;//指向贪吃蛇头结点的指针pSnakeNode _pFood;//指向食物结点的指针int _Score;//贪吃蛇累计的总分int _FoodWeight;//一个食物的分数int _SleepTime;//每走一步休息的时间,时间越短,速度越快,时间越长,速度越慢enum DIRECTION _Dir;//描述蛇的方向enum GAME_STATUS _Status;//游戏的状态:正常、退出、撞墙、吃到自己
}Snake, * pSnake;//游戏开始 - 完成游戏的初始化动作
void GameStart(pSnake ps);//定位坐标
void SetPos(short x, short y);//游戏开始的欢迎界面
void WelComeToGame();//打印地图
void CreateMap();//初始化贪吃蛇
void InitSnake(pSnake ps);//创建食物
void CreateFood(pSnake ps);//游戏的正常运行
void GameRun(pSnake ps);//打印帮助信息
void PrintHelpInfo();//游戏暂定和恢复
void Pause();//蛇的移动
void SnakeMove(pSnake ps);//判断蛇头到达的坐标处是否是食物
int NextIsFood(pSnake ps, pSnakeNode pnext);//吃掉食物
void EatFood(pSnake ps, pSnakeNode pnext);//不吃食物
void NoFood(pSnake ps, pSnakeNode pnext);//蛇是否撞墙
void KillByWall(pSnake ps);//蛇是否自杀
void KillBySelf(pSnake ps);//游戏结束后的善后处理
void GameEnd(pSnake ps);
💧test.c
#define _CRT_SECURE_NO_WARNINGS 1//包含头文件
#include"snake.h"void test()
{int ch = 0;do{Snake snake = { 0 };//创建了贪吃蛇//1. 游戏开始 - 初始化游戏GameStart(&snake);//2. 游戏运行 - 游戏的正常运行过程GameRun(&snake);//3. 游戏结束 - 游戏善后(释放资源)GameEnd(&snake);SetPos(20, 18);printf("再来一局吗?(Y/N):");ch = getchar();getchar();// 清理掉\n} while (ch == 'Y' || ch == 'y');SetPos(0, 27);
}int main()
{//设置程序适应本地环境setlocale(LC_ALL, "");//设置语言环境srand((unsigned int)time(NULL));//游戏睡眠状态//调用函数test();return 0;
}
🌟结束语
今天内容就到这里啦,时间过得很快,大家沉下心来好好学习,会有一定的收获的,大家多多坚持,嘻嘻,成功路上注定孤独,因为坚持的人不多。那请大家举起自己的小说手给博主一键三连,有你们的支持是我最大的动力💞💞💞,回见。