手把手教你实现贪吃蛇
> 作者简介:დ旧言~,目前大二,现在学习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;
}
🌟结束语
今天内容就到这里啦,时间过得很快,大家沉下心来好好学习,会有一定的收获的,大家多多坚持,嘻嘻,成功路上注定孤独,因为坚持的人不多。那请大家举起自己的小说手给博主一键三连,有你们的支持是我最大的动力💞💞💞,回见。
相关文章:

手把手教你实现贪吃蛇
> 作者简介:დ旧言~,目前大二,现在学习Java,c,c,Python等 > 座右铭:松树千年终是朽,槿花一日自为荣。 > 目标:实现贪吃蛇 > 毒鸡汤:时间并不可真…...

存储服务器和普通服务器有哪些区别
存储服务器和普通服务器有哪些区别 典型的服务器会被配置来执行多种功能,如它可以作为文件服务器、打印服务器、应用数据库服务器、Web服务器,甚至可以是集以上多种功能于一身。这样,它就必须有快速的处理器芯片、比较多的RAM以及足够的内部…...

python数据处理作业4:使用numpy数组对象,随机创建4*4的矩阵,并提取其对角元素
每日小语 真理诚然是一个崇高的字眼,然而更是一桩崇高的业绩。如果人的心灵与情感依然健康,则其心潮必将为之激荡不已。——黑格尔 难点:如何创建?取对角元素的函数是什么? gpt代码学习 import numpy as np# 随机创…...

每日一题----昂贵的婚礼
#include <iostream> #include <algorithm> #include <cstring> #include <queue> #include <vector> using namespace std; //本题酋长的允诺也算一个物品,最后一定要交给酋长,那么等级不能超过酋长的等级范围const int N 150 * 15…...

css实战——清除列表中最后一个元素的下边距
需求描述 常见于列表的排版,如文章列表、用户列表、商品列表等。 代码实现 <div class"listBox"><div class"itemBox">文章1</div><div class"itemBox">文章2</div><div class"itemBox"…...

Clickhouse学习笔记(15)—— Clickhouse备份
手动备份 参考官网:Backup and Restore | ClickHouse Docs 简单来说,就是我们可以通过ALTER TABLE ... FREEZE PARTITION ...命令为表分区创建一个本地副本,然后这个副本硬链接到/var/lib/clickhouse/shadow/文件夹,因此其不会耗…...

想买GPT4会员却只能排队?来看看背后的故事!
文章目录 🧐 为什么要进候选名单?🔍 究竟发生了什么?😮 IOS端还能买会员!🤔 网页端为啥不能订会员?第一点:防止黑卡消费第二点:当技术巨头遇上资源瓶颈&#…...

Oracle(17)Managing Roles
目录 一、基础知识 1、基础介绍 2、Predefined Roles 预定义的角色 3、各种角色的介绍 二、基础操作 1、创建角色 2、修改用户默认role 3、回收role 4、删除role 5、为角色授权 6、授予角色给用户 7、查看用户包含的角色: 8、查看角色所包含的权限 9、…...

小程序中如何设置门店信息
小程序是商家转型升级的利器,小程序中门店信息的准确性和完整性对于用户的体验和信任度都有很大的影响。下面具体介绍门店信息怎么在小程序中进行设置。 在小程序管理员后台->门店设置处,可以门店设置相关。主要分为2个模块,一个是门店级…...

SCons
什么是构建工具(系统) 构建工具(software construction tool)是一种软件,它可以**根据一定的规则或指令,将源代码编译成可执行的二进制程序。**这是构建工具最基本也最重要的功能。 实际上构建工具的功能…...

蓝桥杯每日一题2023.11.14
题目描述 题目分析 此题目的最终目标是将字母都填上数使等式符合条件,实际我们发现可以使用搜索将所有符合条件的进行判断(答案:29) 由于小数可能会出现错误故我们将其进行简单变化进行搜索 #include<bits/stdc.h> using…...

力扣labuladong——一刷day33
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、力扣652. 寻找重复的子树 前言 前序位置的代码只能从函数参数中获取父节点传递来的数据,而后序位置的代码不仅可以获取参数数据,还可…...

spring cloud alibaba之nacos
spring cloud nacos 安装和启动nacos # 解压nacos安装包 # tar -zvxf nacos-server-1.4.1.tar.gz# nacos默认是以集群的模式启动,此处先用单机模式 # cd /usr/local/mysoft/nacos/bin # sh startup.sh -m standalone# nacos 日志 # tail -f /usr/local/mysoft/na…...

python自动化第一篇—— 带图文的execl的自动化合并
简述 最近接到一个需求,需要为公司里的一个部门提供一个文件上传自动化合并的系统,以供用户稽核,谈到自动化,肯定是选择python,毕竟python的轮子多。比较了市面上几个用得多的python库,我最终选择了xlwings…...

使用 Redis 实现分布式锁,解决分布式锁原子性、死锁、误删、可重入、自动续期等问题(使用SpringBoot环境实现)
目录 一、前言二、分布式锁具备的特点三、Redis分布式锁的实现核心思路四、分布式锁代码实现(解决分布式锁原子性、死锁、误删、可重入、自动续期等问题)4.1、分布式锁实现工具类4.2、测试分布式锁效果 五、分布式锁常见问题以及解决方法5.1、分布式锁死…...

mysql oracle统计报表每天每月每年SQL
mysql查询当天、昨天、本周、上周、近7天、近30天、本月、上个月、近6个月、本季度、上季度、本年和去年的数据 注意 在 XML 中 < 应该转为 < 当天 SELECT * FROM 表名 WHERE TO_DAYS(时间字段名) TO_DAYS(NOW()); 昨天 SELECT * FROM 表名 WHERE TO_DAYS(NOW()) - TO…...

通过Python设置及读取PDF属性,轻松管理PDF文档
PDF文档属性是嵌入在PDF文档中的一些与文档有关的信息,如作者、制作软件、标题、主题等。PDF属性分为默认属性和自定义属性两种,其中默认属性是一些固定的文档信息,部分信息自动生成(如文件大小、页数、页面大小等信息)…...

10. 深度学习——模型优化
机器学习面试题汇总与解析——模型优化 本章讲解知识点 前言低秩近似剪枝与稀疏约束参数量化二值网络知识蒸馏紧凑的网络结构本专栏适合于Python已经入门的学生或人士,有一定的编程基础。本专栏适合于算法工程师、机器学习、图像处理求职的学生或人士。本专栏针对面试题答案进…...

macos 上彻底卸载 DevEco Studio
1. 退出DevEco Studio: 确保DevEco Studio没有在运行。如果它在Dock中,可以右键点击其图标,然后选择退出。或者使用Command Q快捷键确保应用程序完全退出。 2. 删除DevEco Studio应用程序: 打开“应用程序”文件夹&#x…...

Nginx(五) break,if,return,rewrite和set指令的执行顺序深究
本篇文章主要对break,if,return,rewrite和set这5个指令的执行顺序进行深究,如需了解这5个指令的功能和配置,请参考另一篇文章 Nginx(三) 配置文件详解 由于文章篇幅较长,所以我就先把结论贴出来,…...

八大学习方法(金字塔模型、费曼学习法、布鲁姆学习模型)
在微博上看到博主发的,觉得总结很好,在此摘录:...

K8S的基础知识
K8S的意义与入门 专有名词 容器:包含了运行一个应用程序所需要的所有东西,包括:代码、运行时、各种依赖和配置。pod:K8s调度的最小单元,包含一个或多个容器。一个容器组中的容器具有紧密耦合性,共享资源,存储空间和IP。即同一个容器组中的容器可以通过localhost:xxx访问…...

java:基于jjwt写一个jwt工具类
背景 在Java中,使用JWT(JSON Web Tokens)相关的包通常包括以下内容: jjwt:JJWT是一个非常流行的Java JWT库,它提供了简单易用的API来创建和验证JWT。jose4j:JOSE4J是一个用于处理JSON Web签名…...

AK F.*ing leetcode 流浪计划之半平面求交
欢迎关注更多精彩 关注我,学习常用算法与数据结构,一题多解,降维打击。 本期话题:半平面求交 背景知识 学习资料 视频讲解 https://www.bilibili.com/video/BV1jL411C7Ct/?spm_id_from333.1007.top_right_bar_window_history…...

docker搭建zokeeper集群、kafka集群
三台机器,ip分别为ip1,ip2,ip3 一、安装docker集群 1、三台机器分别拉取镜像 docker pull wurstmeister/zookeeper 2、三台机器分别运行容器 (1)第一台 docker run -d --restartalways --log-driver json-file --log-opt max-size100m --lo…...

【java学习—十四】反射机制调用指定方法、指定属性(5)
文章目录 1. 调用指定方法2. 调用指定属性 1. 调用指定方法 通过反射,调用类中的方法,通过 Method 类完成。步骤: ①通过 Class 类的 getMethod(String name,Class...parameterTypes) 方法取得一个 Method 对象,并设置此…...

PC端微信@所有人逻辑漏洞
(一)过程 这个漏洞是PC端微信,可以越权让非管理员艾特所有人,具体步骤如下 第一步:找一个自己的群(要有艾特所有人的权限)“123”是我随便输入的内容,可以更改,然后按c…...

如何在Windows 10中进行屏幕截图
本文介绍如何在Windows 10中捕获屏幕截图,包括使用键盘组合、使用Snipping Tool、Snipp&Sketch Tool或Windows游戏栏。 使用打印屏幕在Windows 10中捕获屏幕截图 在Windows 10中捕获屏幕截图的最简单方法是按下键盘上的PrtScWindows键盘组合。你将看到屏幕短暂…...

【nlp】2.4 GRU模型
GRU模型 1 GRU介绍2 GRU的内部结构图2.1 GRU结构分析2.2 Bi-GRU介绍2.3 使用Pytorch构建GRU模型2.4 GRU优缺点3 RNN及其变体1 GRU介绍 GRU(Gated Recurrent Unit)也称门控循环单元结构, 它也是传统RNN的变体, 同LSTM一样能够有效捕捉长序列之间的语义关联, 缓解梯度消失或爆…...

国科云:浅谈DNS缓存投毒常见类型和防御策略
为了提升解析效率减轻各级服务器的解析压力,DNS系统中引入了缓存机制,但这同样也带来了较大的安全隐患,为攻击者利用DNS缓存进行投毒攻击创造了条件,对DNS系统的安全造成了巨大破坏。本文国科云将分析缓存投毒的两种主要类型&…...