贪吃蛇项目(小白保姆级教程)

游戏介绍

游戏背景:

贪吃蛇游戏是经典的游戏项目之一,也是很简单的小游戏

实现背景:

这里我们是基于32位的Win32_API进行实现的

需要的知识点:

C语言函数、枚举、结构体、动态内存管理、预处理指令、链表、Win32_API等

适合人群:有数据结构链表的知识,一定C语言代码能力人群

我这里使用到的搜索网站(用来搜索库函数头文件以及windows的库函数):

windows官方网站

SetConsoleCursorInfo 函数 - Windows Console | Microsoft Learn

C语言官方网站

C 关键词 - cppreference.com

C语言搜索网站

cplusplus.com/reference/cstdio/

需要注意

这里的贪吃蛇的实现,我们分装了很多函数,适合小白观看学习,因为函数分装过多,我甚至把本地化一段话都分装成函数。原因:这是一个知识点,可以更加详细的解释和增加注释。

游戏的思维导图

游戏的思维导图可以对照进行实现代码

创建文件

这里分别创建头文件,实现文件,测试文件

设置控制台信息

Win_API

Win_API是Windows应用程序接口(Windows Application Programming Interface)的缩写,它是一组函数、系统服务和程序接口,允许开发者在微软Windows操作系统上创建应用程序。Win32 API 是Windows API的一个主要部分,它为Windows应用程序提供了一系列的函数调用,这些函数可以用来执行各种任务,比如创建和管理窗口、处理用户输入、绘制图形、访问文件系统、网络通信等。


Win32 API 是Windows操作系统的一个核心组件,它为C和C++等编程语言提供了丰富的接口,使得开发者能够利用这些接口来控制和管理Windows操作系统的各个方面。通过调用这些API函数,开发者可以轻松地实现与操作系统相关的复杂功能,而无需关心底层操作系统的具体实现细节。


Win32 API 包括了数千个函数,涵盖了从基本的数据类型和宏定义到复杂的窗口管理和用户界面设计的各个方面。这些API函数被组织成不同的库,如Kernel32.dll、User32.dll、Gdi32.dll等,每个库都包含了与特定功能相关的函数。开发者可以在应用程序中动态地加载这些库,并调用相应的API函数来实现所需的功能。


Win32 API 是Windows平台上软件开发的基础,几乎所有的Windows应用程序都会直接或间接地使用Win32 API。因此,对于Windows平台上的软件开发者来说,了解和掌握Win32 API 是非常重要的。

Windows这个多作业系统除了协调应用程序的执行、分配内存、管理资源之外,它同时也是一个很大
的服务中心,调用这个服务中心的各种服务(每一种服务就是一个函数),可以帮应用程序达到开启
视窗、描绘图形、使用周边设备等目的,由于这些函数服务的对象是应用程序(Application),所以便
称之为ApplicationProgrammingInterface,简称API函数。WIN32_API也就是Microsoft Windows
32位平台的应用程序编程接口

编译器界面窗口的设置

这里使用的是vs编译器

首先我们需要改变属性

这里说明一下,不管你是让windows决定,还是windows控制价台主机,这里都是可以的,

只要不是windows终端就可以

因为不同电脑有些让windows决定产生的效果和windows终端的效果是一样的,所以我们可以使用windows控制台主机,我这里选择的就是windows控制台主机

这里和电脑有点关系,不用担心

设置控制台的大小

设置控制台的程序的大小,我们可以使用windows命令,来控制编译器,只需要进行调用就可以

mode con cols=100 lines=30`mode con cols=100 lines=30` 是一个在DOS和Windows命令提示符(cmd.exe)中使用的命令,用于设置命令行窗口的大小。这个命令的作用是将命令行窗口的宽度和高度分别设置为100列和30行。
- `mode` 是命令行工具,用于配置系统设备的工作模式。
- `con` 指的是控制台(console),也就是命令行窗口。
- `cols=100` 指定命令行窗口的宽度为100个字符。
- `lines=30` 指定命令行窗口的高度为30行。
在Windows操作系统中,你可以通过打开命令提示符(cmd)并输入这个命令来改变窗口的大小。这个命令对于需要更多空间来查看文本输出或者进行文本操作的场合非常有用。

这里我们打开cmd演示一下

可以看到原本的大小

设置之后的大小,为了对比明显,我特地搞得小一点

system 

那么在编译器里面如果调用?并且设置控制台大小
我们只需要使用一个函数

语法格式

在C语言中,system 函数是一个库函数,用于执行宿主系统上的命令。这个函数声明在 stdlib.h 头文件中。它的原型如下:

int system(const char *command);

当你调用 system 函数时,它会将传递给它的字符串作为命令传给命令处理器(在Unix-like系统上是shell,在Windows上是cmd.exe),然后执行这个命令。函数的返回值依赖于具体的实现,但通常情况下,如果命令执行成功,它会返回0;如果执行失败,则会返回一个非零值。

此时我们也就是调用成功并且成功设置控制台大小了

如图

设置控制台的名字

设置控制台的名字也很简单,也一个cmd的使用

title + 名字

在编译器里面进行调用

封装一个控制台的函数

//设置控制台的相关属性
void set_Information()
{system("mode con cols=120 lines=35");system("title 贪吃蛇");
}

隐藏光标的属性 

目的

在编写贪吃蛇这样的文本模式游戏时,隐藏光标是一个常用的技巧,目的是为了提高用户体验和游戏的视觉表现。隐藏光标的属性目的主要包括以下几点:
1. **改善视觉效果**:

光标在屏幕上闪烁可能会分散玩家的注意力,尤其是在移动快速的贪吃蛇游戏中。隐藏光标可以让玩家更专注于游戏本身,而不是被光标干扰。


2. **避免混淆**:

在文本模式下,光标可能会与游戏中的字符混淆,尤其是当光标与游戏中的蛇或食物的字符颜色相同时。隐藏光标可以避免这种混淆,确保游戏的清晰度。


3. **保持游戏界面的整洁**:

贪吃蛇游戏通常有一个简洁的游戏界面,光标的存在可能会破坏这种简洁性。隐藏光标有助于保持游戏界面的整洁和统一。


4. **技术限制**:

在某些情况下,显示光标可能会影响游戏的刷新速度或响应时间。隐藏光标可以减少这种技术限制的影响,使得游戏运行更加流畅。


在C语言或其他编程语言中,通常会使用特定的系统调用来隐藏光标。例如,在Windows操作系统的命令行界面中,可以使用 `CONSOLE_CURSOR_INFO` 结构和 `GetConsoleCursorInfo` 和 `SetConsoleCursorInfo` 函数来隐藏或显示光标。在Linux或Unix系统中,可以使用终端控制序列来隐藏光标,例如在C语言中可以使用 `printf("\e[?25l")` 来隐藏光标,使用 `printf("\e[?25h")` 来显示光标。
隐藏光标是一个简单的步骤,但它可以提高贪吃蛇这类文本模式游戏的总体质量和玩家的游戏体验。

GetStdHandle 函数(句柄函数)

函数的使用

1,获得句柄

炒菜需要菜->锅->锅柄

我们这个时候需要拿到锅柄

简单的说就是,这里有三个参数分别对照的是输入流,屏幕显示,控制台

我们需要的是屏幕的光标消失

所以我们在GetStdHandle 函数里面输入STD_OUTPUT_HANDLE参数

然后命名一个变量进行接收

//举例
HANDLE hOutput = NULL;
//获取标准输出的句柄(用来标识不同设备的数值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);

GetConsoleCursorlnfo(获得光标信息)

此时我们拿到锅柄了,那么我们此时需要拿到锅,有锅才能炒菜

可以看到,我们的函数语法一个是指向句柄,一个是指向参数的指针

所以这里我们还需要讲解一下

CONSOLE_CURSOR_INFO 结构

因为GetConsoleCursorlnfo(获得光标信息)函数事情比较多,里面的参数一个是句柄,一个在这个结构体

我们可以看见这个结构体一个是游标占比一个是游标的可见性,我们这里是设置的游标的可见性,那么参数里面说的很清楚,设置游标的可见性,可见可以直接设置为TRUE

那么不可见就可以设置为FALSE

此时我们回到GetConsoleCursorlnfo(获得光标信息)函数,我们需要了解的是,GetConsoleCursorlnfo(获得光标信息)接收的参数一个是句柄信息,一个是存储游标信息的结构体,但是我们不能直接进行设置

因为GetConsoleCursorlnfo(获得光标信息)本身就是获取光标信息的函数,我们需要先获取光标信息,再设置

我们先上一部分代码

	//句柄//句柄 目的就像锅的把柄一样  可以这样 HANDLE houtpu = GetStdHandle(STD_OUTPUT_HANDLE); HANDLE 数值就是指针HANDLE houtpu = NULL;houtpu = GetStdHandle(STD_OUTPUT_HANDLE);//获得光标信息//拿到句柄之后,需要获得光标的信息,就像我们有了锅的把柄,然后得有锅才能炒菜,改变菜的属性,生->熟//GetConsoleCursorInfo控制台光标信息 第一个参数的光标信息,第二个参数的指针,指向CONSOLE CURSOR INFO的指针//创建一个CONSOLE CURSOR INFO的结构体,这个结构体是描述创建控制台光标信息的,创建并且初始化CONSOLE_CURSOR_INFO cureor_info = { 0 };GetConsoleCursorInfo(houtpu, &cureor_info);//隐藏控制台光标,把创建的结构体指针设置为不可见//dwSize,光标填充的字符单元格的百分比。 此值介于1到100之间。 光标外观会变化,范围从完全填充单元格到单元底部的水平线条。//则此成员为 TRUE。CursorInfo.bVisible=false;//隐藏控制台光标	//bVisible,游标的可见性。 如果光标可见,cureor_info.bVisible = false;

提醒一下,这里的结构体的设置是需要进行初始化的,并且设置可见性,是在获取光标信息下的,因为设置需要先获取才能设置,但是此时的设置还没有设置到控制台里面

也就是此时虽然我们已经设置了光标的属性是不可见,但是此时还是可见的

因为设置光标信息还有一个函数

SetConsoleCursorInfo(设置光标信息)

我们看一下语法信息

我们在windows官网可以查询到,设置光标信息函数的参数一个是句柄函数,一个是结构体的参数

所以我们的完整代码可以写为

	//句柄//句柄 目的就像锅的把柄一样  可以这样 HANDLE houtpu = GetStdHandle(STD_OUTPUT_HANDLE); HANDLE 数值就是指针HANDLE houtpu = NULL;houtpu = GetStdHandle(STD_OUTPUT_HANDLE);//获得光标信息//拿到句柄之后,需要获得光标的信息,就像我们有了锅的把柄,然后得有锅才能炒菜,改变菜的属性,生->熟//GetConsoleCursorInfo控制台光标信息 第一个参数的光标信息,第二个参数的指针,指向CONSOLE CURSOR INFO的指针//创建一个CONSOLE CURSOR INFO的结构体,这个结构体是描述创建控制台光标信息的,创建并且初始化CONSOLE_CURSOR_INFO cureor_info = { 0 };GetConsoleCursorInfo(houtpu, &cureor_info);//隐藏控制台光标,把创建的结构体指针设置为不可见//dwSize,光标填充的字符单元格的百分比。 此值介于1到100之间。 光标外观会变化,范围从完全填充单元格到单元底部的水平线条。//则此成员为 TRUE。CursorInfo.bVisible=false;//隐藏控制台光标	//bVisible,游标的可见性。 如果光标可见,cureor_info.bVisible = false;//设置光标信息//SetConsoleCursorInfo设置光标信息//参数//BOOL WINAPI SetConsoleCursorInfo(//	_In_       HANDLE              hConsoleOutput,//	_In_ const CONSOLE_CURSOR_INFO * lpConsoleCursorInfo//);SetConsoleCursorInfo(houtpu, &cureor_info);

封装一个隐藏光标属性的函数

//隐藏光标的信息
void set_hide()
{//句柄//句柄 目的就像锅的把柄一样  可以这样 HANDLE houtpu = GetStdHandle(STD_OUTPUT_HANDLE); HANDLE 数值就是指针HANDLE houtpu = NULL;houtpu = GetStdHandle(STD_OUTPUT_HANDLE);//获得光标信息//拿到句柄之后,需要获得光标的信息,就像我们有了锅的把柄,然后得有锅才能炒菜,改变菜的属性,生->熟//GetConsoleCursorInfo控制台光标信息 第一个参数的光标信息,第二个参数的指针,指向CONSOLE CURSOR INFO的指针//创建一个CONSOLE CURSOR INFO的结构体,这个结构体是描述创建控制台光标信息的,创建并且初始化CONSOLE_CURSOR_INFO cureor_info = { 0 };GetConsoleCursorInfo(houtpu, &cureor_info);//隐藏控制台光标,把创建的结构体指针设置为不可见//dwSize,光标填充的字符单元格的百分比。 此值介于1到100之间。 光标外观会变化,范围从完全填充单元格到单元底部的水平线条。//则此成员为 TRUE。CursorInfo.bVisible=false;//隐藏控制台光标	//bVisible,游标的可见性。 如果光标可见,cureor_info.bVisible = false;//设置光标信息//SetConsoleCursorInfo设置光标信息//参数//BOOL WINAPI SetConsoleCursorInfo(//	_In_       HANDLE              hConsoleOutput,//	_In_ const CONSOLE_CURSOR_INFO * lpConsoleCursorInfo//);SetConsoleCursorInfo(houtpu, &cureor_info);
}

此时可以发现,界面没有光标显示了

设置定位坐标

目的

在编程和游戏开发中,设置定位坐标的目的是为了确定对象在屏幕或游戏世界中的具体位置。坐标通常由一对数值表示,例如 (x, y),其中 x 表示水平位置,y 表示垂直位置。设置定位坐标的目的包括:


1. **精确控制对象位置**:

通过坐标,开发者可以精确地控制游戏对象(如角色、道具、障碍物等)在屏幕上的位置,从而创建精确的游戏场景和布局。


2. **运动和动画**:

在游戏或动画中,对象的移动是通过不断更新其坐标来实现的。通过改变坐标,可以模拟对象在屏幕上的移动、跳跃、飞翔等动作。


3. **用户交互**:

在用户界面设计中,坐标用于确定按钮、文本框、图标等元素的位置,以便用户能够与之交互。正确的坐标设置可以提升用户体验。


4. **碰撞检测**:

在游戏中,坐标用于检测和响应对象之间的碰撞。通过比较对象的坐标和大小,可以确定是否发生了碰撞,并据此触发相应的游戏逻辑。


5. **图形渲染**:

在图形编程中,坐标用于确定图形元素(如点、线、形状、图像等)的绘制位置。正确的坐标设置可以确保图形按照预期显示。


6. **空间关系和布局**:

坐标用于建立对象之间的空间关系和布局,这对于游戏设计和用户界面设计至关重要。


7. **寻路和导航**:

在需要寻路或导航的游戏中,坐标用于确定角色或物体的移动路径,以及它们如何在不同地点之间移动。


8. **模拟和仿真**:

在模拟和仿真程序中,坐标用于表示现实世界对象的位置,这对于模拟真实世界中的物理现象或过程非常重要。


总之,设置定位坐标是编程和游戏开发中的一个基本概念,它允许开发者创建动态、交互式和有组织的视觉体验。
 

坐标的概念

这里我们可以看见,控制台也是按照坐标系进行计算然后设置围墙和定位的

这里我想表达的是,我们可以看见,一个字符占据的是1x,1y

但是,y比x长

也就是2x=y

所以在之后打印里面,一个宽字符占据的是两个字符的大小

也就是占据2x,1y的大小

COORD 结构

这个结构就是一个很简单的定位位置的结构体,

	//定位光标的位置COORD pos1 = { x,y };

这里注意括号{ },不要写成【】,因为这里是结构体

SetConsoleCursorPosition (定位光标位置)

我们可以看到定位光标的位置还是比较简单的,只需要句柄参数和COOR参数,什么意思呢也就是,我们主需要获得句柄,定义一个结构体位置信息,就可以定位光标的位置

	//设置光标的位置,目的是可以让最后的提示按照想出现的位置进行出现//BOOL WINAPI SetConsoleCursorPosition(//	In HANDLE hConsoleOutput,//句柄//	In COORD  dwCursorPosition//位置//);//获得句柄HANDLE houtpu = NULL;houtpu = GetStdHandle(STD_OUTPUT_HANDLE);//定位光标的位置COORD pos1 = { x,y };SetConsoleCursorPosition(houtpu, pos1);

封装一个设置定位坐标

//定义光标的位置/定义位置
void set_pos(short x, short y)
{//设置光标的位置,目的是可以让最后的提示按照想出现的位置进行出现//BOOL WINAPI SetConsoleCursorPosition(//	In HANDLE hConsoleOutput,//句柄//	In COORD  dwCursorPosition//位置//);//获得句柄HANDLE houtpu = NULL;houtpu = GetStdHandle(STD_OUTPUT_HANDLE);//定位光标的位置COORD pos1 = { x,y };SetConsoleCursorPosition(houtpu, pos1);
}

这里我们可以看到我们可以把欢迎来到贪吃蛇小游戏的界面文字定义到控制台中间

请按任意键继续

这里就是 一个很简单的cmd命令

	system("pause");

只要在欢迎来到贪吃蛇小游戏,下面定位一个位置,并且使用这个cmd命令进行调用就可以

	//1.打印环境界面set_pos(40, 14);wprintf(L"欢迎来到贪吃蛇小游戏\n");//按任意键继续set_pos(40, 15);system("pause");

游戏的界面设置

wprintf

`wprintf` 是一个函数,用于在Windows操作系统中以宽字符(wchar_t)形式输出格式化的字符串到标准输出(通常是控制台或终端)。它是 `printf` 函数的宽字符版本,用于处理多字节字符集,特别是在需要支持Unicode字符的场合。
`wprintf` 函数原型定义在 `stdio.h` 头文件中,如下所示:
 

int wprintf(const wchar_t *format, ...);

参数 `format` 是一个宽字符字符串,它指定了输出的格式和内容。格式字符串可以包含普通字符和格式规范符,格式规范符用于指定后续参数的输出格式。`wprintf` 函数后面的参数列表 `...` 表示函数可以接受多个参数,这些参数将替换格式字符串中的格式规范符。
与 `printf` 函数相比,`wprintf` 使用宽字符 `wchar_t` 作为字符串和字符的存储单位,而 `printf` 使用单字节的 `char`。在UTF-16编码的Windows操作系统中,宽字符通常用于表示Unicode字符。
下面是一个使用 `wprintf` 函数的例子:


#include <stdio.h>
int main() {wchar_t *text = L"Hello, World!";int number = 42;// 使用wprintf输出宽字符字符串和整数wprintf(L"%ls: %d\n", text, number);return 0;
}

在这个例子中,`%ls` 是一个格式规范符,用于输出宽字符字符串(`wchar_t*`),`%d` 用于输出整数。注意,字符串前缀 `L` 表示这是一个宽字符字符串。
在使用 `wprintf` 时,需要确保编译器和运行环境支持宽字符处理。在Windows平台上,这通常不是问题,因为Windows API原生支持宽字符。在Linux和其他Unix-like系统上,你可能需要使用 iconv 或其他库来处理宽字符编码。

本地化 

本地化

提供的函数⽤于控制C标准库中对于不同的地区会产⽣不⼀样⾏为的部分。

在标准中,依赖地区的部分有以下⼏项:

• 数字量的格式

• 货币量的格式

• 字符集

• ⽇期和时间的表⽰形式

类项

通过修改地区,程序可以改变它的⾏为来适应世界的不同区域。但地区的改变可能会影响库的许多部

分,其中⼀部分可能是我们不希望修改的。所以C语⾔⽀持针对不同的类项进⾏修改,下⾯的⼀个宏,

指定⼀个类项:

• LC_COLLATE:影响字符串⽐较函数 strcoll() 和 strxfrm() 。

• LC_CTYPE:影响字符处理函数的⾏为。

• LC_MONETARY:影响货币格式。

• LC_NUMERIC:影响 printf() 的数字格式。

• LC_TIME:影响时间格式 strftime() 和 wcsftime() 。

• LC_ALL - 针对所有类项修改,将以上所有类别设置为给定的语⾔环境。

封装本地化函数,在主函数里面进行调用

//设置当前地区属性
void snake_setlocale()
{//设置当前所在地区的模 char* setlocale (int category, const char* locale);式//• LC_COLLATE:影响字符串⽐较函数 strcoll() 和 strxfrm() 。//	• LC_CTYPE:影响字符处理函数的⾏为。//	• LC_MONETARY:影响货币格式。//	• LC_NUMERIC:影响 printf() 的数字格式。//	• LC_TIME:影响时间格式 strftime() 和 wcsftime() 。//	• LC_ALL - 针对所有类项修改,将以上所有类别设置为给定的语⾔环境。//不设置贪吃蛇本地属性,wprintf就不能打印出来setlocale(LC_ALL, "");
}

打印游戏的运行界面

//打印环境界面
void Welcome_To_Gmae()
{//1.打印环境界面set_pos(40, 14);wprintf(L"欢迎来到贪吃蛇小游戏\n");//按任意键继续set_pos(40, 15);system("pause");//清屏system("cls");set_pos(40, 14);wprintf(L"不能穿墙,可以咬到自己\n");set_pos(40, 15);wprintf(L"用 ↑. ↓. ←. →. 来控制蛇的移动\n");set_pos(40, 16);wprintf(L"按F3加速,F4减速\n");set_pos(40, 17);wprintf(L"加速可以获得更高的分数\n");set_pos(40, 18);wprintf(L"ESC :退出游戏.space(空格):暂停游戏.\n");set_pos(40, 19);wprintf(L"每个食物得分:20分\n");set_pos(90, 28);wprintf(L"精心制作\n");set_pos(90, 29);system("pause");system("cls");
}

这里就是采取定位,定位之后进行打印

看一下效果图

绘制地图

//这里是我们子啊头文件定义的围墙,方便我们随时更换围墙
#define WALL L'□'//实现文件 3.绘制地图
void SnakeMap()
{//上for (int i = 0; i < 29; i++){wprintf(L"%lc", WALL);}//下set_pos(0, 26);for (int i = 0; i < 29; i++){wprintf(L"%lc", WALL);}//左for (int i = 0; i < 26; i++){set_pos(0, i);wprintf(L"%lc", WALL);}//右for (int i = 0; i < 26; i++){set_pos(56, i);wprintf(L"%lc", WALL);}}

这里还是不难的,简单的说就是进行定位,然后进行打印地图,当然此时是蛇还是没有的


绘制地图的逻辑解释:
1. **绘制上边**:
   - 第一个 `for` 循环从 `i = 0` 开始,到 `i < 29` 结束,循环次数为29次。打印了29个围墙
   - 在每次循环中,使用 `wprintf` 函数打印一个宽字符 `WALL`,这代表地图的上边。
   - 因为循环体的作用是打印字符,所以循环结束后,会在当前行打印一排墙。
2. **绘制下边**:
   - `set_pos(0, 26);` 调用了一个假设的 `set_pos` 函数(这个函数没有在代码中定义),目的是将光标移动到第0列第26行的位置,这样下一个打印的字符就会出现在地图的底部。
   - 接下来的 `for` 循环与绘制上边类似,也是循环29次,打印一排墙,作为地图的下边。
3. **绘制左边**:
   - 第三个 `for` 循环从 `i = 0` 开始,到 `i < 26` 结束,循环次数为26次。
   - 在每次循环中,首先使用 `set_pos(0, i);` 将光标移动到第0列的第 `i` 行。
   - 然后打印一个宽字符 `WALL`,这代表地图的左边。
4. **绘制右边**
   - 第四个 `for` 循环与绘制左边类似,也是循环26次。
   - 在每次循环中,使用 `set_pos(56, i);` 将光标移动到第56列的第 `i` 行。
   - 然后打印一个宽字符 `WALL`,这代表地图的右边。
综合以上步骤,`SnakeMap` 函数会绘制一个由墙壁包围的地图,地图的宽度为58个字符(左右各一个边界,加上中间的56个空位),高度为28个字符(上下各一个边界,加上中间的26个空位)。这个地图是贪吃蛇游戏的基本框架,贪吃蛇在这个区域内移动,并且不能穿过墙壁。

效果图
 

补充说明

最终围墙的大小是偶数位的

图的尺寸和位置设置是基于偶数设计的。这是因为在某些文本模式下,字符单元格的宽度和高度可能不相等,特别是在使用宽字符(例如UTF-16编码的字符)时。在这种情况下,宽字符可能占据两个标准字符单元格的宽度,这就可能导致显示问题时字符单元格不成对出现。

以下是一些可能的原因:

  1. 宽字符的显示

    • 在宽字符模式下,每个宽字符可能需要两个字符单元格来显示。因此,如果地图的尺寸不是偶数,那么在绘制地图边界时可能会出现字符错位的问题。
  2. 光标定位的准确性

    • 在文本模式下,光标定位通常是基于字符单元格的。如果地图的尺寸是奇数,那么在尝试将光标定位到地图的角落或边缘时,可能会出现光标定位不准确的情况。
  3. 对称性

    • 使用偶数尺寸的地图可以确保地图在水平和垂直方向上都具有对称性,这有助于简化绘制逻辑和游戏逻辑。
  4. 边界条件

    • 在游戏逻辑中,处理边界条件(例如,蛇撞墙)可能会更加简单,如果地图的尺寸是偶数,那么判断蛇是否撞墙的算法可能会更直接。
  5. 蛇和食物:

    蛇的每个节点的x坐标必须是2个倍数,否则可能会出现蛇的⼀个节点有⼀半⼉出现在墙体中,另外⼀般在墙外的现象,坐标不好对⻬

总之,使用偶数尺寸的地图可能是为了确保显示的一致性和游戏逻辑的简化。然而,这并不是一个硬性规定,只是根据具体情况做出的设计选择。也可以使用奇数尺寸。

接下来我们马上步入正题,蛇的初始化

蛇的初始化 

创建蛇的节点(头文件实现)

//创建节点
typedef struct Snake_Snakenode
{int x;int y;struct Snake_Snakenode* next;
}Snake_Snakenode, * pSnake_Snakenode;

蛇的本质就是链表

创建蛇的状态(头文件)

//创建蛇的状态
//方向,头节点,状态,食物,一个食物的分数,时间,得分情况
typedef struct Snake_State
{pSnake_Snakenode _SNAKE_HEAD;//蛇的头节点pSnake_Snakenode _SNAKE_FOOD;//蛇的食物enum _STATE _SNAKE_STATE;//蛇的状态enum _DIR _SNAKE_DIR;//蛇的方向int _SNAKE_SCORE;//总成绩int _SNAKE_SLEEP_TIME;//蛇中间运动的时候的休息时间int _SNAKE_ONE_FOOD;//一个食物的分数
}Snake_State, * pSnake_State;

这里蛇需要有几个状态

方向,头节点,状态,食物,一个食物的分数,时间,得分情况

创建蛇绘制在地图(实现文件)

//创建蛇
void Snake_Create(pSnake_State ps)
{//初始蛇是五个节点pSnake_Snakenode cur = NULL;for (int i = 0; i < 5; i++){//创建节点cur = (pSnake_Snakenode)malloc(sizeof(Snake_Snakenode));if (cur == NULL){perror("Snake_Create:pSnake_Snakenode newnode:error:");return;}//进行定位cur->next = NULL;cur->x= POS_X + i * 2;cur->y = POS_Y;//进行头插if (ps->_SNAKE_HEAD == NULL){ps->_SNAKE_HEAD = cur;}else{cur->next = ps->_SNAKE_HEAD;ps->_SNAKE_HEAD = cur;}}//进行打印cur = ps->_SNAKE_HEAD;while (cur){Snake_Cursor_Position(cur->x, cur->y);wprintf(L"%lc", BOOY);cur = cur->next;}//初始化蛇的状态ps->_SNAKE_STATE = OK;//状态ps->_SNAKE_SLEEP_TIME = 200;//休息时间ps->_SNAKE_SCORE = 0;//总成绩ps->_SNAKE_ONE_FOOD = 20;//一个食物的分数ps->_SNAKE_DIR = RIGHT;//蛇的方向
}

这段代码是一个创建蛇的函数,用于初始化一个蛇形游戏中的蛇的状态和位置。在这个函数中,蛇被表示为一个链表,每个节点包含蛇的一段的位置信息。
下面是代码的解释:


1. `Snake_Create` 函数接受一个指向 `Snake_State` 结构的指针 `ps`,这个结构可能包含了蛇的状态信息,如头节点、状态、休息时间、得分等。


2. 在 `for` 循环中,代码创建了五个节点,每个节点代表蛇的一段。每个节点的 `x` 坐标从 `POS_X` 开始,每次增加 2,`y` 坐标保持为 `POS_Y`。


3. `malloc` 用于动态分配内存,创建一个新的蛇节点。如果分配失败,`malloc` 会返回 `NULL`,此时使用 `perror` 打印错误信息并返回。


4. 每个新创建的节点被插入到链表的头部,形成一个新的蛇。这是通过头插法实现的,新节点成为新的头节点,其 `next` 指针指向原来的头节点。


5. 在蛇的节点创建完成后,代码使用一个循环遍历蛇的每个节点,并使用 `Snake_Cursor_Position` 函数将光标定位到蛇的每个节点的位置,并打印出蛇的图形(可能是字符 `BOOY`)。


6. 最后,代码初始化蛇的状态,如设置蛇的状态为 `OK`,设置蛇的休息时间为 200 毫秒,设置蛇的得分为 0,设置一个食物的分数为 20,设置蛇的初始方向为 `RIGHT`。


这个函数是蛇形游戏初始化阶段的一部分,用于设置蛇的初始状态。在实际的游戏中,蛇会根据用户的输入或其他游戏逻辑进行移动,可能会吃到食物并增长,也可能会遇到边界或自身而游戏结束。
 

创建食物(实现文件)

//创建食物
void Snake_food(pSnake_State ps)
{int x = 0; int y = 0;again:do{x = rand() % 74 + 2;y = rand() % 28 + 1;} while (x % 2 != 0);//判断是不是和身体重合pSnake_Snakenode headcur = ps->_SNAKE_HEAD;while (headcur){if (x == headcur->x && y == headcur->y){goto again;}headcur = headcur->next;}//创建食物//pSnake_Snakenode curfood = (pSnake_Snakenode)malloc(sizeof(Snake_Snakenode));pSnake_Snakenode curfood = NULL;//必须初始化curfood = (pSnake_Snakenode)malloc(sizeof(Snake_Snakenode));if (curfood == NULL){perror("pSnake_Snakenode curfood:error:");return;}curfood->next = NULL;curfood->x = x;curfood->y = y;Snake_Cursor_Position(x, y);wprintf(L"%lc", FOOD);//创建的食物放到蛇的状态里面去ps->_SNAKE_FOOD = curfood;
}

这段代码定义了一个函数 `Snake_food`,其目的是在蛇形游戏中创建食物。食物在游戏中的作用通常是让蛇去吃,从而增长蛇的长度,增加游戏的得分。
下面是代码的解释:


1. `Snake_food` 函数接受一个指向 `Snake_State` 结构的指针 `ps`,这个结构可能包含了蛇的状态信息,如头节点、状态、休息时间、得分等。


2. 使用 `rand()` 函数生成随机坐标 `x` 和 `y` 来放置食物。食物的位置需要在游戏界面的范围内,并且 `x` 坐标是偶数,这可能是为了与蛇的节点的坐标对齐。


3. 使用一个 `do-while` 循环来确保 `x` 坐标是偶数。如果生成的 `x` 是奇数,循环会再次执行,直到生成一个偶数。


4. 使用一个 `while` 循环遍历蛇的每个节点,检查新生成的食物坐标是否与蛇的身体重合。如果重合,使用 `goto` 语句跳转到 `again` 标签,重新生成食物的坐标。


5. 分配内存创建一个食物节点 `curfood`。如果内存分配失败,使用 `perror` 打印错误信息并返回。


6. 将食物的坐标设置为生成的 `x` 和 `y`,并将其 `next` 指针设置为 `NULL`。


7. 使用 `Snake_Cursor_Position` 函数将光标定位到食物的位置,并打印出食物的图形(可能是字符 `FOOD`)。


8. 将创建的食物节点存储在 `Snake_State` 结构的 `_SNAKE_FOOD` 成员中,这样蛇就可以在游戏中吃掉这个食物了。


这个函数是蛇形游戏的一部分,用于在游戏界面中随机位置生成食物。在实际的游戏中,这个函数可能会被周期性地调用,以在蛇吃掉食物后生成新的食物。
 

游戏的运行逻辑

随机生成食物

随机生成食物其实本质上还是坐标是生成,生成坐标就需要创建节点

但是需要知道的一点是我们创建的蛇是偶数位置的,所以我们吃的食物也得是偶数位置的,但是,

1,我们这里是食物创建是随机数值,我们不知道实物创建是偶数还是奇数,所以我们需要给一个循环

2,我们创建的食物还不能和身体进行重合(否则就进行重新创建)

3,创建的区间应该是围墙里面,不能是围墙外面(否则就进行重新创建)

4,这些条件都满足之后,我们把做坐标放到节点里面

5,最后进行定位和打印

//创建食物
void Snake_food(pSnake_State ps)
{int x = 0; int y = 0;again:do{x = rand() % 74 + 2;y = rand() % 28 + 1;} while (x % 2 != 0);//判断是不是和身体重合pSnake_Snakenode headcur = ps->_SNAKE_HEAD;while (headcur){if (x == headcur->x && y == headcur->y){goto again;}headcur = headcur->next;}//创建食物//pSnake_Snakenode curfood = (pSnake_Snakenode)malloc(sizeof(Snake_Snakenode));pSnake_Snakenode curfood = NULL;//必须初始化curfood = (pSnake_Snakenode)malloc(sizeof(Snake_Snakenode));if (curfood == NULL){perror("pSnake_Snakenode curfood:error:");return;}curfood->next = NULL;curfood->x = x;curfood->y = y;Snake_Cursor_Position(x, y);wprintf(L"%lc", FOOD);//创建的食物放到蛇的状态里面去ps->_SNAKE_FOOD = curfood;
}

这段代码是用于在一个蛇游戏中创建食物的功能。以下是代码的详细解释:


1. `void Snake_food(pSnake_State ps)`:这是一个函数声明,意味着这个函数接受一个指向 `Snake_State` 结构体的指针作为参数,但没有返回值。


2. `int x = 0; int y = 0;`:定义了两个整数变量 `x` 和 `y`,用来存储食物的位置。


3. `again:`:这是一个标签,用于 `goto` 语句,如果食物位置不合理,会跳转到这个标签重新生成食物位置。


4. `do { ... } while (x % 2 != 0);`:这是一个 `do-while` 循环,首先执行循环体一次,然后检查条件 `x % 2 != 0`。如果条件为真,继续执行循环体;如果为假,退出循环。循环的目的是确保 `x` 是一个偶数,因为代码中提到“while (x % 2 != 0)”,但实际上蛇的食物位置应该在网格的交点上,即 x 和 y 坐标都应该是一个偶数(如果是从 0 开始的话)


5. `pSnake_Snakenode headcur = ps->_SNAKE_HEAD;`:定义了一个指向蛇头节点的指针 `headcur`,用于在蛇的身体中遍历,以检查新产生的食物位置是否与蛇的身体重合。


6. `while (headcur)`:这是一个循环,只要 `headcur` 不是 `NULL`,就会继续执行。

7. `if (x == headcur->x && y == headcur->y)`:检查当前食物位置 `(x, y)` 是否与蛇的头部位置相同。

8. `goto again;`:如果食物位置与蛇头位置相同,跳转到 `again` 标签,重新生成食物位置。

9. `curfood = (pSnake_Snakenode)malloc(sizeof(Snake_Snakenode));`:动态分配一个 `Snake_Snakenode` 类型大小的内存块,并将地址强制转换为 `pSnake_Snakenode` 类型,然后将其赋值给 `curfood` 指针。

10. `if (curfood == NULL)`:检查 `malloc` 是否分配成功。如果失败,打印错误信息并返回。

11. `curfood->next = NULL;`:初始化新分配的节点,使其 `next` 指针为 `NULL`。

12. `curfood->x = x;`:将食物节点的 `x` 坐标设置为新产生的食物位置 `x`。

13. `curfood->y = y;`:将食物节点的 `y` 坐标设置为新产生的食物位置 `y`。

14. `Snake_Cursor_Position(x, y);`:这个函数可能是用来将光标移动到食物位置的函数,但在这段代码中没有给出定义。

15. `wprintf(L"%lc", FOOD);`:在控制台上输出食物字符。`wprintf` 是用于宽字符的输出函数,`%lc` 用于输出一个宽字符。`FOOD` 应该是一个定义了食物字符的常量。

16. `ps->_SNAKE_FOOD = curfood;`:将新产生的食物节点赋值给 `Snake_State` 结构体中的 `_SNAKE_FOOD` 字段,这样食物就保存在蛇的状态中,可以在游戏中使用。

虚拟键代码

虚拟键码 (Winuser.h) - Win32 apps | Microsoft Learn

在Windows操作系统中,虚拟键代码(Virtual-Key Codes)是一组用来表示键盘上按键的数值。这些代码通常用于Windows API函数,以便程序能够识别和处理键盘输入。
虚拟键代码是按键的物理位置定义的,而不是按字符本身定义的。这意味着,无论用户的键盘布局如何,同一物理按键的虚拟键代码都是相同的。例如,无论用户使用的是美式键盘、德式键盘还是日式键盘,字母 'A' 键的虚拟键代码都是 `VK_A`。
虚拟键代码通常以 `VK_` 开头,后面跟着一个或多个大写字母,表示特定的按键。例如:
- `VK_A` 表示 'A' 键。
- `VK_B` 表示 'B' 键。
- `VK_SPACE` 表示空格键。
- `VK_RETURN` 表示回车键。
- `VK_ESCAPE` 表示 ESC 键。
- `VK_UP` 表示向上箭头键。
- `VK_DOWN` 表示向下箭头键。
- `VK_LEFT` 表示向左箭头键。
- `VK_RIGHT` 表示向右箭头键。
- `VK_F1` 到 `VK_F12` 表示功能键 F1 到 F12。
在程序中,可以通过 `GetAsyncKeyState`、`GetKeyState` 或 `TranslateMessage` 等Windows API函数来检测虚拟键代码,从而判断用户是否按下了某个键。
 

if (KEY_PRESS(VK_UP) && ps->_SNAKE_DIR != DOWN)
{ps->_SNAKE_DIR = UP;
}

这里的 `KEY_PRESS(VK_UP)` 是一个宏或函数,用来检查用户是否按下了向上箭头键(`VK_UP`)。如果按下了,并且蛇当前的方向不是向下(`DOWN`),则将蛇的方向设置为向上(`UP`)。
这些虚拟键代码使得开发者能够以一种与键盘布局无关的方式处理键盘输入,从而编写出更加通用和易于移植的软件。
简单的说虚拟键代码的目的就是,进行识别是否按键

封装一个宏

我们封装一个宏,进行虚拟键代码的判断

#define KEY_PRESS(vk) (GetAsyncKeyState(vk)&1?1:0)

宏 `KEY_PRESS(vk)` 是一个用于检测指定虚拟键 `vk` 是否被按下的辅助宏。它使用了 `GetAsyncKeyState` 函数,这是Windows API中的一个函数,用于检查异步键盘输入的状态。


下面是宏 `KEY_PRESS(vk)` 的详细解释:
- `GetAsyncKeyState(vk)`:这个函数接受一个虚拟键代码 `vk` 作为参数,并返回该键的异步键状态。返回值是一个16位的整数,其中第15位(从最低位开始的最高位)表示该键是否被按下。如果该位为1,则键被按下;如果为0,则键没有按下。


- `&1`:这是一个位运算符,用于对 `GetAsyncKeyState(vk)` 的返回值进行按位与操作。这里 `1` 表示二进制的最低位为1,其他位为0。因此,`&1` 操作实际上是在检查

`GetAsyncKeyState(vk)` 返回值的第15位是否为1。


- `? :`:这是C语言中的三元条件运算符。如果 `GetAsyncKeyState(vk)&1` 的结果为真(非零),则表达式的值为 `1`,表示键被按下;如果为假(零),则表达式的值为 `0`,表示键没有被按下。


综上所述,`KEY_PRESS(vk)` 宏的目的是简化对 `GetAsyncKeyState` 函数的调用,并返回一个布尔值(0或1),指示指定的虚拟键 `vk` 是否被按下。在您的代码中,这个宏被用于检测用户的键盘输入,并根据这些输入来更新蛇的方向或游戏状态。
 

按键信息(实现文件)

//蛇的运行逻辑
void Snake_RunGame(pSnake_State ps)
{do{//打印分数Snake_Cursor_Position(90, 20);wprintf(L"当前的得分是:%d", ps->_SNAKE_SCORE);Snake_Cursor_Position(90, 24);wprintf(L"一个食物的分数是:%d", ps->_SNAKE_ONE_FOOD);if (KEY_PRESS(VK_UP) && ps->_SNAKE_DIR != DOWN){ps->_SNAKE_DIR = UP;}else if (KEY_PRESS(VK_DOWN) && ps->_SNAKE_DIR != UP){ps->_SNAKE_DIR = DOWN;}else if (KEY_PRESS(VK_LEFT) && ps->_SNAKE_DIR != RIGHT){ps->_SNAKE_DIR = LEFT;}else if (KEY_PRESS(VK_RIGHT) && ps->_SNAKE_DIR != LEFT){ps->_SNAKE_DIR = RIGHT;}if (KEY_PRESS(VK_ESCAPE))//退出{ps->_SNAKE_STATE = KILL_ESE;}if (KEY_PRESS(VK_F4))//减速{if (ps->_SNAKE_ONE_FOOD > 10){ps->_SNAKE_SLEEP_TIME += 30;ps->_SNAKE_ONE_FOOD = ps->_SNAKE_ONE_FOOD - 5;}}if (KEY_PRESS(VK_F3))//加速{if (ps->_SNAKE_ONE_FOOD < 45){ps->_SNAKE_SLEEP_TIME -= 30;ps->_SNAKE_ONE_FOOD = ps->_SNAKE_ONE_FOOD + 5;}}if (KEY_PRESS(VK_SPACE))//暂停{SNAKEVK_SPACE();}//蛇的移动Snake_move(ps);//蛇的每次移动的休眠时间Sleep(ps->_SNAKE_SLEEP_TIME);} while (ps->_SNAKE_STATE == OK);
}

这段代码定义了一个函数 `Snake_RunGame`,它实现了蛇形游戏的主要运行逻辑。这个函数包含了蛇的移动、用户输入的检测、得分的更新以及游戏速度的控制。
下面是代码的解释:


1. `Snake_RunGame` 函数接受一个指向 `Snake_State` 结构的指针 `ps`,这个结构包含了蛇的状态信息,如头节点、状态、休息时间、得分等。


2. 函数使用一个 `do-while` 循环来持续运行游戏,直到蛇的状态不再是 `OK`(例如,蛇撞墙或吃到自己时)。


3. 在循环内部,首先使用 `Snake_Cursor_Position` 和 `wprintf` 函数打印当前得分和一个食物的分数。


4. 接下来,使用 `KEY_PRESS` 宏(或函数)来检测用户的键盘输入。根据用户按下的键(上、下、左、右箭头键),更新蛇的方向。如果蛇当前不是朝相反方向移动,蛇的方向会被更新。


5. 如果用户按下 `ESC` 键,游戏状态被设置为 `KILL_ESE`,表示游戏结束。


6. 如果用户按下 `F4` 键,游戏速度会减慢,蛇的休息时间增加,同时每个食物的分数减少。


7. 如果用户按下 `F3` 键,游戏速度会加快,蛇的休息时间减少,同时每个食物的分数增加。


8. 如果用户按下空格键,游戏会暂停。这可能是通过调用一个名为 `SNAKEVK_SPACE` 的函数实现的,但这个函数在提供的代码片段中没有定义。


9. `Snake_move` 函数被调用来处理蛇的移动逻辑。这个函数可能会更新蛇的位置,检查是否有食物被吃掉,以及蛇是否撞墙或吃到自己。


10. `Sleep` 函数用于控制游戏的更新速度,根据 `ps->_SNAKE_SLEEP_TIME` 的值来暂停一段时间。


11. `do-while` 循环的条件是蛇的状态为 `OK`,如果蛇的状态变为其他值(如 `KILL_ESE`),循环结束,游戏结束。


这个函数是蛇形游戏的核心部分,它负责处理游戏的主要逻辑和用户交互。在实际的游戏中,`Snake_move` 函数的实现细节对于游戏的行为至关重要。
 

这里代码有些函数是写完直接进行调用的,下面我会一一进行解释

蛇的运行逻辑(实现代码(主体逻辑))

//蛇的运行逻辑
void Snake_RunGame(pSnake_State ps)
{do{//打印分数Snake_Cursor_Position(90, 20);wprintf(L"当前的得分是:%d", ps->_SNAKE_SCORE);Snake_Cursor_Position(90, 24);wprintf(L"一个食物的分数是:%d", ps->_SNAKE_ONE_FOOD);if (KEY_PRESS(VK_UP) && ps->_SNAKE_DIR != DOWN){ps->_SNAKE_DIR = UP;}else if (KEY_PRESS(VK_DOWN) && ps->_SNAKE_DIR != UP){ps->_SNAKE_DIR = DOWN;}else if (KEY_PRESS(VK_LEFT) && ps->_SNAKE_DIR != RIGHT){ps->_SNAKE_DIR = LEFT;}else if (KEY_PRESS(VK_RIGHT) && ps->_SNAKE_DIR != LEFT){ps->_SNAKE_DIR = RIGHT;}if (KEY_PRESS(VK_ESCAPE))//退出{ps->_SNAKE_STATE = KILL_ESE;}if (KEY_PRESS(VK_F4))//减速{if (ps->_SNAKE_ONE_FOOD > 10){ps->_SNAKE_SLEEP_TIME += 30;ps->_SNAKE_ONE_FOOD = ps->_SNAKE_ONE_FOOD - 5;}}if (KEY_PRESS(VK_F3))//加速{if (ps->_SNAKE_ONE_FOOD < 45){ps->_SNAKE_SLEEP_TIME -= 30;ps->_SNAKE_ONE_FOOD = ps->_SNAKE_ONE_FOOD + 5;}}if (KEY_PRESS(VK_SPACE))//暂停{SNAKEVK_SPACE();}//蛇的移动Snake_move(ps);//蛇的每次移动的休眠时间Sleep(ps->_SNAKE_SLEEP_TIME);} while (ps->_SNAKE_STATE == OK);
}

 蛇的暂停函数(实现文件)

//暂停
void SNAKEVK_SPACE()
{while (1){if (KEY_PRESS(VK_SPACE)){break;}Sleep(200);}
}

`SNAKEVK_SPACE` 函数是一个用于处理游戏暂停逻辑的函数。当游戏处于暂停状态时,这个函数会等待用户按下空格键(`VK_SPACE`)来恢复游戏。


下面是 `SNAKEVK_SPACE` 函数的详细解释:


1. 函数内部有一个 `while (1)` 无限循环,这意味着循环会一直执行,直到遇到 `break` 语句。


2. 在循环内部,使用 `KEY_PRESS` 宏来检查空格键是否被按下。如果 `KEY_PRESS(VK_SPACE)` 返回 `true`(即空格键被按下),则执行 `break` 语句,退出循环。


3. 如果空格键没有被按下,循环会继续执行,并且在每次迭代中使用 `Sleep(200)` 来暂停200毫秒。这样可以减少循环的执行频率,避免占用过多的CPU资源。


4. 当用户按下空格键时,`SNAKEVK_SPACE` 函数会退出循环,从而允许游戏逻辑继续执行,游戏恢复。


这个函数通常会在游戏的主循环中调用,当检测到用户按下了空格键时,主循环会调用 `SNAKEVK_SPACE` 函数,游戏进入暂停状态。当用户再次按下空格键时,游戏会从暂停状态恢复。

下面会实现

蛇的移动函数Snake_move​​​​​​​

蛇的移动(实现文件)

//蛇的移动
void Snake_move(pSnake_State ps)
{//创建的下一个节点pSnake_Snakenode newnode = (pSnake_Snakenode)malloc(sizeof(Snake_Snakenode));if (newnode == NULL){perror("Snake_move:newnode:error:");return;}switch (ps->_SNAKE_DIR){case UP:newnode->x = ps->_SNAKE_HEAD->x;newnode->y = ps->_SNAKE_HEAD->y - 1;break;case DOWN:newnode->x = ps->_SNAKE_HEAD->x;newnode->y = ps->_SNAKE_HEAD->y + 1;break;case RIGHT:newnode->x = ps->_SNAKE_HEAD->x + 2;newnode->y = ps->_SNAKE_HEAD->y;break;case LEFT:newnode->x = ps->_SNAKE_HEAD->x - 2;newnode->y = ps->_SNAKE_HEAD->y;break;}//判断下一个节点是不是食物if (IsFood(ps, newnode))//如果是食物{//吃掉食物,并且创建食物EatFood(ps, newnode);newnode = NULL;}else//如果不是食物{//没有遇见食物,正常移动NoFood(ps, newnode);newnode = NULL;}//不能咬到自己Kill_myself(ps);//不能撞墙Kill_wall(ps);
}

`Snake_move` 函数是蛇形游戏中的核心函数之一,负责处理蛇的移动逻辑。这个函数根据蛇的方向来计算蛇的新头部位置,并检查这个位置是否是食物、是否撞到自己或墙壁。
下面是 `Snake_move` 函数的详细解释:


1. 首先,函数分配一个新节点 `newnode`,这个节点将代表蛇移动后的新头部。


2. 使用一个 `switch` 语句根据蛇的方向 `ps->_SNAKE_DIR` 来计算新节点的 `x` 和 `y` 坐标。例如,如果蛇向上移动,则 `y` 坐标减1;如果蛇向右移动,则 `x` 坐标加2(假设蛇的每个节点占两个单位宽度)。


3. `IsFood` 函数被调用来检查新节点是否位于食物的位置。如果是食物,则调用 `EatFood` 函数来处理吃食物的逻辑,这通常包括增加蛇的长度和分数,以及重新生成食物。


4. 如果新节点不是食物,则调用 `NoFood` 函数来处理正常移动的逻辑,这通常包括在蛇的头部添加新节点,并删除蛇的尾部节点。


5. `Kill_myself` 函数被调用来检查蛇是否咬到了自己。如果蛇的头部与身体的任何部分重合,游戏通常会结束。


6. `Kill_wall` 函数被调用来检查蛇是否撞到了墙壁。如果蛇的头部超出了游戏界面的边界,游戏也通常会结束。


7. 在函数的最后,如果新节点 `newnode` 被分配了内存,它会被设置为 `NULL`,以便在函数结束时释放内存。


这个函数是蛇形游戏逻辑的关键部分,它确保了蛇的移动符合游戏规则,并且正确地处理了各种游戏状态的变化。
 

判断下一个节点是不是食物(吃掉或者移动)(实现文件)

这里只有不是食物的时候会进行移动,是食物的话进行头插,

头插的原因是食物在身体前面

当然这些总体在循环里面

这里判断的思路是

1,首先判断下一个节点是不是食物,我们需要去结构体里面,食物阶段去寻找,对比坐标是不是一样的

2,是食物的时候,我们需要进行头插,此时需要注意的一点是,食物是一个节点,传递过来的节点也就是蛇移动的下一个方块也是一个节点,所以我们需要进行头插食物,释放传递过来的节点。然后打印这个蛇

3,不是食物的时候我们需要进行移动,并且释放最后一个节点,并且在尾部打印空格,进行覆盖尾部,防止形成拖尾

//判断下一个节点是不是食物
int IsFood(pSnake_State ps, pSnake_Snakenode newnode)
{return (ps->_SNAKE_FOOD->x == newnode->x && ps->_SNAKE_FOOD->y == newnode->y);
}//下一个节点是食物,吃掉食物
void EatFood(pSnake_State ps, pSnake_Snakenode newnode)
{//头插ps->_SNAKE_FOOD->next = ps->_SNAKE_HEAD;ps->_SNAKE_HEAD = ps->_SNAKE_FOOD;//释放节点free(newnode);newnode = NULL;//打印蛇pSnake_Snakenode cur = ps->_SNAKE_HEAD;while (cur){Snake_Cursor_Position(cur->x, cur->y);wprintf(L"%lc", BOOY);cur = cur->next;}//得分ps->_SNAKE_SCORE += ps->_SNAKE_ONE_FOOD;//再次创建食物Snake_food(ps);}//没有遇见食物,正常移动
void NoFood(pSnake_State ps, pSnake_Snakenode newnode)
{//没有遇见食物,需要头插空节点,释放最后一个节点newnode->next = ps->_SNAKE_HEAD;ps->_SNAKE_HEAD = newnode;//打印蛇pSnake_Snakenode cur = ps->_SNAKE_HEAD;while (cur->next->next){Snake_Cursor_Position(cur->x, cur->y);wprintf(L"%lc", BOOY);cur = cur->next;}//最后一个空格打印成空Snake_Cursor_Position(cur->next->x, cur->next->y);wprintf(L"  ");//释放最后一个节点(节点不释放还是产生拖尾效果)free(cur->next);cur->next = NULL;}

这段代码是用于处理蛇类游戏中的食物逻辑,其中包含三个函数,分别是判断下一个节点是否为食物、吃掉食物时的处理以及没有遇见食物时的正常移动处理。下面是这些函数的中文解释:
1. `IsFood` 函数:这个函数用来判断蛇即将移动到的下一个节点是否是食物。它通过比较食物节点的坐标(`ps->_SNAKE_FOOD->x` 和 `ps->_SNAKE_FOOD->y`)与新节点的坐标(`newnode->x` 和 `newnode->y`)是否相等来判断。如果相等,说明下一个节点是食物,函数返回 `TRUE`;否则返回 `FALSE`。


2. `EatFood` 函数:当蛇吃到食物时,这个函数会被调用。它执行以下操作:
   - 将食物节点移动到蛇头的位置,即食物的 `next` 指针指向原来的蛇头,然后将 `_SNAKE_HEAD` 设置为食物节点。
   - 释放新节点的内存,因为新节点现在成为了蛇的一部分。
   - 打印更新后的蛇的位置到控制台。
   - 增加分数,分数的增加量由 `_SNAKE_ONE_FOOD` 决定。
   - 调用 `Snake_food` 函数在游戏中放置新的食物。


3. `NoFood` 函数:当蛇移动到一个不是食物的位置时,这个函数会被调用。它执行以下操作:
   - 将新节点添加到蛇头的位置,新节点的 `next` 指针指向原来的蛇头,然后将 `_SNAKE_HEAD` 设置为新节点。
   - 打印更新后的蛇的位置到控制台,除了最后一个节点,因为它后面没有节点了。
   - 释放最后一个节点,以避免在屏幕上留下尾巴效果。


在这段代码中,`ps` 是一个指向 `Snake_State` 结构体的指针,这个结构体可能包含了蛇的状态信息,如食物和蛇的位置等。`newnode` 是一个指向 `Snake_Snakenode` 结构体的指针,这个结构体可能代表了蛇的每一个节点。`_SNAKE_FOOD`、`_SNAKE_HEAD`、`_SNAKE_SCORE` 和 `_SNAKE_ONE_FOOD` 是 `ps` 结构体中的成员,分别表示食物节点、蛇头节点、得分和每吃一个食物增加的分数。
`Snake_Cursor_Position` 函数用来设置控制台光标的位置,`wprintf` 函数用来在控制台打印宽字符,`BOOY` 是一个宽字符常量,代表了蛇或食物在控制台上的表示。这个常量应该是在其他地方定义的。

 不能碰到自己+不能撞墙(实现文件)

最后判断能不能撞墙能不能咬到自己

//不能碰到自己
void Kill_myself(pSnake_State ps)
{//头结点的下一个节点,因为头结点不能咬到头结点pSnake_Snakenode cur = ps->_SNAKE_HEAD->next;while (cur){if (cur->x == ps->_SNAKE_HEAD->x && cur->y == ps->_SNAKE_HEAD->y){ps->_SNAKE_STATE = KILL_MYSELS;break;}cur = cur->next;}
}
//不能撞墙
void Kill_wall(pSnake_State ps)
{if (ps->_SNAKE_HEAD->x == 0 || ps->_SNAKE_HEAD->x == 76 || ps->_SNAKE_HEAD->y == 1 || ps->_SNAKE_HEAD->y == 30){ps->_SNAKE_STATE = KILL_WALL;}
}

`Kill_myself` 和 `Kill_wall` 函数分别用于检查蛇是否咬到了自己或者撞到了墙壁,并在发生这些情况时更新游戏状态。
下面是这两个函数的详细解释:
1. `Kill_myself` 函数:
   - 这个函数检查蛇的头节点是否与身体的任何部分重合,这会在蛇移动时发生。
   - 函数遍历蛇的身体(从第二个节点开始,因为头节点不能咬到自己),使用一个 `while` 循环和一个指针 `cur` 来访问每个节点。
   - 如果发现头节点的 `x` 和 `y` 坐标与身体任何节点的坐标相同,这意味着蛇咬到了自己,函数将蛇的状态 `ps->_SNAKE_STATE` 设置为 `KILL_MYSELS`,这通常意味着游戏结束。
2. `Kill_wall` 函数:
   - 这个函数检查蛇的头节点是否超出了游戏界面的边界。
   - 函数检查头节点的 `x` 和 `y` 坐标是否等于界面的边缘坐标(例如,`x` 坐标为0或76,`y` 坐标为1或30)。
   - 如果头节点超出了这些边界,函数将蛇的状态 `ps->_SNAKE_STATE` 设置为 `KILL_WALL`,这也通常意味着游戏结束。
这两个函数在 `Snake_move` 函数中被调用,以确保蛇的移动符合游戏规则。如果蛇咬到了自己或撞到了墙壁,游戏状态会被更新,从而在游戏循环中触发游戏的结束。

蛇的结束逻辑

#include"Snake.h"
void Snake01()
{int input = 0;do{//这里需要进行初始化,否则创建蛇的时候,会导致死循环Snake_State ps = { 0 };//控制面板的操作Snake_Control_Panel();//隐藏光标信息Snake_Hide_Cursor();//初始化界面Snake_interface();//蛇的初始化Snake_Initialize(&ps);//蛇的运行逻辑Snake_RunGame(&ps);//是否再来一局Snake_Cursor_Position(24, 13);wprintf(L"是否再来一局(Y/N):\n");Snake_Cursor_Position(42, 13);scanf("%s", &input);} while (input == 'y' || input == 'Y');}
int main()
{srand((unsigned int)time(NULL));//不设置贪吃蛇本地属性,wprintf就不能打印出来setlocale(LC_ALL, "");//贪吃蛇的主要逻辑Snake01();//贪吃蛇的结束标志的所在位置Snake_Cursor_Position(0, 40);return 0;
}

蛇的结束逻辑这里我们不单独写一下了,直接在主函数里面实现,简单是说就行循环

最后那个定位的坐标的意思是,把结束的标语定位到最后面。美观

解释一下代码的意思
1. `Snake01` 函数:这是游戏的主体部分,它包含了一个 `do-while` 循环,用于重复游戏过程直到用户选择不再玩。
   - `Snake_State ps = { 0 };`:这里初始化了一个 `Snake_State` 结构体,用于存储游戏的状态,包括蛇的位置、食物的位置、得分等。
   - `Snake_Control_Panel();`:这个函数可能会显示游戏控制面板,比如游戏设置或者游戏选项。
   - `Snake_Hide_Cursor();`:隐藏控制台的光标,以免在游戏过程中干扰玩家。
   - `Snake_interface();`:初始化游戏界面,可能包括清屏、设置文本颜色等。
   - `Snake_Initialize(&ps);`:初始化游戏,包括创建蛇和食物,以及初始化游戏状态。
   - `Snake_RunGame(&ps);`:运行游戏循环,处理蛇的移动、食物的生成、碰撞检测等。
   - `Snake_Cursor_Position(24, 13); wprintf(L"是否再来一局(Y/N):\n");`:在控制台定位光标并询问用户是否想要再次玩游戏。
   - `scanf("%s", &input);`:读取用户的输入,用户可以输入 'Y' 或 'N' 来选择是否再来一局。
   - `do { ... } while (input == 'y' || input == 'Y');`:这是一个循环,只有当用户输入 'Y' 或 'y' 时,游戏才会重新开始。


2. `main` 函数:这是程序的入口点,它设置了随机种子(用于生成随机数)和本地设置(为了让 `wprintf` 能够正确打印宽字符)。
   - `srand((unsigned int)time(NULL));`:使用当前时间来初始化随机数生成器的种子,这样每次运行游戏时产生的随机数序列都会不同。
   - `setlocale(LC_ALL, "");`:设置本地环境,以确保 `wprintf` 能够正确处理宽字符。
   - `Snake01();`:调用游戏主体函数,开始游戏。
   - `Snake_Cursor_Position(0, 40);`:在控制台定位光标,通常是为了在游戏结束时显示结束标志或者分数。
   - `return 0;`:程序结束,返回 0。
整个游戏逻辑的主要部分都在 `Snake01` 函数中,它包含了游戏的初始化、运行和结束逻辑。游戏运行时,它会不断循环,直到用户选择退出。

代码的总结

Snake.h

#define _CRT_SECURE_NO_WARNINGS 1
#define KEY_PRESS(vk) (GetAsyncKeyState(vk)&1?1:0)
#pragma once
#include<stdio.h>
#include<stdlib.h> 
#include<windows.h>
#include<stdbool.h>
#include<string.h>
#include<locale.h>
#include<time.h>
#define POS_X 24
#define POS_Y 5#define BOOY L'●'
#define FOOD L'★'
#define WALL L'□'
//控制面板的操作
void Snake_Control_Panel();//隐藏光标信息
void Snake_Hide_Cursor();//指定光标的位置
void Snake_Cursor_Position(short x , short y);//初始化界面
void Snake_interface();//蛇的状态
enum _STATE
{OK,//正常KILL_ESE,//退出KILL_WALL,//撞墙KILL_MYSELS//咬到自己
};//蛇的方向
enum _DIR
{UP,//上DOWN,//下LEFT,//左RIGHT//右
};
//创建节点
typedef struct Snake_Snakenode
{int x;int y;struct Snake_Snakenode* next;
}Snake_Snakenode, * pSnake_Snakenode;//创建蛇的状态
//方向,头节点,状态,食物,一个食物的分数,时间,得分情况
typedef struct Snake_State
{pSnake_Snakenode _SNAKE_HEAD;//蛇的头节点pSnake_Snakenode _SNAKE_FOOD;//蛇的食物enum _STATE _SNAKE_STATE;//蛇的状态enum _DIR _SNAKE_DIR;//蛇的方向int _SNAKE_SCORE;//总成绩int _SNAKE_SLEEP_TIME;//蛇中间运动的时候的休息时间int _SNAKE_ONE_FOOD;//一个食物的分数
}Snake_State, * pSnake_State;//蛇的初始化
void Snake_Initialize(pSnake_State ps);
绘制地图
void Snake_Map();
创建蛇
void Snake_Create(pSnake_State ps);
创建食物
void Snake_food(pSnake_State ps);//蛇的运行逻辑
void Snake_RunGame(pSnake_State ps);
暂停
void SNAKEVK_SPACE();
蛇的移动
void Snake_move(pSnake_State ps);
判断下一个节点是不是食物
int IsFood(pSnake_State ps, pSnake_Snakenode newnode);
下一个节点是食物,吃掉食物
void EatFood(pSnake_State ps, pSnake_Snakenode newnode);//蛇的结束

Snake.c

#include"Snake.h"
//控制面板的操作
void Snake_Control_Panel()
{system("mode con cols=130 lines=45 ");system("title 贪吃蛇");
}
//隐藏光标信息
void Snake_Hide_Cursor()
{//获得句柄HANDLE input = GetStdHandle(STD_OUTPUT_HANDLE);//创建光标结构体CONSOLE_CURSOR_INFO info;//先获得光标信息GetConsoleCursorInfo(input, &info);//再设置为不可见info.bVisible = false;//设置光标信息SetConsoleCursorInfo(input, &info);
}
//指定光标的位置
void Snake_Cursor_Position(short x, short y)
{//获得句柄HANDLE input = GetStdHandle(STD_OUTPUT_HANDLE);//定位坐标COORD pos = { x,y };//设置光标信息SetConsoleCursorPosition(input, pos);
}
//初始化界面
void Snake_interface()
{//1.打印环境界面Snake_Cursor_Position(47, 19);wprintf(L"欢迎来到贪吃蛇小游戏\n");//按任意键继续Snake_Cursor_Position(100, 35);system("pause");//清屏system("cls");Snake_Cursor_Position(47, 16);wprintf(L"不能穿墙,可以咬到自己\n");Snake_Cursor_Position(47, 17);wprintf(L"用 ↑. ↓. ←. →. 来控制蛇的移动\n");Snake_Cursor_Position(47, 18);wprintf(L"按F3加速,F4减速\n");Snake_Cursor_Position(47, 19);wprintf(L"加速可以获得更高的分数\n");Snake_Cursor_Position(47, 20);wprintf(L"ESC :退出游戏.space(空格):暂停游戏.\n");Snake_Cursor_Position(47, 21);wprintf(L"每个食物得分:20分\n");Snake_Cursor_Position(100, 35);wprintf(L"贾杰森精心制作\n");Snake_Cursor_Position(100, 36);system("pause");system("cls");//游戏界面的相关介绍Snake_Cursor_Position(90, 25);wprintf(L"不能穿墙,不能咬到自己\n");Snake_Cursor_Position(90, 26);wprintf(L"用 ↑. ↓. ←. →. 来控制蛇的移动\n");Snake_Cursor_Position(90, 27);wprintf(L"按F3加速,F4减速\n");Snake_Cursor_Position(90, 28);wprintf(L"加速可以获得更高的分数\n");Snake_Cursor_Position(90, 29);wprintf(L"ESC :退出游戏.space(空格):暂停游戏.\n");Snake_Cursor_Position(90, 30);wprintf(L"贾杰森精心制作\n");
}
//绘制地图
void Snake_Map()
{//建立围墙(偶数)绘制地图Snake_Cursor_Position(0, 0);for (int i = 0; i < 40; i++){wprintf(L"%lc", WALL);}Snake_Cursor_Position(0, 31);for (int i = 0; i < 40; i++){wprintf(L"%lc", WALL);}for (int i = 0; i < 31; i++){Snake_Cursor_Position(0, i);wprintf(L"%lc", WALL);}for (int i = 0; i < 31; i++){Snake_Cursor_Position(78, i);wprintf(L"%lc", WALL);}
}//创建蛇
void Snake_Create(pSnake_State ps)
{//初始蛇是五个节点pSnake_Snakenode cur = NULL;for (int i = 0; i < 5; i++){//创建节点cur = (pSnake_Snakenode)malloc(sizeof(Snake_Snakenode));if (cur == NULL){perror("Snake_Create:pSnake_Snakenode newnode:error:");return;}//进行定位cur->next = NULL;cur->x= POS_X + i * 2;cur->y = POS_Y;//进行头插if (ps->_SNAKE_HEAD == NULL){ps->_SNAKE_HEAD = cur;}else{cur->next = ps->_SNAKE_HEAD;ps->_SNAKE_HEAD = cur;}}//进行打印cur = ps->_SNAKE_HEAD;while (cur){Snake_Cursor_Position(cur->x, cur->y);wprintf(L"%lc", BOOY);cur = cur->next;}//初始化蛇的状态ps->_SNAKE_STATE = OK;//状态ps->_SNAKE_SLEEP_TIME = 200;//休息时间ps->_SNAKE_SCORE = 0;//总成绩ps->_SNAKE_ONE_FOOD = 20;//一个食物的分数ps->_SNAKE_DIR = RIGHT;//蛇的方向
}
//暂停
void SNAKEVK_SPACE()
{while (1){if (KEY_PRESS(VK_SPACE)){break;}Sleep(200);}
}
//创建食物
void Snake_food(pSnake_State ps)
{int x = 0; int y = 0;again:do{x = rand() % 74 + 2;y = rand() % 28 + 1;} while (x % 2 != 0);//判断是不是和身体重合pSnake_Snakenode headcur = ps->_SNAKE_HEAD;while (headcur){if (x == headcur->x && y == headcur->y){goto again;}headcur = headcur->next;}//创建食物//pSnake_Snakenode curfood = (pSnake_Snakenode)malloc(sizeof(Snake_Snakenode));pSnake_Snakenode curfood = NULL;//必须初始化curfood = (pSnake_Snakenode)malloc(sizeof(Snake_Snakenode));if (curfood == NULL){perror("pSnake_Snakenode curfood:error:");return;}curfood->next = NULL;curfood->x = x;curfood->y = y;Snake_Cursor_Position(x, y);wprintf(L"%lc", FOOD);//创建的食物放到蛇的状态里面去ps->_SNAKE_FOOD = curfood;
}
//判断下一个节点是不是食物
int IsFood(pSnake_State ps, pSnake_Snakenode newnode)
{return (ps->_SNAKE_FOOD->x == newnode->x && ps->_SNAKE_FOOD->y == newnode->y);
}
//下一个节点是食物,吃掉食物
void EatFood(pSnake_State ps, pSnake_Snakenode newnode)
{//头插ps->_SNAKE_FOOD->next = ps->_SNAKE_HEAD;ps->_SNAKE_HEAD = ps->_SNAKE_FOOD;//释放节点free(newnode);newnode = NULL;//打印蛇pSnake_Snakenode cur = ps->_SNAKE_HEAD;while (cur){Snake_Cursor_Position(cur->x, cur->y);wprintf(L"%lc", BOOY);cur = cur->next;}//得分ps->_SNAKE_SCORE += ps->_SNAKE_ONE_FOOD;//再次创建食物Snake_food(ps);}
//没有遇见食物,正常移动
void NoFood(pSnake_State ps, pSnake_Snakenode newnode)
{//没有遇见食物,需要头插空节点,释放最后一个节点newnode->next = ps->_SNAKE_HEAD;ps->_SNAKE_HEAD = newnode;//打印蛇pSnake_Snakenode cur = ps->_SNAKE_HEAD;while (cur->next->next){Snake_Cursor_Position(cur->x, cur->y);wprintf(L"%lc", BOOY);cur = cur->next;}//最后一个空格打印成空Snake_Cursor_Position(cur->next->x, cur->next->y);wprintf(L"  ");//释放最后一个节点(节点不释放还是产生拖尾效果)free(cur->next);cur->next = NULL;}
//不能碰到自己
void Kill_myself(pSnake_State ps)
{//头结点的下一个节点,因为头结点不能咬到头结点pSnake_Snakenode cur = ps->_SNAKE_HEAD->next;while (cur){if (cur->x == ps->_SNAKE_HEAD->x && cur->y == ps->_SNAKE_HEAD->y){ps->_SNAKE_STATE = KILL_MYSELS;break;}cur = cur->next;}
}
//不能撞墙
void Kill_wall(pSnake_State ps)
{if (ps->_SNAKE_HEAD->x == 0 || ps->_SNAKE_HEAD->x == 76 || ps->_SNAKE_HEAD->y == 1 || ps->_SNAKE_HEAD->y == 30){ps->_SNAKE_STATE = KILL_WALL;}
}
//蛇的移动
void Snake_move(pSnake_State ps)
{//创建的下一个节点pSnake_Snakenode newnode = (pSnake_Snakenode)malloc(sizeof(Snake_Snakenode));if (newnode == NULL){perror("Snake_move:newnode:error:");return;}switch (ps->_SNAKE_DIR){case UP:newnode->x = ps->_SNAKE_HEAD->x;newnode->y = ps->_SNAKE_HEAD->y - 1;break;case DOWN:newnode->x = ps->_SNAKE_HEAD->x;newnode->y = ps->_SNAKE_HEAD->y + 1;break;case RIGHT:newnode->x = ps->_SNAKE_HEAD->x + 2;newnode->y = ps->_SNAKE_HEAD->y;break;case LEFT:newnode->x = ps->_SNAKE_HEAD->x - 2;newnode->y = ps->_SNAKE_HEAD->y;break;}//判断下一个节点是不是食物if (IsFood(ps, newnode))//如果是食物{//吃掉食物,并且创建食物EatFood(ps, newnode);newnode = NULL;}else//如果不是食物{//没有遇见食物,正常移动NoFood(ps, newnode);newnode = NULL;}//不能咬到自己Kill_myself(ps);//不能撞墙Kill_wall(ps);
}
//蛇的运行逻辑
void Snake_RunGame(pSnake_State ps)
{do{//打印分数Snake_Cursor_Position(90, 20);wprintf(L"当前的得分是:%d", ps->_SNAKE_SCORE);Snake_Cursor_Position(90, 24);wprintf(L"一个食物的分数是:%d", ps->_SNAKE_ONE_FOOD);if (KEY_PRESS(VK_UP) && ps->_SNAKE_DIR != DOWN){ps->_SNAKE_DIR = UP;}else if (KEY_PRESS(VK_DOWN) && ps->_SNAKE_DIR != UP){ps->_SNAKE_DIR = DOWN;}else if (KEY_PRESS(VK_LEFT) && ps->_SNAKE_DIR != RIGHT){ps->_SNAKE_DIR = LEFT;}else if (KEY_PRESS(VK_RIGHT) && ps->_SNAKE_DIR != LEFT){ps->_SNAKE_DIR = RIGHT;}if (KEY_PRESS(VK_ESCAPE))//退出{ps->_SNAKE_STATE = KILL_ESE;}if (KEY_PRESS(VK_F4))//减速{if (ps->_SNAKE_ONE_FOOD > 10){ps->_SNAKE_SLEEP_TIME += 30;ps->_SNAKE_ONE_FOOD = ps->_SNAKE_ONE_FOOD - 5;}}if (KEY_PRESS(VK_F3))//加速{if (ps->_SNAKE_ONE_FOOD < 45){ps->_SNAKE_SLEEP_TIME -= 30;ps->_SNAKE_ONE_FOOD = ps->_SNAKE_ONE_FOOD + 5;}}if (KEY_PRESS(VK_SPACE))//暂停{SNAKEVK_SPACE();}//蛇的移动Snake_move(ps);//蛇的每次移动的休眠时间Sleep(ps->_SNAKE_SLEEP_TIME);} while (ps->_SNAKE_STATE == OK);
}
//蛇的初始化
void Snake_Initialize(pSnake_State ps)
{//绘制地图Snake_Map();//创建蛇Snake_Create(ps);//创建食物Snake_food(ps);//蛇的运行逻辑Snake_RunGame(ps);//蛇的结束}

test.c

#include"Snake.h"
void Snake01()
{int input = 0;do{//这里需要进行初始化,否则创建蛇的时候,会导致死循环Snake_State ps = { 0 };//控制面板的操作Snake_Control_Panel();//隐藏光标信息Snake_Hide_Cursor();//初始化界面Snake_interface();//蛇的初始化Snake_Initialize(&ps);//蛇的运行逻辑Snake_RunGame(&ps);//是否再来一局Snake_Cursor_Position(24, 13);wprintf(L"是否再来一局(Y/N):\n");Snake_Cursor_Position(42, 13);scanf("%s", &input);} while (input == 'y' || input == 'Y');}
int main()
{srand((unsigned int)time(NULL));//不设置贪吃蛇本地属性,wprintf就不能打印出来setlocale(LC_ALL, "");//贪吃蛇的主要逻辑Snake01();//贪吃蛇的结束标志的所在位置Snake_Cursor_Position(0, 40);return 0;
}

图片详解

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/7147.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

分布式光伏管理系统和一般的光伏管理系统相比有什么区别?

随着全球对可再生能源的关注度日益提高&#xff0c;光伏技术作为其中的佼佼者&#xff0c;已经得到了广泛的应用。在光伏技术中&#xff0c;管理系统扮演着至关重要的角色&#xff0c;它关乎着光伏电站的运行效率、能源产出以及运维成本等多个方面。其中&#xff0c;分布式光伏…

搜索算法系列之四(斐波那契)

以下算法被验证过&#xff0c;如有什么问题或有补充的欢迎留言。 前言 斐波那契数列&#xff0c;又称黄金分割数列&#xff0c;是由意大利数学家&#xff08;Leonardo Fibonacci&#xff09;在1202年提出的。这个数列的递推关系是F(0)1&#xff0c;F(1)1&#xff0c;F(n)F(n-…

【数据库】docker搭建mysql8一主两从节点,配置proxysql读写分离

docker搭建mysql8一主两从节点&#xff0c;配置proxysql读写分离 一、docker 搭建 mysql8 一主两从节点1.1 相关配置文件与docker启动1.2 半同步复制1.3 主从同步异常处理 二、mysql 中间件 ProxySql 配置读写分离2.1 在mysql服务里创建给proxySQL访问的用户2.2 安装ProxySql及…

测试用例执行的结果pass_fail_block_skip

pass fail block skip 测试用例的执行结果通常包括以下几个方面&#xff1a; 1. **测试结果状态**&#xff1a;通常分为“通过”、“失败”、“阻塞”和“跳过”等状态。 - **通过**&#xff1a;测试用例执行完毕&#xff0c;预期结果与实际结果一致。 - **失败**&am…

【MySQL】——用户和权限管理(二)

&#x1f4bb;博主现有专栏&#xff1a; C51单片机&#xff08;STC89C516&#xff09;&#xff0c;c语言&#xff0c;c&#xff0c;离散数学&#xff0c;算法设计与分析&#xff0c;数据结构&#xff0c;Python&#xff0c;Java基础&#xff0c;MySQL&#xff0c;linux&#xf…

大模型争霸的下一站:不仅是超越GPT-4,更是寻求模型之间的平衡应用

文 | 智能相对论 作者 | 沈浪 知名科学杂志《Nature》发表了一篇关于大模型规模参数大小争议的文章《In Al, is bigger always better?》——AI大模型&#xff0c;越大越好吗&#xff1f;随着大模型应用走向实践&#xff0c;这一问题不可避免地成为了当前AI行业发展的焦点与…

OpenGL 入门(二)—— 渲染摄像头采集的预览画面

本篇主要内容&#xff1a; 将摄像头采集到的图像通过 OpenGL 绘制到屏幕上FBO 离屏渲染 在开始上述流程前&#xff0c;我们有必要对 SurfaceTexture 做一个简单了解&#xff0c;因为 OpenGL 需要通过它获取要绘制的图像。 1、认识 SurfaceTexture SurfaceTexture 是 Androi…

(论文阅读-优化器)Selectivity Estimation using Probabilistic Models

目录 摘要 一、简介 二、单表估计 2.1 条件独立Condition Independence 2.2 贝叶斯网络Bayesian Networks 2.3 查询评估中的贝叶斯网络 三、Join选择性估计 3.1 两表Join 3.2 概率关系模型 3.3 使用PRMs的选择性估计 四、PRM构建 4.1 评分标准 4.2 参数估计 4.3 结…

堡垒机——网络技术手段

目录 一、简介 1.什么是跳板机 2.跳板机缺陷 3.什么是堡垒机 4.为什么要使用堡垒机 4.1堡垒机设计理念 4.2堡垒机的建设目标 4.3堡垒机的价值 4.4总结 5.堡垒机的分类 6.堡垒机的原理 7.堡垒机的身份认证 8.堡垒机的运维方式常见有以下几种 9.堡垒机其他常见功能…

基于springboot+vue+Mysql的在线动漫信息平台

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…

大数据分析入门之10分钟掌握GROUP BY语法

前言 书接上回大数据分析入门10分钟快速了解SQL。 本篇将会进一步介绍group by语法。 基本语法 SELECT column_name, aggregate_function(column_name) FROM table_name GROUP BY column_name HAVING condition假设我们有students表&#xff0c;其中有id,grade_number,class…

网络文件共享

存储类型分三类 直连式存储&#xff1a;DAS存储区域网络&#xff1a;SAN网络附加存储&#xff1a;NAS 三种存储架构的应用场景 DAS虽然比较古老了&#xff0c;但是还是很适用于那些数据量不大&#xff0c;对磁盘访问速度要求较高的中小企业SAN多适用于文件服务器&#xff0c…

C/C++ BM33 二叉树的镜像

文章目录 前言题目解决方案一1.1 思路阐述1.2 源码 总结 前言 镜像说的好听&#xff0c;无非就是换下节点。 题目 操作给定的二叉树&#xff0c;将其变换为源二叉树的镜像。 数据范围&#xff1a;二叉树的节点数 0 ≤ n ≤ 1000 0≤n≤1000 0≤n≤1000&#xff0c; 二叉树每…

华为ensp中USG6000V防火墙双机热备VRRP+HRP原理及配置

作者主页&#xff1a;点击&#xff01; ENSP专栏&#xff1a;点击&#xff01; 创作时间&#xff1a;2024年5月6日20点26分 华为防火墙双机热备是一种高可用性解决方案&#xff0c;可以将两台防火墙设备组成一个双机热备组&#xff0c;实现主备切换。当主用防火墙出现故障时&…

景源畅信:抖音运营做什么工作内容?

在如今这个信息爆炸的时代&#xff0c;抖音已经成为了人们生活中不可或缺的一部分。无论是消磨时间、获取信息还是展示自我&#xff0c;抖音都扮演着重要的角色。那么&#xff0c;作为抖音运营&#xff0c;他们需要做些什么呢? 一、内容策划与制作 抖音运营的首要任务就是内容…

【动态规划】路径问题

1.不同路径 不同路径 思路&#xff1a; 状态表示 状态转移方程 class Solution { public:int uniquePaths(int m, int n) {// 创建dp表// 初始化// 填表// 返回值vector<vector<int>> dp(m 1, vector<int>(n 1));dp[0][1] 1;for(int i 1; i < m; i…

【数据结构】C++语言实现栈(详细解读)

c语言中的小小白-CSDN博客c语言中的小小白关注算法,c,c语言,贪心算法,链表,mysql,动态规划,后端,线性回归,数据结构,排序算法领域.https://blog.csdn.net/bhbcdxb123?spm1001.2014.3001.5343 给大家分享一句我很喜欢我话&#xff1a; 知不足而奋进&#xff0c;望远山而前行&am…

AI预测体彩排3第3套算法实战化赚米验证第2弹2024年5月6日第2次测试

由于今天白天事情比较多&#xff0c;回来比较晚了&#xff0c;趁着还未开奖&#xff0c;赶紧把预测结果发出来吧~今天是第2次测试~ 2024年5月6日排列3预测结果 6-7码定位方案如下&#xff1a; 百位&#xff1a;2、3、1、5、0、6 十位&#xff1a;4、3、6、8、0、9 个位&#xf…

4.任务创建和删除的API函数

一、简介 二、动态创建任务函数:xTaskCreate() 此函数用于使用动态的方式创建任务&#xff0c;任务的任务控制块以及任务的栈空间所需的内存&#xff0c;均由 FreeRTOS 从 FreeRTOS 管理的堆中分配&#xff0c;若使用此函数&#xff0c;需要在 FreeRTOSConfig.h 文件 中将宏 c…

【智能算法】PID搜索算法(PSA)原理及实现

目录 1.背景2.算法原理2.1算法思想2.2算法过程 3.结果展示4.参考文献5.代码获取 1.背景 2023年&#xff0c;Y Gao受到PID控制理论启发&#xff0c;提出了PID搜索算法&#xff08;PID-based Search Algorithm, PSA&#xff09;。 2.算法原理 2.1算法思想 PID算法是控制领域的…