贪吃蛇(C语言超详细版)
目录
前言:
总览:
API:
控制台程序(Console):
设置坐标:
COORD:
GetStdHandle:
STD_OUTPUT_HANDLE参数:
SetConsoleCursorPosition:
隐藏光标:
GetConsoleCursorInfo:
CONSOLE_CURSOR_INFO参数:
SetConsoleCursorInfo:
获取按键信息:
GetAsyncKeyState:
设置宽字符:
setlocale函数:
宽字符的打印:
贪吃蛇的实现:
初始化游戏:
初始化蛇结构体:
初始化游戏:
打印地图:
初始化蛇并打印:
创建食物:
游戏运行:
打印帮助信息:
判断按键:
蛇移动的函数:
判断蛇的状态:
游戏善后工作:
所有代码:
总结:
前言:
C语言到底能不能用来做游戏?今天我就要告诉你们,完全可以!但是用C语言来完成一个游戏,我们还需要很多预备知识才能将游戏运行。
在我们要去完成一个项目时,都需要先想好它的逻辑,我们将一个大问题分为若干个子问题去解决,接下来我们就开始正式开始完成贪吃蛇。
总览:
我们先来看游戏的运行过程:
贪吃蛇运行过程
可以看到控制台不是很大,而且有标题:贪吃蛇。最重要的是我们每次看到的光标不见了。这是为什么呢?
实现贪吃蛇会使用一些Win32 API知识,我们需要学习一下。
API:
什么是API?我喜欢先把它的全称说一下:Application Programming Interface。简称 API 函数。
Windows 这个多作业系统除了协调应用程序的执行、分配内存、管理资源之外,它同时也是一个很大的服务中心,调用这个服务中心的各种服务(每一种服务就是一个函数),可以帮应用程序达到开启视窗、描绘图形、使用周边设备等目的,由于这些函数服务的对象是应用程序(Application),所以便称之为 Application Programming Interface,简称 API 函数。WIN32 API 也就是 Microsoft Windows 32 位平台的应用程序编程接口。
说人话就是函数,我们能直接使用(应该就是使用Windows系统都可以使用这些函数)。
控制台程序(Console):
平时我们运行起来的黑框程序其实就是控制台程序。我们可以使用cmd命令来设置控制台窗口的长度:比如设置控制台窗口的大小, 30行,100列。
mode con cols=100 lines=30
我们使用C语言在 Windows环境下模拟实现贪食蛇小游戏。
注意,我们在控制终端以前一定要做到以下修改:
游戏是否能正常运行起来,这一步至关重要!
我们可以修改控制台的标题,这些都是控制台的命令,我们都可以用C语言的 system 函数来执行这些系统命令,我们要包含stdlib.h头文件。
#include<stdio.h>
#include<stdlib.h>
int main()
{system("mode con cols=50 lines=20");system("title 贪吃蛇");return 0;
}
可以发现,控制台终端标题没有变成贪吃蛇,这是因为程序运行结束了,程序没有运行结束之前,标题是会变成我们设置的贪吃蛇的。
所以为了方便观察,我们加上getchar来防止程序运行结束。
当然,我们也可以使用系统命令pause来暂行程序。
system("pause");//暂停
设置坐标:
COORD:
COORD是 Windows API 中定义的一个结构体,表示一个字符在控制台屏幕缓冲区的坐标,坐标系(0,0)的原点位于缓冲区的顶部左侧单元格。
GetStdHandle:
GetStdHandle:是一个 Windows API 函数,它用于从一个特定的标准设备(标准输入、标准输出或标准错误)中读得一个句柄(用来标识不同设备的数值),使用这个句柄可以操作设备。
也就是说,你要操作这个控制台程序,你要获得这个控制台权限。而这个GetStdHandle函数,就可以让我们从一个特定设备获取句柄。
好比我们现在打开多个控制台程序:
我们此时就可以在我们写的程序中获取自己控制台的句柄,以至于不会去干扰其他控制台。
STD_OUTPUT_HANDLE参数:
当我们在控制台中输出,要使用STD_OUTPUT_HANDLE参数。
我们可以看到GetStdHandle函数返回的是一个HANDLE,所以我们要接收一下。
SetConsoleCursorPosition:
SetConsoleCursorPosition:设置指定控制台屏幕缓冲区中的光标位置,我们将想要设置的光标信息放在COORD类型的pos中,调用SetConsoleCursorPosition函数将光标位置设置到指定的位置。
也就是说这是一套组合拳,我们都要一起配合使用,我们要先使用GetStdHandle获取当前控制台句柄,并用HANDLE类型接收,之后使用COORD定义一个结构体设置光标的坐标,最后使用SetConsoleCursorPositon正式把坐标设置到控制台中(记得引入头文件)。
#include<stdio.h>
#include<windows.h>
#include<stdlib.h>
int main()
{//获取句柄HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);//设置光标的第一个参数//根据句柄设置光标位置COORD pos = { 20, 5 };SetConsoleCursorPosition(handle, pos);//设置光标位置printf("hehe\n");return 0;
}
但是每次这样写很繁琐,我们为了方便定位,可以直接将其封装为一个函数。
void SetPos(int x, int y)
{//获取句柄HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);//设置光标的第一个参数//根据句柄设置光标位置COORD pos = { x, y };SetConsoleCursorPosition(handle, pos);//设置光标位置
}int main()
{SetPos(20, 5);printf("hehe\n");SetPos(25, 5);printf("haha\n");return 0;
}
隐藏光标:
GetConsoleCursorInfo:
GetConsoleCursorInfo:检索有关指定控制台屏幕缓冲区的光标大小和可见性的信息。
因为我们在运行程序时,是不会出现光标的,所以我们要通过此函数来隐藏光标。
第一个参数是句柄,指定那个控制台;第二个参数是PCONSOLE_CURSOR_INFO 是指向 CONSOLE_CURSOR_INFO 结构的指针,该结构接收有关主机游标(光标)的信息。
CONSOLE_CURSOR_INFO参数:
CONSOLE_CURSOR_INFO:这个结构体,包含有关控制台光标信息。
dwSize:由游标填充的字符单元的百分比。 该值介于 1 到 100 之间。 游标外观各不相同,范围从完全填充单元到显示为单元底部的横线。
bVisible:游标的可见性。 如果游标可见,则此成员为 TRUE。
我们可以将bVisible参数设置为false,这样就隐藏了光标。
但是当我们修改完以后光标的信息,就像设置光标位置一样,还需要设置才能生效,此时就需要用到 SetConsoleCursorInfo 函数。
SetConsoleCursorInfo:
SetConsoleCursorInfo:设置指定控制台屏幕缓冲区的光标大小和可见性。
和设置光标是一样的,也是一套组合拳:
#include<stdio.h>
#include<windows.h>
int main()
{//COORD pos = { 40, 10 };//要包含头文件//设置坐标//获取句柄HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);//设置光标的第一个参数CONSOLE_CURSOR_INFO cursor_info = { 0 };//设置光标的第二个参数//包含有关控制台的游标信息GetConsoleCursorInfo(handle, &cursor_info);//设置光标信息cursor_info.dwSize = 100;SetConsoleCursorInfo(handle, &cursor_info);return 0;
}
比如此时我们将dwSize设置为100。
小总结: 我们使用API不用去纠结很多细节,我们可以直接使用,要做到会用,至于更多细节的东西,我们以后还会慢慢学习到的,决不能因噎废食!
获取按键信息:
我们运行贪吃蛇,肯定是根据按键来控制的,所以我们此时就来学习和按键有关的API函数。
GetAsyncKeyState:
GetAsyncKeyState:获取按键情况。
将键盘上每个键的虚拟键值传递给函数,函数通过返回值来分辨按键的状态。
GetAsyncKeyState的返回只是short类型,在上一次调用GetAsyncKeyState函数之后,如果返回的16位的short数据中,最高位是1,说明按键的状态是按下;如果最高是0,说明按键的状态是抬起;如果最低位被置为1则说明该按键被按过,否则为0。
如果我们要判断一个件是否被按过,可以检测GetAsyncKeyState返回值的最低位是否为1。
因为按键只很多,不方便展示,各位可以直接点击链接进入(虚拟键码 (Winuser.h) - Win32 apps | Microsoft Learn)。
//获取键盘按键情况
#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");}}//可以监控别人键盘return 0;
}
设置宽字符:
为了方便贪吃蛇运行,我们可以发现,平时我们在控制台中的光标是长方形,但是贪吃蛇的身体每个节点都是一个规则的正方形,所以我们要使用宽字符。
我们知道C语言默认的使用ASCII字符集编码的,采用的是单字节编码,但是ASCII字符集只包含128个字符,和明显不够用。
为了使C语言国际化,C语言标准中不断加入了国际化的支持。比如:加入了宽字符的类型 wchar_t 和宽字符的输入和输出函数,加入了<locale.h>头文件,其中提供了允许程序员针对特定地区(通常是国家或者说某种特定语言的地理区域)调整程序行为的函数。
比如,简体中文常见的编码格式是GB2312,使用两个字节表示一个汉字。所以理论上最多可以表示256 × 256 = 65536 个符号。
<locale.h>提供的函数用于控制C标准库中对于不同的地区会产生不一样行为的部分。
比如表示钱:¥(人民币) $(美元)。还有符号和日期等。
通过修改地区,程序可以改变它的行为来适应世界的不同区域。但地区的改变可能会影响库的许多部分,其中一部分可能是我们不希望修改的。所以C语言支持针对不同的类项进行修改,下面的一个宏,指定一个类项。
setlocale函数:
这个函数也是API函数,它是设置本地化的函数,我们先来观察它的源码:
char *setlocale(int category, const char *locale)
第一个参数是控制哪些种类(比如符号、时间等),当然参数是以宏的方式传递的。
如果读不懂的话,你只需要记住一点LC_ALL影响所有的就行了,至于其他的,可以用到再慢慢研究。
第二个参数是决定了当前编译模式。
分为: “C”(正常模式) 和 “”(本地模式)。
在任意程序执行开始时,都会隐式执行调用 setlocale(LC_ALL, "C");
#include<locale.h>
//适应本地化
//宽字符占据两个字节
int main()
{char* loc;loc = setlocale(LC_ALL, NULL);printf("默认的本地信息:%s\n", loc);loc = setlocale(LC_ALL, "");printf("设置后的本地信息:%s\n", loc);return 0;
}
宽字符的打印:
如果在屏幕上打印宽字符,宽字符的字面量必须加上前缀 L ,否则C语言会把字面量当做窄字符类型处理。前缀L在单引号前面,表示宽字符,宽字符的打印使用 wprintf ,对应 wprintf() 的占位符为 %lc ;在双引号前面,表示宽字符,对应 wprintf() 的占位符为 %ls。
#include<locale.h>
int main()
{setlocale(LC_ALL, "");//适配本地模式wchar_t ch1 = L'中';//宽字符wchar_t ch2 = L'国';wchar_t ch3 = L'☆';wprintf(L"%lc\n", ch1);wprintf(L"%lc\n", ch2);wprintf(L"%lc\n", ch3);printf("ab\n");return 0;
}
可以发现,宽字符占据两个位置。
贪吃蛇的实现:
因为贪吃蛇是最终的目标,我们先将其分为若干个子问题。
为了方便,我们还是分为3个文件(一个头文件,两个源文件)。
test.c:贪吃蛇游戏的测试
snake.c:函数的实现
snake.h:贪吃蛇游戏中的类型声明,函数的声明。
初始化游戏:
初始化蛇结构体:
我们如何去维护一个“蛇”呢?我们可以用链表的方式。所以我们先去创建一个蛇身节点的结构体。
typedef struct SnakeNode
{int x;//横坐标int y;//纵坐标struct SnakeNode* next;
}SnakeNode, *pSnakeNode;//相当于
typedef struct SnakeNode* pSnakeNode;
但是此时我们只维护了一个蛇身节点,所以此时我们在定义一个Snake结构体。
而这个Snake结构体中,我们需要找到蛇头的节点,维护食物,分数,方向,蛇的状态,蛇的速度,蛇的当前分数,和食物的分数。
这里注意,其实食物也是蛇身上的节点,因为我们最终会把它吃掉;蛇的速度其实就是休眠时间。
//游戏状态
enum GAME_STATUS
{OK = 1,//正常运行ESC = 2,//正常退出KILL_BY_WALL,//撞墙KILL_BY_SELF,//撞到自身VICTORY//获胜
};//蛇的方向
enum DIRECTION
{UP = 1,DOWN,LEFT,RIGHT
};
typedef struct Snake
{pSnakeNode pSnake;//维护整条蛇的指针pSnakeNode pFood;//指向食物的指针int Score;//当前累计分数int FoodWeight;//一个食物的分数int SleepTime;//蛇休眠的时间,休眠时间越短,速度越快;反之越慢enum GAME_STATUS status;//游戏当前的状态enum DIRECTION dir;//蛇当前的方向
}Snake, *pSnake;
这里还需要注意蛇的状态有5种(其实也是游戏的运行状态):
- 正常运行(OK)
- 正常退出(ESC)
- 撞墙而死(KILL_BY_WALL)
- 咬到自己而死(KILL_BY_SELF)
- 游戏获胜(VICTORY)
蛇的行驶方向:
- 向上(UP)
- 向下(DOWN)
- 向左(LEFT)
- 向右(RIGHT)
为了方便使用,我们使用枚举类型来定义。
此时我们主函数中要先适应本地化,之后创建一条“蛇”,之后初始化游戏。
初始化游戏:
void test()
{//分为3大步//我们先创建贪吃蛇Snake snake = { 0 };GameStart(&snake);//游戏开始前的初始化//GameRun();//玩游戏的过程//GameEnd();//善后工作
}int main()
{//首先适应本地化setlocale(LC_ALL, "");test();//完成贪吃蛇游戏的测试return 0;
}
首先我们要设置控制台大小和标题,之后我们要隐藏光标信息并且打印欢迎信息。
//设置坐标
void SetPos(int x, int y)
{//获取设备句柄HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);//根据句柄设置光标位置COORD pos = { x, y };SetConsoleCursorPosition(handle, pos);
}//打印欢迎信息
void WelcomeToGame()
{//打印欢迎信息SetPos(35, 10);printf("欢迎来到贪吃蛇小游戏\n");SetPos(36, 20);system("pause");system("cls");//清屏//打印功能介绍信息SetPos(15, 10);printf("用↑,↓,←,→来控制蛇的移动,F3是加速,F4是减速\n");SetPos(15, 11);printf("加速能得到更高的分数...\n");SetPos(38, 20);system("pause");system("cls");//清屏
}//游戏开始前的初始化
void GameStart(pSnake ps)
{//设置控制台的信息:窗口大小,窗口名system("mode con cols=100 lines=30");system("title 贪吃蛇");//隐藏光标HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);//获取句柄CONSOLE_CURSOR_INFO cursorInfor = { 0 };GetConsoleCursorInfo(handle, &cursorInfor);//获取控制台光标信息cursorInfor.bVisible = false;//包含stdbool头文件SetConsoleCursorInfo(handle, &cursorInfor);//设置光标信息//打印欢迎信息WelcomeToGame();//getchar();//暂停程序,方便观察
}
打印地图:
我们适应本地化以后,就可以使用宽字符来打印地图了。我们先来总览一下地图:
初始状态,假设蛇的长度是5,蛇身的每个节点是●,在固定的一个坐标处,比如(26,4)处开始出现蛇,连续5个节点。
注意,蛇的每个节点x坐标必须是2的倍数,否则蛇身节点可能会出现在墙体中,坐标没有对齐出现故障。
食物也必须在墙体内生成(x坐标必须是2的倍数),坐标不能和蛇的身体重合,之后打印★。
因为我们需要蛇的身体节点是正方形,所以一个节点占据两个空间,为了方便使用,我们将墙体定义为宏。我们要先设置光标位置之后用宽字符打印字符:
#define WALL L'□'
//绘制地图
void CreateMap()
{//上SetPos(0, 0);int i = 0;for (i = 0; i <= 56; i += 2){wprintf(L"%lc", WALL);}//下SetPos(0, 26);for (i = 0; i <= 56; i += 2){wprintf(L"%lc", WALL);}//左for (i = 1; i <= 25; i++){SetPos(0, i);wprintf(L"%lc", WALL);}//右for (i = 1; i <= 25; i++){SetPos(56, i);wprintf(L"%lc", WALL);}getchar();
}
初始化蛇并打印:
之后根据我们之前的图形去初始化一个蛇。我们再看一眼就会爆炸的地图:
这个蛇的节点是头插实现,我们设置一个宏先来固定其实坐标,之后创建5个节点,每次设置好横纵坐标后顺带打印节点。之后设置蛇的其他初始状态。
和之前一样,为了方便打印,我们将蛇的身体节点设为一个宏。
#define BODY L'●'//设默认的起始坐标
#define POS_X 26
#define POS_Y 4//初始化蛇
void InitSnake(pSnake ps)
{pSnakeNode cur = (pSnake)malloc(sizeof(SnakeNode));//创建5个蛇身节点int i = 0;for (i = 0; i < 5; i++){cur = (pSnake)malloc(sizeof(SnakeNode));if (cur == NULL){perror("InitSnake()::malloc()");return;}cur->x = POS_X + (i * 2);cur->y = POS_Y;cur->next = NULL;//创建同时顺便打印SetPos(cur->x, cur->y);wprintf(L"%lc", BODY);//头插if (ps->pSnake == NULL){ps->pSnake = cur;}else{cur->next = ps->pSnake;ps->pSnake = cur;}}//贪吃蛇其他初始化信息ps->dir = RIGHT;//方向默认向右ps->FoodWeight = 10;//食物的分数ps->pFood = NULL;//默认食物信息ps->Score = 0;//默认0分ps->SleepTime = 200;//默认休眠200毫秒ps->status = OK;//开始游戏默认状态//getchar();
}
创建食物:
之后我们就要来初始化食物,这个食物必须是随机出现的,坐标就是随机的;坐标必须在墙体内部;坐标不能出现在蛇身上。
#define FOOD L'★'//创建食物
void CreateFood(pSnake ps)
{//随机生成坐标//观察墙体,必须在墙体内部生成//x : 2 ~ 54 0 ~ 52 + 2//y : 1 ~ 25 0 ~ 24 + 1int x = 0;int y = 0;again://必须保证 x 是偶数do{x = rand() % 53 + 2;y = rand() % 25 + 1;} while (x % 2 != 0);//和蛇的每个节点比较pSnakeNode cur = ps->pSnake;while(cur){if (x == cur->x && y == cur->y){goto again;}cur = cur->next;}//创建食物pSnakeNode food = (pSnakeNode)malloc(sizeof(SnakeNode));if (food == NULL){perror("CreateFood()::malloc()");return;}food->x = x;food->y = y;food->next = NULL;ps->pFood = food;//打印食物SetPos(x, y);//记住设置光标位置wprintf(L"%lc", FOOD);//getchar();}
注意这里的食物横坐标必须是2的倍数!
游戏运行:
打印帮助信息:
这个就是纯打印,没啥说的。
//打印帮助信息
void PrintfHelpInfo()
{SetPos(65, 15);printf("1.不能穿墙,不能咬到自己");SetPos(65, 16);printf("2.用↑,↓,←,→来控制蛇的移动");SetPos(65, 17);printf("3.F3是加速,F4是减速");SetPos(65, 18);printf("ESC:退出游戏 space:暂停游戏");SetPos(65, 20);printf("作者:假油淦");getchar();
}
注意这里的getchar是为了暂停程序方便我们观察,当我们正式运行游戏时一定要把所有的getchar函数去除。
判断按键:
因为是循环判断按键,所以我们使用do while语句来检测按键。
注意我们比如当前是向右走,就不能向左走;向上走就不能向下走,所以要判断一下。
这里我们检测暂停时封装一个函数,只来用于睡眠,如果再次按到空格,则跳出循环。
F3是加速,意味着睡眠时间变短,食物分数变高,但是要有上限。
//玩游戏的过程
void GameRun(pSnake ps)
{//打印帮助信息PrintfHelpInfo();do{//当前的分数情况SetPos(65, 10);printf("总分:%5d\n", ps->Score);SetPos(65, 11);printf("食物的分值:%02d\n", ps->FoodWeight);//检测按键//上、下、左、右、ESC、空格、F3、F4//虚拟键码if (KEY_PRESS(VK_UP) && ps->dir != DOWN){//按了上键当前贪吃蛇方向不朝下走ps->dir = UP;}else if (KEY_PRESS(VK_DOWN) && ps->dir != UP){ps->dir = DOWN;}else if (KEY_PRESS(VK_LEFT) && ps->dir != RIGHT){ps->dir = LEFT;}else if (KEY_PRESS(VK_RIGHT) && ps->dir != LEFT){ps->dir = RIGHT;}else if (KEY_PRESS(VK_ESCAPE)){ps->status = ESC;//正常退出break;}else if (KEY_PRESS(VK_SPACE)){//游戏要暂停pause();//暂停和恢复暂停}else if (KEY_PRESS(VK_F3)){//加速//睡眠时间变短if (ps->SleepTime > 80){ps->SleepTime -= 30;ps->FoodWeight += 2;}}else if (KEY_PRESS(VK_F4)){//减速if (ps->FoodWeight > 2){ps->SleepTime += 30;ps->FoodWeight -= 2;}}//睡眠一下Sleep(ps->SleepTime);//走一步} while (ps->status == OK);
}
· 此时,我们已经完成了二分之一了,就差蛇移动的函数未完成了(加把油!)。
蛇移动的函数:
我们先判断下一个节点是不是食物。
注意:因为我们是先检测按键的,所以我们判断当前蛇的行驶方向之后在特定的位置创建一个pNext节点让蛇吃掉,之后判断是不是食物。
如果是食物,则把开辟的pNext节点吃掉,并采用头插,此时多出了食物节点,所以我们销毁食物节点。并把总分加上去。
//判断蛇头的下一步要走的位置处是否是食物
int NextIsFood(pSnake ps, pSnakeNode pNext)
{if (ps->pFood->x == pNext->x && ps->pFood->y == pNext->y){return 1;//下一个坐标是食物}else{return 0;}
}
//下一步要走的位置就是食物
void EatFood(pSnake ps, pSnakeNode pNext)
{//头插pNext->next = ps->pSnake;ps->pSnake = pNext;//打印蛇身pSnakeNode cur = ps->pSnake;while (cur){SetPos(cur->x, cur->y);wprintf(L"%lc", BODY);cur = cur->next;}//把next节点吃掉了,此时分数要变ps->Score += ps->FoodWeight;//所以要释放食物节点free(ps->pFood);//还要创建食物CreateFood(ps);
}
如果不是食物,则把pNext节点挂在蛇身上,也就是把pNext头插,之后尾删一个元素。 此时我们可以头插完以后顺便打印蛇身,一定记住设置光标位置之后打印。
我们将最后一个节点(就是删除的尾节点打印成两个空格)。
//下一步要走的位置不是食物
void NotEatFood(pSnake ps, pSnakeNode pNext)
{//头插并尾删pNext->next = ps->pSnake;ps->pSnake = pNext;pSnakeNode prev = ps->pSnake;pSnakeNode cur = ps->pSnake;while (cur->next){//顺带打印SetPos(cur->x, cur->y);wprintf(L"%lc", BODY);prev = cur;cur = cur->next;}//先将尾节点打印为空SetPos(cur->x, cur->y);printf(" ");//注意这里是两个空格//此时释放prev->next = NULL;free(cur);
}
判断蛇的状态:
蛇每走一步,就需要判断游戏的状态,因为蛇有可能撞墙而死;也有可能撞到自己;也有可能是游戏胜利。所以我们还是需要分情况讨论。
我们先判断蛇是不是撞墙而死。
//蛇撞墙
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 (cur->x == ps->pSnake->x && cur->y == ps->pSnake->y){ps->status = KILL_BY_SELF;return;}cur = cur->next;}
}
最后就是获胜,我们通过蛇身节点来判断是否获胜,通过观察,我们发现一个是675个宽字符占位符。
//计算蛇身节点个数
int SnakeNodeCount(pSnake ps)
{pSnakeNode cur = ps->pSnake;int count = 0;while (cur){count++;cur = cur->next;}return count;
}//判断获胜
void IsVictory(pSnake ps)
{//我们一共有675个宽字符的格子//我们直接去判断蛇身节点有多少个去判断输赢int ret = SnakeNodeCount(ps);if (ret == 675){ps->status = VICTORY;}else{return;}
}
此时我们就需要去完成最后的善后工作了。
游戏善后工作:
使用switch语句来判断是那种情况结束了游戏,之后逐个释放蛇身节点。
//善后工作
void GameEnd(pSnake ps)
{SetPos(18, 12);switch (ps->status){case ESC:printf("主动退出游戏,正常退出游戏\n");break;case KILL_BY_WALL:printf("很遗憾,撞墙了,游戏结束\n");SetPos(22, 13);printf("最终得分为:%d\n", ps->Score);break;case KILL_BY_SELF:printf("很遗憾,咬到自己了,游戏结束\n");SetPos(22, 13);printf("最终得分为:%d\n", ps->Score);break;case VICTORY:printf("恭喜你,获得胜利!\n");break;}//释放贪吃蛇的链表资源pSnakeNode cur = ps->pSnake;pSnakeNode nextPos = ps->pSnake;while (cur){nextPos = cur->next;free(cur);cur = nextPos;}free(ps->pFood);ps->pSnake = NULL;
}
所有代码:
snake.h头文件:
#pragma once
#include<stdio.h>
#include<windows.h>
#include<stdlib.h>
#include<locale.h>
#include<stdbool.h>
#include<time.h>#define WALL L'□'
#define BODY L'●'
#define FOOD L'★'//设默认的起始坐标
#define POS_X 26
#define POS_Y 4//检测按键
#define KEY_PRESS(VK) ((GetAsyncKeyState(VK) & 0x1) ? 1 : 0)//游戏状态
enum GAME_STATUS
{OK = 1,//正常运行ESC = 2,//正常退出KILL_BY_WALL,//撞墙KILL_BY_SELF,//撞到自身VICTORY//获胜
};//蛇的方向
enum DIRECTION
{UP = 1,DOWN,LEFT,RIGHT
};typedef struct SnakeNode
{int x;//横坐标int y;//纵坐标struct SnakeNode* next;
}SnakeNode, * pSnakeNode;//相当于
//typedef struct SnakeNode* pSnakeNode;//贪吃蛇
typedef struct Snake
{pSnakeNode pSnake;//维护整条蛇的指针pSnakeNode pFood;//指向食物的指针int Score;//当前累计分数int FoodWeight;//一个食物的分数int SleepTime;//蛇休眠的时间,休眠时间越短,速度越快;反之越慢enum GAME_STATUS status;//游戏当前的状态enum DIRECTION dir;//蛇当前的方向
}Snake, *pSnake;//游戏开始前的初始化
void GameStart(pSnake ps);//打印欢迎信息
void WelcomeToGame();//绘制地图
void CreateMap();//初始化蛇
void InitSnake(pSnake ps);//创建食物
void CreateFood(pSnake ps);//玩游戏的过程
void GameRun(pSnake ps);//打印帮助信息
void PrintfHelpInfo();//蛇移动的函数
void SnakeMove(pSnake ps);//判断蛇头的下一步要走的位置处是否是食物
int NextIsFood(pSnake ps, pSnakeNode pNext);//下一步要走的位置就是食物
void EatFood(pSnake ps, pSnakeNode pNext);//下一步要走的位不是食物
void NotEatFood(pSnake ps, pSnakeNode pNext);//蛇撞墙
void KillByWall(pSnake ps);//蛇撞到自己
void KillBySelf(pSnake ps);//判断获胜
void IsVictory(pSnake ps);//善后工作
void GameEnd(pSnake ps);
test.c源文件:
#define _CRT_SECURE_NO_WARNINGS 1
#include"snake.h"void test()
{int ch = 0;do{//创建贪吃蛇Snake snake = { 0 };GameStart(&snake);//游戏开始前的初始化GameRun(&snake);//玩游戏过程GameEnd(&snake);//善后工作SetPos(20, 15);printf("再来一局吗?(Y/N):");ch = getchar();getchar();//清理\n} while (ch == 'Y' || ch == 'y');
}int main()
{//首先适应本地化setlocale(LC_ALL, "");//设置时间种子srand((unsigned int)time(NULL));test();//完成贪吃蛇游戏的测试SetPos(0, 26);return 0;
}
snake.c源文件:
#define _CRT_SECURE_NO_WARNINGS 1
#include"snake.h"//设置坐标
void SetPos(int x, int y)
{//获取设备句柄HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);//根据句柄设置光标位置COORD pos = { x, y };SetConsoleCursorPosition(handle, pos);
}//打印欢迎信息
void WelcomeToGame()
{//打印欢迎信息SetPos(35, 10);printf("欢迎来到贪吃蛇小游戏\n");SetPos(36, 20);system("pause");system("cls");//清屏//打印功能介绍信息SetPos(15, 10);printf("用↑,↓,←,→来控制蛇的移动,F3是加速,F4是减速\n");SetPos(15, 11);printf("加速能得到更高的分数...\n");SetPos(38, 20);system("pause");system("cls");//清屏
}//绘制地图
void CreateMap()
{//上SetPos(0, 0);int i = 0;for (i = 0; i <= 56; i += 2){wprintf(L"%lc", WALL);}//下SetPos(0, 26);for (i = 0; i <= 56; i += 2){wprintf(L"%lc", WALL);}//左for (i = 1; i <= 25; i++){SetPos(0, i);wprintf(L"%lc", WALL);}//右for (i = 1; i <= 25; i++){SetPos(56, i);wprintf(L"%lc", WALL);}//getchar();
}//初始化蛇
void InitSnake(pSnake ps)
{pSnakeNode cur = (pSnake)malloc(sizeof(SnakeNode));//创建5个蛇身节点int i = 0;for (i = 0; i < 5; i++){cur = (pSnake)malloc(sizeof(SnakeNode));if (cur == NULL){perror("InitSnake()::malloc()");return;}cur->x = POS_X + (i * 2);cur->y = POS_Y;cur->next = NULL;//创建同时顺便打印SetPos(cur->x, cur->y);wprintf(L"%lc", BODY);//头插if (ps->pSnake == NULL){ps->pSnake = cur;}else{cur->next = ps->pSnake;ps->pSnake = cur;}}//贪吃蛇其他初始化信息ps->dir = RIGHT;//方向默认向右ps->FoodWeight = 10;//食物的分数ps->pFood = NULL;//默认食物信息ps->Score = 0;//默认0分ps->SleepTime = 200;//默认休眠200毫秒ps->status = OK;//开始游戏默认状态//getchar();
}//创建食物
void CreateFood(pSnake ps)
{//随机生成坐标//观察墙体,必须在墙体内部生成//x : 2 ~ 54 0 ~ 52 + 2//y : 1 ~ 25 0 ~ 24 + 1int x = 0;int y = 0;again://必须保证 x 是偶数do{x = rand() % 53 + 2;y = rand() % 25 + 1;} while (x % 2 != 0);//和蛇的每个节点比较pSnakeNode cur = ps->pSnake;while(cur){if (x == cur->x && y == cur->y){goto again;}cur = cur->next;}//创建食物pSnakeNode food = (pSnakeNode)malloc(sizeof(SnakeNode));if (food == NULL){perror("CreateFood()::malloc()");return;}food->x = x;food->y = y;food->next = NULL;ps->pFood = food;//打印食物SetPos(x, y);//记住设置光标位置wprintf(L"%lc", FOOD);//getchar();}//游戏开始前的初始化
void GameStart(pSnake ps)
{//设置控制台的信息:窗口大小,窗口名system("mode con cols=100 lines=30");system("title 贪吃蛇");//隐藏光标HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);//获取句柄CONSOLE_CURSOR_INFO cursorInfor = { 0 };GetConsoleCursorInfo(handle, &cursorInfor);//获取控制台光标信息cursorInfor.bVisible = false;//包含stdbool头文件SetConsoleCursorInfo(handle, &cursorInfor);//设置光标信息//打印欢迎信息WelcomeToGame();//绘制地图CreateMap();//初始化蛇InitSnake(ps);//创建食物CreateFood(ps);//getchar();//暂停程序,方便观察
}//打印帮助信息
void PrintfHelpInfo()
{SetPos(65, 15);printf("1.不能穿墙,不能咬到自己");SetPos(65, 16);printf("2.用↑,↓,←,→来控制蛇的移动");SetPos(65, 17);printf("3.F3是加速,F4是减速");SetPos(65, 18);printf("ESC:退出游戏 space:暂停游戏");SetPos(65, 20);printf("作者:假油淦");//getchar();
}//暂停和恢复暂停
void pause()
{while (1){Sleep(100);if (KEY_PRESS(VK_SPACE)){break;}}
}//判断蛇头的下一步要走的位置处是否是食物
int NextIsFood(pSnake ps, pSnakeNode pNext)
{if (ps->pFood->x == pNext->x && ps->pFood->y == pNext->y){return 1;//下一个坐标是食物}else{return 0;}
}//下一步要走的位置就是食物
void EatFood(pSnake ps, pSnakeNode pNext)
{//头插pNext->next = ps->pSnake;ps->pSnake = pNext;//打印蛇身pSnakeNode cur = ps->pSnake;while (cur){SetPos(cur->x, cur->y);wprintf(L"%lc", BODY);cur = cur->next;}//把next节点吃掉了,此时分数要变ps->Score += ps->FoodWeight;//所以要释放食物节点free(ps->pFood);//还要创建食物CreateFood(ps);
}//下一步要走的位置不是食物
void NotEatFood(pSnake ps, pSnakeNode pNext)
{//头插并尾删pNext->next = ps->pSnake;ps->pSnake = pNext;pSnakeNode prev = ps->pSnake;pSnakeNode cur = ps->pSnake;while (cur->next){//顺带打印SetPos(cur->x, cur->y);wprintf(L"%lc", BODY);prev = cur;cur = cur->next;}//先将尾节点打印为空SetPos(cur->x, cur->y);printf(" ");//注意这里是两个空格//此时释放prev->next = NULL;free(cur);
}//蛇移动的函数
void SnakeMove(pSnake ps)
{//创建一个节点pSnakeNode pNext = (pSnakeNode)malloc(sizeof(SnakeNode));if (pNext == NULL){perror("SnakeMove()::malloc()");return;}pNext->next = NULL;switch (ps->dir){case UP:pNext->x = ps->pSnake->x;pNext->y = ps->pSnake->y - 1;break;case DOWN:pNext->x = ps->pSnake->x;pNext->y = ps->pSnake->y + 1;break;case LEFT:pNext->x = ps->pSnake->x - 2;pNext->y = ps->pSnake->y;break;case RIGHT:pNext->x = ps->pSnake->x + 2;pNext->y = ps->pSnake->y;break;}//判断下一个坐标是否是食物if (NextIsFood(ps, pNext)){//是食物吃掉EatFood(ps, pNext);}else{//不是食物NotEatFood(ps, pNext);}}//蛇撞墙
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 (cur->x == ps->pSnake->x && cur->y == ps->pSnake->y){ps->status = KILL_BY_SELF;return;}cur = cur->next;}
}//计算蛇身节点个数
int SnakeNodeCount(pSnake ps)
{pSnakeNode cur = ps->pSnake;int count = 0;while (cur){count++;cur = cur->next;}return count;
}//判断获胜
void IsVictory(pSnake ps)
{//我们一共有675个宽字符的格子//我们直接去判断蛇身节点有多少个去判断输赢int ret = SnakeNodeCount(ps);if (ret == 675){ps->status = VICTORY;}else{return;}
}//玩游戏的过程
void GameRun(pSnake ps)
{//打印帮助信息PrintfHelpInfo();do{//当前的分数情况SetPos(65, 10);printf("总分:%5d\n", ps->Score);SetPos(65, 11);printf("食物的分值:%02d\n", ps->FoodWeight);//检测按键//上、下、左、右、ESC、空格、F3、F4//虚拟键码if (KEY_PRESS(VK_UP) && ps->dir != DOWN){//按了上键当前贪吃蛇方向不朝下走ps->dir = UP;}else if (KEY_PRESS(VK_DOWN) && ps->dir != UP){ps->dir = DOWN;}else if (KEY_PRESS(VK_LEFT) && ps->dir != RIGHT){ps->dir = LEFT;}else if (KEY_PRESS(VK_RIGHT) && ps->dir != LEFT){ps->dir = RIGHT;}else if (KEY_PRESS(VK_ESCAPE)){ps->status = ESC;//正常退出break;}else if (KEY_PRESS(VK_SPACE)){//游戏要暂停pause();//暂停和恢复暂停}else if (KEY_PRESS(VK_F3)){//加速//睡眠时间变短if (ps->SleepTime > 80){ps->SleepTime -= 30;ps->FoodWeight += 2;}}else if (KEY_PRESS(VK_F4)){//减速if (ps->FoodWeight > 2){ps->SleepTime += 30;ps->FoodWeight -= 2;}}//睡眠一下Sleep(ps->SleepTime);//走一步SnakeMove(ps);//此时蛇已经走了一步了,我们需要判断游戏是否结束//蛇撞墙KillByWall(ps);//蛇撞到自己KillBySelf(ps);//判断获胜IsVictory(ps);} while (ps->status == OK);
}//善后工作
void GameEnd(pSnake ps)
{SetPos(18, 12);switch (ps->status){case ESC:printf("主动退出游戏,正常退出游戏\n");break;case KILL_BY_WALL:printf("很遗憾,撞墙了,游戏结束\n");SetPos(22, 13);printf("最终得分为:%d\n", ps->Score);break;case KILL_BY_SELF:printf("很遗憾,咬到自己了,游戏结束\n");SetPos(22, 13);printf("最终得分为:%d\n", ps->Score);break;case VICTORY:printf("恭喜你,获得胜利!\n");break;}//释放贪吃蛇的链表资源pSnakeNode cur = ps->pSnake;pSnakeNode nextPos = ps->pSnake;while (cur){nextPos = cur->next;free(cur);cur = nextPos;}free(ps->pFood);ps->pSnake = NULL;
}
总结:
如果你通过该文章完成了一次,其实你会发现,也没那么难,无非就是根据游戏的进程一步一步去实现每个最微小的步骤。其实所有的难题,都是聚沙成塔的,只要你一步一个脚印去实现,真的没有难题!
API帮助了我们实现了贪吃蛇,我们可能不知道其内部的实现,但是我们只需要去学会使用它即可。如果你可以独立完成一次,那么恭喜你,你已经彻底学会了C语言,接下来就可以往更高的地点出发。
相关文章:

贪吃蛇(C语言超详细版)
目录 前言: 总览: API: 控制台程序(Console): 设置坐标: COORD: GetStdHandle: STD_OUTPUT_HANDLE参数: SetConsoleCursorPosition: …...

python(django)之流程接口管理后台开发
1、在models.py中加入流程接口表和单一接口表 代码如下: from django.db import models from product.models import Product# Create your models here.class Apitest(models.Model):apitestname models.CharField(流程接口名称, max_length64)apitester model…...

Hive入门
什么是hive? - Hive是Facebook开发并贡献给Hadoop开源社区的。它是建立在 Hadoop体系架构上的一层 SQL抽象,使得数据相关人 员使用他们最为熟悉的SQL语言就可以进行海量数据的处理、 分析和统计工作 - Hive将数据存储于HDFS的数据文件映射为一张数据库…...

【神经网络】得分函数,损失函数~
目录 引言 一、神经网络概述 1 定义 2 基本原理 二、得分函数 1 定义 2 应用方法 3 与神经网络 三、损失函数 1 定义 2实现方法 3 与神经网络 四、得分函数与损失函数的协同作用 1 关系 2 实际应用 六、代码事例 、总结与展望 引言 在人工智能与机…...

FFmepg--AVFilter过滤器使用以及yuv视频裁剪
文章目录 AVFilter 流程:api核心代码变量yuv视频裁剪AVFilter 流程: ⾸先使⽤split滤波器将input流分成两路流(main和tmp),然后分别对两路流进⾏处理。对于tmp流,先经过crop滤波器进⾏裁剪处理,再经过flip滤波器进⾏垂直⽅向上的翻转操作,输出的结果命名为flip流。再将…...

.net使用excel的cells对象没有value方法——学习.net的Excel工作表问题
$exception {"Public member Value on type Range not found."} System.MissingMemberException 代码准备运行问题解决1. 下载别的版本的.net框架2. 安装3. 运行 代码 Imports Excel Microsoft.office.Interop.Excel Public Class Form1Private Sub Button1_Click(…...

string类的详细模拟实现
string类的模拟实现 文章目录 string类的模拟实现前言1. 类的框架设计2. 构造函数与析构函数3. 拷贝构造与重载赋值运算符函数4. 运算符重载5. 成员函数6. 迭代器的实现7. 非成员函数8. 单元测试总结 前言 在现代编程中,字符串处理是每个程序员都会遇到的基本任…...

【EasyX】 使用说明
EasyX 使用说明 基本说明 EasyX 是针对 C 的图形库,可以帮助 C/C 初学者快速上手图形和游戏编程。 比如,可以基于 EasyX 图形库很快的用几何图形画一个房子,或者一辆移动的小车,可以编写俄罗斯方块、贪吃蛇、黑白棋等小游戏&am…...

以太坊基金会JUSTIN DRAKE确认出席Hack.Summit() 2024区块链开发者大会
以太坊基金会JUSTIN DRAKE确认将出席由Hack VC主办,AltLayer、Berachain协办,并获得了Solana、The Graph、Blockchain Academy、ScalingX、0G、SNZ以及数码港的大力支持,本次大会由Techub News承办的Hack.Summit() 2024区块链开发者盛会。 Ju…...
数学建模常用代码
SVM分类器 1.命令函数部分: clear;%清屏 clc; X load(data.txt); n length(X);%总样本数量 y X(:,4);%类别标志 X X(:,1:3); TOL 0.0001;%精度要求 C 1;%参数,对损失函数的权重 b 0;%初始设置截距b Wold 0;%未更新a时的W(a) Wnew 0;%更新a后的…...

学点儿Java_Day7_在实体类当中IDEA无法进行单元测试(@Test没有启动按钮)
在敲代码体会继承和访问修饰符的时候忽然遇到了单元测试不管用的情况,表现为没有启动按钮 经过一番折腾,发现我的测试是在具有构造函数的实体类Person当中进行的,当我把所有的构造函数删除后,启动按钮又出来了,加…...
C语言:二叉树基础
一、树 1.1 树的概念 1.树是有n个节点组成的具有层次关系的集合,是一种非线性的结构。 2.树的第一个节点称为根,根没有前驱节点。 3.除了根节点,其余每个节点都只有一个前驱节点,有0个或多个后继节点。 4.节点的度&#x…...
LeetCode热题Hot100-两数之和
充分意识到Coding能力的重要性,重启算法刷题之旅。 没想到这么简单的题目都写的磕磕绊绊。 一刷只写自己的解,二刷再看有没有其他更巧妙的方法~ 题目: 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目…...

鸿蒙实战开发-如何通过拖动滑块调节应用内字体大小
介绍 本篇Codelab将介绍如何使用基础组件Slider,通过拖动滑块调节应用内字体大小。要求完成以下功能: 实现两个页面的UX:主页面和字体大小调节页面。拖动滑块改变字体大小系数,列表页和调节页面字体大小同步变化。往右拖动滑块字…...

matlab实现神经网络检测手写数字
一、要求 1.计算sigmoid函数的梯度; 2.随机初始化网络权重; 3.编写网络的代价函数。 二、算法介绍 神经网络结构: 不正则化的神经网络的代价函数: 正则化: S型函数求导: 反向传播算法&…...
增强现实与虚拟现实中的大模型应用:沉浸式体验的创新
增强现实与虚拟现实中的大模型应用:沉浸式体验的创新 1. 背景介绍 随着技术的进步,增强现实(AR)和虚拟现实(VR)正在成为越来越受欢迎的沉浸式体验方式。大模型,如神经网络和深度学习模型&…...

【数据分析案列】--- 北京某平台二手房可视化数据分析
一、引言 本案列基于北京某平台的二手房数据,通过数据可视化的方式对二手房市场进行分析。通过对获取的数据进行清冼(至关重要),对房屋价格、面积、有无电梯等因素的可视化展示,我们可以深入了解北京二手房市场的特点…...
【Golang星辰图】创造美丽图表,洞察数据:解析Go语言中的数据可视化和数据分析库
解锁数据的力量:深入研究Go语言中的数据可视化和数据分析库 前言 本文将介绍Go语言中几个优秀的数据可视化和数据分析库,以帮助开发者更好地处理和分析数据。这些库提供了丰富的功能和工具,可用于创建漂亮的可视化图表、进行数值计算和数据…...

阿里云原生:如何熟悉一个系统
原文地址:https://mp.weixin.qq.com/s/J8eK-qRMkmHEQZ_dVts9aQ?poc_tokenHMA-_mWjfcDmGVW6hXX1xEDDvuJPE3pL9-8uSlyY 导读:本文总结了熟悉系统主要分三部分:业务学习、技术学习、实战。每部分会梳理一些在学习过程中需要解答的问题,这些问题…...
Scala第十一章节(正则表达式和异常处理)
4. 正则表达式 4.1 概述 所谓的正则表达式指的是正确的,符合特定规则的式子, 它是一门独立的语言, 并且能被兼容到绝大多数的编程语言中。在scala中, 可以很方便地使用正则表达式来匹配数据。具体如下: Scala中提供了Regex类来定义正则表达式.要构造一个Regex对象࿰…...

大数据学习栈记——Neo4j的安装与使用
本文介绍图数据库Neofj的安装与使用,操作系统:Ubuntu24.04,Neofj版本:2025.04.0。 Apt安装 Neofj可以进行官网安装:Neo4j Deployment Center - Graph Database & Analytics 我这里安装是添加软件源的方法 最新版…...

Appium+python自动化(十六)- ADB命令
简介 Android 调试桥(adb)是多种用途的工具,该工具可以帮助你你管理设备或模拟器 的状态。 adb ( Android Debug Bridge)是一个通用命令行工具,其允许您与模拟器实例或连接的 Android 设备进行通信。它可为各种设备操作提供便利,如安装和调试…...

基于Flask实现的医疗保险欺诈识别监测模型
基于Flask实现的医疗保险欺诈识别监测模型 项目截图 项目简介 社会医疗保险是国家通过立法形式强制实施,由雇主和个人按一定比例缴纳保险费,建立社会医疗保险基金,支付雇员医疗费用的一种医疗保险制度, 它是促进社会文明和进步的…...

第一篇:Agent2Agent (A2A) 协议——协作式人工智能的黎明
AI 领域的快速发展正在催生一个新时代,智能代理(agents)不再是孤立的个体,而是能够像一个数字团队一样协作。然而,当前 AI 生态系统的碎片化阻碍了这一愿景的实现,导致了“AI 巴别塔问题”——不同代理之间…...
快刀集(1): 一刀斩断视频片头广告
一刀流:用一个简单脚本,秒杀视频片头广告,还你清爽观影体验。 1. 引子 作为一个爱生活、爱学习、爱收藏高清资源的老码农,平时写代码之余看看电影、补补片,是再正常不过的事。 电影嘛,要沉浸,…...

tauri项目,如何在rust端读取电脑环境变量
如果想在前端通过调用来获取环境变量的值,可以通过标准的依赖: std::env::var(name).ok() 想在前端通过调用来获取,可以写一个command函数: #[tauri::command] pub fn get_env_var(name: String) -> Result<String, Stri…...
华为OD最新机试真题-数组组成的最小数字-OD统一考试(B卷)
题目描述 给定一个整型数组,请从该数组中选择3个元素 组成最小数字并输出 (如果数组长度小于3,则选择数组中所有元素来组成最小数字)。 输入描述 行用半角逗号分割的字符串记录的整型数组,0<数组长度<= 100,0<整数的取值范围<= 10000。 输出描述 由3个元素组成…...

GraphQL 实战篇:Apollo Client 配置与缓存
GraphQL 实战篇:Apollo Client 配置与缓存 上一篇:GraphQL 入门篇:基础查询语法 依旧和上一篇的笔记一样,主实操,没啥过多的细节讲解,代码具体在: https://github.com/GoldenaArcher/graphql…...

Mysql故障排插与环境优化
前置知识点 最上层是一些客户端和连接服务,包含本 sock 通信和大多数jiyukehuduan/服务端工具实现的TCP/IP通信。主要完成一些简介处理、授权认证、及相关的安全方案等。在该层上引入了线程池的概念,为通过安全认证接入的客户端提供线程。同样在该层上可…...

如何把工业通信协议转换成http websocket
1.现状 工业通信协议多数工作在边缘设备上,比如:PLC、IOT盒子等。上层业务系统需要根据不同的工业协议做对应开发,当设备上用的是modbus从站时,采集设备数据需要开发modbus主站;当设备上用的是西门子PN协议时…...