贪吃蛇详解

Win32 API介绍:

在写贪吃蛇这款游戏时需要用到一些有关Win32 API的知识, 接下来我会将设计到的知识点列举并讲解:

首先我们先了解一下Win32 API是什么,Windows这个多作业系统除了协调应⽤程序的执⾏、分配内存、管理资源之外,它同时也是⼀个很⼤ 的服务中⼼,调⽤这个服务中⼼的各种服务(每⼀种服务就是⼀个函数),可以帮应⽤程序达到开启 视窗、描绘图形、使⽤周边设备等⽬的,由于这些函数服务的对象是应⽤程序(Application),所以便 称之为Application Programming Interface,简称API函数。WIN32 API也就是Microsoft Windows  32位平台的应⽤程序编程接⼝。

控制台程序:

控制台就是我们在运行代码时出现的那个窗口:

有些人的可能是下面这张图的样子:

这个是无法实现贪吃蛇的,我们需要进行设置上的修改,改成第一个图的样子即可,具体步骤如下:

在更改完设置后我们就可以尝试对控制台进行一些操作了,比如说我们在玩贪吃蛇游戏的时候需要一个大小合适的游戏窗口,并且将控制台的标题改成“贪吃蛇”,这样会让我们写出来的游戏更加完美,接下来我来介绍一下这些命令:

1.mode命令:

(参考链接mode | Microsoft Learn )

代码:
system("mode con cols=30 lines=30");
演示效果:

我们把列和行的大小都设置为30,这样只是让效果更加明显,在实际使用时需要我们不断去尝试运行去找到一个合适的大小。

2.title命令

参考链接(title | Microsoft Learn)

代码:
    system("title 贪吃蛇");
演示效果:

 这里我使用了“pause”命令,它会使代码暂停下来,如果不加这个命令的话,我们不会看到效果,因为在程序运行结束时,title也会结束,我们不妨看一下没有这个命令时的效果:

 

控制台屏幕上的坐标COORD:

参考链接(COORD 结构 - Windows Console | Microsoft Learn)

COORD是Windows  API中定义的⼀个结构体,表⽰⼀个字符在控制台屏幕幕缓冲区上的坐标,坐标系(0,0)的原点位于缓冲区的顶部左侧单元格。

COORD的类型:

typedef struct _COORD {SHORT X;SHORT Y;
} COORD, *PCOORD;

控制台其实是有坐标的,但它会与我们平常认识的有些许不同,如下图所示:

需要注意的是x和y坐标的单位长度是不一样的,大概就是1 :2的比例,如下图所示:

在打印东西的时候,总会在光标处打印,打印一下光标就会向后移动一下,在我们实现贪吃蛇的时候会需要在特定的位置进行打印,那就需要将光标放在指定的位置,也就是上面所讲的坐标,接下来我就介绍一下如何实现在指定位置打印的功能。

 GetStdHandle 函数:

参考链接(GetStdHandle 函数 - Windows Console | Microsoft Learn)

仔细观察运行窗口会发现有个光标在一直闪烁,设想一下,在使用贪吃蛇时总会有一个光标在闪来闪去,这会非常影响游戏体验,那我们就要想办法将光标隐藏掉。

怎么隐藏呢?这就需要我们获得光标的一个控制权,那光标的控制权又在哪里,这就需要我们设想一下炒菜的这么一个情景,在颠锅的时候我们需要手持锅的把手,再回到代码的世界里,控制台就是这口大锅,GetStdHandle就是获得控制台控制权的一个函数,这里我们将这种控制权叫做句柄

有了这样的思考,这个函数的定义也就易于理解了。

GetStdHandle是⼀个Windows  API函数。它⽤于从⼀个特定的标准设备(标准输⼊、标准输出或标 准错误)中取得⼀个句柄(⽤来标识不同设备的数值),使⽤这个句柄可以操作设备。

函数声明:(并不需要我们自己声明,写出来只是为了便于理解)

不难看出函数的返回类型是HANDLE,它是一个指向句柄的指针,如果获取失败,就会返回空指针;

HANDLE GetStdHandle(DWORD nStdHandle);

函数的参数只有一个,这种类型的参数只有三种,只需选择需要的然后copy到代码中即可。

示例:

HANDLE hOutput = NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值) 
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
if (hOutput == NULL)//如果获取失败,直接退出程序并打印错误信息
{perror("GetStdHandle");exit(1);
}

 GetConsoleCursorInfo 函数:

参考链接(GetConsoleCursorInfo 函数 - Windows Console | Microsoft Learn)

既然已经拿到句柄,我们就可以进行操作了,怎么操作呢?这还需要借助一个函数。

GetConsoleCursorInfo的作用就是检索有关指定控制台屏幕缓冲区的光标⼤⼩和可⻅性的信息。

语法:

可见函数有两个参数,一个就是我们使用 GetStdHandle获得的句柄,

BOOL WINAPI GetConsoleCursorInfo(_In_  HANDLE               hConsoleOutput,_Out_ PCONSOLE_CURSOR_INFO lpConsoleCursorInfo
);
PCONSOLE_CURSOR_INFO 是指向 CONSOLE_CURSOR_INFO 结构的指针,该结构接收有关主机游标

另一个是是指向 CONSOLE_CURSOR_INFO 结构的指针,该结构接收有关主机游标,它包含两个类型的变量,一个是由游标填充的字符单元的百分比,另一个是光标的显示与隐藏。

typedef struct _CONSOLE_CURSOR_INFO {DWORD dwSize;BOOL  bVisible;
} CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO;

示例:

HANDLE hOutput = NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值) 
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息 
CursorInfo.bVisible = false;

我们来看一下效果:发现光标并没有消失,要想实现对光标的设置,还需要调用一个函数。

 SetConsoleCursorInfo函数:

参考链接(SetConsoleCursorInfo 函数 - Windows Console | Microsoft Learn)

设置指定控制台屏幕缓冲区的光标的⼤⼩和可⻅性。

语法:SetConsoleCursorInfo函数的参数与GetConsoleCursorInfo 函数的参数类型相同 。

BOOL WINAPI SetConsoleCursorInfo(_In_       HANDLE              hConsoleOutput,_In_ 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 函数:

参考链接(SetConsoleCursorPosition 函数 - Windows Console | Microsoft Learn)

在隐藏好光标后,就可以尝试将光标放置到指定坐标上了,这就需要借助SetConsoleCursorPosition 函数。

语法:参数有两个,分别是获得的句柄和创建的COORD类型的变量。

BOOL WINAPI SetConsoleCursorPosition(_In_ HANDLE hConsoleOutput,_In_ COORD  dwCursorPosition
);

示例:

COORD pos = { 10, 5};HANDLE hOutput = NULL;//获取标准输出的句柄(⽤来标识不同设备的数值) hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//设置标准输出上光标的位置为pos SetConsoleCursorPosition(hOutput, pos);

效果图:

学到这里,我们就可以尝试去封装一个函数来控制光标的位置,具体代码如下: 

//设置光标的坐标 
void SetPos(short x, short y)
{COORD pos = { x, y };HANDLE hOutput = NULL;//获取标准输出的句柄(⽤来标识不同设备的数值) hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//设置标准输出上光标的位置为pos SetConsoleCursorPosition(hOutput, pos);
}

这样我们就可以通过调用该函数并传入想要设置的坐标就可以实现这一功能。

GetAsyncKeyState函数:

参考链接(getAsyncKeyState 函数 (winuser.h) - Win32 apps | Microsoft Learn)

思考一下,在玩贪吃蛇游戏的时候,我们会通过按相应的按键来控制蛇的移动,这种功能是如何实现的?接下来就介绍一下实现这个功能的函数。

GetAsyncKeyState函数会获取按键情况,我们需要知道的一点是键盘上的所有按键都有一个专属的虚拟值,将键盘上每个键的虚拟键值传递给函数,函数通过返回值来分辨按键的状态。GetAsyncKeyState 的返回值是short类型,在上⼀次调⽤ GetAsyncKeyState 函数后,如果 返回的16位的short数据中,最⾼位是1,说明按键的状态是按下,如果最⾼是0,说明按键的状态是抬 起;如果最低位被置为1则说明,该按键被按过,否则为0。 

如果我们要判断⼀个键是否被按过,可以检测GetAsyncKeyState返回值的最低值是否为1。

语法:

SHORT GetAsyncKeyState(int vKey
);

示例:

这里我用了一个宏定义,宏的内容就是( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 ),0x1其实就等价于十进制中的1,写成二进制为0000 0000 0000 0000 0000 0000 0000 0001,与1进行&得到的是最后一位是否是1。这样可以让代码看起来更加简洁。

#include <stdio.h>
#include <windows.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;
}

效果演示:

我们按键盘上的数字,按几控制台上就会打印几。

接下来我们步入正题,讲解一下贪吃蛇游戏的实现思路。

 贪吃蛇游戏设计与分析:

联想一下我们自己玩游戏的时候,刚进入游戏它会打印一个欢迎界面,然后显示游戏规则,最后进入游戏,贪吃蛇的速度和方向通过我们的控制在一定的区域内吃食物,蛇头碰到自己或者撞到墙会直接结束游戏,游戏期间还可以加速和减速,加速吃到的食物分数会更高,减速吃到的食物分数会低一点,游戏期间可以暂停,直接退出。

那我们就以这个思路来写代码:(接下来的功能实现我都会以函数的形式进行书写,最终会有完整代码)

游戏运行前数据的初始化:

0.设置窗口的大小并隐藏光标:

这里就使用我们上面所讲的对光标的状态设置和控制台设置的知识点,写起来也会相对轻松一点。

代码:

//0.设置窗口的大小并隐藏光标
void Std_set()
{system("mode con cols=100 lines=30");system("title 贪吃蛇");HANDLE houtput = NULL;houtput = GetStdHandle(STD_OUTPUT_HANDLE);CONSOLE_CURSOR_INFO con;GetConsoleCursorInfo(houtput, &con);con.bVisible = false;SetConsoleCursorInfo(houtput, &con);
}

1.欢迎界面的打印:

只需要我们将光标放到合适的位置打印”欢迎进入贪吃蛇小游戏“,如果不进行位置调整 会很难看。大家可以对比一下:

显然是第二种比较美观,而位置的设置完全可以调用我们前面封装的函数,那么我们的欢迎界面也就完成了。

代码:

#include<stdio.h>//设置光标的坐标 
void SetPos(int x, int y)
{COORD pos = { x, y };HANDLE hOutput = NULL;//获取标准输出的句柄(⽤来标识不同设备的数值) hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//设置标准输出上光标的位置为pos SetConsoleCursorPosition(hOutput, pos);
}int main()
{SetPos(35, 11);printf("欢迎来到贪吃蛇小游戏。\n");system("pause");return 0;
}

2.规则说明界面的打印:

操作与欢迎界面的打印一模一样,只需要更换一下打印的内容即可,在这我还是进行一个演示并放置参考代码。

效果演示:

代码:

//2.规则说明界面
void Print_rule()
{Set_Con(15, 10);printf("请用↑. ↓. ←. →控制蛇移动的方向,F3为加速,F4为减速,加速会获得更高的分数。\n\n\n\n\n\n\n");system("pause");//system("cls");
}

设想一下,贪吃蛇的速度和方向通过我们的控制在一定的区域内吃食物,蛇头碰到自己或者撞到墙会直接结束游戏。

根据设想,我们就会有这么一个代码:

//游戏的初始化
void Set_Game(Snake* ppsnake)
{//0.设置窗口的大小并隐藏光标Std_set();//1.欢迎界面Print_wel();//2.规则说明界面Print_rule();//3.绘制地图Print_wall();//4.绘制蛇Print_snake(ppsnake);//5.绘制食物Print_food(ppsnake);
}

接下来就是地图的绘制

3.地图的绘制:

这里地图的大小就由我规定了,如有需要,可以自行更改。

地图其实也就是我们的墙体,但是墙体我想要用特殊的图案进行绘制,比如效果图是这样的:

 这个图案可以在输入法中寻找,以下是具体步骤:

那我们进行实操:

代码:

//3.绘制地图
void Print_wall()
{for (int i = 0; i < 29; i++){wprintf(L"%lc", L'□');}Set_Con(0, 26);for (int i = 0; i < 29; i++){wprintf(L"%lc", L'□');}for (int i = 1; i < 26; i++){Set_Con(0, i);wprintf(L"%lc", L'□');}for (int i = 1; i < 26; i++){Set_Con(56, i);wprintf(L"%lc", L'□');}
}

效果演示: 

发现和我们预想的不太一样,这就涉及到另一个知识:

如果想在屏幕上打印宽字符,怎么打印呢?

宽字符的字⾯量必须加上前缀“L”,否则C语⾔会把字⾯量当作窄字符类型处理。前缀“L”在单引 号前⾯,表⽰宽字符,对应 wprintf() 的占位符为 %lc ;在双引号前⾯,表⽰宽字符串,对应 wprintf() 的占位符为 %ls 。

代码:

int main()
{//适配到本地setlocale(LC_ALL, "");wchar_t ch = L'□';wprintf(L"%lc", ch);return 0;
}

本地化设置:

这⾥再简单的讲⼀下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,使 ⽤两个字节表⽰⼀个汉字,所以理论上最多可以表⽰256x256=65536个符号。在俄语编码中⼜会代表另⼀个符号。但是不管怎样,所有这 些编码⽅式中,0--127表⽰的符号是⼀样的,不⼀样的只是128--255的这⼀段。 ⾄于亚洲国家的⽂字,使⽤的符号就更多了,汉字就多达10万左右。⼀个字节只能表⽰256种符号, 肯定是不够的,就必须使⽤多个字节表达⼀个符号。⽐如,简体中⽂常⻅的编码⽅式是GB2312,使 ⽤两个字节表⽰⼀个汉字,所以理论上最多可以表⽰256x256=65536个符号。

后来为了使C语⾔适应国际化,C语⾔的标准中不断加⼊了国际化的⽀持。⽐如:加⼊了宽字符的类型 wchar_t 和宽字符的输⼊和输出函数,加⼊了头⽂件,其中提供了允许程序员针对特定 地区(通常是国家或者说某种特定语⾔的地理区域)调整程序⾏为的函数。

在游戏地图上,我们打印墙体使⽤宽字符:□,打印蛇使⽤宽字符●,打印⻝物使⽤宽字符◆ 普通的字符是占⼀个字节的,这类宽字符是占⽤2个字节。

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

 在标准中,依赖地区的部分

有以下⼏项:

• 数字量的格式

• 货币量的格式

• 字符集

• ⽇期和时间的表⽰形式

类项:

通过修改地区,程序可以改变它的⾏为来适应世界的不同区域。但地区的改变可能会影响库的许多部 分,其中⼀部分可能是我们不希望修改的。

所以C语⾔⽀持针对不同的类项进⾏修改,下⾯的⼀个宏, 指定⼀个类项:

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

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

 • LC_MONETARY:影响货币格式。

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

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

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

setlocale函数:

参考链接(setlocale - C++ Reference)

语法:

char* setlocale (int category, const char* locale);

setlocale的第⼀个参数可以是前⾯说明的类项中的⼀个,那么每次只会影响⼀个类项,如果第⼀个参 数是LC_ALL,就会影响所有的类项。

 C标准给第⼆个参数仅定义了2种可能取值:"C"(正常模式)和""(本地模式)。

了解之后就可以直接投入使用了:

代码:

#include<stdio.h>
#include<locale.h>//3.绘制地图
void Print_wall()
{for (int i = 0; i < 29; i++){wprintf(L"%lc", L'□');}Set_Con(0, 26);for (int i = 0; i < 29; i++){wprintf(L"%lc", L'□');}for (int i = 1; i < 26; i++){Set_Con(0, i);wprintf(L"%lc", L'□');}for (int i = 1; i < 26; i++){Set_Con(56, i);wprintf(L"%lc", L'□');}
}int main()
{//适配到本地setlocale(LC_ALL, "");Print_wall();//system("pause");return 0;
}

效果演示:

观察到最后一行没有打印出来,其实不然,只是下面的文字将其遮挡住了,只需加一个”pause“命令即可看到效果:

 4.绘制蛇:

这里会涉及到链表的知识,在前面的文章中有讲过,那蛇其实就是一个链表而已,每个结点里面储存着各自结点的x, y坐标,用于蛇身的打印,需要注意的是需要将蛇头的结点储存起来,以便找到整条蛇。

所以我们需要创建一个蛇的结点类型,代码如下:

//蛇身的结点
typedef struct SnakeNode
{//结点的坐标short x;short y;//指向下一个结点struct SnakeNode* next;
}snakeNode;

假设我们将蛇身的长度初始为5,增加结点的时候采用尾插的方式,蛇头的坐标为(24,5),

那么初始化蛇身的代码就可以是如下:

//4.绘制蛇
void Print_snake(Snake* ppsnake) 
{snakeNode* cur = NULL;for (int i = 0; i < 5; i++){cur = (snakeNode*)malloc(sizeof(snakeNode));if (cur == NULL){perror("malloc");exit(1);}cur->x = 24 + 2 * i;cur->y = 5;cur->next = NULL;//尾插if (ppsnake->psnake == NULL){ppsnake->psnake = cur;}else{cur->next = ppsnake->psnake;ppsnake->psnake = cur;}}cur = ppsnake->psnake;//打印蛇身while (cur){Set_Con(cur->x, cur->y);wprintf(L"%lc", L'●');cur = cur->next;}}

当然,我们在绘制蛇的时候可以顺便将整个游戏的数据初始化一下,比如说游戏状态,蛇的初始方向,吃一个食物获得的分数,获得的总分数,蛇的速度(也就是两次打印之间的时间间隔)。这就需要我们写一些枚举和结构体,具体如下:

//蛇的方向
typedef enum DIRECTION {UP = 1,DOWN,LEFT,RIGHT,
}DIRECTION;//游戏状态
typedef enum GAME_STATUS
{//正常运行REGULAR= 1, //撞墙OVER_BY_WALL,//撞到自己OVER_BY_SELF,//正常退出OVER_NORMAL
}GAME_STATUS;//贪吃蛇
typedef struct Snake
{//蛇的头结点snakeNode* psnake;//食物snakeNode* pfood;//蛇的方向DIRECTION dir;//蛇的速度int sleep_time;//时间和速度成反比//一个食物的分数int foof_weight;//总分数int score;//游戏状态GAME_STATUS status;
}Snake;

绘制蛇的代码优化:

//4.绘制蛇
void Print_snake(Snake* ppsnake) 
{snakeNode* cur = NULL;for (int i = 0; i < 5; i++){cur = (snakeNode*)malloc(sizeof(snakeNode));if (cur == NULL){perror("malloc");exit(1);}cur->x = 24 + 2 * i;cur->y = 5;cur->next = NULL;//尾插if (ppsnake->psnake == NULL){ppsnake->psnake = cur;}else{cur->next = ppsnake->psnake;ppsnake->psnake = cur;}}cur = ppsnake->psnake;//打印蛇身while (cur){Set_Con(cur->x, cur->y);wprintf(L"%lc", L'●');cur = cur->next;}//设置食物分数ppsnake->foof_weight = 10;//总分ppsnake->score = 0;//速度ppsnake->sleep_time = 200;//初始方向ppsnake->dir = RIGHT;//游戏状态ppsnake->status = REGULAR;}

 

5.绘制食物:

食物的结点类型与蛇的相同,只不过只有一个结点,但是该结点的坐标不能与蛇身相同并且坐标是随机的,关于产生随机值这个知识在前面扫雷的实现里有讲,所以这里就不细说了。

代码:

//5.绘制食物
void Print_food(Snake* ppsnake)
{int x = 0;int y = 0;
again:do{x = rand() % 53 + 2;y = rand() % 25 + 1;} while (x % 2 != 0);snakeNode* cur = ppsnake->psnake;while (cur){if (cur->x == x && cur->y == y){goto again;}cur = cur->next;}snakeNode*  p = (snakeNode*)malloc(sizeof(snakeNode));if (p == NULL){perror("malloc");return;}p->x = x;p->y = y;p->next = NULL;Set_Con(x, y);wprintf(L"%lc", L'◆');ppsnake->pfood = p;
}

 游戏运行:

0.实现按键判断及反应:

将要判断的按键的虚拟值传入写的宏中即可,判断后进行相应的反应。

代码:

//暂停游戏
void PAUSE()
{while (1){Sleep(100);if (KEY_PASS(VK_SPACE)){break;}}
}void StartGame(Snake* ppsnake)
{do {if (KEY_PASS(VK_UP) && ppsnake->dir != DOWN)//向上{ppsnake->dir = UP;}else if (KEY_PASS(VK_DOWN) && ppsnake->dir != UP)//向下{ppsnake->dir = DOWN;}else if (KEY_PASS(VK_LEFT) && ppsnake->dir != RIGHT)//向左{ppsnake->dir = LEFT;}else if (KEY_PASS(VK_RIGHT) && ppsnake->dir != LEFT)//向右{ppsnake->dir = RIGHT;}else if (KEY_PASS(VK_SPACE))//暂停,也就是写一个死循环,永远处于休眠状态{PAUSE();}else if (KEY_PASS(VK_ESCAPE))//正常退出游戏{ppsnake->status = OVER_NORMAL;}else if (KEY_PASS(VK_F3))//加速{//对加速次数进行限制,防止蛇出现瞬移的情况if (ppsnake->foof_weight < 18){ppsnake->sleep_time -= 30;ppsnake->foof_weight += 2;}}else if (KEY_PASS(VK_F4))//减速{//与加速相同,要做次数限制if (ppsnake->foof_weight > 2){ppsnake->sleep_time += 30;ppsnake->foof_weight -= 2;}}Snakemove(ppsnake);//移动蛇Sleep(ppsnake->sleep_time);//等价于蛇的速度} while (ppsnake->status == REGULAR);
}

1.帮助信息的打印: 

想要达到一个这样的效果:

我们只需要找到 合适的坐标,接着打印一下就可以了,可以把这个函数在游戏运行里调用,因为总分和每个食物的分数是要进行更新的。

添加此功能后的代码:

//暂停游戏
void PAUSE()
{while (1){Sleep(100);if (KEY_PASS(VK_SPACE)){break;}}
}//打印帮助信息
void Print_help()
{Set_Con(64, 14);printf("按ESC退出游戏");Set_Con(64, 15);printf("按F3加速,按F4减速");
}void StartGame(Snake* ppsnake)
{Print_help();do {Set_Con(64, 12);printf("总分数:%d", ppsnake->score);Set_Con(64, 13);printf("当前每个食物的分数:%02d", ppsnake->foof_weight);if (KEY_PASS(VK_UP) && ppsnake->dir != DOWN)//向上{ppsnake->dir = UP;}else if (KEY_PASS(VK_DOWN) && ppsnake->dir != UP)//向下{ppsnake->dir = DOWN;}else if (KEY_PASS(VK_LEFT) && ppsnake->dir != RIGHT)//向左{ppsnake->dir = LEFT;}else if (KEY_PASS(VK_RIGHT) && ppsnake->dir != LEFT)//向右{ppsnake->dir = RIGHT;}else if (KEY_PASS(VK_SPACE))//暂停,也就是写一个死循环,永远处于休眠状态{PAUSE();}else if (KEY_PASS(VK_ESCAPE))//正常退出游戏{ppsnake->status = OVER_NORMAL;}else if (KEY_PASS(VK_F3))//加速{//对加速次数进行限制,防止蛇出现瞬移的情况if (ppsnake->foof_weight < 18){ppsnake->sleep_time -= 30;ppsnake->foof_weight += 2;}}else if (KEY_PASS(VK_F4))//减速{//与加速相同,要做次数限制if (ppsnake->foof_weight > 2){ppsnake->sleep_time += 30;ppsnake->foof_weight -= 2;}}Snakemove(ppsnake);//移动蛇Sleep(ppsnake->sleep_time);//等价于蛇的速度} while (ppsnake->status == REGULAR);
}

接下来实现蛇的移动 ,蛇的移动其实就是每隔一定时间重新打印蛇,只不过这个时间很短,人的肉眼难以分辨罢了,所以我们只需要着力去实现Snakemove函数。

2.蛇的移动:

实现该函数的思路大概就是去创建一个新的结点,这个结点储存着蛇头的下一个位置结点,需要注意的一点是,防止蛇头只有一半接触到食物,效果如下,所以我们需要蛇身x坐标一直是2的倍数,接下来开始写代码。

 

代码:

//蛇的移动
void Snakemove(Snake* pp)
{snakeNode* pnew = (snakeNode*)malloc(sizeof(snakeNode));if (pnew == NULL){perror("malloc");return;}switch (pp->dir){case UP:pnew->x = pp->psnake->x;pnew->y = pp->psnake->y - 1;break;case DOWN:pnew->x = pp->psnake->x;pnew->y = pp->psnake->y + 1;break;case LEFT:pnew->x = pp->psnake->x - 2;pnew->y = pp->psnake->y;break;case RIGHT:pnew->x = pp->psnake->x + 2;pnew->y = pp->psnake->y;break;}}

 

 3.判断下一个位置是否是食物:

在蛇移动的过程中,下一个结点可能会是食物,所以我们要做出相应的反应,大纲如下:

//蛇的移动
void Snakemove(Snake* pp)
{snakeNode* pnew = (snakeNode*)malloc(sizeof(snakeNode));if (pnew == NULL){perror("malloc");return;}switch (pp->dir){case UP:pnew->x = pp->psnake->x;pnew->y = pp->psnake->y - 1;break;case DOWN:pnew->x = pp->psnake->x;pnew->y = pp->psnake->y + 1;break;case LEFT:pnew->x = pp->psnake->x - 2;pnew->y = pp->psnake->y;break;case RIGHT:pnew->x = pp->psnake->x + 2;pnew->y = pp->psnake->y;break;}//判断下一个结点是不是食物if (IF_FOOF(pnew, pp)){//是食物:EatFood(pnew, pp);pnew = NULL;}else{//不是食物:NoFood(pnew, pp);}}

接下来我们来实现EatFood和NoFood这两个函数,只需要判断蛇移动的下一个结点的x,y坐标是否相同即可,所以在传参的时候需要将蛇头的下一个结点传过去。

EatFood函数:

如果下一个结点是食物,可以直接将其化作新的蛇头,正好蛇的长度加一,重新打印蛇身,在吃掉食物的同时,我们要再创建一个新的食物,并且在总分中加上食物的分数,具体代码如下:

//是食物:
void EatFood(snakeNode* pnew, Snake* pp)
{//把新的结点加到蛇身上pp->pfood->next = pp->psnake;pp->psnake = pp->pfood;snakeNode* cur = pp->psnake;//释放新的结点free(pnew);pnew = NULL;//重新打印蛇身while (cur != NULL){Set_Con(cur->x, cur->y);wprintf(L"%lc", L'●');cur = cur->next;}pp->score += pp->foof_weight;//创建新的食物:Print_food(pp);
}

 

NoFood函数:

如果下一个结点不是食物,我们需要把新的结点加到蛇的身上,让其成为新的蛇头,并且重新打印蛇身,需要注意的是原来蛇的尾巴需要释放掉,不然蛇会越来越长,不过在释放之前需要用到蛇的尾部结点去将原来尾部的位置用两个空格遮盖住,具体代码如下:

//不是食物:
void NoFood(snakeNode* pnew, Snake* pp)
{//把新的结点加到蛇身上pnew->next = pp->psnake;pp->psnake = pnew;snakeNode* cur = pp->psnake;//重新打印蛇身while (cur->next->next != NULL){Set_Con(cur->x, cur->y);wprintf(L"%lc", L'●');cur = cur->next;}Set_Con(cur->next->x, cur->next->y);printf("  ");//释放尾部结点free(cur->next);cur->next = NULL;
}

 判断完下一个结点是不是食物后还要判断是否撞墙和撞到自己,大纲如下:

//蛇的移动
void Snakemove(Snake* pp)
{snakeNode* pnew = (snakeNode*)malloc(sizeof(snakeNode));if (pnew == NULL){perror("malloc");return;}switch (pp->dir){case UP:pnew->x = pp->psnake->x;pnew->y = pp->psnake->y - 1;break;case DOWN:pnew->x = pp->psnake->x;pnew->y = pp->psnake->y + 1;break;case LEFT:pnew->x = pp->psnake->x - 2;pnew->y = pp->psnake->y;break;case RIGHT:pnew->x = pp->psnake->x + 2;pnew->y = pp->psnake->y;break;}//判断下一个结点是不是食物if (IF_FOOF(pnew, pp)){//是食物:EatFood(pnew, pp);pnew = NULL;}else{//不是食物:NoFood(pnew, pp);}//撞到墙KillByWall(pp);//撞到自己KillBySelf(pp);
}

4.是否撞墙和撞到自己

接下来我们尝试去实现KillByWall和KillBySelf函数:

KillByWall函数

只需判断蛇头是否超出我们前面绘制地图时给出的范围,如果超过了就修改游戏状态即可,具体代码如下:

//撞到墙
void KillByWall(Snake* pp)
{if (pp->psnake->x == 0 || pp->psnake->x == 56 || pp->psnake->y == 0 || pp->psnake->y == 26){pp->status = OVER_BY_WALL;}
}

 

KillBySelf函数:

将除蛇头以外的蛇身的每一个结点与蛇头的x,y坐标进行对比即可,具体代码如下:

//撞到自己
void KillBySelf(Snake* pp)
{snakeNode* cur = pp->psnake->next;while (cur){if (cur->x == pp->psnake->x && cur->y == pp->psnake->y){pp->status = OVER_BY_SELF;break;}cur = cur->next;}
}

到这里游戏的运行就已经完成了,但是我们还要对游戏结束后进行善后工作。

5.游戏结束善后工作:

首先要提示玩家游戏为什么结束,然后释放蛇的结点,具体代码如下:

//结束游戏
void EndGame(Snake* pp)
{switch (pp->status){case OVER_BY_SELF:  printf("撞到自己了,本局游戏结束!!!");break;case OVER_BY_WALL:printf("撞到墙了,本局游戏结束!!!");break;case OVER_NORMAL:printf("本局游戏已正常退出!!!");break;}//释放结点snakeNode* cur = pp->psnake;while (cur){snakeNode* del = cur;cur = cur->next;free(del);}
}

 

 细节补充:

在传参上我们需要注意因为我们在实现功能的时候对结构体中的内容进行了修改,所以要传结构体指针。

我们还可以在增加一些细节,比如使用文件操作来储存历史最高纪录,这方面的知识在通讯录的实现中讲过,感兴趣可以去看一下。

游戏所有代码: 

到这里已经将所有功能都实现了,现在就将他们的合体展示出来,分为三个文件:

snake.h:

#pragma once
#include<Windows.h>
#include<stdio.h>
#include<stdbool.h>
#include<locale.h>
#include<stdlib.h>
#include<time.h>
#define KEY_PASS(vk) ((GetAsyncKeyState(vk)&1)?1:0)//蛇身的结点
typedef struct SnakeNode
{//结点的坐标short x;short y;//指向下一个结点struct SnakeNode* next;
}snakeNode;//蛇的方向
typedef enum DIRECTION {UP = 1,DOWN,LEFT,RIGHT,
}DIRECTION;//游戏状态
typedef enum GAME_STATUS
{//正常运行REGULAR= 1, //撞墙OVER_BY_WALL,//撞到自己OVER_BY_SELF,//正常退出OVER_NORMAL
}GAME_STATUS;//贪吃蛇
typedef struct Snake
{//蛇的头结点snakeNode* psnake;//食物snakeNode* pfood;//蛇的方向DIRECTION dir;//蛇的速度int sleep_time;//时间和速度成反比//一个食物的分数int foof_weight;//总分数int score;//游戏状态GAME_STATUS status;
}Snake;//游戏的初始化
void Set_Game(Snake* ppsnake);
//光标位置的设置
void Set_Con(int x, int y);
//0.设置窗口的大小并隐藏光标
void Std_set();
//1.欢迎界面
void Print_wel();
//2.规则说明界面
void Print_rule();
//3.绘制地图
void Print_wall();
//4.绘制蛇
void Print_snake(Snake* ppsnake);
//5.绘制食物
void Print_food(Snake* ppsnake);
//开始游戏
void StartGame(Snake* snake);
//判断下一个结点是不是食物
int IF_FOOF(snakeNode* pnew, Snake* pp);
//是食物:
void EatFood(snakeNode* pnew,Snake* pp);
//不是食物:
void NoFood(snakeNode* pnew, Snake* pp);
//撞到墙
void KillByWall(Snake* pp);
//撞到自己
void KillBySelf(Snake* pp);
//结束游戏
void EndGame(Snake* pp);

snake.c:

#include"snake.h"//游戏的初始化
void Set_Game(Snake* ppsnake)
{//0.设置窗口的大小并隐藏光标Std_set();//1.欢迎界面Print_wel();//2.规则说明界面Print_rule();//3.绘制地图Print_wall();//4.绘制蛇Print_snake(ppsnake);//5.绘制食物Print_food(ppsnake);
}//光标位置的设置
void Set_Con(int x, int y)
{HANDLE output = NULL;output = GetStdHandle(STD_OUTPUT_HANDLE);COORD coor = { x, y };SetConsoleCursorPosition(output, coor);
}//0.设置窗口的大小并隐藏光标
void Std_set()
{system("mode con cols=100 lines=30");system("title 贪吃蛇");HANDLE houtput = NULL;houtput = GetStdHandle(STD_OUTPUT_HANDLE);CONSOLE_CURSOR_INFO con;GetConsoleCursorInfo(houtput, &con);con.bVisible = false;SetConsoleCursorInfo(houtput, &con);
}//1.欢迎界面
void Print_wel()
{Set_Con(40, 12);printf("欢迎来到贪吃蛇小游戏!!!\n\n\n\n");system("pause");system("cls");
}//2.规则说明界面
void Print_rule()
{Set_Con(15, 10);printf("请用↑. ↓. ←. →控制蛇移动的方向,F3为加速,F4为减速,加速会获得更高的分数。\n\n\n\n\n\n\n");system("pause");system("cls");
}//3.绘制地图
void Print_wall()
{for (int i = 0; i < 29; i++){wprintf(L"%lc", L'□');}Set_Con(0, 26);for (int i = 0; i < 29; i++){wprintf(L"%lc", L'□');}for (int i = 1; i < 26; i++){Set_Con(0, i);wprintf(L"%lc", L'□');}for (int i = 1; i < 26; i++){Set_Con(56, i);wprintf(L"%lc", L'□');}
}//4.绘制蛇
void Print_snake(Snake* ppsnake) 
{snakeNode* cur = NULL;for (int i = 0; i < 5; i++){cur = (snakeNode*)malloc(sizeof(snakeNode));if (cur == NULL){perror("malloc");exit(1);}cur->x = 24 + 2 * i;cur->y = 5;cur->next = NULL;//尾插if (ppsnake->psnake == NULL){ppsnake->psnake = cur;}else{cur->next = ppsnake->psnake;ppsnake->psnake = cur;}}cur = ppsnake->psnake;//打印蛇身while (cur){Set_Con(cur->x, cur->y);wprintf(L"%lc", L'●');cur = cur->next;}//设置食物分数ppsnake->foof_weight = 10;//总分ppsnake->score = 0;//速度ppsnake->sleep_time = 200;//初始方向ppsnake->dir = RIGHT;//游戏状态ppsnake->status = REGULAR;}//5.绘制食物
void Print_food(Snake* ppsnake)
{int x = 0;int y = 0;
again:do{x = rand() % 53 + 2;y = rand() % 25 + 1;} while (x % 2 != 0);snakeNode* cur = ppsnake->psnake;while (cur){if (cur->x == x && cur->y == y){goto again;}cur = cur->next;}snakeNode*  p = (snakeNode*)malloc(sizeof(snakeNode));if (p == NULL){perror("malloc");return;}p->x = x;p->y = y;p->next = NULL;Set_Con(x, y);wprintf(L"%lc", L'◆');ppsnake->pfood = p;
}//打印帮助信息
void Print_help()
{Set_Con(64, 14);printf("按ESC退出游戏");Set_Con(64, 15);printf("按F3加速,按F4减速");
}//暂停游戏
void PAUSE()
{while (1){Sleep(100);if (KEY_PASS(VK_SPACE)){break;}}
}//判断下一个结点是不是食物
int IF_FOOF(snakeNode* pnew, Snake* pp)
{return (pp->pfood->x == pnew->x && pp->pfood->y == pnew->y);
}//是食物:
void EatFood(snakeNode* pnew, Snake* pp)
{//把新的结点加到蛇身上pp->pfood->next = pp->psnake;pp->psnake = pp->pfood;snakeNode* cur = pp->psnake;//释放新的结点free(pnew);pnew = NULL;//重新打印蛇身while (cur != NULL){Set_Con(cur->x, cur->y);wprintf(L"%lc", L'●');cur = cur->next;}pp->score += pp->foof_weight;//创建新的食物:Print_food(pp);
}//不是食物:
void NoFood(snakeNode* pnew, Snake* pp)
{//把新的结点加到蛇身上pnew->next = pp->psnake;pp->psnake = pnew;snakeNode* cur = pp->psnake;//重新打印蛇身while (cur->next->next != NULL){Set_Con(cur->x, cur->y);wprintf(L"%lc", L'●');cur = cur->next;}Set_Con(cur->next->x, cur->next->y);printf("  ");//释放尾部结点free(cur->next);cur->next = NULL;
}//撞到墙
void KillByWall(Snake* pp)
{if (pp->psnake->x == 0 || pp->psnake->x == 56 || pp->psnake->y == 0 || pp->psnake->y == 26){pp->status = OVER_BY_WALL;}
}//撞到自己
void KillBySelf(Snake* pp)
{snakeNode* cur = pp->psnake->next;while (cur){if (cur->x == pp->psnake->x && cur->y == pp->psnake->y){pp->status = OVER_BY_SELF;break;}cur = cur->next;}
}//蛇的移动
void Snakemove(Snake* pp)
{snakeNode* pnew = (snakeNode*)malloc(sizeof(snakeNode));if (pnew == NULL){perror("malloc");return;}switch (pp->dir){case UP:pnew->x = pp->psnake->x;pnew->y = pp->psnake->y - 1;break;case DOWN:pnew->x = pp->psnake->x;pnew->y = pp->psnake->y + 1;break;case LEFT:pnew->x = pp->psnake->x - 2;pnew->y = pp->psnake->y;break;case RIGHT:pnew->x = pp->psnake->x + 2;pnew->y = pp->psnake->y;break;}//判断下一个结点是不是食物if (IF_FOOF(pnew, pp)){//是食物:EatFood(pnew, pp);pnew = NULL;}else{//不是食物:NoFood(pnew, pp);}//撞到墙KillByWall(pp);//撞到自己KillBySelf(pp);
}//开始游戏
void StartGame(Snake* ppsnake)
{Print_help();do {Set_Con(64, 12);printf("总分数:%d", ppsnake->score);Set_Con(64, 13);printf("当前每个食物的分数:%02d", ppsnake->foof_weight);if (KEY_PASS(VK_UP) && ppsnake->dir != DOWN)//向上{ppsnake->dir = UP;}else if (KEY_PASS(VK_DOWN) && ppsnake->dir != UP)//向下{ppsnake->dir = DOWN;}else if (KEY_PASS(VK_LEFT) && ppsnake->dir != RIGHT)//向左{ppsnake->dir = LEFT;}else if (KEY_PASS(VK_RIGHT) && ppsnake->dir != LEFT)//向右{ppsnake->dir = RIGHT;}else if (KEY_PASS(VK_SPACE))//暂停{PAUSE();}else if (KEY_PASS(VK_ESCAPE))//正常退出游戏{ppsnake->status = OVER_NORMAL;}else if (KEY_PASS(VK_F3))//加速{if (ppsnake->foof_weight < 18){ppsnake->sleep_time -= 30;ppsnake->foof_weight += 2;}}else if (KEY_PASS(VK_F4))//减速{if (ppsnake->foof_weight > 2){ppsnake->sleep_time += 30;ppsnake->foof_weight -= 2;}}Snakemove(ppsnake);Sleep(ppsnake->sleep_time);} while (ppsnake->status == REGULAR);
}//结束游戏
void EndGame(Snake* pp)
{switch (pp->status){case OVER_BY_SELF:  printf("撞到自己了,本局游戏结束!!!");break;case OVER_BY_WALL:printf("撞到墙了,本局游戏结束!!!");break;case OVER_NORMAL:printf("本局游戏已正常退出!!!");break;}//释放结点snakeNode* cur = pp->psnake;while (cur){snakeNode* del = cur;cur = cur->next;free(del);}
}

test.c:


#include"snake.h"void test1()
{char ch = 0;do {system("cls");//创建贪吃蛇Snake snake = { 0 };//初始化游戏//0.设置窗口的大小并隐藏光标//1.欢迎界面//2.规则说明界面//3.绘制地图//4.绘制蛇//5.绘制食物//6.设置游戏的相关信息Set_Game(&snake);//开始游戏StartGame(&snake);//结束游戏EndGame(&snake);Set_Con(20, 15);printf("再来一局?(Y/N):");ch = getchar();while (getchar() != '\n');} while (ch == 'Y' || ch == 'y');Set_Con(0, 27);
}int main()
{//适配到本地setlocale(LC_ALL, "");srand((unsigned int)time(NULL));//测试游戏test1();return 0;
}

 

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

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

相关文章

普乐蛙VR航天航空体验馆VR双人旋转座椅元宇宙VR飞船

多长假来袭&#xff01;&#xff01;想为门店寻找更多新鲜有趣的吸粉体验&#xff1f;想丰富景区体验&#xff1f;别着急&#xff0c;小编为你准备了一款爆款设备——时光穿梭机&#xff0c;720无死角旋转&#xff01;&#xff01;吸睛、刺激体验&#xff0c;将亲子、闺蜜、情侣…

Java对象不再使用时,为什么要赋值为 null

相信大家在面试Java开发的时候,会遇到比较多的问题是Java的内存管理,这里面涉及到Java垃圾回收机制,以及JVM调优等等,那么今天跟大家讨论一个问题:Java对象不再使用时,为什么要赋值为 null ? 1、Java内存管理 在Java中,对象是在堆内存中分配的。 这部分内存用于存储…

科技论文网站:中国科技论文在线

文章目录 1. Intro2. Main3. Cons Evaluation彩蛋&#xff1a;科学素质 这是作者最后一次发 这种类型的宣传 科普文章 1. Intro 中国科技论文在线是经教育部批准&#xff0c;由教育部科技发展中心主办&#xff0c; 利用现代信息技术手段&#xff0c;打破传统出版物的概念&…

Scrapy爬虫框架入门(豆瓣电影Top 250)

文章目录 Scrapy 官网Scrapy 文档GithubScrapy 简介项目结构爬虫实现XPath 教程创建 Scrapy 项目配置用户代理网页 dom 元素 IP 代理池IP代理池作用配置IP代理池申请IP代理池 Scrapy 官网 https://scrapy.org/ Scrapy 文档 https://docs.scrapy.org/en/latest/ Github htt…

在windows上安装MySQL数据库全过程

1.首先在MySQL的官网找到其安装包 在下图中点击MySQL Community(gpl) 找到MySQL Community Server 选择版本进行安装包的下载 2.安装包&#xff08;Windows (x86, 64-bit), MSI Installer&#xff09;安装步骤 继续点击下一步 继续进行下一步&#xff0c;直到出现此界面&#…

Vue3+vite优化基础架构(1)--- 使用unplugin-vue-components

Vue3vite优化基础架构&#xff08;1&#xff09;--- 使用unplugin-vue-components 说明安装unplugin-vue-componentsvite.config.js中使用unplugin-vue-components/vite 说明 这里记录下自己在Vue3vite的项目使用unplugin-vue-components/vite来自定义组件自动全局引入svg雪碧…

【八股】Spring Boot

SpringBoot是如何实现自动装配的&#xff1f; 首先&#xff0c;SpringBoot的核心注解SpringBootApplication里面包含了三个注解&#xff0c;SpringBootConfigurationEnableAutoConfigurationComponentScan&#xff0c;其中EnableAutoConfiguration是实现自动装配的注解&#x…

用自然语言连接系统的认知,用Function Calling 连接大模型和业务

前言 本篇文章&#xff0c;我们重点介绍Function Calling的机制和应用&#xff0c;在其原理上&#xff0c;也讲解了为什么会有plugin、GPTs出现等等。 核心要点&#xff1a; 1.大模型应用的一切&#xff0c;是自然语言连接系统的认知 2.在Function Calling的应用中&#xff0…

重要文件怕丢失怎么办?汇帮数据备份软件帮你解决文件丢失的烦恼 帮你实现文件实时备份的好方法

随着数字时代的来临&#xff0c;电脑文件已成为我们日常生活和工作中不可或缺的一部分。然而&#xff0c;无论是个人用户还是企业用户&#xff0c;都可能面临数据丢失的风险&#xff0c;如硬盘故障、病毒攻击、误删除等。为了确保数据安全&#xff0c;实现电脑文件的实时备份变…

等保合规:保护企业网络安全的必要性与优势

前言 无论是多部网络安全法律法规的出台&#xff0c;还是最近的“滴滴被安全审查”事件&#xff0c;我们听得最多的一个词&#xff0c;就是“等保。” 只要你接触安全类工作&#xff0c;听得最多的一个词&#xff0c;一定是“等保。” 那么&#xff0c;到底什么是“等保”呢…

Redis网络相关的结构体 和 reactor模式

目录 1. epoll的封装 结构体aeApiStae 创建epoll fd的封装 epoll_ctl的封装 epoll_wait的封装 2. 结构体aeFileEvent、aeFiredEvent、aeTimeEvent 结构体aeFileEvent 结构体aeFiredEvent 结构体aeTimeEvent 3. struct aeEventLoop aeEventLoop相关的函数 1. 创建eve…

Rust HTTP 客户端:易于使用、功能强大 | 开源日报 No.228

seanmonstar/reqwest Stars: 8.9k License: Apache-2.0 reqwest 是一个易于使用且功能强大的 Rust HTTP 客户端。 异步和阻塞客户端支持普通数据、JSON、urlencoded 和 multipart 数据格式可定制的重定向策略支持 HTTP 代理和系统原生 TLS 或 rustls 的 HTTPSCookie 存储功能…

JVM学习笔记(五)内存模型

目录 1、原子性 1.1 问题分析 1.2 解决方法 2、可见性 2.1 退不出的循环 2.2 解决办法 3、有序性 3.1 诡异的结果 3.2 解决办法 3.3 有序性理解 3.4 happens-before 4、CAS与原子类 4.1 CAS 4.2 乐观锁与悲观锁 4.3 原子操作类 5、synchronized 优化 5.1 轻量…

17(第十六章,数据管理组织与角色期望)

目录 概述 基本概念 首席数据官 个人角色 执行官角色 业务角色 IT角色 混合角色 组织架构模式 概述 本章书上的重点内容其实不多&#xff0c;了解的地方例如数据管理组织的结构包括分散、网络、混合、联邦、集中等运营模式。 之前也提过的RACI&#xff08;谁负责&am…

AWTK MODBUS Client channel 模型

名称&#xff1a;modbus_client_channel 功能&#xff1a;通过 modbus 协议访问远程 slave 设备上的数据&#xff0c;需要配合 modbus_client模型一起使用。用于将 modbus client 中的 channel 包装成view_model或者view_model_array 一般来说不需要&#xff0c;直接使用modbus…

CentOS 系统的优缺点

CentOS &#xff08;社区企业操作系统的缩写&#xff09;是一个基于红帽企业 Linux (RHEL)的免费开源发行版&#xff0c; 旨在为服务器和工作站提供稳定、可靠和安全的平台。 不应将其与CentOS Stream 混淆&#xff0c;后者是即将发布的 RHEL 版本的上游开发平台。 CentOS Li…

C++初阶之入门

零、什么是C C是基于C语言而产生的&#xff0c;它既可以进行C语言的过程化程序设计&#xff0c;又可以进行以抽象数据类型为特点的基于对象的程序设计&#xff0c;还可以进行面向对象的程序设计。 C缺点之一&#xff0c;是相对许多语言复杂&#xff0c;而且难学难精。许多人说学…

一般神经网络的微分与网络参数的初始化

(文章的主要内容来自电科的顾亦奇老师的 Mathematical Foundation of Deep Learning, 有部分个人理解) 一般深度神经网络的微分 上周讨论的前向和反向传播算法可以推广到任意深度神经网络的微分。 对于一般的网络来说&#xff0c;可能无法逐层分割&#xff0c;但仍然可以用流…

抖音小店运营过程中,每天必须要做的7件事情!少一件都不行!

大家好&#xff0c;我是电商小V 最近很多新手小伙伴刚做店&#xff0c;操作抖音小店每天需要做的事情都是哪些呢&#xff1f;感觉自己一天很忙&#xff0c;但是也没有忙出来结果&#xff0c;思绪很乱&#xff0c;关于这个问题咱们今天就来详细的说一下&#xff0c;给你们梳理总…

02 贪吃蛇

前言 呵呵 这是不知道 在哪里看到的 别人做的一个贪吃蛇 因此 也把我 之前的 贪吃蛇 移植上来了 当然 这个不过是为了 简单的入门了解, 呵呵 然后 c版本的贪吃蛇 需要先移植成 c 版本, 然后 再根据 单片机相关 设计调整 比如 led 点阵的输出, 比如 c99 语法的一些不兼容…