当前位置: 首页 > news >正文

石龙镇网站建设做英文网站违法吗

石龙镇网站建设,做英文网站违法吗,wordpress搜索栏,qq网页版在线登录手把手教兄弟们如何写贪吃蛇 目录 一、游戏背景 二、课程目标 三、课程定位 四、技术要点 五、相关Win32API介绍 5.1 Win32API 5.2 控制台程序#xff08;Console#xff09; 5.3 控制台屏幕上的坐标COORD 5.4 GetStdHandle 5.5 GetConsoleCursorInfo 5.6 SetCo…手把手教兄弟们如何写贪吃蛇 目录 一、游戏背景 二、课程目标 三、课程定位 四、技术要点 五、相关Win32API介绍 5.1 Win32API 5.2 控制台程序Console 5.3 控制台屏幕上的坐标COORD 5.4 GetStdHandle 5.5 GetConsoleCursorInfo  5.6 SetConsoleCursorInfo 5.7 SetConsoleCursorPosition 5.8 GetAsyncKetState 六、贪吃蛇游戏设计与分析 6.1 地图 6.1.1  本地化 6.1.2 类项 6.1.3 setlocale函数 6.1.4 宽字符打印 6.1.5 地图坐标 6.2 蛇身和食物  6.3 数据结构设计 6.4 游戏流程设计  七、核心逻辑实现分析 7.1 游戏主逻辑 7.2 GameStart函数的实现 7.2.1 控制台窗口大小和标题的设置 7.2.2 鼠标光标的隐藏 7.2.3 打印欢迎界面 7.2.4 创建地图 7.2.5  初始化蛇 7.2.6 创建第一个食物 7.3 GameRun函数的实现 7.3.1 打印游戏的帮助信息 7.3.2 打印分数  ​7.3.3 检测按键 7.3.4 蛇移动SnakeMove 7.3.4.1 判断下一个节点是不是食物NextIsFood 7.3.4.2 下一个节点是食物EatFood 7.3.4.3 下一个节点不是食物NoFood 7.3.4.4  检测蛇是否撞墙 7.3.4.5  检测蛇是否撞到自己 7.3.4.6 SnakeMove的整体代码 7.4 GameEnd函数的实现 7.4.1 判断是哪一种情况导致游戏结束 7.4.2 释放蛇身的链表 八、问题和后续 九、代码 9.1 snakke.c部分 9.2 snake.h部分 9.3 test.c部分 一、游戏背景 贪吃蛇是久负盛名的游戏它也和俄罗斯方块扫雷等游戏位列经典游戏的行列。 在编程语言的教学中我们以贪吃蛇为例从设计到代码实现来提升学生的编程能力和逻辑能力。 二、课程目标 使用C语言在Windows环境的控制台中模拟实现经典小游戏贪吃蛇。 实现基本的功能 贪吃蛇地图绘制 蛇吃食物的功能 上、下、左、右方向键控制蛇的动作 蛇撞墙死亡 蛇撞自身死亡 计算得分 蛇加速、减速 暂停游戏 三、课程定位 提高对编程的兴趣 对C语言语法做⼀个基本的巩固。 对游戏开发有兴趣的同学做⼀个启发。 项目适合C语⾔学完的同学有⼀定的代码能力初步接触数据结构中的链表。 四、技术要点 C语言函数、枚举、结构体、动态内存管理、预处理指令、链表、Win32 API等。 五、相关Win32API介绍 本次实现贪吃蛇会使用到的⼀些Win32 API知识接下来我们就学习⼀下。 5.1 Win32API Windows 这个多作业系统除了协调应用程序的执行、分配内存、管理资源之外 它同时也是⼀个很⼤的服务中心调用这个服务中心的各种服务每⼀种服务就是⼀个函数可以帮应用程序达到开启视窗、描绘图形、使用周边设备等目的由于这些函数服务的对象是应用程(Application)所以便称之为 Application Programming Interface简称 API 函数。WIN32 API也就是Microsoft Windows32位平台的应用程序编程接口。 5.2 控制台程序Console 平常我们运行起来的黑框程序其实就是控制台程序 我们可以使用cmd命令来设置控制台窗口的长宽设置控制台窗口的大小30行100列 mode  con cols100 lines30 我们按winr键输出cmd打开控制台程序然后输出上面的控制台命令我们的控制台窗口就会变成100列和30行。如下所示 我们这个控制台的颜色为黑色可以右键点开控制台数据来进行改变背景颜色为黑色。如下所示 我们在序号1位置右键点开属性然后到序号2点击颜色 到序号3选择屏幕背景然后选择序号4的灰色最后点击确认然后再次打开控制台控制台程序就是灰色的了。改成灰色之后控制台程序可能上面看不到任何的字体这是因为屏幕背景颜色是灰色屏幕字体也是灰色所以我们要将屏幕字体改成黑色最后点击确认。如下所示 最后我们的控制台就变成这样了 因为我们是做贪吃蛇的小游戏我们不想要让控制台显示命令提示符如下所示 我们可以使用 title 贪吃蛇来改变标题如下所示 这些都是在控制台执行的命令我们如何在VS上使用这些命令呢 答案是我们可以使用C语言函数system函数来执行使用system函数需要引用头文件stdlib.h如下所示 #includestdio.h #includestdlib.h int main() {//设置控制台相关程序system(mode con cols30 lines30);system(title 贪吃蛇);return 0; } 我们运行上述代码控制台程序就会变成30行30列标题变成贪吃蛇但是我们运行时候会发现控制台程序是30行30列但是标题不是贪吃蛇这是为什么呢?这是因为我们运行代码后程序已经结束了所以就不会显示贪吃蛇了我们可以在return前加入getchar()或者system(pause)来暂停程序不让程序结束。如下所示 #includestdio.h #includestdlib.h int main() {//设置控制台相关程序system(mode con cols100 lines30);system(title 贪吃蛇);system(pause);return 0; } 运行如下所示  5.3 控制台屏幕上的坐标COORD 把基础的背景设置完成之后我们需要在控制台屏幕上输出贪吃蛇的信息食物信息和字幕信息这个时候我们就需要使用到坐标的概念了控制台屏幕上每一个小方框都有一个坐标如下所示 控制台屏幕坐标轴如下所示 COORD 是Windows API中定义的⼀个结构体表示⼀个字符在控制台屏幕幕缓冲区上的坐标坐标系(00) 的原点位于缓冲区的顶部左侧单元格。 COORD类型的声明如下所示 typedef struct _ COORD {         SHORT X;         SHORT Y; } COORD, *PCOORD 使用这个COORD类型要包含头文件windows.h。 假设我们现在要找下面这个点的信息就可以定义一个COORD类型的变量来代表这个位置 COORD pos{30,6}   //假设这个点的坐标是30,6 5.4 GetStdHandle GetStdHandle是一个Windows API函数。它用于从一个特定的标准设备如标准输入标准输出标准错误中取得一个句柄用来表示不同设备的数值使用这个句柄可以操作设备。 例如这个贪吃蛇需要在控制台上打印一些信息我们就需要拿到这个控制台程序的句柄有了这个句柄就可以来操作这个控制台程序了。 函数原型如下 HANDLE  GetStdHandle(DWORD nStdHandle); 使用这个程序需要一个参数参数有如下三种选择 我们需要使用屏幕信息所以就需要传第二个参数。 同时这个函数也有一个返回值返回的是这个屏幕的句柄有了句柄我们就可以来操作这个屏幕了。 实例如下 #includewindows.h int main() {//获得标准输出设备的句柄HANDLE houtput GetStdHandle(STD_OUTPUT_HANDLE);//需要引用头文件windows.hreturn 0; } 5.5 GetConsoleCursorInfo  在我们使用system(pause)或者getchar()后我们需要输入任意一个字符继续运行控制台程序后有一个光标会一直闪烁会导致游戏的视觉不太好所以就需要使用这个函数GetConsoleCursorInfo来设置这个光标的信息。 GetConsoleCursorInfo函数就是检索有关指定控制台屏幕缓冲区的光标大学和可见性信息。 函数原型如下 BOOL WINAPI GetConsoleCursorInfo (         HANDLE hConsoleOutput,         PCONSOLE_CURSOR_INFO lpConsoleCursorInfo ); 这个函数有两个参数 第一个参数就是HANDLE就是要给这个函数一个控制台输出窗口的句柄。表示获取哪一个控制台程序的光标信息 第二次参数是PCONSOLE_CURSOR_INFO 它是指向 CONSOLE_CURSOR_INFO 结构的指针该结构接收有关主机游标光标的信息。这个结构如下所示 typedef struct _CONSOLE_CURSOR_INFO {         DWORD dwSize;  //由光标填充的字符单位百分比         BOOL  bVisible;//光标的可见性如果光标可见值为TRUE。 } CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO; 实例如下 #includestdio.h #includestdlib.h #includewindows.h int main() {//获得标准输出设备的句柄HANDLE houtput GetStdHandle(STD_OUTPUT_HANDLE);//需要引用头文件windows.h//创建CONSOLE_CURSOR_INFO类型的变量CONSOLE_CURSOR_INFO cursor_info { 0 };//使用GetConsoleCursorInfo函数来获取屏幕的光标信息。GetConsoleCursorInfo(houtput, cursor_info);//打印光标信息printf(%d\n, cursor_info.dwSize);system(pause);return 0; } 输出如下所示 这个输出25是指这个光标占整个字符单元格高度的25%。  我们使用以下代码来改变这个光标的信息 #includestdio.h #includestdlib.h #includewindows.h int main() {//获得标准输出设备的句柄HANDLE houtput GetStdHandle(STD_OUTPUT_HANDLE);//需要引用头文件windows.h//创建CONSOLE_CURSOR_INFO类型的变量CONSOLE_CURSOR_INFO cursor_info { 0 };//使用GetConsoleCursorInfo函数来获取屏幕的光标信息。GetConsoleCursorInfo(houtput, cursor_info);cursor_info.dwSize 50;//打印光标信息printf(%d\n, cursor_info.dwSize);system(pause);return 0; } 我们运行后发现这个光标的高度占比是50%但是实际上我们在控制台上的光标高度还是25%如下所示 因此我们就需要使用5.6的SetConsoleCursorInfo函数来设置光标信息。 5.6 SetConsoleCursorInfo 设置指定控制台屏幕缓冲区的光标的大小和可见性。 函数原型如下所示 BOOL WINAPI SetConsoleCursorInfo (         HANDLE hConsoleOutput,         const CONSOLE_CURSOR_INFO *lpConsoleCursorInfo ) 第一个参数还是句柄表示我们要设置哪一个控制台屏幕的光标信息。 第二个参数还是指向 CONSOLE_CURSOR_INFO 结构的指针。 这两个参数与GetConsoleCursorInfo函数的参数一样。 实例如下所示 #includestdio.h #includestdlib.h #includewindows.h int main() {//获得标准输出设备的句柄HANDLE houtput GetStdHandle(STD_OUTPUT_HANDLE);//需要引用头文件windows.h//创建CONSOLE_CURSOR_INFO类型的变量CONSOLE_CURSOR_INFO cursor_info { 0 };//使用GetConsoleCursorInfo函数来获取屏幕的光标信息。GetConsoleCursorInfo(houtput, cursor_info);//修改光标的百分比高度cursor_info.dwSize 50;//设置光标信息SetConsoleCursorInfo(houtput, cursor_info);//打印光标信息printf(%d\n, cursor_info.dwSize);system(pause);return 0; } 运行代码如下所示 我们可以发现这个光标的百分比高度从25%到50%了。 我们在控制台中并不想显示这个光标信息我们就可以修改cursor_info结构体的第二个变量将其改为false 如下所示 #includestdio.h #includestdlib.h #includewindows.h #includestdbool.h int main() {//获得标准输出设备的句柄HANDLE houtput GetStdHandle(STD_OUTPUT_HANDLE);//需要引用头文件windows.h//创建CONSOLE_CURSOR_INFO类型的变量CONSOLE_CURSOR_INFO cursor_info { 0 };//使用GetConsoleCursorInfo函数来获取屏幕的光标信息。GetConsoleCursorInfo(houtput, cursor_info);//修改光标可见度cursor_info.bVisible false;//使用false需要包含头文件stdbool.h//设置光标信息SetConsoleCursorInfo(houtput, cursor_info);system(pause);return 0; } 运行结果如下所示 我们可以发现那个光标看不见了。  5.7 SetConsoleCursorPosition 设置指定控制台屏幕缓冲区中的光标位置我们将想要设置的坐标信息放在COORD类型的pos中调用SetConsoleCursorPosition函数将光标位置设置到指定的位置。 函数原型如下所 BOOL WINAPI SetConsoleCursorPosition (         HANDLE hConsoleOutput,         COORD pos ); 第一个参数是句柄表示要操作哪个控制台。 第二个参数是COORD类型的结构体。 实例如下 #includestdio.h #includestdlib.h #includewindows.h int main() {//获取标准输出设备的句柄HANDLE houtput GetStdHandle(STD_OUTPUT_HANDLE);//定义光标的位置COORD pos { 10,6 };SetConsoleCursorPosition(houtput, pos);system(pause);return 0; } 运行结果如下 我们可以发现光标的信息改变了。坐标10,6位置是从“请”开始的然后依次向后打印。 在后续打印实物蛇和中文信息会经常重新定位光标的位置。所以我们可以将定位光标位置封装成一个函数。如下所示 void set_pos(short x, short y) {//获得标准输出设备的句柄HANDLE houtput GetStdHandle(STD_OUTPUT_HANDLE);//定位光标位置COORD pos { x,y };SetConsoleCursorPosition(houtput, pos); } 5.8 GetAsyncKetState 获取按键情况我们会使用上下左右等键来控制蛇的移动我们就可以使用这个函数来获取我们按了哪一个键。 GetAsyncKetState函数原型如下 SHORT GetAsyncKeyState ( int vKey) 键盘上每个键的虚拟键值传递给函数函数通过返回值来分辨按键的状态。 虚拟键值链接如下Virtual-Key 代码 Winuser.h - Win32 apps | Microsoft Learn GetAsyncKeyState 的返回值是short类型在上⼀次调用 GetAsyncKeyState 函数后如果 返回的16位的short数据中最高位是1说明按键的状态是按下如果最高是0说明按键的状态是抬起如果最低位被置为1则说明该按键被按过否则为0。 如果我们要判断⼀个键是否被按过可以检测GetAsyncKeyState返回值的最低值是否为1.我们可以来用define来定义以下宏如果该键被按过返回1否则返回0。 #define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) 0x1) ? 1 : 0 ) 实例检测数字键 #includestdio.h #includewindows.h #define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) 0x1) ? 1 : 0 ) int main() {while (1){if (KEY_PRESS(0x30)){printf(0\n);}else if (KEY_PRESS(0x31)){printf(1\n);}else if (KEY_PRESS(0x32)){printf(2\n);}else if (KEY_PRESS(0x33)){printf(3\n);}else if (KEY_PRESS(0x34)){printf(4\n);}else if (KEY_PRESS(0x35)){printf(5\n);}else if (KEY_PRESS(0x36)){printf(6\n);}else if (KEY_PRESS(0x37)){printf(7\n);}else if (KEY_PRESS(0x38)){printf(8\n);}else if (KEY_PRESS(0x39)){printf(9\n);}}return 0; } 如果我们按哪一个数字键该数字键就会被打印到屏幕上。 六、贪吃蛇游戏设计与分析 6.1 地图 起始的三个地图如下所示 这里不得不讲⼀下控制台窗口的⼀些知识如果想在控制台的窗口中指定位置输出信息我们得知道该位置的坐标所以首先介绍⼀下控制台窗口的坐标知识。 控制台窗口的坐标如下所示横向是X轴从左向右依次增长纵向是Y轴从上到下依次增长。 在游戏地图上我们打印墙体使用宽字符□打印蛇使用宽字符●打印⻝物使用宽字符★ 普通的字符是占⼀个字节的这类宽字符是占用2个字节。 这里再简单的讲⼀下C语言的国际化特性相关的知识过去C语言并不适合非英语国家地区使用。C语言最初假定字符都是单字节的。但是这些假定并不是在世界的任何地方都适用。 C语言字符默认是采用ASCII编码的ASCII字符集采用的是单字节编码且只使用了单字节中的低7位最⾼位是没有使用的可表示为0xxxxxxxx可以看到ASCII字符集共包含128个字符在英语国家中128个字符是基本够用的但是在其他国家语言中比如在法语中字母上方有注音符号它就无法用 ASCII 码表示。于是⼀些欧洲国家就决定利用字节中闲置的最高位编入新的符号。比如法语中的é的编码为130⼆进制10000010。这样⼀来这些欧洲国家使用的编码体系可以表示最多256个符号。但是这里又出现了新的问题。不同的国家有不同的字木因此哪怕它们都使用256个符号的编码⽅式代表的字木却不⼀样。比如130在法语编码中代表了é在希伯来语编码中却代表了字木Gimel (些编码方式中0--127表示的符号是⼀样的不⼀样的只是128--255的这⼀段。 至于亚洲国家的⽂字使用的符号就更多了汉字就多达10万左右。⼀个字节只能表示256种符号肯定是不够的就必须使用多个字节表达⼀个符号。比如简体中⽂常见的编码方式是 GB2312使用两个字节表示⼀个汉字所以理论上最多可以表示 256 x 256 65536 个符号。 后来为了使C语言适应国际化C语言的标准中不断加如了国际化的支持。比如加入了宽字符的类型 wchar_t 和宽字符的输入和输出函数加入了locale.h头文件其中提供了允许程序员针对特定地区通常是国家或者说某种特定语言的地理区域调整程序行为的函数。 6.1.1 locale.h本地化 locale.h提供的函数用于控制C标准库中对于不同的地区会产生不⼀样行为的部分。 在标准中依赖地区的部分有以下几项 数字量的格式         货币量的格式         字符集         日期和时间的表示形式 6.1.2 类项 通过修改地区程序可以改变它的⾏为来适应世界的不同区域。但地区的改变可能会影响库的许多部分其中⼀部分可能是我们不希望修改的。所以C语言支持针对不同的类项进行修改下面的⼀个宏指定⼀个类项 LC_COLLATE影响字符串比较函数 strcoll() 和 strxfrm() 。 LC_CTYPE影响字符处理函数的⾏为。 LC_MONETARY影响货币格式。 LC_NUMERIC影响 printf() 的数字格式。 LC_TIME影响时间格式 strftime() 和 wcsftime() 。 LC_ALL - 针对所有类项修改将以上所有类别设置为给定的语言环境 6.1.3 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, );//切换到本地环境 6.1.4 宽字符打印 那如果想在屏幕上打印宽字符怎么打印呢 宽字符的字面量必须加上前缀“L”否则 C 语言会把字面量当作窄字符类型处理。前缀“L”在单引号前面表⽰宽字符对应 wprintf() 的占位符为 %lc 在双引号前面表⽰宽字符串对应wprintf() 的占位符为 %ls 。 实例宽字符的打印 #includestdio.h #includelocale.h int main() {setlocale(LC_ALL, );//切换本地环境char a a;char b b;printf(%c%c, a, b);wchar_t wc1 L比;wchar_t wc2 L特;wprintf(L\n%lc\n%lc\n, wc1,wc2);wprintf(L%lc\n, L●);return 0; } 输出结果如下所示 从输出的结果来看我们发现⼀个普通字符占⼀个字符的位置但是打印⼀个汉字字符占用2个字符的位置那么我们如果要在贪吃蛇中使用宽字符就得处理好地图上坐标的计算。 普通字符和宽字符打印出宽度的展示如下 6.1.5 地图坐标 我们假设实现⼀个棋盘27⾏58列的棋盘行和列可以根据自己的情况修改再围绕地图画出墙如下 6.2 蛇身和食物  初始化状态假设蛇的长度是5蛇身的每个节点是●在固定的⼀个坐标处比如(24, 5)处开始出现蛇连续5个节点。 注意蛇的每个节点的x坐标必须是2个倍数否则可能会出现蛇的⼀个节点有⼀半儿出现在墙体中另外⼀般在墙外的现象坐标不好对齐。 关于食物就是在墙体内随机生成⼀个坐标x坐标必须是2的倍数坐标不能和蛇的身体重合然后打印★。如下所示 6.3 数据结构设计 在游戏运行的过程中蛇每次吃⼀个食物蛇的身体就会变长⼀节如果我们使用链表存储蛇的信息那么蛇的每⼀节其实就是链表的每个节点。每个节点只要记录好蛇身节点在地图上的坐标就行所以蛇节点结构如下 typedef struct SnakeNode {         int x;         int y; struct SnakeNode * next;   }SnakeNode, * pSnakeNode; 但是除了保存蛇节点之外我们还需要保存很多信息例如蛇的方向总分数一个食物的分数睡眠时间食物的位置游戏的状态等等我们可以定义这些变量如果我们不把他们封装在一个结构体中这些变量就会很零散散落到各处所以我们还可以再新建一个结构体来管理蛇的各种信息如下所示 typedef struct Snake {         pSnakeNode _pSnake; // 维护整条蛇的指针         pSnakeNode _pFood; // 维护⻝物的指针         enum DIRECTION _Dir; // 蛇头的⽅向 , 默认是向右         enum GAME_STATUS _Status; // 游戏状态         int _Socre; // 游戏当前获得分数         int _foodWeight; // 默认每个⻝物 10 分         int _SleepTime; // 每⾛⼀步休眠时间 }Snake, * pSnake 蛇的方向可以一 一列举出来所以我们选择枚举类型如下所示 // ⽅向 enum DIRECTION { UP 1 , DOWN, LEFT, RIGHT }; 同样的游戏的状态也可以一 一列举出来也可以使用枚举类型如下所示 // 游戏状态 enum GAME_STATUS { OK, // 正常运⾏ KILL_BY_WALL, // 撞墙 KILL_BY_SELF, // 咬到⾃⼰ END_NOMAL // 正常结束 } 6.4 游戏流程设计  七、核心逻辑实现分析 我们设置三个文件 test.c游戏的测试 snack.c游戏的实现 snack.h游戏的函数声明和类型声明 7.1 游戏主逻辑 程序开始就设置程序支持本地模式然后进入游戏的主逻辑。 主逻辑分为3个过程 游戏开始GameStart完成游戏的初始化 游戏运行GameRun完成游戏运行逻辑的实现  游戏结束GameEnd完成游戏结束的说明实现资源释放 首先设置适配本地环境如下所示 set_locale(LC_ALL,); //需要引用头文件#includelocale.h 三个框架如下所示 #includesnack.h//完成游戏的测试逻辑 void test() {//创建贪吃蛇Snake snake { 0 };//初始化游戏:// 1打印环境界面2功能介绍3绘制地图4创建蛇5创建食物6设置游戏相关信息GameStart(snake);//运行游戏GameRun(snake);//结束游戏--善后工作GameEnd(snake); } int main() {//设置适配本地环境set_locale(LC_ALL, );//需要引用头文件locale.htest();return 0; } 7.2 GameStart函数的实现 这个模块完成游戏的初始化任务 1控制台窗口大小和标题的设置                2鼠标光标的隐藏         3打印欢迎界面         4创建地图         5初始化蛇         6创建第⼀个食物 7.2.1 控制台窗口大小和标题的设置 //1控制台窗口大小和标题的设置system(mode con cols100 lines30);//使用system需要包含头文件stdlib.hsystem(title 贪吃蛇); 7.2.2 鼠标光标的隐藏 //2鼠标光标的隐藏 HANDLE houtputGetStdHandle(STD_OUTPUT_HANDLE);//使用这个函数需要包含头文件windows.h CONSOLE_CURSOR_INFO CursorInfo; GetConsoleCursorInfo(houtput, CursorInfo);//获取控制台光标信息 CursorInfo.bVisible false;//隐藏控制台光标使用false需要包含头文件stdbool.h SetConsoleCursorInfo(houtput, CursorInfo);//设置控制台光标状态 7.2.3 打印欢迎界面 我们写一个WelcomeToGame的函数来实现起始三张地图的打印首先我们要在控制台屏幕中间打字就需要重新定位光标我们写一个SetPos函数来重新定位光标如下所示 void SetPos(short x, short y) {//获得标准输出设备的句柄HANDLE houtput GetStdHandle(STD_OUTPUT_HANDLE);//使用这个函数需要包含头文件windows.h//定位光标位置COORD pos { x,y }; //使用这个COORD类型要包含头文件windows.h。SetConsoleCursorPosition(houtput, pos);//使用这个函数需要包含头文件windows.h } 第一个欢迎界面代码如下 //打印欢迎界面 void WelcomeToGame() {//第一个界面的打印SetPos(40, 14);printf(欢迎来到贪吃蛇小游戏\n);SetPos(42, 20);//第二个界面的打印} 我们运行代码之后如下所示 这个输出表示程序已经运行结束了我们不想要程序结束 所以我们在在后面加上一个system“pause”如下所示 //打印欢迎界面 void WelcomeToGame() {//第一个界面的打印SetPos(40, 14);printf(欢迎来到贪吃蛇小游戏\n);SetPos(42, 20);system(pause);//第二个界面的打印} 运行如下所示 第一个界面已经成功的输出了那我们现在如何打印第二张界面呢 代码如下所示 //打印欢迎界面 void WelcomeToGame() {//第一个界面的打印SetPos(40, 14);printf(欢迎来到贪吃蛇小游戏\n);SetPos(42, 20);system(pause);//第二个界面的打印system(cls);//清空屏幕SetPos(25, 14);wprintf(L用↑.↓.←.→来控制蛇的移动按F3加速F4减速\n);SetPos(35, 15);wprintf(L加速能得到更高的分数\n);SetPos(37, 17);system(pause);system(cls); } 7.2.4 创建地图 使用CreateMap函数来创建地图。我们实现⼀个棋盘27行58列的棋盘。如下所示我们创建上和下墙体的时候由于光标是默认左向右的所以我们只需要定位一次光标就可以了但是我们创建左和右墙体时候需要光标向下移动所以我们每一次打印墙体都需要重新定位如下所示 //4创建地图,实现⼀个棋盘27⾏58列的棋盘 void CreateMap() {// 上//SetPos(0,0),可以不用写因为光标默认从0,0开始的int i 0;for (i 0; i 29; i) {wprintf(L%lc, WALL);}// 下SetPos(0, 26);for (i 0; i 29; i) {wprintf(L%lc, WALL);}// 左for (i 1; i 26; i) {SetPos(0, i);wprintf(L%lc, WALL);}// 右for (i 1; i 26; i) {SetPos(56, i);wprintf(L%lc, WALL);}getchar();//不想让程序结束写到后面可以删掉也可能后面实现其他函数的时候加在后面 } WALL是我们使用#define定义的一个常量此外我们还定义的其他的两个常量如下所示 #define WALL L□ #define BODY L● #define FOOD L★ 运行结果如下棋盘成功的创建了 7.2.5  初始化蛇 蛇最开始长度为5节每节对应链表的⼀个节点蛇身的每⼀个节点都有自己的坐标。 创建5个节点然后将每个节点存放在链表中进行管理。创建完蛇身后将蛇的每⼀节打印在屏幕上。 蛇的初始位置从 (24,5) 开始。 如下所示 再设置当前游戏的状态蛇移动的速度默认的方向初始成绩每个食物的分数。 游戏状态是OK         蛇的移动速度200毫秒         蛇的默认方向RIGHT         初始成绩0         每个食物的分数10 我们使用InitSnake函数来完成上面任务。代码如下同时将CreateMap函数中的getchar()放到InitSnake函数后面. //初始化蛇 void InitSnake(pSnake ps) {int i 0;pSnakeNode cur NULL;for (i 0; i 5; i) {cur (pSnakeNode)malloc(sizeof(SnakeNode));if (cur NULL){perror(InitSnake()::malloc);return;}cur-next NULL;cur-x POS_X 2 * i;cur-y POS_Y;//头插法插入链表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-_Dir RIGHT;//方向默认向右ps-_foodWeight 10;ps-_SleepTime 200;ps-_Socre 0;ps-_Status OK;getchar(); } 运行结果如下 7.2.6 创建第一个食物 我们使用CreateFood函数来创建第一个食物我们的食物也是一个SnakeNode类型的节点食物的位置必须在棋盘内部不能是贪吃蛇的节点的位置同时食物的x轴坐标必须是2的倍数然后打印食物。使用rand函数来生成xy。如下所示 同时将InitSnake函数中的getchar()放到CreateFood函数后面. //6创建第⼀个食物 void CreateFood(pSnake ps) {int x 0;//x的取值范围2到54int y 0;//y的取值1到25 again:do {x rand() % 53 2;y rand() % 25 1;} while (x % 2 ! 0);//生成x是2的倍数//x和y坐标不能和蛇的身体冲突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 (pfood NULL) {perror(CreateFood()::malloc);return;}pfood-x x;pfood-y y;pfood-next NULL;//打印食物SetPos(x, y);wprintf(L%lc, FOOD);ps-_pFood pfood;getchar();} 运行如下所示 7.3 GameRun函数的实现 游戏运行期间右侧打印帮助信息提升玩家。 根据游戏状态检查游戏是否继续如果是状态是OK游戏继续否则游戏结束。 如果游戏继续就是检测按键情况确定蛇下⼀步的方向或者是否加速减速是否暂停或者退出游戏 7.3.1 打印游戏的帮助信息 使用PrinthelpInfo函数完成此操作将CreateFood函数中的getchar()放到PrintHelpInfo函数后面。如下所示 //打印帮助信息 void PrintHelpInfo() {SetPos(64, 14);printf(不能穿墙不能咬到自己);SetPos(64, 15);printf(用↑.↓.←.→来控制蛇的移动\n);SetPos(64, 16);printf(按F3加速F4减速);SetPos(64, 17);printf(按ESC退出游戏按空格暂停游戏);getchar(); } 运行如下所示 7.3.2 打印分数  如下所示 void GameRun(pSnake ps) {//打印帮助信息PrintHelpInfo();do{//打印总分数和食物的分值SetPos(64, 10);printf(总分数%d, ps-_Socre);SetPos(64, 11);printf(当前食物的分数%d, ps-_foodWeight);} while (ps-_StatusOK);} 运行如下所示 7.3.3 检测按键 检测按键状态我们封装了一个宏如下所示 # define KEY_PRESS(VK) ((GetAsyncKeyState(VK)0x1) ? 1 : 0) 需要的虚拟按键的罗列 上VK_UP 下VK_DOWN 左VK_LEFT 右VK_RIGHT 空格VK_SPACE ESCVK_ESCAPE F3VK_F3 F4VK_F4 代码实现如下所示 //游戏运行 void GameRun(pSnake ps) {//打印帮助信息PrintHelpInfo();do{//打印总分数和食物的分值SetPos(64, 10);printf(总分数%d, ps-_Socre);SetPos(64, 11);printf(当前食物的分数%d, 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_SPACE)) {//暂停Pause();}else if (KEY_PRESS(VK_ESCAPE)){//退出ps-_Status END_NORMAL;}else if (KEY_PRESS(VK_F3)) {//加速//每按一次休眠减少30ms,不能无限加速,加一次速食物加2分if (ps-_SleepTime 80) {ps-_SleepTime - 30;ps-_foodWeight 2;}}else if(KEY_PRESS(VK_F4)){//减速//每按一次休眠增加30ms,不能无限减速减一次速食物减2分if (ps-_foodWeight 2) {ps-_SleepTime 30;ps-_foodWeight - 2;}}//贪吃蛇走一步} while (ps-_StatusOK);} 7.3.4 蛇移动SnakeMove 蛇移动需要确定方向根据方向来确定下一步走哪去并且还要确定下一步是不是食物如果是食物就吃掉食物并且生成新的食物更新分数如果不是食物就需要重新调整蛇的位置还要检测蛇走一步会不会撞墙会不会撞到自己。 为了方便操作我们把蛇的下一步位置存起来定义一个新的节点如果下一步不是食物就把这个节点头插到蛇节点中并且把最后一个节点释放切记还要在这个蛇尾打印一个空字符。如果是食物就把食物这个节点插入蛇。 创建新节点代码如下 //蛇移动 void SnackMove(pSnake ps) {//创建一个节点表示蛇即将要到了下一个节点pSnakeNode pNextNode(pSnakeNode)malloc(sizeof(SnakeNode));if (pNextNode NULL) {perror(SnakeMove()::malloc);return;}switch (ps-_Dir) {case UP:pNextNode-x ps-_pSnake-x;pNextNode-y ps-_pSnake-y - 1;break;case DOWN:pNextNode-x ps-_pSnake-x;pNextNode-y ps-_pSnake-y 1;break;case LEFT:pNextNode-x ps-_pSnake-x - 2;pNextNode-y ps-_pSnake-y;break;case RIGHT:pNextNode-x ps-_pSnake-x 2;pNextNode-y ps-_pSnake-y;break;}} 7.3.4.1 判断下一个节点是不是食物NextIsFood 如果下一步不是食物返回0。 如果下一步是食物返回1。 代码如下所示 //判断下一个坐标是不是食物 int NextIsFood(pSnakeNode pn, pSnake ps) {return (ps-_pFood-x pn-x ps-_pFood-y pn-y); } 7.3.4.2 下一个节点是食物EatFood 如果下一个节点是食物我们就使用头插法将这个节点插入到贪吃蛇。将下一个节点位置信息插入蛇之后就可以释放下一个节点了蛇吃点食物之后蛇会变长再打印一下蛇。然后吃到食物会加分数。最后在重新创建食物 代码如下所示 //下一个位置是食物吃掉食物 void EatFood(pSnakeNode pn, pSnake ps) {//头插法将食物吃掉ps-_pFood-next ps-_pSnake;ps-_pSnake ps-_pFood;//下一个位置已经保存到贪吃蛇里面了可以释放下一个位置的节点free(pn);pn NULL;//打印pSnakeNode cur ps-_pSnake;while (cur) {SetPos(cur-x, cur-y);wprintf(L%lc, BODY);cur cur-next;}ps-_Socre ps-_Socre ps-_foodWeight;//重新创建新的食物CreateFood(ps); } 7.3.4.3 下一个节点不是食物NoFood 当下一个节点不是食物时我们同样的将下一个节点头插到蛇中然后释放最后一个节点并且在最后一个位置打印空字符“ ”如果不打印空字符蛇走一步就会变长尾巴没有处理。 代码如下 //下一个位置不是食物 void NoFood(pSnakeNode pn, pSnake ps) {pn-next ps-_pSnake;ps-_pSnake pn;//找尾节点顺便打印蛇pSnakeNode cur ps-_pSnake;while (cur-next-next ! NULL) {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;} 7.3.4.4  检测蛇是否撞墙 当蛇移动一次后可能会撞墙我们写一个KillByWall函数来检测蛇是否撞墙。 代码如下 //检测蛇是否撞墙 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;} } 7.3.4.5  检测蛇是否撞到自己 当蛇移动一次后也有可能会撞到自己我们写一个KillBySelf函数来检测蛇是否撞到自己。 代码如下 //检测蛇是否撞到自己 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;break;}cur cur-next;} }7.3.4.6 SnakeMove的整体代码 //蛇移动 void SnakeMove(pSnake ps) {//创建一个节点表示蛇即将要到了下一个节点pSnakeNode pNextNode(pSnakeNode)malloc(sizeof(SnakeNode));if (pNextNode NULL) {perror(SnakeMove()::malloc);return;}switch (ps-_Dir) {case UP:pNextNode-x ps-_pSnake-x;pNextNode-y ps-_pSnake-y - 1;break;case DOWN:pNextNode-x ps-_pSnake-x;pNextNode-y ps-_pSnake-y 1;break;case LEFT:pNextNode-x ps-_pSnake-x - 2;pNextNode-y ps-_pSnake-y;break;case RIGHT:pNextNode-x ps-_pSnake-x 2;pNextNode-y ps-_pSnake-y;break;}if (NextIsFood(pNextNode, ps)) {//是食物EatFood(pNextNode, ps);}else {//不是食物NoFood(pNextNode, ps);}//检测是否撞到墙KillByWall(ps);//检测是否撞到自己KillBySelf(ps);} 7.4 GameEnd函数的实现 当蛇撞墙撞到自己或者主动选择ESC退出游戏就会结束结束之后我们就需要释放贪吃蛇的信息。 7.4.1 判断是哪一种情况导致游戏结束 我们首先使用一个switch来判断是哪一种情况导致游戏结束代码如下 //结束游戏 void GameEnd(pSnake ps) {SetPos(24,12);switch (ps-_Status) {case END_NORMAL:printf(你主动结束游戏\n);break;case KILL_BY_WALL:printf(你撞到墙上游戏结束\n);break;case KILL_BY_SELF:printf(你撞到了自己游戏结束\n);break;}//释放蛇身的链表 } 运行结果如下 游戏结束之后下面的信息显示的不是很好我们在游戏结束后使用SetPos再次地位光标到0,27 .如下所示 7.4.2 释放蛇身的链表 代码如下 //释放蛇身的链表pSnakeNode cur ps-_pSnake;while (cur) {pSnakeNode del cur;cur cur-next;free(del);} 八、问题和后续 当我们运行游戏的时候按F4来进行减速食物的分数会出错这是因为当两位数变成一位数时那个位置的信息0并没与消去所以会出现那样的情况。如下所示 解决这个问题很简单当我们打印食物分数的时候以%2d的方式打印就可以了如下所示 printf(当前食物的分数%2d, ps-_foodWeight);  当这一句游戏结束之后可还想要继续玩代码如下所示  //完成游戏的测试逻辑 void test() {int ch 0;do {//创建贪吃蛇Snake snake { 0 };//初始化游戏:// 1打印环境界面2功能介绍3绘制地图4创建蛇5创建食物6设置游戏相关信息GameStart(snake);//运行游戏GameRun(snake);//结束游戏--善后工作GameEnd(snake);SetPos(20, 15);printf(再来一局吗(Y/N):);ch getchar();} while (chy||chY);SetPos(0, 27); } 当我们写出上述代码之后ch将会得到我们输入的字符但是我们不仅仅只是输入了一个y或者n还要一个空格这个空格会导致第二次ch得到一个空格导致游戏结束那现在我们怎么办呢 我们可以在ch得到字符y之后再次使用getchar()把这个空格读走。  但是另外一个问题又来了当我们第一次输入y游戏重新开始第二次游戏结束的时候第一次输入的y会留在屏幕上如下所示 如何解决这个问题呢我们可以在游戏开始之前使用system(cls);来清空屏幕下一次就不会出现这种情况了。 如果我们在输入的时候卡了连续输入了几个y会导致游戏结束后下一次继续开始而没有等我们自己输入那这个问题怎么解决呢 我们可以使用一个while循环来读取第一个字符后面的字符如下所示 void test() {int ch 0;do {system(cls);//创建贪吃蛇Snake snake { 0 };//初始化游戏:// 1打印环境界面2功能介绍3绘制地图4创建蛇5创建食物6设置游戏相关信息GameStart(snake);//运行游戏GameRun(snake);//结束游戏--善后工作GameEnd(snake);SetPos(20, 15);printf(再来一局吗(Y/N):);ch getchar();while (getchar() ! \n);} while (chy||chY);SetPos(0, 27); } 九、代码 9.1 snakke.c部分 #pragma once #includelocale.h #includestdio.h #includewindows.h #includestdbool.h #includestdlib.h #includetime.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,KILL_BY_WALL,KILL_BY_SELF,END_NORMAL }; //蛇身的节点类型 typedef struct SnakeNode {//坐标int x;int y;//执行下一个节点的指针struct SnakeNode* next; }SnakeNode, * pSnakeNode;//贪吃蛇 typedef struct Snake {pSnakeNode _pSnake;//维护整条蛇的指针pSnakeNode _pFood;//维护⻝物的指针enum DIRECTION _Dir;//蛇头的⽅向,默认是向右enum GAME_STATUS _Status;//游戏状态int _Socre;//游戏当前获得分数int _foodWeight;//默认每个⻝物10分int _SleepTime;//每⾛⼀步休眠时间,时间越短速度越快时间越长速度越慢 }Snake, * pSnake;//函数的声明 void GameStart(pSnake ps);//打印欢迎界面 void WelcomeToGame(); //定位光标 void SetPos(short x,short y);//4创建地图 void CreateMap(); //5: 初始化蛇 void InitSnake(pSnake ps); //6创建第⼀个食物 void CreateFood(pSnake ps);//游戏运行 void GameRun(pSnake ps); //打印帮助信息 void PrintHelpInfo();//蛇移动 void SnakeMove(pSnake ps); //判断下一个节点是不是食物 int NextIsFood(pSnakeNode pn, pSnake ps); //下一个位置是食物吃掉食物 void EatFood(pSnakeNode pn, pSnake ps); //下一个位置不是食物 void NoFood(pSnakeNode pn, pSnake ps);//检测蛇是否撞墙 void KillByWall(pSnake ps); //检测蛇是否撞到自己 void KillBySelf(pSnake ps);//结束游戏 void GameEnd(pSnake ps); 9.2 snake.h部分 #define _CRT_SECURE_NO_WARNINGS 1 #includesnack.h //定位光标 void SetPos(short x, short y) {//获得标准输出设备的句柄HANDLE houtput GetStdHandle(STD_OUTPUT_HANDLE);//使用这个函数需要包含头文件windows.h//定位光标位置COORD pos { x,y }; //使用这个COORD类型要包含头文件windows.h。SetConsoleCursorPosition(houtput, pos);//使用这个函数需要包含头文件windows.h } //打印欢迎界面 void WelcomeToGame() {//第一个界面的打印SetPos(40, 14);printf(欢迎来到贪吃蛇小游戏\n);SetPos(42, 20);system(pause);//第二个界面的打印system(cls);//清空屏幕SetPos(25, 14);wprintf(L用↑.↓.←.→来控制蛇的移动按F3加速F4减速\n);SetPos(35, 15);wprintf(L加速能得到更高的分数\n);SetPos(37, 17);system(pause);system(cls); } //4创建地图,实现⼀个棋盘27⾏58列的棋盘 void CreateMap() {// 上//SetPos(0,0),可以不用写因为光标默认从0,0开始的int i 0;for (i 0; i 29; i) {wprintf(L%lc, WALL);}// 下SetPos(0, 26);for (i 0; i 29; i) {wprintf(L%lc, WALL);}// 左for (i 1; i 26; i) {SetPos(0, i);wprintf(L%lc, WALL);}// 右for (i 1; i 26; i) {SetPos(56, i);wprintf(L%lc, WALL);} } //初始化蛇 void InitSnake(pSnake ps) {int i 0;pSnakeNode cur NULL;for (i 0; i 5; i) {cur (pSnakeNode)malloc(sizeof(SnakeNode));if (cur NULL){perror(InitSnake()::malloc);return;}cur-next NULL;cur-x POS_X 2 * i;cur-y POS_Y;//头插法插入链表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-_Dir RIGHT;//方向默认向右ps-_foodWeight 10;ps-_SleepTime 200;ps-_Socre 0;ps-_Status OK;} //6创建第⼀个食物 void CreateFood(pSnake ps) {int x 0;//x的取值范围2到54int y 0;//y的取值1到25 again:do {x rand() % 53 2;y rand() % 25 1;} while (x % 2 ! 0);//生成x是2的倍数//x和y坐标不能和蛇的身体冲突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 (pfood NULL) {perror(CreateFood()::malloc);return;}pfood-x x;pfood-y y;pfood-next NULL;//打印食物SetPos(x, y);wprintf(L%lc, FOOD);ps-_pFood pfood;//getchar();} void GameStart(pSnake ps) {//1控制台窗口大小和标题的设置system(mode con cols100 lines30);//使用system需要包含头文件stdlib.hsystem(title 贪吃蛇);//2鼠标光标的隐藏HANDLE houtputGetStdHandle(STD_OUTPUT_HANDLE);//使用这个函数需要包含头文件windows.hCONSOLE_CURSOR_INFO CursorInfo;GetConsoleCursorInfo(houtput, CursorInfo);//获取控制台光标信息CursorInfo.bVisible false;//隐藏控制台光标使用false需要包含头文件stdbool.hSetConsoleCursorInfo(houtput, CursorInfo);//设置控制台光标状态//3打印欢迎界面WelcomeToGame();//4创建地图CreateMap();//5初始化蛇InitSnake(ps);//6创建第⼀个食物CreateFood(ps);//SetPos(0, 27);//system(pause); }//打印帮助信息 void PrintHelpInfo() {SetPos(64, 14);printf(不能穿墙不能咬到自己);SetPos(64, 15);printf(用↑.↓.←.→来控制蛇的移动\n);SetPos(64, 16);printf(按F3加速F4减速);SetPos(64, 17);printf(按ESC退出游戏按空格暂停游戏); } void Pause() {while (1) {Sleep(200);if (KEY_PRESS(VK_SPACE)){break;}} } //判断下一个坐标是不是食物 int NextIsFood(pSnakeNode pn, pSnake ps) {return (ps-_pFood-x pn-x ps-_pFood-y pn-y); } //下一个位置是食物吃掉食物 void EatFood(pSnakeNode pn, pSnake ps) {//头插法将食物吃掉ps-_pFood-next ps-_pSnake;ps-_pSnake ps-_pFood;//下一个位置已经保存到贪吃蛇里面了可以释放下一个位置的节点free(pn);pn NULL;//打印pSnakeNode cur ps-_pSnake;while (cur) {SetPos(cur-x, cur-y);wprintf(L%lc, BODY);cur cur-next;}ps-_Socre ps-_Socre ps-_foodWeight;//重新创建新的食物CreateFood(ps); } //下一个位置不是食物 void NoFood(pSnakeNode pn, pSnake ps) {pn-next ps-_pSnake;ps-_pSnake pn;//找尾节点顺便打印蛇pSnakeNode cur ps-_pSnake;while (cur-next-next ! NULL) {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;break;}cur cur-next;} } //蛇移动 void SnakeMove(pSnake ps) {//创建一个节点表示蛇即将要到了下一个节点pSnakeNode pNextNode(pSnakeNode)malloc(sizeof(SnakeNode));if (pNextNode NULL) {perror(SnakeMove()::malloc);return;}switch (ps-_Dir) {case UP:pNextNode-x ps-_pSnake-x;pNextNode-y ps-_pSnake-y - 1;break;case DOWN:pNextNode-x ps-_pSnake-x;pNextNode-y ps-_pSnake-y 1;break;case LEFT:pNextNode-x ps-_pSnake-x - 2;pNextNode-y ps-_pSnake-y;break;case RIGHT:pNextNode-x ps-_pSnake-x 2;pNextNode-y ps-_pSnake-y;break;}if (NextIsFood(pNextNode, ps)) {//是食物EatFood(pNextNode, ps);}else {//不是食物NoFood(pNextNode, ps);}//检测是否撞到墙KillByWall(ps);//检测是否撞到自己KillBySelf(ps);} //游戏运行 void GameRun(pSnake ps) {//打印帮助信息PrintHelpInfo(); do{//打印总分数和食物的分值SetPos(64, 10);printf(总分数%d, ps-_Socre);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_SPACE)) {//暂停Pause();}else if (KEY_PRESS(VK_ESCAPE)){//退出ps-_Status END_NORMAL;}else if (KEY_PRESS(VK_F3)) {//加速//每按一次休眠减少30ms,不能无限加速,加一次速食物加2分if (ps-_SleepTime 80) {ps-_SleepTime - 30;ps-_foodWeight 2;}}else if(KEY_PRESS(VK_F4)){//减速//每按一次休眠增加30ms,不能无限减速减一次速食物减2分if (ps-_foodWeight 2) {ps-_SleepTime 30;ps-_foodWeight - 2;}}//贪吃蛇走一步SnakeMove(ps);//走一步休息一下Sleep(ps-_SleepTime);} while (ps-_StatusOK); }//结束游戏 void GameEnd(pSnake ps) {SetPos(24,12);switch (ps-_Status) {case END_NORMAL:printf(你主动结束游戏\n);break;case KILL_BY_WALL:printf(你撞到墙上游戏结束\n);break;case KILL_BY_SELF:printf(你撞到了自己游戏结束\n);break;}//释放蛇身的链表pSnakeNode cur ps-_pSnake;while (cur) {pSnakeNode del cur;cur cur-next;free(del);} } 9.3 test.c部分 #define _CRT_SECURE_NO_WARNINGS 1 #includesnack.h//完成游戏的测试逻辑 void test() {int ch 0;do {system(cls);//创建贪吃蛇Snake snake { 0 };//初始化游戏:// 1打印环境界面2功能介绍3绘制地图4创建蛇5创建食物6设置游戏相关信息GameStart(snake);//运行游戏GameRun(snake);//结束游戏--善后工作GameEnd(snake);SetPos(20, 15);printf(再来一局吗(Y/N):);ch getchar();while (getchar() ! \n);} while (chy||chY);SetPos(0, 27); } int main() {//设置适配本地环境setlocale(LC_ALL, );//需要引用头文件locale.hsrand((unsigned int)time(NULL));test();return 0; }
http://www.w-s-a.com/news/872398/

相关文章:

  • 网站开发的前后端是什么注册网站多少钱一年
  • 彩票网站建设需要什么网站未备案被阻断怎么做
  • wordpress 版权声明网站优化排名哪家性价比高
  • dedecms网站关键词外包做网站平台 一分钟
  • 酒网站建设游戏分类网站怎么做
  • 仿牌网站安全北京大良网站建设
  • ps中怎样做网站轮播图片吉林省网站建设公司
  • 广西网站建设-好发信息网温江做网站哪家好
  • 网站建设属于什么职位类别南京哪个网站建设比较好
  • wdcp 网站备份东莞网站建设五金建材
  • 天津制作网站的公司电话wordpress架设进出销
  • tomcat做静态网站prestashop和wordpress
  • 上海响应式建站wap网站微信分享代码
  • 四川建筑人才招聘网南昌网站优化
  • 南充网站建设制作重庆有的设计网站大全
  • 深圳沙井做网站公司网站搭建谷歌seo
  • 学校资源网站的建设方案山西省住房城乡建设厅网站
  • 医疗行业网站建设深圳网络科技公司排名
  • 企业形象型网站建设wordpress chess
  • 网站的域名起什么好处罗湖网站建设公司乐云seo
  • 网站的服务器在哪里sem推广软件选哪家
  • 科技网站欣赏婚庆公司经营范围
  • 网站后台管理系统php校园网站建设意见表填写
  • 网站建设问题调查常州百度推广代理公司
  • net网站开发学习谷歌优化培训
  • 企业网站公众号广东网站建设方便
  • 2008r2网站建设张店网站建设方案
  • 企业网站首页学生做的网站成品
  • 网站开发 架构设计企业信息管理系统的组成不包括
  • 网站维护模式网页传奇游戏平台排行