阿里云 备案 网站服务内容,网站平面设计培训,做菠菜网站有没有被骗的,网站 做购物车 作者简介#xff1a;დ旧言~#xff0c;目前大二#xff0c;现在学习Java#xff0c;c#xff0c;c#xff0c;Python等 座右铭#xff1a;松树千年终是朽#xff0c;槿花一日自为荣。 目标#xff1a;实现贪吃蛇 毒鸡汤#xff1a;时间并不可真… 作者简介დ旧言~目前大二现在学习JavaccPython等 座右铭松树千年终是朽槿花一日自为荣。 目标实现贪吃蛇 毒鸡汤时间并不可真的帮我们去解决哪些问题它只不过是会把原来怎么也想不通的问题变得不再重要了。 望小伙伴们点赞收藏✨加关注哟 前言 作为零零后的我们想必贪吃蛇都玩过吧博主记得当时还在小霸王里面玩的这款小游戏十分怀念记忆深刻每次都是偷偷躲在被窝里面玩为了防止别抓基本上是躲在厕所里面玩上个厕所上个十年丑事不必再提。作为一个程序员捏当然要简单的实现它啦也是为曾经的自己画上一个圆满的句号吧。那咱们闲话少谈直接手撕贪吃蛇。 ⭐游戏背景 我们采用的是VS2019编译环境所以家人们准备好编译环境当然咱们上手不能直接上代码可能一开始上代码就是从入门到放弃我懂大家的捏。咱们先介绍游戏到时候反手就说这个博主教我们怎么玩游戏取关必须取关家人们不先知道怎么玩你怎么知道游戏的逻辑给这些人拖出去斩了。 不知道大家还记得在C语言中我们实现了两款游戏一个是三子棋另一个是扫雷这些游戏和贪吃蛇是久负盛名的游戏它也和俄罗斯⽅块扫雷等游戏位列经典游戏的⾏列。 博主后面这些经典小游戏都会一一实现刺激
⭐游戏演示
看的是不是很神奇一点也不神奇不信你继续向下看。 贪吃蛇 ⭐实现目标
使⽤C语⾔在Windows环境的控制台中模拟实现经典⼩游戏贪吃蛇 实现基本的功能 • 贪吃蛇地图绘制 • 蛇吃⻝物的功能 上、下、左、右⽅向键控制蛇的动作 • 蛇撞墙死亡 • 蛇撞⾃⾝死亡 • 计算得分 • 蛇⾝加速、减速 • 暂停游戏
⭐技术要点 C语⾔、数据结构链表、枚举、结构体、动态内存管理、预处理指令、win32 API这个咱们后面讲解这些基本上都学过哟像链表不知道大家能不能手撕链表不会的拖出去打30大板。 ⭐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 COORD 是Windows API中定义的⼀种结构表示⼀个字符在控制台屏幕上的坐标 typedef struct _COORD {SHORT X;SHORT Y;
} COORD, * PCOORD; 给坐标赋值 COORD pos { 10 , 15 }; GetStdHandle GetStdHandle是⼀个Windows API函数。它⽤于从⼀个特定的标准设备标准输⼊、标准输出或标准错误中取得⼀个句柄⽤来标识不同设备的数值使⽤这个句柄可以操作设备。简单的来讲就是可以用键盘控制控制台。 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 设置指定控制台屏幕缓冲区中的光标位置我们将想要设置的坐标信息放在COORD类型的pos中调用SetConsoleCursorPosition函数将光标位置设置到指定的位置。 BOOL WINAPI SetConsoleCursorPosition ( HANDLE hConsoleOutput, COORD pos ); 实例 COORD pos { 10 , 5 }; HANDLE hOutput NULL ; // 获取标准输出的句柄 ( ⽤来标识不同设备的数值 ) hOutput GetStdHandle (STD_OUTPUT_HANDLE); // 设置标准输出上光标的位置为 pos SetConsoleCursorPosition (hOutput,pos); SetPos封装⼀个设置光标位置的函数 // 设置光标的坐标 void SetPos ( short x, short y) { COORD pos { x, y }; HANDLE hOutput NULL ; // 获取标准输出的句柄 ( ⽤来标识不同设备的数值 ) hOutput GetStdHandle (STD_OUTPUT_HANDLE); // 设置标准输出上光标的位置为 pos SetConsoleCursorPosition (hOutput, pos); } GetAsyncKeyState 获取按键情况GetAsyncKeyState的函数原型如下 SHORT GetAsyncKeyState ( int vKey ); 将键盘上每个键的虚拟键值传递给函数函数通过返回值来分辨按键的状态。 是short类型在上⼀次调⽤ GetAsyncKeyState 函数后如果返回的16位的short数据中最⾼位是1说明按键的状态是按下如果最⾼是0说明按键的状态是抬起如果最低位被置为1则说明该按键被按过否则为0。如果我们要判断⼀个键是否被按过可以检测GetAsyncKeyState返回值的最低值是否为1. # define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) 0x1) ? 1 : 0 ) ⭐贪吃蛇游戏设计与分析 这里我们得了解游戏的逻辑和框架别慌一步一步慢慢来 地图设计 咱们看看地图是如何设计的 既然要设计地图必然需要在VS2019下控制台的坐标问题。 这里不得不讲一下控制台窗口的一些知识如果想在控制台的窗⼝中指定位置输出信息我们得知道该位置的坐标所以首先介绍一下控制台窗⼝的坐标知识。控制台窗口的坐标如下所示横向的是X轴从左向右依次增长纵向是Y轴从上到下依次增长。 在游戏地图上我们打印墙体使⽤宽字符□打印蛇使用宽字符●打印食物使用宽字符★普通的字符是占⼀个字节的这类宽字符是占用2个字节。这里再简单的讲⼀下C语言的国际化特性相关的知识过去C语言并不适合非英语国家地区使用。C语言最初假定字符都是但自己的。但是这些假定并不是在世界的任何地方都适用。 后来为了使C语言适应国家化C语言的标准中不断加入了国际化的支持。比如加入和宽字符的类型 wchar_t 和宽字符的输⼊和输出函数加入和locale.h头文件其中提供了允许程序员针对特定 地区通常是国家或者说某种特定语言的地理区域调整程序行为的函数。 setlocale函数 char* setlocale (int category, const char* locale); setlocale 函数用于修改当前地区可以针对⼀个类项修改也可以针对所有类项。setlocale 的第一个参数可以是前面说明的类项中的一个那么每次只会影响一个类项如果第一个参数是LC_ALL就会影响所有的类项。 C标准给第⼆个参数仅定义了2种可能取值C和 。在任意程序执⾏开始都会隐藏式执⾏调⽤: setlocale(LC_ALL, C); 当地区设置为C时库函数按正常方式执行小数点是一个点。 当程序运行起来后想改变地区就只能显⽰调⽤setlocale函数。用 作为第2个参数调setlocale 函数就可以切换到本地模式这种模式下程序会适应本地环境。比如切换到我们的本地模式后就支持宽字符汉字的输出等。 setlocale (LC_ALL, ); // 切换到本地环境 我们发现⼀个普通字符占⼀个字符的位置但是打印⼀个汉字字符占⽤2个字符的位置那么我们如果要在贪吃蛇中使⽤宽字符就得处理好地图上坐标的计算。 地图坐标 我们假设实现一个棋盘27行58列的棋盘行和列可以根据自己的情况修改再围绕地图画出墙 如下 蛇身和食物 初始化状态假设蛇的长度是5蛇身的每个节点是●在固定的一个坐标处比如(24, 5)处开始出现蛇连续5个节点。 注意蛇的每个节点的x坐标必须是2个倍数否则可能会出现蛇的⼀个节点有一半儿出现在墙体中另外⼀般在墙外的现象坐标不好对齐。 关于食物就是在墙体内随机生成⼀个坐标x坐标必须是2的倍数坐标不能和蛇的身体重合然后打印★。 游戏流程设计 ⭐核心逻辑实现分析重点 咱们从三个方面来讲解贪吃蛇的核心逻辑分析test.c主函数snake.h包含头文件snake.c主函数的实现跟我们玩链表差不多都是老套路了那贪吃蛇的实现正式上路咯启程。 snake.h头文件的实现 像头文件这个东西必然是要包含一些头文件的当然这只是它的一部分作用像我们定义的链表为了在各个源文件都能使用我们就在头文件实现链表。如果函数很多就把函数头包函数在头文件中方便调用那咱们看看在snake.h中有啥 //包含头文件
#include locale.h
#includestdio.h
#includeassert.h
#includestdlib.h
#includewindows.h
#includetime.h
#includestdbool.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 cols100 lines30);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);}//初始化蛇的位置在245cur-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源文件的实现
直接上代码好叭
//包含头文件
#includesnake.hvoid 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//包含头文件
#includesnake.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);}//初始化蛇的位置在245cur-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 cols100 lines30);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
#includestdio.h
#includeassert.h
#includestdlib.h
#includewindows.h
#includetime.h
#includestdbool.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//包含头文件
#includesnake.hvoid 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;
}
结束语 今天内容就到这里啦时间过得很快大家沉下心来好好学习会有一定的收获的大家多多坚持嘻嘻成功路上注定孤独因为坚持的人不多。那请大家举起自己的小说手给博主一键三连有你们的支持是我最大的动力回见。