贪吃蛇(C语言详解)
贪吃蛇游戏运行画面-CSDN直播
目录
贪吃蛇游戏运行画面-CSDN直播
1. 实验目标
2. Win32 API介绍
2.1 Win32 API
2.2 控制台程序(Console)
2.3 控制台屏幕上的坐标COORD
2.4 GetStdHandle
2.5 GetConsoleCursorlnfo
2.5.1 CONSOLE_CURSOR_INFO
2.6 SetConsoleCursorlnfo
2.7 SetConsoleCursorPosition
2.8 GetAsyncKeyState
3. 贪吃蛇准备阶段
3.1 地图
3.1.1 本地化
3.1.2 类型
3.1.3 setlocale函数
3.14 宽字符的打印
3.1.5 地图坐标
3.2 蛇身和食物
3.3 链表定义蛇身
3.4 结构体维护贪吃蛇游戏
3.5 枚举定义蛇的方向和游戏状态
3.6 确定游戏流程设计
4. 游戏开始(GameStart)
4.1 设置游戏窗口大小和名字以及隐藏光标
4.2 打印欢迎界面
4.3 绘制地图
4.4 初始化蛇身
4.5 创建食物
5. 游戏运行(GameRun)
5.1 打印帮助信息(PrintHelpInfo)
5.2 按键判断与打印得分
5.3 蛇身移动(SnakeMove)
5.3.1 判断移动过程中是否遇到食物(NextIsFood)
5.3.1.1 吃食物(EatFood)
5.3.1.2 不吃食物(NoFood)
5.3.2 撞到墙游戏结束(KillByWall)
5.3.3 咬到自身游戏结束(KillBySelf)
6. 游戏结束(GameEnd)
6.1 总代码
1. 实验目标
- 贪吃蛇地图绘制
- 蛇吃食物的功能(上、下、左、右方向键控制蛇的动作)
- 蛇撞墙死亡
- 蛇撞自身死亡
- 计算得分
- 蛇身加速、减速
- 暂停游戏
2. Win32 API介绍
2.1 Win32 API
Windows 这个多作业系统除了协调应用程序的执行、分配内存、管理资源之外,它同时也是一个很大的服务中心,调用这个服务中心的各种服务(每一种服务就是一个函数),可以帮应用程序达到开启视窗、描绘图形、使用周边设备等目的,由于这些函数服务的对象是应用程序(Application),所以便称之为 Application Programming Interface,简称 API 函数。WIN32 API 也就是 Microsoft Windows 32 位平台的应用程序编程接口。
其实说人话就是:如果你要基于Windows操作系统来编写一些程序,则Windows会提供各种接口,便于你完成一些功能
2.2 控制台程序(Console)
mode con cols=100 lines=30
也可以通过命令设置控制台窗口的名字:
title 贪吃蛇
#include <stdlib.h>int main()
{//设置控制台窗口的长度:设置控制台窗口的大小,30行,100列system("mode con cols=100 lines=30");//设置cmd窗口名称system("title 贪吃蛇");return 0;
}
运行效果图
执行完后我们会发现窗口大小调制好了,但窗口名却没有,这是因为程序已经结束了。
解决方法:
- getchar(); 执行到这行会停下来,等待接收一个字符
- system("pause") 执行到这行命令程序会暂停
2.3 控制台屏幕上的坐标COORD
注意:使用COORD需要包含头文件<windows.h>
COORD是WindowsAPI中定义的一个结构体,表示一个字符在控制台屏幕幕缓冲区上的坐标,坐标系(0,0)的原点位于缓冲区的顶部左侧单元格。
typedef struct _COORD {SHORT X;SHORT Y;
} COORD, *PCOORD;
给坐标赋值:
COORD pos = { 10, 15 };
2.4 GetStdHandle
使用需要包含头文件<windows.h>
GetStdHandle是一个Windows API函数。它用于从一个特定的标准设备(标准输入、标准输出或标准错误)中取得一个句柄(用来标识不同设备的数值),使用这个句柄可以操作设备。HANDLE GetStdHandle(DWORD nStdHandle);
说人话:要操作特定的控制台程序就要获得它的操作权限,能要识别你在操作谁


2.5 GetConsoleCursorlnfo
检索有关指定控制台屏幕缓冲区的光标大小和可见性的信息
BOOL WINAPI GetConsoleCursorInfo(
HANDLE hConsoleOutput,
PCONSOLE_CURSOR_INFO lpConsoleCursorInfo
);
PCONSOLE_CURSOR_INFO是指向CONSOLE_CURSOR_INFO 结构的指针,该结构接收有关主机游标(光标)的信息总结:使用GetConsoleCursorlnfo传入的第一个参数是句柄,为何需要句柄?原因是如要隐藏光标首先需要获得当前控制台对应的光标信息。第二个参数是结构体指针
HANDLE hOutput = NULL;//获取标准输出设备的句柄(⽤来标识不同设备的数值)hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//定义结构体变量CONSOLE_CURSOR_INFO CursorInfo;GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息//调用完这个函数后就能把hOutput对应的光标信息填充到这个结构体变量中去
2.5.1 CONSOLE_CURSOR_INFO
typedef struct _CONSOLE_CURSOR_INFO {DWORD dwSize;BOOL bVisible;
} CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO;
- dwSize,由光标填充的字符单元格的百分比。 此值介于1到100之间。 光标外观会变化,范围从完全填充单元格到单元底部的水平线条。
- bVisible,游标的可见性。 如果光标可见,则此成员为 TRUE。
CursorInfo.bVisible = false; //隐藏控制台光标
2.6 SetConsoleCursorlnfo
BOOL WINAPI SetConsoleCursorInfo(HANDLE hConsoleOutput,const CONSOLE_CURSOR_INFO *lpConsoleCursorInfo
);
实例:
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <stdbool.h>int main()
{HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//隐藏光标操作CONSOLE_CURSOR_INFO CursorInfo;GetConsoleCursorInfo(hOutput, &CursorInfo);//获得控制台光标信息CursorInfo.bVisible = false;//隐藏控制台光标,false需包含头文件stdbool.hSetConsoleCursorInfo(hOutput, &CursorInfo);//设置控制台光标状态return 0;
}
调试:


2.7 SetConsoleCursorPosition
BOOL WINAPI SetConsoleCursorPosition(HANDLE hConsoleOutput,COORD pos
);
第一个参数传入的是句柄,第二个参数传入的是坐标信息,也就是COORD类型的结构体变量。
实例:
#include <stdio.h>
#include <windows.h> int main()
{COORD pos = { 10,5 };HANDLE hOutput = NULL;//获得标准输出的句柄(用来标识不同设备的数值)hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//设置标准输出上光标的位置为posSetConsoleCursorPosition(hOutput, pos);printf("haha\n");return 0;
}
运行结果:
如果我们不去设置指定光标位置,那么haha就会在这里被输出
由于日后我们可能会多次使用设置指定光标位置,所以我们不妨封装一个设置光标位置的函数Setpos
//设置光标位置
void SetPos(short x, short y)
{COORD pos = { x,y };HANDLE hOutput = NULL;//获得标准输出的句柄(用来标识不同设备的数值)hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//设置标准输出上光标的位置为posSetConsoleCursorPosition(hOutput, pos);
}
2.8 GetAsyncKeyState
SHORT GetAsyncKeyState(int vKey
);
这个函数需要你传一个虚拟键值进去,然后该函数会检测,传进去的虚拟键值所代表的按键是否被按过,函数通过返回值来分辨按键的状态。(返回值类似是short)
如果返回的这个数据的二进制位的最高位为1,则代表按键状态是按下
如果返回的这个数据的二进制位的最高位为0,则代表按键状态是抬起
如果返回的这个数据的二进制位的最低位为1,则代表该键被按过
如果返回的这个数据的二进制位的最低位为0,则代表该键没被按过
如果我们要判断⼀个键是否被按过,可以检测GetAsyncKeyState返回值的最低值是否为1
#define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )
举个例子
虚拟键值表如下
https://learn.microsoft.com/zh-cn/windows/win32/inputdev/virtual-key-codes
3. 贪吃蛇准备阶段
3.1 地图




在游戏地图上,我们打印墙体使用宽字符:□,打印蛇使用宽字符●,打印食物使用宽字符★普通的字符是占一个字节的,这类宽字符是占用2个字节。这里再简单的讲一下C语言的国际化特性相关的知识,过去C语言并不适合非英语国家(地区)使用。C语言最初假定字符都是自己的。但是这些假定并不是在世界的任何地方都适用。
C语言字符默认是采用ASCII编码的,ASCII字符集采用的是单字节编码,且只使用了单字节中的低7位,最高位是没有使用的,可表示为0xxxxxxx;可以看到,ASCII字符集共包含128个字符,在英语国家中,128个字符是基本够用的,但是,在其他国家语言中,比如,在法语中,字母上方有注音符号,它就无法用 ASCII 码表示。于是,一些欧洲国家就决定,利用字节中闲置的最高位编入新的符号。比如,法语中的é的编码为130(⼆进制10000010)。这样一来,这些欧洲国家使用的编码体系,可以表示最多256个符号。但是,这里又出现了新的问题。不同的国家有不同的字母,因此,哪怕它们都使用256个符号的编码方式,代表的字母却不一样。比如,130在法语编码中代表了é,在希伯来语编码中却代表了字母Gimel, 在俄语编码中又会代表另一个符号。但是不管怎样,所有这些编码方式中,0--127表示的符号是一样的,不一样的只是128--255的这一段。至于亚洲国家的文字,使用的符号就更多了,汉字就多达10万左右。一个字节只能表示256种符号,肯定是不够的,就必须使用多个字节表达一个符号。比如,简体中文常见的编码方式是 GB2312,使用两个字节表示一个汉字,所以理论上最多可以表 256 x 256 = 65536 个符号。
后来为了使C语言适应国际化,C语言的标准中不断加入了国际化的支持。比如:加入和宽字符的类型wchar_t 和宽字符的输入和输出函数,加入和<locale.h>头文件,其中提供了允许程序员针对特定地区(通常是国家或者说某种特定语言的地理区域)调整程序行为的函数。
3.1.1 <locale.h>本地化
- 数字量的格式
- 货币量的格式
- 字符集
- 日期和时间的表示形式
3.1.2 类型
通过修改地区,程序可以改变它的行为来适应世界的不同区域。但地区的改变可能会影响库的许多部分,其中一部分可能是我们不希望修改的。所以C语言支持针对不同的类项进行修改,下面的一个宏,指定一个类项:
- LC_COLLATE:影响字符串比较函数 strco1l()和 strxfrm()。
- LC_CTYPE:影响字符处理函数的行为。
- LC_MONETARY:影响货币格式。
- LC_NUMERIC:影响printf()的数字格式。
- LC_TIME:影响时间格式strftime()和wcsftime()。
- LC_ALL-针对所有类项修改,将以上所有类别设置为给定的语言环境。
3.1.3 setlocale函数
使用setlocale函数需要包含头文件<locale.h>
char* setlocale (int category, const char* locale);
setlocale(LC_ALL, "C");
setlocale(LC_ALL, "");//切换本地环境
扩展:
- setlocale的返回值是一个字符串指针,表示已经设置好的格式。如果调用失败,则返回空指针NULL。
- setlocale()可以用来查询当前地区,这时第二个参数设为NULL就可以了。
#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;
}
运行结果:
3.14 宽字符的打印
那如果想在屏幕上打印宽字符,怎么打印呢?宽字符的字面量必须加上前缀L,否则C语言会把字面量当作窄字符类型处理。前缀L在单引号前面,表示宽字符,宽字符的打印使用wprintf,对应wprintf()的占位符为%lc;在双引号前面,表示宽字符串,对应wprintf()的占位符为%ls
#include <stdio.h>
#include <locale.h>int main()
{setlocale(LC_ALL, "");wchar_t ch1 = L'中';wchar_t ch2 = L'国';wchar_t ch3 = L'□';wchar_t ch4 = L'☆';printf("%c%c\n", 'a', 'b');wprintf(L"%lc\n", ch1);wprintf(L"%lc\n", ch2);wprintf(L"%lc\n", ch3);wprintf(L"%lc\n", ch4);return 0;
}
运行结果:

3.1.5 地图坐标

- 列:最好是2的倍数,因为宽字符占2位
- 我们可以看一个正常字符占的大小,我们不难发现,一个字符占的大小的宽度是比较窄的,但是高度是比较长的
3.2 蛇身和食物

3.3 链表定义蛇身
typedef struct SnakeNode
{int x;int y;struct SnakeNode* next;
}SnakeNode, * pSnakeNode;
3.4 结构体维护贪吃蛇游戏
typedef struct Snake //定义贪吃蛇
{pSnakeNode pSnake;//维护整条蛇的指针pSnakeNode pFood;//维护⻝物的指针enum DIRECTION Dir;//蛇头的⽅向默认是向右enum GAME_STATUS Status;//游戏状态int Socre;//当前获得分数int foodWeight;//默认每个⻝物10分int SleepTime;//每⾛⼀步休眠时间(蛇休眠的时间,休眠的时间越短,蛇的速度越快,休眠的时间越长,蛇的速度越慢)
}Snake, * pSnake;
3.5 枚举定义蛇的方向和游戏状态
蛇的方向,可以一一列举,使用枚举
//方向
enum DIRECTION
{UP = 1,DOWN,LEFT,RIGHT
};
游戏状态,可以一一列举,使用枚举
//游戏状态
enum GAME_STATUS
{OK,//正常运⾏KILL_BY_WALL,//撞墙KILL_BY_SELF,//咬到⾃⼰ESC//强制退出游戏
};
3.6 确定游戏流程设计
4. 游戏开始(GameStart)
首先我们先创建3个文件
snake.h ----> 贪吃蛇游戏中类型的声明,函数的声明
snake.c ----> 函数的实现
test.c ----> 贪吃蛇游戏的测试
游戏主逻辑(test.c)
#include "snake.h"void test()
{srand((unsigned int)time(NULL));int ch = 0;do{ Snake ps = { 0 };//游戏开始前的初始化GameStart(&ps);//游戏玩的过程GameRun(&ps);//游戏结束GameEnd(&ps);SetPos(16,13);printf("再来一局吗?(Y/N):");scanf(" %c", &ch);} while (ch == 'Y' || ch == 'y');
}int main()
{//适配本地中文环境setlocale(LC_ALL, "");test();SetPos(0, 27);return 0;
}
4.1 设置游戏窗口大小和名字以及隐藏光标
//游戏开始
void GameStart(pSnake ps)
{//设置控制台的信息,窗口大小,窗口名system("mode con cols=100 lines=30");system("title 贪吃蛇");//隐藏光标HANDLE hOutput = NULL;hOutput = GetStdHandle(STD_OUTPUT_HANDLE);CONSOLE_CURSOR_INFO CursorInfo;GetConsoleCursorInfo(hOutput, &CursorInfo);CursorInfo.bVisible = false;SetConsoleCursorInfo(hOutput, &CursorInfo);//打印欢迎信息WelcomeToGame();
}
首先我们要让窗口大小100行,30列,有人是不是会疑惑为什么不是58行,27列,我们不是之前给的图片就是这样的嘛?
那是因为这只是地图的大小,地图大小外还有提示的信息,就比如下图一样,所以我们给的大小就会比地图大小还要大一些
4.2 打印欢迎界面
效果图:
要实现这个效果图,首先肯定要用到COORD和SetConsoleCursorPosition来设置光标位置
//定位光标位置
void SetPos(int x, int y)
{COORD pos = { x,y };HANDLE hOutput = NULL;hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//在屏幕上设置指定光标位置SetConsoleCursorPosition(hOutput, pos);
}
接下来就是打印欢迎信息
//打印欢迎信息
void WelcomeToGame()
{//欢迎信息SetPos(35,10);printf("欢迎来带贪吃蛇小游戏\n");SetPos(38, 20);//暂停system("pause");//清屏system("cls");//功能介绍SetPos(15, 10);printf("用 ↑ . ↓ . ← . → 来控制蛇的移动,F3是加速,F4是减速");SetPos(15, 11);printf("加速将得到更高的分数。");SetPos(38, 20);system("pause");system("cls");
}
4.3 绘制地图
#define WALL L'□'
打印墙体代码:
//绘制地图
void CreateMap()
{SetPos(0, 0);int i = 0;//一个宽字符占2位,所以i加的是2//i到56就行了,因为输出WALL就能把56个57空间给占了//上框框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);}//左框框//这里i只要加1的原因是宽字符和正常字符的高度是一样的//只有宽字符和正常字符的宽度有差2倍for (i = 1; i < 26; i++){SetPos(0, i);wprintf(L"%lc", WALL);}//右框框for (i = 1; i < 26; i++){SetPos(56, i);wprintf(L"%lc", WALL);}
}
效果图:
4.4 初始化蛇身
我们可以按照下面这张图来进行初始化蛇身与食物

#define BODY L'●'
1.打印蛇身
//初始化蛇身
//ps是维护整条蛇的地址
void InitSnake(pSnake ps)
{pSnakeNode cur = NULL;//按照图片上的指示创建5个节点int i = 0;for (i = 0; i < 5; i++){cur = (pSnakeNode)malloc(sizeof(SnakeNode));if (cur == NULL){perror("InitSnake():malloc()");return;}cur->x = POS_X + 2 * i;cur->y = POS_Y;cur->next = NULL;//创建好后就进行头插cur->next = ps->pSnake;ps->pSnake = cur;}
我把行和列封装成一个宏,这样的好处是方便后续修改
#define POS_X 24
#define POS_Y 5
头插法的解析
2.创建好后打印蛇身
//创建好后就打印蛇的身体while (cur){SetPos(cur->x, cur->y);wprintf(L"%lc", BODY);cur = cur->next;}
3.初始化贪吃蛇的其他数据
//初始化贪吃蛇的数据ps->SleepTime = 200;ps->Socre = 0;ps->Status = OK;ps->Dir = RIGHT;ps->foodWeight = 10;ps->pFood = NULL;
4.初始化蛇身总代码
//初始化蛇身
void InitSnake(pSnake ps)
{pSnakeNode cur = NULL;//按照图片上的指示创建5个节点int i = 0;for (i = 0; i < 5; i++){cur = (pSnakeNode)malloc(sizeof(SnakeNode));if (cur == NULL){perror("InitSnake():malloc()");return;}cur->x = POS_X + 2 * i;cur->y = POS_Y;cur->next = NULL;//创建好后就进行头插cur->next = ps->pSnake;ps->pSnake = cur;}//创建好后就打印蛇的身体while (cur){SetPos(cur->x, cur->y);wprintf(L"%lc", BODY);cur = cur->next;}//初始化贪吃蛇的数据ps->SleepTime = 200;ps->Socre = 0;ps->Status = OK;ps->Dir = RIGHT;ps->foodWeight = 10;ps->pFood = NULL;
}
4.5 创建食物
- 食物是随机出现的,所以坐标就是随机的(但是生成的坐标x必须是2的倍数)
- 生成的坐标必须在墙内
- 生成的坐标不能在蛇的身上
由于打印食物宽字符L'★'后续可能会多次用到,所以我们把它封装成一个宏
#define FOOD L'★'
1.创建食物坐标
int x = 0;int y = 0;
agin://使食物坐标必须要在墙内,并且x的坐标必须要是2的倍数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 agin;}cur = cur->next;}
x = rand() % 53 + 2;
rand() % 53生成范围是0~52,后面加2,所以x的范围则是2~54
y = rand() % 25 + 1;rand() % 25 + 1;生成范围是0~24,后面加1,所以y的范围则是1到25
首先我们已经保证生成的坐标是在墙内
然后我们还要判断x是否是2的倍数,所以我们用了个do-while循环来进行判断
后续我们又用了while循环来判断生成的食物节点是否与蛇身的某个节点重合,如果一样,我们就用goto语句来进行跳转,使坐标重新生成
使用rand(),我们需要包含头文件<stdlib.h> 为了防止生成的随机数与后续再次执行的程序一样,我用了srand((unsigned int)time(NULL));来修改种子,如果有不懂srand和time的同学可以看我之前写的博客(猜数字游戏),然后使用srand函数需要包含头文件<stdlib.h>;使用time需要包含头文件<time.h>
走到这一步我们生成的x与y坐标就已经符合要求了,接下来就是生成一个节点(pFood),让x与y赋值给pFood中的x与y,最后让维护蛇指针内的pFood指针来指向这个节点
pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode));if (pFood == NULL){perror("CreateFood()::malloc()");return;}pFood->x = x;pFood->y = y;SetPos(x, y);wprintf(L"%lc", FOOD);ps->pFood = pFood;
创建食物总代码
//创建食物
void CreateFood(pSnake ps)
{int x = 0;int y = 0;
agin://使食物坐标必须要在墙内,并且x的坐标必须要是2的倍数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 agin;}cur = cur->next;}pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode));if (pFood == NULL){perror("CreateFood()::malloc()");return;}pFood->x = x;pFood->y = y;SetPos(x, y);wprintf(L"%lc", FOOD);ps->pFood = pFood;
}
5. 游戏运行(GameRun)
首先我们再次看一遍游戏流程设计
- 游戏运行期间,右侧打印帮助信息,提示玩家
- 根据游戏状态检查游戏是否继续,如果是状态是OK,游戏继续,否则游戏结束。
- 如果游戏继续,就是检测按键情况,确定蛇下一步的方向,或者是否加速减速,是否暂停或者退出游戏。
- 确定了蛇的方向和速度,蛇就可以移动了。
5.1 打印帮助信息(PrintHelpInfo)
首先我们可以看到程序运行起来后右侧的帮助信息
因为我们设置游戏窗口的时候设置的是100列,30行,所以设置帮助信息我是一开始是把光标定位到62列17行,大家可以按自己的想法来,不一定要和我一样
//打印帮助信息
void PrintHelpInfo()
{SetPos(62, 17);printf("1.不能穿墙,不能咬到自己");SetPos(62, 18);printf("2.用↑.↓.←.→分别控制蛇的移动。");SetPos(62, 19);printf("3.F3是加速,F4是减速");SetPos(62, 20);printf("ESC:退出游戏 SPACE:暂停游戏");
}
5.2 按键判断与打印得分
首先,我们需要知道每个键的虚拟键值,如下:
https://learn.microsoft.com/zh-cn/windows/win32/inputdev/virtual-key-codes
然后我整理出了本程序需要用到的虚拟键值
然后我们就要判断我们是否有按过这些键,就可以用到前面GetAsyncKeyState中定义的宏
#define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )
游戏玩的过程总代码
//游戏玩的过程
void GameRun(pSnake ps)
{//打印帮助信息PrintHelpInfo();do{//打印当前得分SetPos(62, 10);printf("得分:%d", ps->Socre);SetPos(62, 11);printf("每个食物得分:%-2d", ps->foodWeight);if (KEY_PRESS(VK_UP) && ps->Dir != DOWN){ps->Dir = UP;}else if (KEY_PRESS(VK_DOWN) && ps->Dir != UP){ps->Dir = DOWN;}else if (KEY_PRESS(VK_LEFT) && ps->Dir != RIGHT){ps->Dir = LEFT;}else if (KEY_PRESS(VK_RIGHT) && ps->Dir != LEFT){ps->Dir = RIGHT;}else if (KEY_PRESS(VK_SPACE)){//进行休眠pause();}else if (KEY_PRESS(VK_F3)){//加速if (ps->SleepTime >= 50){ps->SleepTime -= 30;ps->foodWeight += 2;}}else if (KEY_PRESS(VK_F4)){//减速if (ps->foodWeight > 2){ps->SleepTime += 30;ps->foodWeight -= 2;}}else if (KEY_PRESS(VK_ESCAPE)){//按ESC键退出ps->Status = ESC;break;}//蛇每走一步都需要休眠,休眠时间越短,蛇移动速度就越快Sleep(ps->SleepTime);SnakeMove(ps);//判断是否撞墙了KillByWall(ps);//判断是否撞到自己了KillBySelf(ps);} while (ps->Status == OK);
}
当我们按下了上下左右的时候,我们还要与当前蛇行走的方向进行判断
- 当我们按上的时候,蛇当前运动方向是不能向下的
- 当我们按下的时候,蛇当前运动方向是不能向上的
- 当我们按左的时候,蛇当前运动方向是不能向右的
- 当我们按右的时候,蛇当前运动方向是不能向左的
- 当我们按ESC键的时候,我们就把当前游戏状态进行修改,然后直接退出循环
- 当我们按下F3就会加速,休眠时间变短,但时间不可能减成负数,索性我们就规定当休眠时间大于等于50的时候,我们才能加速,当然休眠时间变短一次吃食物分数就会变多
- 当我们按下F4就会减速,休眠时变长,一次吃的食物分数变少,但是我们不能把分数减到0吧,不能吃一个食物一分都不得,所以我们规定只有食物分数大于2时我们按了F4才有效果
注意事项(打印每个食物得分):
打印每个食物的得分有个小细节就是要用%2d(或者%-2d,%3d,%-4d都可以),就是不要用%d来打印,因为一开始默认每个食物的得分是10分,后续我们是可以用F3和F4来控制蛇移动的速度,速度决定每吃一个食物所得分数。如果我们速度慢下来了,食物所得分数就会变少,由原来的10分变成8分或者更低,但因为一开始我们默认打印了10分,然后减了一次速度按理来说是要打印8分,但用%d打印的结果是80,原因是10后面的0没有被覆盖掉
5.3 蛇身移动(SnakeMove)
当然我们在蛇身移动前我们可以让蛇先休眠一下
蛇身移动的主要思想:
- 先创建下一个节点,根据移动方向和蛇头的坐标,蛇移动到下一个位置的坐标。
- 确定了下一个位置后,看下一个位置是否是食物(NextIsFood),是食物就做吃食物处理 (EatFood),如果不是食物则做前进一步的处理(NoFood)。
- 蛇身移动后,判断此次移动是否会造成撞墙(KillByWall)或者撞上自己蛇身(KillBySelf),从而影响游戏的状态。
移动过程:
向上和向下
- 向上走:首先创建的新节点是在蛇头上面,由图可以看出新节点y坐标就是蛇头坐标y减1,而新节点x坐标则是和蛇头x坐标一样
- 向下走:首先创建的新节点是在蛇头下面,由图可以看出新节点y坐标就是蛇头坐标y加1,而新节点x坐标则是和蛇头x坐标一样

- 向左走:首先创建的新节点是在蛇头左边,由图可以看出新节点x坐标是蛇头节点x坐标减2(减2的原因是宽字符打印宽度占2位),新节点y坐标则是和蛇头节点y坐标一样
- 向右走:首先创建的新节点是在蛇头右边,由图可以看出新节点x坐标是蛇头节点x坐标加2(加2的原因是宽字符打印宽度占2位),新节点y坐标则是和蛇头节点y坐标一样
//蛇移动过程
void SnakeMove(pSnake ps)
{pSnakeNode pNextNode = (pSnakeNode)malloc(sizeof(SnakeNode));if (pNextNode == NULL){perror("SnakeMove()::malloc()");return;}switch (ps->Dir){case UP:pNextNode->x = ps->pSnake->x;pNextNode->y = ps->pSnake->y - 1;break;case DOWN:pNextNode->x = ps->pSnake->x;pNextNode->y = ps->pSnake->y + 1;break;case LEFT:pNextNode->x = ps->pSnake->x - 2;pNextNode->y = ps->pSnake->y;break;case RIGHT:pNextNode->x = ps->pSnake->x + 2;pNextNode->y = ps->pSnake->y;break;}//移动的过程中,判断是否遇到食物if (NextIsFood(pNextNode, ps)){//遇到食物EatFood(pNextNode, ps);}else{//没遇到食物NotEatFood(pNextNode, ps);}
}
5.3.1 判断移动过程中是否遇到食物(NextIsFood)
当然在蛇移动的过程中,可能移动的下一个节点就是食物,所以我们还要判断是否遇到了食物
//判断移动过程中是否碰到食物
int NextIsFood(pSnakeNode pNextNode, pSnake ps)
{return (pNextNode->x == ps->pFood->x && pNextNode->y == ps->pFood->y);
}
相等返回1,不相等返回0
5.3.1.1 吃食物(EatFood)
如果返回值是1,则我们遇到了食物,那么我们就把新节点头插到贪吃蛇身上,然后打印蛇身
同时,我们之前定义食物的时候还动态malloc了一块空间,我们既然用新节点的空间头插到贪吃蛇上面,那我们就应该将原本食物的节点给销毁(free)
吃掉了食之后,我们原先在地图上的食物就被覆盖了,那么此时我们就应该再创建一个食物
同时,我们吃掉了一个食物之后,我们的分数也应该变高,就让原先的分数加上一个食物的分数
//下一步要走的位置处就是食物,就吃掉食物
void EatFood(pSnakeNode pNextNode, pSnake ps)
{pNextNode->next = ps->pSnake;ps->pSnake = pNextNode;pSnakeNode cur = ps->pSnake;ps->Socre += ps->foodWeight;//打印蛇身while (cur){SetPos(cur->x, cur->y);wprintf(L"%lc", BODY);cur = cur->next;}free(ps->pFood);CreateFood(ps);
}
首先我不知道看到这里的小伙伴会不会有这个疑问:
我们知道食物和新节点是不同的地址(两次malloc开辟出来的),但是他们两个空间重合了(食物和下一个节点在地图上存放空间重合),所以会不会不理解释放食物为什么不会把下一个节点空间也释放了?
原因:
创建出来的下一个节点和食物这个节点只是x和y坐标一样,节点是完全不同的两个节点,所以释放食物对创建出来的下一个节点没有影响
5.3.1.2 不吃食物(NoFood)
当下一个节点不是食物的时候,我们就先将下一个节点头插到贪吃蛇上面
同时我们需要知道,本来贪吃蛇是已经被打印出来了的(初始化蛇身的时候),所以我们只需要将新的头节点打印出来,同时将尾节点打印成空格并释放,我们就能在视觉上达到贪吃蛇走一步的效果
至于找到尾结点对我们来说可是很轻松的,用while循环就能办到
注意:SetPos到尾结点位置之后要打印两个空格,注意,是两个空格,因为尾结点宽字符占2位
//pSnakeNode pNextNode 是下⼀个节点的地址
//pSnake ps 维护蛇的指针
//下一步要走的位置处不是食物,就不吃食物
void NotEatFood(pSnakeNode pNextNode, pSnake ps)
{pNextNode->next = ps->pSnake;ps->pSnake = pNextNode;pSnakeNode cur = ps->pSnake;while (cur->next->next){SetPos(cur->x, cur->y);//每走一步顺便把蛇身节点打印出来wprintf(L"%lc", BODY);cur = cur->next;}//没遇到食物,首先要将新节点头插还要找到最后一个节点给释放掉//释放前要找到尾结点的位置,然后输出两个空//这样才能把尾节点图表给覆盖掉SetPos(cur->next->x, cur->next->y);printf(" ");free(cur->next);cur->next = NULL;
}
我不知道有没有小伙伴没看我前面介绍有这一个疑问,反正当时我是有这个疑问的:
原因是:这里就是不进行打印的,使用的是上一次打印留下的图形,走到cur->next->next是为了释放下一个节点,所以这个函数没有进行清屏,而是用printf(" ");来把最后一个图形覆盖掉
5.3.2 撞到墙游戏结束(KillByWall)
判断蛇是否撞到墙,我们只需要判断贪吃蛇的头节点是否在墙壁所圈定的范围之内,如果不在这个范围内,那就证明蛇已经撞到墙了
接着,我们需要将游戏的状态更改为 KILL_BY_WALL
当蛇向后走了一步时,判断到状态不为 OK,就会跳出循环,游戏结束
//判断是否撞墙了
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;
}
5.3.3 咬到自身游戏结束(KillBySelf)
我们要判断蛇是否会撞到自己,我们只需要将头节点和蛇身的每一个坐标一一比对,当发现有相同的时候,就说明蛇已经咬到自己了
接着,我们需要将游戏的状态更改为 KILL_BY_SELF
当蛇向后走了一步时,判断到状态不为 OK,就会跳出循环,游戏结束
//检测是否撞自己
void KillBySelf(pSnake ps)
{pSnakeNode cur = ps->pSnake->next;while (cur){if (ps->pSnake->x == cur->x && ps->pSnake->y == cur->y){ps->Status = KILL_BY_SELF;return;}cur = cur->next;}
}
6. 游戏结束(GameEnd)
游戏状态不再是OK(游戏继续)的时候,要告知游戏结束的原因,并且释放蛇身节点与食物节点。
至于删除蛇身,我们可以定义两个指针,一个指向要删除的节点,一个指向下一个节点
这是因为如果我们将该节点的空间释放掉之后,我们就找不到下一个节点了,所以我们才需要两个节点,而循环的条件就是当指针cur指向 NULL 的时候,循环停止
在释放完之后,不忘释放食物的空间
/游戏结束
void GameEnd(pSnake ps)
{SetPos(18, 10);switch (ps->Status){case KILL_BY_WALL:printf("很遗憾撞墙了,游戏结束");break;case KILL_BY_SELF:printf("很遗憾撞到自身了,游戏结束");break;case ESC:printf("按了ESC键,正常退出");break;}//释放蛇身节点和食物节点pSnakeNode cur = ps->pSnake;pSnakeNode pNextNode = NULL;while (cur){pNextNode = cur;cur = cur->next;free(pNextNode);}free(ps->pFood);ps = NULL;
}
6.1 总代码
test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "snake.h"void test()
{srand((unsigned int)time(NULL));int ch = 0;do{ Snake ps = { 0 };//游戏开始前的初始化GameStart(&ps);//游戏玩的过程GameRun(&ps);//游戏结束GameEnd(&ps);SetPos(16,13);printf("再来一局吗?(Y/N):");scanf(" %c", &ch);} while (ch == 'Y' || ch == 'y');
}int main()
{//适配本地中文环境setlocale(LC_ALL, "");test();SetPos(0, 27);return 0;
}
snake.h
#pragma once
#include <stdio.h>
#include <windows.h>
#include <stdbool.h>
#include <locale.h>
#include <stdlib.h>//随机数
#include <time.h>#define WALL L'□'
#define BODY L'●'
#define FOOD L'★'
#define POS_X 24
#define POS_Y 5
#define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )//定义蛇身节点
typedef struct SnakeNode
{int x;int y;struct SnakeNode* next;
}SnakeNode, *pSnakeNode;//定义贪吃蛇
typedef struct Snake
{pSnakeNode pSnake;//维护整条蛇的指针pSnakeNode pFood;//维护食物的指针enum DIRECTION Dir;//蛇头的方向默认是向右enum GAME_STATUS Status;//游戏状态int Socre;//当前获得分数int foodWeight;//默认每个食物10分int SleepTime;//每走一步休眠时间
}Snake, * pSnake;//定义蛇的方向
enum DIRECTION
{UP = 1,DOWN,LEFT,RIGHT
};//游戏状态
enum GAME_STATUS
{OK,//正常运行KILL_BY_WALL,//撞墙KILL_BY_SELF,//咬到自己ESC, //按了ESC键退出,正常退出
};//游戏开始前的准备
void GameStart(pSnake ps);
//打印欢迎信息
void WelcomeToGame();
//定位控制台光标位置
void SetPos(int x, int y);
//绘制地图
void CreateMap();
//初始化蛇身
void InitSnake(pSnake ps);
//创建食物
void CreateFood(pSnake ps);//游戏运行的整个逻辑
void GameRun(pSnake ps);
//打印帮助信息
void PrintHelpInfo();
//蛇移动过程
void SnakeMove(pSnake ps);
//判断蛇头的下一步要走的位置处是否是食物
int NextIsFood(pSnakeNode pNextNode, pSnake ps);
//下一步要走的位置处就是食物,就吃掉食物
void EatFood(pSnakeNode pNextNode, pSnake ps);
//下一步要走的位置处不是食物,不吃食物
void NotEatFood(pSnakeNode pNextNode, pSnake ps);
//检测是否撞墙
void KillByWall(pSnake ps);
//检测是否撞自己
void KillBySelf(pSnake ps);//游戏结束,处理善后工作
void GameEnd(pSnake ps);
snake.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "snake.h"//定位光标位置
void SetPos(int x, int y)
{COORD pos = { x,y };HANDLE hOutput = NULL;hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//在屏幕上设置指定光标位置SetConsoleCursorPosition(hOutput, pos);
}//打印欢迎信息
void WelcomeToGame()
{//欢迎信息SetPos(35,10);printf("欢迎来带贪吃蛇小游戏\n");SetPos(38, 20);//暂停system("pause");//清屏system("cls");//功能介绍SetPos(15, 10);printf("用 ↑ . ↓ . ← . → 来控制蛇的移动,F3是加速,F4是减速");SetPos(15, 11);printf("加速将得到更高的分数。");SetPos(38, 20);system("pause");system("cls");
}//绘制地图
void CreateMap()
{SetPos(0, 0);int i = 0;//一个宽字符占2位,所以i加的是2//i到56就行了,因为输出WALL就能把56个57空间给占了//上框框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);}//左框框//这里i只要加1的原因是宽字符和正常字符的高度是一样的//只有宽字符和正常字符的宽度有差2倍for (i = 1; i < 26; i++){SetPos(0, i);wprintf(L"%lc", WALL);}//右框框for (i = 1; i < 26; i++){SetPos(56, i);wprintf(L"%lc", WALL);}}//初始化蛇身
void InitSnake(pSnake ps)
{pSnakeNode cur = NULL;//按照图片上的指示创建5个节点int i = 0;for (i = 0; i < 5; i++){cur = (pSnakeNode)malloc(sizeof(SnakeNode));if (cur == NULL){perror("InitSnake():malloc()");return;}cur->x = POS_X + 2 * i;cur->y = POS_Y;cur->next = NULL;//创建好后就进行头插cur->next = ps->pSnake;ps->pSnake = cur;}//创建好后就打印蛇的身体while (cur){SetPos(cur->x, cur->y);wprintf(L"%lc", BODY);cur = cur->next;}//初始化贪吃蛇的数据ps->SleepTime = 200;ps->Socre = 0;ps->Status = OK;ps->Dir = RIGHT;ps->foodWeight = 10;ps->pFood = NULL;
}//创建食物
void CreateFood(pSnake ps)
{int x = 0;int y = 0;
agin://使食物坐标必须要在墙内,并且x的坐标必须要是2的倍数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 agin;}cur = cur->next;}pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode));if (pFood == NULL){perror("CreateFood()::malloc()");return;}pFood->x = x;pFood->y = y;SetPos(x, y);wprintf(L"%lc", FOOD);ps->pFood = pFood;
}//游戏开始
void GameStart(pSnake ps)
{//设置控制台的信息,窗口大小,窗口名system("mode con cols=100 lines=30");system("title 贪吃蛇");//隐藏光标HANDLE hOutput = NULL;hOutput = GetStdHandle(STD_OUTPUT_HANDLE);CONSOLE_CURSOR_INFO CursorInfo;GetConsoleCursorInfo(hOutput, &CursorInfo);CursorInfo.bVisible = false;SetConsoleCursorInfo(hOutput, &CursorInfo);//打印欢迎信息WelcomeToGame();//绘制地图CreateMap();//初始化蛇身InitSnake(ps);//创建食物CreateFood(ps);
}//打印帮助信息
void PrintHelpInfo()
{SetPos(62, 17);printf("1.不能穿墙,不能咬到自己");SetPos(62, 18);printf("2.用↑.↓.←.→分别控制蛇的移动。");SetPos(62, 19);printf("3.F3是加速,F4是减速");SetPos(62, 20);printf("ESC:退出游戏 SPACE:暂停游戏");
}//暂停过程
void pause()
{while (1){Sleep(100);if (KEY_PRESS(VK_SPACE)){break;}}
}//判断移动过程中是否碰到食物
int NextIsFood(pSnakeNode pNextNode, pSnake ps)
{return (pNextNode->x == ps->pFood->x && pNextNode->y == ps->pFood->y);
}//下一步要走的位置处就是食物,就吃掉食物
void EatFood(pSnakeNode pNextNode, pSnake ps)
{pNextNode->next = ps->pSnake;ps->pSnake = pNextNode;pSnakeNode cur = ps->pSnake;ps->Socre += ps->foodWeight;//打印蛇身while (cur){SetPos(cur->x, cur->y);wprintf(L"%lc", BODY);cur = cur->next;}free(ps->pFood);CreateFood(ps);
}//下一步要走的位置处不是食物,不吃食物
void NotEatFood(pSnakeNode pNextNode, pSnake ps)
{pNextNode->next = ps->pSnake;ps->pSnake = pNextNode;pSnakeNode cur = ps->pSnake;while (cur->next->next){SetPos(cur->x, cur->y);//每走一步顺便把蛇身节点打印出来wprintf(L"%lc", BODY);cur = cur->next;}//没遇到食物,首先要将新节点头插还要找到最后一个节点给释放掉//释放前要找到尾结点的位置,然后输出两个空//这样才能把尾节点图表给覆盖掉SetPos(cur->next->x, cur->next->y);printf(" ");free(cur->next);cur->next = NULL;
}//判断是否撞墙了
void KillByWall(pSnake ps)
{if (ps->pSnake->x == 0 ||ps->pSnake->x == 56 ||ps->pSnake->y == 0 ||ps->pSnake->y == 26)ps->Status = KILL_BY_WALL;
}//检测是否撞自己
void KillBySelf(pSnake ps)
{pSnakeNode cur = ps->pSnake->next;while (cur){if (ps->pSnake->x == cur->x && ps->pSnake->y == cur->y){ps->Status = KILL_BY_SELF;return;}cur = cur->next;}
}//蛇移动过程
void SnakeMove(pSnake ps)
{pSnakeNode pNextNode = (pSnakeNode)malloc(sizeof(SnakeNode));if (pNextNode == NULL){perror("SnakeMove()::malloc()");return;}switch (ps->Dir){case UP:pNextNode->x = ps->pSnake->x;pNextNode->y = ps->pSnake->y - 1;break;case DOWN:pNextNode->x = ps->pSnake->x;pNextNode->y = ps->pSnake->y + 1;break;case LEFT:pNextNode->x = ps->pSnake->x - 2;pNextNode->y = ps->pSnake->y;break;case RIGHT:pNextNode->x = ps->pSnake->x + 2;pNextNode->y = ps->pSnake->y;break;}//移动的过程中,判断是否遇到食物if (NextIsFood(pNextNode, ps)){//遇到食物EatFood(pNextNode, ps);}else{//没遇到食物NotEatFood(pNextNode, ps);}
}//游戏玩的过程
void GameRun(pSnake ps)
{//打印帮助信息PrintHelpInfo();do{//打印当前得分SetPos(62, 10);printf("得分:%d", ps->Socre);SetPos(62, 11);printf("每个食物得分:%-2d", ps->foodWeight);if (KEY_PRESS(VK_UP) && ps->Dir != DOWN){ps->Dir = UP;}else if (KEY_PRESS(VK_DOWN) && ps->Dir != UP){ps->Dir = DOWN;}else if (KEY_PRESS(VK_LEFT) && ps->Dir != RIGHT){ps->Dir = LEFT;}else if (KEY_PRESS(VK_RIGHT) && ps->Dir != LEFT){ps->Dir = RIGHT;}else if (KEY_PRESS(VK_SPACE)){//进行休眠pause();}else if (KEY_PRESS(VK_F3)){//加速if (ps->SleepTime >= 50){ps->SleepTime -= 30;ps->foodWeight += 2;}}else if (KEY_PRESS(VK_F4)){//减速if (ps->foodWeight > 2){ps->SleepTime += 30;ps->foodWeight -= 2;}}else if (KEY_PRESS(VK_ESCAPE)){//按ESC键退出ps->Status = ESC;break;}//蛇每走一步都需要休眠,休眠时间越短,蛇移动速度就越快Sleep(ps->SleepTime);SnakeMove(ps);//判断是否撞墙了KillByWall(ps);//判断是否撞到自己了KillBySelf(ps);} while (ps->Status == OK);
}//游戏结束
void GameEnd(pSnake ps)
{SetPos(18, 10);switch (ps->Status){case KILL_BY_WALL:printf("很遗憾撞墙了,游戏结束");break;case KILL_BY_SELF:printf("很遗憾撞到自身了,游戏结束");break;case ESC:printf("按了ESC键,正常退出");break;}//释放蛇身节点和食物节点pSnakeNode cur = ps->pSnake;pSnakeNode pNextNode = NULL;while (cur){pNextNode = cur;cur = cur->next;free(pNextNode);}free(ps->pFood);ps = NULL;
}
相关文章:

贪吃蛇(C语言详解)
贪吃蛇游戏运行画面-CSDN直播 目录 贪吃蛇游戏运行画面-CSDN直播 1. 实验目标 2. Win32 API介绍 2.1 Win32 API 2.2 控制台程序(Console) 2.3 控制台屏幕上的坐标COORD 2.4 GetStdHandle 2.5 GetConsoleCursorlnfo 2.5.1 CONSOLE_CURSOR_INFO …...

国际以太网专线(IEPL)与国际专线(IPLC)服务
中国联通国际公司产品: 国际以太网专线 (IEPL)/国际专线(IPLC) 在全球化的今天,企业越来越依赖于高速、稳定且安全的国际网络连接来支持其跨国业务活动。中国联通国际公司作为中国领先的电信运营商之一,在这一领域提供了多种优质…...

vue 子父组件互相改值
在Vue.js中,子组件想要修改父组件的状态(如数据属性的值)时,通常遵循以下步骤: 父组件向子组件传递数据:通过props(属性)将需要被子组件操作的值传入子组件。例如,在父组…...

java之拼图小游戏(开源)
public class LoginJFrame extends JFrame {//表示登录界面,以后所有跟登录相关的都写在这里public LoginJFrame() {//设置界面的长和宽this.setSize(603,680);//设置界面的标题this.setTitle("拼图登陆界面");//设置界面置顶this.setAlwaysOnTop(true);/…...

Linux Shell批量测试IP连通性
Linux 通过Shell脚本来实现读取txt文件中的IP地址,并使用telnet对其后的所有端口进行测试,判断是否可以连接。每个IP地址的端口测试时间限制为5秒。 IP文件 : ips.txt 192.168.1.1 22,80,443 192.168.1.2 21,25,110 192.168.1.3 8080每一行包含一个IP地…...

已解决:anaocnda如何备份环境与安装环境
1.使用pip进行备份 激活对应的虚拟环境,切换到桌面或者想备份的位置。 备份即可: pip freeze > requirements.txt如何安装备份? pip install -r requirements.txt2.使用conda进行备份 激活对应的虚拟环境,切换到桌面或者想…...

自动化与高效设计:推理技术在FPGA中的应用
想象一下,你正在设计一个复杂的电路系统,就像在搭建一座精巧的积木城堡。你手头有各种形状和功能的积木块,这些积木块可以组合成任何你需要的结构。在这个过程中,你有两种主要的方法:一种是手动挑选和搭建每一块积木&a…...

对react模块和模块化理解
在React开发中,模块化和React模块是两个紧密相关但又有区别的概念。理解它们对于构建高效、可维护的React应用至关重要。 模块化 模块化是一种将大型代码库拆分成更小、更易于管理的部分(即模块)的软件设计技术。每个模块都封装了特定的功能…...

CAN总线-----帧格式
目录 前言 一、CAN总线帧格式分类 1.数据帧(重点) 2.遥控帧 3.错误帧 4.过载帧 5.间隔帧 二、位填充 三、波形实例 前言 本期我们就开始学习CAN总线的帧格式,对应帧格式的话,在前面我们学习I2C协议和SPI协议等协议的时候…...

UE网络同步(一) —— 一个项目入门UE网络同步之概念解释
最近在学习UE网络同步,发现了一个非常好的教程,并且附带了项目文件,这里从这个小项目入手,理解UE的网络同步 教程链接:https://www.youtube.com/watch?vJOJP0CvpB8w 项目链接:https://github.com/awforsyt…...

MATLAB中rsf2csf函数用法
目录 语法 说明 示例 将实数 Schur 形式变换为复数 Schur 形式 rsf2csf函数的功能是将实数 Schur 形式转换为复数 Schur 形式。 语法 [Unew,Tnew] rsf2csf(U,T) 说明 [Unew,Tnew] rsf2csf(U,T) 将实矩阵 X 的 [U,T] schur(X) 的输出从实数 Schur 形式变换为复数 Sc…...

Java基础 文字小游戏
souf System.out.printf("你好啊%s","张三") 输出你好啊张三 System.out.printn()放在中间可以换行 System.out.printf("%s你好啊%s","张三","李四") 输出 张三你好啊李四 只有输出没有换行效果。 制作一个文字小游戏…...

「数组」归并排序 / if语句优化|小区间插入优化(C++)
概述 在上一篇文章中,我们介绍了快速排序以及随机快速排序: 「数组」快速排序 / 随机值优化|小区间插入优化(C) 今天,我们来介绍归并排序。 相比于快速排序是冒泡排序融合了分治思想后形成的究极promax进化版&…...

颠覆传统 北大新型MoM架构挑战Transformer模型,显著提升计算效率
挑战传统的Transformer模型设计 在深度学习和自然语言处理领域,Transformer模型已经成为一种标准的架构,广泛应用于各种任务中。传统的Transformer模型依赖于一个固定的、按深度排序的层次结构,每一层的输出都作为下一层的输入。这种设计虽然…...

接口优化笔记
索引 添加索引 where条件的关键自动或者order by后面的排序字段可以添加索引加速查询 索引只能通过删除新增进行修改,无法直接修改。 # 查看表的索引 show index from table_name; show create table table_name; # 添加索引 alter table table_name add index …...

pandas 科学计数法显示
我注意到pandas中有一个问题, 默认情况下,就是其中的数据的小数位不能超过6位,比如0.0000007就会被显示为0,这个结果如下 全部以科学技术显示 import pandas as pd import numpy as np# 设置显示格式为科学计数法 pd.options.d…...

PHP正则替换字符串中的图片地址
在PHP中,可以使用preg_replace()函数来实现正则表达式的替换功能。以下是一个简单的例子,演示如何替换字符串中的图片地址。 double $str 图片地址1:<img src"http://example.com/image1.jpg"> 图片地址2:<i…...

基于多商户AI智能名片商城小程序的粉丝忠诚度提升策略:深度融合足额法则与多维度激励体系
摘要:在数字化浪潮的推动下,多商户AI智能名片商城小程序以其独特的商业模式和技术优势,正逐步成为连接商家与消费者,特别是粉丝群体的重要平台。本文深入探讨了如何通过深度融合足额法则与多维度激励体系,有效提升多商…...

BigDecimal高精度运算
1. BigDecimal是什么类型,为什么可以转为double BigDecimal 是 Java 中用于表示任意精度的十进制数的类。它主要用于金融和商业计算,能够提供比 double 类型更高精度的运算,特别是在处理货币等需要精确计算的场景中。 1.1 BigDecimal 的基…...

C/C++实现蓝屏2.0
🚀欢迎互三👉:程序猿方梓燚 💎💎 🚀关注博主,后期持续更新系列文章 🚀如果有错误感谢请大家批评指出,及时修改 🚀感谢大家点赞👍收藏⭐评论✍ 前…...

Unity音频管理器插件AudioToolKit
Unity音频管理器插件AudioToolKit 介绍AudioToolKit介绍具体用法总结 介绍 最近在自己写音频管理器的时候在网上发现了一款比较好用并且功能很全的一个音频管理插件,叫做AudioToolKit的插件。 如果需要的可以直接从我资源中找AudioToolKit。 AudioToolKit介绍 A…...

搜维尔科技:驾驶模拟器背后的技术: Varjo的虚拟/混合现实 (VR/XR)提供独特的优势,最终加快汽车开发创新的步伐
专业驾驶模拟器广泛应用于车辆开发,帮助汽车行业在开发过程的早期做出更好的设计决策。总体目标是为测试驾驶员提供最真实的驾驶体验,包括动态动作和声音,并测试控制算法或辅助系统等功能。环境越真实,驾驶员的体验就越接近最终车…...

OSL 冠名赞助Web3峰会 “FORESIGHT2024”圆满收官
OSL 望为香港数字资产市场发展建设添砖加瓦 (香港,2024 年 8 月 13 日)- 8 月 11 日至 12 日, 由 香港唯一专注数字资产的上市公司 OSL 集团(863.HK)冠名赞助,Foresight News、 Foresight Ventu…...

LeetCode 3148.矩阵中的最大得分:每个元素与其左或上元素之差的最大值(原地修改O(1)空间)
【LetMeFly】3148.矩阵中的最大得分:每个元素与其左或上元素之差的最大值(原地修改O(1)空间) 力扣题目链接:https://leetcode.cn/problems/maximum-difference-score-in-a-grid/ 给你一个由 正整数 组成、大小为 m x n 的矩阵 g…...

主流的开源大型语言模型
本期我们来聊聊目前主流的开源大型语言模型。这些模型就像是AI界的超级英雄,各具特色,为我们的研究和开发提供了强大的力量。🚀 GPT-Neo:这是EleutherAI的杰作,它模仿了OpenAI的GPT-3。GPT-Neo虽然规模小一些…...

【自动驾驶】话题通信
目录 构建发布者构建订阅者编写lanch文件自动启动节点测试运行ROS的目录结构 切换到工作空间的src目录下: 构建发布者 catkin_create_pkg publisher std_msgs rospy roscpp编写发布者程序: // 1.包含头文件 #include "ros/ros.h" #include &…...

【Linux】中的软件安装:深入探索RPM、SRPM与YUM
🐇明明跟你说过:个人主页 🏅个人专栏:《Linux :从菜鸟到飞鸟的逆袭》🏅 🔖行路有良友,便是天堂🔖 目录 一、引言 1、Linux的起源与发展 2、RPM、SRPM与YUM的简要介…...

uniapp自定义请求头信息header
添加请求头:uniapp自定义请求头信息header,如下:添加tenant-id参数 代码...

SpringBoot整合Liquibase
1、是什么? Liquibase官网 Liquibase是一个开源的数据库管理工具,可以帮助开发人员管理和跟踪数据库变更。它可以与各种关系型数据库和NoSQL数据库一起使用,并提供多种数据库任务自动化功能,例如数据库迁移、版本控制和监控。Li…...

虚幻5|给武器添加碰撞检测与伤害
本章内容衔接上两章,需要完成上两章才能用本章内容 虚幻5|角色武器装备的数据库学习(不只是用来装备武器,甚至是角色切换也很可能用到)-CSDN博客虚幻5|普通攻击,使用接口更方便-CSDN博客 如有疑问,可访问…...