使用Win32API实现贪吃蛇小游戏

news2024/11/16 1:53:59

目录

C语言贪吃蛇项目

基本功能

需要的基础内容

Win32API

介绍

控制台程序部分指令

设置控制台窗口的长宽

设置控制台的名字

控制台在屏幕上的坐标位置结构体COORD

检索指定标准设备的句柄(标准输入、标准输出或标准错误)

光标信息结构体类型CONSOLE_CURSOR_INFO

检索有关指定控制台屏幕上的光标大小和可见性的信息

指定的控制台屏幕缓冲区设置光标的大小和可见性

设置指定控制台屏幕缓冲区中的光标位置

获取按键状态

宽字符打印与本地化

宽字符

头文件locale.h与本地化

类项

设置本地化

宽字符的打印

贪吃蛇项目设计

游戏流程设计

贪吃蛇界面的打印

打印游戏欢迎界面

打印游戏规则界面

打印游戏地图界面

打印游戏地图右侧的帮助信息

贪吃蛇结构体设计

贪吃蛇进行过程设计

初始化蛇身以及游戏信息

初始化食物

游戏进行部分

贪吃蛇运行结束

贪吃蛇项目实现


C语言贪吃蛇项目

基本功能

使用C语言在Windows环境的控制台中模拟实现贪吃蛇

  • 贪吃蛇地图绘制
  • 蛇吃食物的功能(使用键盘按键控制蛇)
  • 蛇撞墙死亡
  • 蛇撞自身死亡
  • 计算得分
  • 蛇身加速、减速
  • 暂停游戏

需要的基础内容

函数、枚举、结构体、动态内存管理、预处理指令、链表、Win32API

Win32API

介绍

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

控制台程序部分指令

设置控制台窗口的长宽
  1. 在运行中输入cmd后打开命令提示符
  2. 输入mode con cols=100 lines=30后按Enter执行(cols表示列,lines表示行)

如图所示:

参考mode命令文档:mode | Microsoft Learn

设置控制台的名字
  1. 在运行中输入cmd后打开命令提示符
  2. 输入title (需要的名字)后按Enter执行

参考title命令文档:title | Microsoft Learn

可以在C语言中使用system函数执行上述命令

📌

使用system函数需要包含头文件stdlib.h

#define _CRT_SECURE_NO_WARNINGS 1

#include <stdlib.h>//使用system函数需要包含头文件stdlib.h
#include <stdio.h>

int main()
{
    //设置控制台窗口的长宽:mode con cols=100 lines=30
    system("mode con cols=100 lines=30");//设置列为100列,行为30行
    //设置控制台窗口的名称:title 贪吃蛇
    system("title 贪吃蛇");
    getchar();//防止程序提前自动结束
    //等价于使用
    system("pause");

    return 0;
}
控制台在屏幕上的坐标位置结构体COORD

COORDWindowsAPI中定义的⼀个结构体,表示一个字符在控制台屏幕上显示时的坐标,坐标系(0, 0)的原点位于屏幕的顶部左侧单元格

//COORD结构体原型
typedef struct _COORD {
    SHORT X;
    SHORT Y;
} COORD, *PCOORD;//对_COORD结构体进行重命名为COORD,以及对_COORD结构体指针重命名为*PCOORD

参考COORD文档:COORD 结构 - Windows Console | Microsoft Learn

使用COORD设置字符显示坐标:

📌

使用COORD结构体需要包含头文件windows.h

COORD pos = {10, 15};//将x轴坐标设置为10,y轴坐标设置为15
检索指定标准设备的句柄(标准输入、标准输出或标准错误)

使用GetStdHandle函数进行获取。

GetStdHandle是⼀个WindowsAPI函数。它用于从⼀个特定的标准设备(标准输入、标准输出或标

准错误)中取得⼀个句柄(用来标识不同设备的数值),使用这个句柄可以操作设备

参考GetStdHandle文档:GetStdHandle 函数 - Windows Console | Microsoft Learn

//函数原型
HANDLE WINAPI GetStdHandle(_In_ DWORD nStdHandle);

//参数说明
nStdHandle [in]标准设备。 此参数的取值可为下列值之一
STD_INPUT_HANDLE((DWORD)-10)    标准输入设备。 最初,这是输入缓冲区 CONIN$ 的控制台
STD_OUTPUT_HANDLE((DWORD)-11)    标准输出设备。 最初,这是活动控制台屏幕缓冲区 CONOUT$
STD_ERROR_HANDLE((DWORD)-12)    标准错误设备。 最初,这是活动控制台屏幕缓冲区 CONOUT$

返回类型
如果函数执行成功,则返回值为指定设备的句柄
如果函数执行失败,则返回值为 INVALID_HANDLE_VALUE
如果应用程序没有关联的标准句柄(例如在交互式桌面上运行的服务),并且尚未重定向这些句柄,则返回值为 NULL
📌

使用GetStdHandle函数需要包含头文件windows.h

代码实例:

HANDLE hOutput = NULL;//hOutput相当于一个指针类型的变量
//获取标准输出的句柄(用来标识不同的标准输出设备的数值) ,即需要操作的控制台程序
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
💡

对于句柄的简单理解:需要操作(按键响应等)控制台程序时,首先得获取到控制台这个应用,而句柄就是帮助获取控制台应用的一个“把手”

光标信息结构体类型CONSOLE_CURSOR_INFO
//光标信息结构体原型
typedef struct _CONSOLE_CURSOR_INFO {
  DWORD dwSize;//光标占比大小
  BOOL  bVisible;//光标是否可见
} CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO;//将结构体类型重命名为CONSOLE_CURSOR_INFO,并将结构体指针类型重命名为*PCONSOLE_CURSOR_INFO

dwSize
由游标填充的字符单元的百分比。 该值介于 1 到 100 之间。 游标外观各不相同,范围从完全填充单元到显示为单元底部的横线。
 bVisible
光标的可见性。 如果光标可见,则此成员为 TRUE,默认为可见
📌

dwsize的表现形式:默认为25

参考CONSOLE_CURSOR_INFO结构体类型文档:CONSOLE_CURSOR_INFO 结构 - Windows Console | Microsoft Learn

📌

使用CONSOLE_CURSOR_INFO需要包含头文件windows.h

代码实例

CONSOLE_CURSOR_INFO CursorInfo.bVisible = false;//隐藏光标 
检索有关指定控制台屏幕上的光标大小和可见性的信息

使用函数GetConsoleCursorInfo进行获取

//函数原型
BOOL WINAPI GetConsoleCursorInfo(_In_ HANDLE hConsoleOutput, 
                                 _Out_ PCONSOLE_CURSOR_INFO lpConsoleCursorInfo
);

//参数说明
第一个参数hConsoleOutput [in],获取指定控制台屏幕缓冲区的句柄。可以通过GetStdHandle函数获取
第二个参数lpConsoleCursorInfo [out],指向 CONSOLE_CURSOR_INFO 结构的指针,该结构接收有关控制台光标的信息

返回类型
如果该函数成功,则返回值为非零值
如果函数失败,则返回值为零

参考GetConsoleCursorInfo函数文档:GetConsoleCursorInfo 函数 - Windows Console | Microsoft Learn

📌

使用GetConsoleCursorInfo函数需要包含头文件windows.h

代码实例

HANDLE hOutput = NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值) 
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO CursorInfo;//使用光标结构体类型定义变量
GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息
指定的控制台屏幕缓冲区设置光标的大小和可见性

使用SetConsoleCursorInfo 进行设置

//函数原型
BOOL WINAPI SetConsoleCursorInfo(
  _In_       HANDLE              hConsoleOutput,
  _In_ const CONSOLE_CURSOR_INFO *lpConsoleCursorInfo
);

//参数说明
第一个参数hConsoleOutput [in],控制台屏幕缓冲区的句柄
第二个参数pConsoleCursorInfo [in],指向 CONSOLE_CURSOR_INFO 结构的指针,该结构为控制台屏幕缓冲区的光标提供新的参数

//返回类型
如果该函数成功,则返回值为非零值
如果函数失败,则返回值为零

参考SetConsoleCursorInfo函数文档:SetConsoleCursorInfo 函数 - Windows Console | Microsoft Learn

📌

使用SetConsoleCursorInfo函数需要包含windows.h

代码实例

HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
//隐藏光标操作 
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息 
CursorInfo.bVisible = false; //隐藏控制台光标 
SetConsoleCursorInfo(hOutput, &CursorInfo);//设置控制台光标状态
设置指定控制台屏幕缓冲区中的光标位置

使用函数SetConsoleCursorPosition 进行设置

//函数原型
BOOL WINAPI SetConsoleCursorPosition(
  _In_ HANDLE hConsoleOutput,
  _In_ COORD  dwCursorPosition
);

//参数说明
第一个参数为hConsoleOutput [in],获取控制台屏幕缓冲区的句柄
第二个参数为dwCursorPosition [in],指定新光标位置(以字符为单位)的 COORD 结构。 坐标是屏幕缓冲区字符单元的列和行。 坐标必须位于控制台屏幕缓冲区的边界以内

//返回类型
如果该函数成功,则返回值为非零值
如果函数失败,则返回值为零

参考SetConsoleCursorPosition文档:SetConsoleCursorPosition 函数 - Windows Console | Microsoft Learn

代码实例

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

使用GetAsyncKeyState 函数获取

将键盘上每个键的虚拟键值传递给函数,函数通过返回值来分辨按键的状态。

GetAsyncKeyState 的返回值是short类型,在上⼀次调用 GetAsyncKeyState 函数后,如果返回的16位的short数据中,最高位是1,说明按键的状态是按下,如果最高是0,说明按键的状态是抬起;如果最低位被置为1则说明,该按键被按过,否则为0

//函数原型
SHORT GetAsyncKeyState([in] int vKey);

//参数说明
[in] vKey,类型: int,虚拟密钥代码。

参考GetAsyncKeyState函数文档:getAsyncKeyState 函数 (winuser.h) - Win32 apps | Microsoft Learn

参考虚拟密钥代码文档:虚拟键码 (Winuser.h) - Win32 apps | Microsoft Learn

  • 定义宏检测按键是否被按过
#define KEY_PRESS(VK)  ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )
📌

使用GetAsyncKeyState函数需要包含头文件windows.h

宽字符打印与本地化

宽字符

在游戏地图上,打印墙体使用宽字符:□,打印蛇使用宽字符●,打印食物使用宽字符★

普通的字符(即窄字符)是占1个字节的,而宽字符是占用2个字节

出现窄字符和宽字符区别的原因:
C语言字符默认是采用ASCII编码的,ASCII字符集采用的是单字节编码,且只使用了单字节中的低7位,最高位是没有使用的,可表示为0xxxxxxx,故ASCII字符集共包含128个字符,在英语国家中,128个字符是基本够用的,但是,在其他国家语言中,例如,在法语中,字母上方有注音符号,它就无法用基本的ASCII码表示。于是,⼀些欧洲国家就决定,利用字节中闲置的最高位编入新的符号。比如,法语中的é的编码为130(二进制10000010)。这样一来,这些欧洲国家使用的编码体系,可以表示最多256个符号。但是,不同的国家有不同的字母,因此,尽管都使用256个符号的编码方式,代表的字母却不⼀样。例如,130在法语编码中代表了é,在希伯来语编码中却代表了字母Gimel,在俄语编码中代表另⼀个符号。但是所有这些编码方式中,0--127表示的符号是一样的,不一样的只是128--255的这⼀段。
至于亚洲国家的文字,使用的符号就更多了,汉字就多达10万左右。一个字节只能表示256种符号,就必须使用多个字节表达⼀个符号。比如,简体中文常见的编码方式是GB2312,使用两个字节表示⼀个汉字,所以理论上最多可以表示256x256=65536个符号

为了使C语言适应国际化,C语言的标准中不断加入了国际化的支持。例如:加入宽字符的类型wchar_t 和宽字符的输入wscanf()和输出函数wprintf(),并且使用时需要包含头文件locale.h,其中提供了允许程序员针对特定地区(通常是国家或者说某种特定语言的地理区域)调整程序行为的函数

头文件locale.h与本地化

locale.h提供的函数用于控制C标准库中对于不同的地区会产生不一样行为的部分。

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

  • 数字量的格式
  • 货币量的格式
  • 字符集
  • 日期和时间的表示形式
类项

通过修改地区,程序可以改变它的行为来适应世界的不同区域。但地区的改变可能会影响库的许多部分,其中一部分可能是不希望修改的。所以C语言支持针对不同的类项进行修改,下面的一个宏,指定一个类项:

类项

影响内容

LC_ALL

以下列出的所有类别

LC_COLLATE

strcoll_stricollwcscoll_wcsicollstrxfrm_strncoll_strnicoll_wcsncoll_wcsnicollwcsxfrm 函数

LC_CTYPE

字符处理函数(不受影响的 isdigitisxdigitmbstowcsmbtowc 除外)

LC_MONETARY

localeconv 函数返回的货币格式信息

LC_NUMERIC

格式化输出例程(例如printf)、数据转换例程和localeconv返回的非货币格式设置信息的十进制点字符。 除小数点字符之外,LC_NUMERIC 还设置返回localeconv的千位分隔符和分组控制字符串

LC_TIME

strftimewcsftime 函数

设置本地化

使用函数setlocale函数进行设置

setlocale函数用于修改当前地区,可以针对一个类项修改,也可以针对所有类项

//函数原型
char *setlocale(int category, const char *locale);

//参数说明
第一个参数为category,表示受区域设置影响的分类。
第二个参数为locale,表示区域设置说明符

//返回类型
如果提供了有效的locale和category,函数会返回指向与指定的locale和category关联的字符串的指针。 如果locale参数为NULL,函数会返回当前区域设置。
如果将无效参数传递给任一函数,则返回值为NULL。

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

C标准给第⼆个参数仅定义了2种可能取值:"C"(即NULL)和""

在任意程序执行开始,都会隐藏式执行调用:

setlocale(LC_ALL, "C");//当地区设置为"C"时,库函数按正常方式执行

需要改变地区时,就只能显示调用setlocale函数。将" "作为第2个参数,调用setlocale函数就可以切换到本地模式,这种模式下程序会适应本地环境。

例如:切换到我们的本地模式后则支持宽字符(汉字)的输出等

setlocale(LC_ALL, "");//切换到本地环境

函数参考文档:setlocale | Microsoft Learn

代码实例

#define _CRT_SECURE_NO_WARNINGS 1

#include <stdio.h>
#include <locale.h>

int main()
{
    printf("当前环境为:%s\n", setlocale(LC_ALL, NULL));
    printf("设置后的本地环境为:%s\n", setlocale(LC_ALL, ""));

    return 0;
}
输出结果:
当前环境为:C
设置后的本地环境为:Chinese (Simplified)_China.936

宽字符的打印

在C语言中,宽字符的字面量前必须加上前缀L,否则C语言会把字面量当作窄字符类型处理。前缀L在单引号前面,表示宽字符。

宽字符的打印使用函数wprintf(),对应wprintf()的占位符为%lc;打印宽字符串时,对应的占位符为%ls并且在双引号前,需要加上前缀L

代码实例

#define _CRT_SECURE_NO_WARNINGS 1

#include <stdio.h>
#include <locale.h>

int main()
{
    printf("设置后的本地环境为:%s\n", setlocale(LC_ALL, ""));
    wchar_t c = L'中';
    wchar_t c1 = L'国';
    wchar_t c2 = L'■';
    wchar_t c3 = L'●';
    wchar_t c4 = L'★';
    char* string = "ab";

    wprintf(L"%lc\n", c);
    wprintf(L"%lc\n", c1);
    printf("%s\n", string);
    wprintf(L"%lc\n", c2);
    wprintf(L"%lc\n", c3);
    wprintf(L"%lc\n", c4);

    return 0;
}
输出结果如下图:

普通字符和宽字符打印的宽度展示如下:

贪吃蛇项目设计

游戏流程设计

贪吃蛇界面的打印

打印游戏欢迎界面
  • 效果图

  • 设计思路

游戏开始界面只需要打印文字,但是需要确定文字的打印位置

  1. 设置光标隐藏,封装成函数void HideCursor();,防止出现打印完成后光标闪烁问题
  2. 设置控制台的大小和标题,使用mode命令和title命令
  3. 设置光标位置,将该操作封装成函数void SetPos(int x, int y);
  4. 将欢迎界面的打印和设置光标位置封装成函数void Welcome();
  5. 调用pause指令显示“请按任意键继续...”,亦可防止程序提前结束
  6. 最后调用cls指令清除当前页面打印的内容,方便打印下一界面,实现页面跳转的效果

代码示例

//隐藏光标
void HideCursor()
{
    HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
    //隐藏光标
    CONSOLE_CURSOR_INFO cursor = { 0 };
    //先获取光标信息
    GetConsoleCursorInfo(hOutput, &cursor);
    cursor.bVisible = false;//设置光标为不可见
    //再传入新的光标信息
    SetConsoleCursorInfo(hOutput, &cursor);
}

//设置光标位置
void SetPos(int x, int y)
{
    HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
    COORD pos = { x, y };
    SetConsoleCursorPosition(hOutput, pos);
}

//欢迎界面
void Welcome()
{
    system("mode con cols=100 lines=30");
    system("title 贪吃蛇");
    //定位光标
    SetPos(33, 12);
    printf("欢迎来到贪吃蛇的世界,尽情游玩吧!");
    SetPos(40, 28);
    system("pause");
    system("cls");
}
打印游戏规则界面
  • 效果图如下

  • 设计思路

游戏规则界面同样只是打印基本文字信息

  1. 通过调用SetPos函数在每一次打印过程中设置光标位置
  2. 将游戏规则的打印和设置光标位置函数的调用封装成函数void Rules();
  3. 调用pause指令显示“请按任意键继续...”,亦可防止程序提前结束
  4. 最后调用cls指令清除当前页面打印的内容,方便打印下一界面,实现页面跳转的效果
📌

注意设置光标隐藏函数只需要在程序运行起来后执行一次即可,故在游戏界面打印部分只需要调用一次,不需要放置到Welcome()函数内

代码示例

//游戏规则介绍界面
void Rules()
{
    SetPos(45, 10);
    printf("游戏规则");
    SetPos(35, 12);
    printf("使用WASD或↑↓←→键操作蛇的移动");
    SetPos(30, 13);//重新设置坐标位置防止出现文字打印覆盖
    printf("按下空格即可加速,按下左alt或右alt取消加速");
    SetPos(22, 14);
    printf("加速可以加分,加速一次+2分,最高加8分,取消加速-2分,直到减到初始值");
    SetPos(40, 28);
    system("pause");
    system("cls");
}
打印游戏地图界面
  • 效果图

  • 设计思路

游戏地图的打印依旧是纯打印字符,但是需要注意打印的是宽字符

参考下图

  1. 地图的打印封装成函数void Map();,在该函数中调用SetPos函数设置每次打印的位置
  2. 在打印地图上半部分边界和下半部分边界时,需要注意因为宽字符横向占两个宽度,故需要确保宽字符的数量尽量保证其为2的倍数,而纵向方向上依旧是占用一个宽度
  3. 在打印左右部分边界时,需要注意此时宽字符纵向是一个宽度

代码示例

//打印游戏地图界面
void Map()
{
    //设置地区
    setlocale(LC_ALL, "");
    //打印地图上部分边界
    SetPos(0, 0);
    for (int i = 0; i < 29; i++)
    {
        wprintf(L"%lc", MAP);
    }
    //打印地图下部分边界
    SetPos(0, 26);
    for (int i = 0; i < 29; i++)
    {
        wprintf(L"%lc", MAP);
    }
    //打印地图左部分边界
    for (int i = 1; i <= 25; i++)
    {
        SetPos(0, i);
        wprintf(L"%lc", MAP);
    }
    //打印地图右部分边界
    for (int i = 1; i <= 25; i++)
    {
        SetPos(56, i);//注意此处是56而不是28,因为宽字符横向占两个宽度
        wprintf(L"%lc", MAP);
    }
    getchar();
}
打印游戏地图右侧的帮助信息
  • 效果图

  • 设计思路

帮助信息依旧是纯打印文字,只需要设置好打印位置即可

  1. 将帮助信息的打印封装成函数void Help();
//打印游戏地图右侧帮助信息
void Help()
{
    SetPos(60, 20);
    printf("不能穿墙,不能咬到自己");
    SetPos(60, 21);
    printf("用WASD或↑↓←→控制蛇的移动方向");
    SetPos(60, 22);
    printf("按下空格进入加速,按下左alt或右alt取消加速");
    SetPos(60, 23);
    printf("使用ESC退出游戏,使用P暂停游戏");
    SetPos(60, 26);
    printf("Copy Right@EPSDA");
}

贪吃蛇结构体设计

需要进行设计的结构体类型有:

  1. 蛇身节点
  2. 游戏状态
  3. 蛇的移动方向
  4. 游戏信息
📌

设计时需要注意,蛇吃掉食物,食物变成自身节点,这一过程相当于食物也是蛇身节点类型

//枚举游戏状态
enum state
{
    Normal = 1,//正常状态
    Error,//错误状态
    KillBySelf,//自己咬到自己
    KillbByWall//撞墙
};

//枚举蛇的方向
enum dir
{
    Up = 1,
    Down,
    Left,
    Right
};

//定义蛇身节点
typedef struct SnakeNode
{
    int x;
    int y;
    struct SnakeNode* next;
}Snode;

//定义游戏信息结构体
typedef struct GameInfo
{
    Snode* pSnake;//指向蛇头的指针
    Snode* pFood;//指向食物的指针
    int FoodWeight;//一个食物的得分
    int score;//当前总得分
    enum dir direct;//蛇的移动方向,默认向右
    enum state Srun;//游戏状态
    int sleepTime;//蛇的移动速度,睡眠时间越短,蛇越快,默认为200
}Sinfo;

贪吃蛇进行过程设计

初始化蛇身以及游戏信息
//初始化蛇身以及除食物以外的游戏信息
void Init(Sinfo* ps)
{
    //确保有蛇对象
    assert(ps);
    //优先蛇身的尾结点
    //通过头插实现蛇身整体
    Snode* cur = NULL;
    for (int i = 0; i <= 4; i++)
    {
        //创建蛇节点
        cur = (Snode*)malloc(sizeof(Snode));
        assert(cur);
        cur->x = 24 + 2 * i;
        cur->y = 5;
        cur->next = NULL;
        //将节点连起来
        if (ps->pSnake == NULL)
        {
            ps->pSnake = cur;//使游戏信息中维护蛇身的指针指向蛇尾
        }
        else
        {
            cur->next = ps->pSnake;
            ps->pSnake = cur;
        }
    }

    //打印蛇(遍历链表)
    //使用指向头结点的ps->pSnake指针
    Snode* pcur = ps->pSnake;
    setlocale(LC_ALL, "");
    while (pcur)
    {
        SetPos(pcur->x, pcur->y);
        wprintf(L"%lc", BODY);
        pcur = pcur->next;
    }

    //初始化其他信息
    ps->FoodWeight = 10;
    ps->sleepTime = 200;
    ps->score = 0;
    ps->Srun = Normal;
    ps->direct = Right;
}
初始化食物
//初始化食物
//食物本质也是蛇身,所以依旧可以使用蛇身结构体
void FoodInit(Sinfo* pf)
{
    assert(pf);
    //随机生成食物
    Snode* food = (Snode*)malloc(sizeof(Snode));
    assert(food);
    pf->pFood = food;
    //设置食物坐标
    //但是食物坐标不能与蛇坐标重合
create:
    do
    {
        //确保食物坐标不会超出地图范围
        pf->pFood->x = rand() % 53 + 2;
        pf->pFood->y = rand() % 24 + 1;
    } while (pf->pFood->x % 2 != 0);//确保食物的x坐标为2的倍数

    //确保食物所在位置不是蛇的位置,并且蛇身每个节点都需要比较
    Snode* cur = pf->pSnake;
    while (cur)
    {
        if (pf->pFood->x == cur->x && pf->pFood->y == cur->y)
        {
            goto create;
        }
        cur = cur->next;
    }

    //打印食物
    setlocale(LC_ALL, "");
    SetPos(pf->pFood->x, pf->pFood->y);
    wprintf(L"%lc", FOOD);
}
游戏进行部分
//判断下一个坐标是不是食物
bool NextIsFood(Sinfo* snake, Snode* pfood)
{
    assert(snake);
    assert(pfood);
    if (snake->pSnake->x == snake->pFood->x && snake->pSnake->y == snake->pFood->y)
    {
        return true;
    }
    else
    {
        return false;
    }
}

//吃食物
void EatFood(Sinfo* snake, Snode* pfood)
{
    assert(snake);
    assert(pfood);

    //是食物进行头插
    pfood->next = snake->pSnake;
    snake->pSnake = pfood;

    //打印新蛇身
    Snode* pcur = snake->pSnake;
    setlocale(LC_ALL, "");
    while (pcur)
    {
        SetPos(pcur->x, pcur->y);
        wprintf(L"%lc", BODY);
        pcur = pcur->next;
    }

    snake->score += snake->FoodWeight;
    //释放原来旧的食物
    free(snake->pFood);
    //重新创建食物
    FoodInit(snake);
}

//不是食物则正常移动
void NotEatFood(Sinfo* snake, Snode* pnext)
{
    assert(snake);
    assert(pnext);

    pnext->next = snake->pSnake;
    snake->pSnake = pnext;
    //不是食物时,蛇移动不会增加身体的长度
    Snode* pcur = snake->pSnake;
    setlocale(LC_ALL, "");
    while (pcur->next->next != NULL)
    {
        SetPos(pcur->x, pcur->y);
        wprintf(L"%lc", BODY);
        pcur = pcur->next;
    }
    //释放掉最后一个节点,防止蛇身长度增加
    SetPos(pcur->next->x, pcur->next->y);
    printf("  ");
    free(pcur->next);
    pcur->next = NULL;
}

//撞到自身
void Kill_Self(Sinfo* snake)
{
    assert(snake);

    //不可撞到自身相当于不可以撞到自身的第二个之后的所有节点
    Snode* pcur = snake->pSnake->next;
    while (pcur)
    {
        if (pcur->x == snake->pSnake->x && pcur->y == snake->pSnake->y)
        {
            snake->Srun = KillBySelf;
            return;
        }
        pcur = pcur->next;
    }
}

//撞到墙
void Kill_Wall(Sinfo* snake)
{
    assert(snake);

    //不可以撞到墙相当于头结点不可以等于墙的坐标
    Snode* pcur = snake->pSnake;
    if (pcur->x == 0 || pcur->x == 56 || pcur->y == 0 || pcur->y == 26)
    {
        snake->Srun = KillByWall;
        return;
    }
}

//蛇的移动
void Move(Sinfo* snake)
{
    //默认移动方向向右
    assert(snake);
    //创建新节点用于移动时存储新坐标
    Snode* psnake = (Snode*)malloc(sizeof(Snode));
    assert(psnake);
    psnake->next = NULL;
    switch (snake->direct)
    {
    case Up:
        psnake->x = snake->pSnake->x;
        psnake->y = snake->pSnake->y - 1;
        break;
    case Down:
        psnake->x = snake->pSnake->x;
        psnake->y = snake->pSnake->y + 1;
        break;
    case Left:
        psnake->x = snake->pSnake->x - 2;
        psnake->y = snake->pSnake->y;
        break;
    case Right:
        psnake->x = snake->pSnake->x + 2;
        psnake->y = snake->pSnake->y;
        break;
    default:
        break;
    }
    
    //移动过程中是否遇到食物
    if (NextIsFood(snake, psnake))//遇到食物
    {
        EatFood(snake, psnake);
    }
    else
    {
        NotEatFood(snake, psnake);
    }

    Kill_Self(snake);

    Kill_Wall(snake);
}

//游戏暂停
void SleepStatus()
{
    while (1)
    {
        //循环睡眠
        Sleep(200);
        //同时检测按键
        if (KEYPRESSED(0x50))
        {
            break;
        }
    }
}

//游戏过程部分
void GameRun(Sinfo* snake)
{
    //读取键盘输入控制蛇的移动
    do
    {
        //打印得分情况
        SetPos(60, 10);
        printf("一个食物%02d分", snake->FoodWeight);
        SetPos(60, 11);
        printf("当前得分:%02d", snake->score);
        //向上走,原方向不能向下
        if (KEYPRESSED(VK_UP) && snake->direct != Down)
        {
            snake->direct = Up;
        }
        //向下走,原方向不能向上
        else if (KEYPRESSED(VK_DOWN) && snake->direct != Up)
        {
            snake->direct = Down;
        }
        //向左走,原方向不能向右
        else if (KEYPRESSED(VK_LEFT) && snake->direct != Right)
        {
            snake->direct = Left;
        }
        //向右走,原方向不能向左
        else if (KEYPRESSED(VK_RIGHT) && snake->direct != Left)
        {
            snake->direct = Right;
        }
        else if (KEYPRESSED(VK_ESCAPE))
        {
            snake->Srun = Quit;
            break;
        }
        else if (KEYPRESSED(0x50))
        {
            SleepStatus();//暂停游戏
        }
        else if (KEYPRESSED(VK_SPACE))
        {
            //加速,最多加到320
            if (snake->sleepTime < 320)
            {
                snake->sleepTime += 30;
                snake->FoodWeight += 2;
            }
        }
        else if (KEYPRESSED(VK_LMENU) || KEYPRESSED(VK_RMENU))
        {
            //减速
            if (snake->sleepTime > 200)
            {
                snake->sleepTime -= 30;
                snake->FoodWeight -= 2;
            }
        }
        //蛇的移动
        Move(snake);
        Sleep(snake->sleepTime);
    } while (snake->Srun == Normal);
}

贪吃蛇运行结束

//游戏结束
void GameEnd(Sinfo* snake)
{
    assert(snake);
    //释放开辟过的空间
    SetPos(15, 12);
    switch (snake->Srun)
    {
    case Quit:
        printf("游戏退出");
        break;
    case KillByWall:
        printf("墙可不是食物嗷(头部出现大包)");
        break;
    case KillBySelf:
        printf("没有闹饥荒嗷,自己不好吃");
        break;
    }

    while (snake->pSnake)
    {
        Snode* BlockToFree = snake->pSnake;
        snake->pSnake = snake->pSnake->next;
        free(BlockToFree);
    }
    free(snake->pFood);
    snake = NULL;
}

贪吃蛇项目实现

//测试文件
#define _CRT_SECURE_NO_WARNINGS 1

#include "Snake_game.h"

void gameTest()
{
    int ch = 0;
    do
    {
        //游戏开始
        GameStart();
        //游戏进行
        srand((unsigned int)time(NULL));
        Sinfo snake = { 0 };
        Init(&snake);
        //初始化蛇身以及除食物以外的游戏信息
        //初始化食物
        FoodInit(&snake);
        GameRun(&snake);
        //游戏结束
        GameEnd(&snake);
        SetPos(15, 10);
        printf("还要继续嘛?(Y/N)");
        ch = getchar();
        getchar();//清理缓冲区的\n
    } while (ch == 'Y' || ch == 'y');

}

int main()
{
    gameTest();
    SetPos(0, 28);
    return 0;
}

//头文件
#pragma once

#include <stdio.h>
#include <windows.h>
#include <stdlib.h>
#include <assert.h>
#include <locale.h>
#include <stdbool.h>
#include <time.h>

//定义宏检测按键是否被按过
#define KEYPRESSED(vk) (GetAsyncKeyState(vk) & 0x1 ? 1 : 0)
//定义蛇身图案
#define BODY L'●'
//定义食物图案
#define FOOD L'★'
//定义地图图案
#define MAP L'□'

//枚举游戏状态
enum state
{
    Normal = 1,//正常状态
    Quit,//退出状态
    KillBySelf,//自己咬到自己
    KillByWall//撞墙
};

//枚举蛇的方向
enum dir
{
    Up = 1,
    Down,
    Left,
    Right
};

//定义蛇身节点
typedef struct SnakeNode
{
    int x;
    int y;
    struct SnakeNode* next;
}Snode;

//定义游戏信息结构体
typedef struct GameInfo
{
    Snode* pSnake;//指向蛇头的指针
    Snode* pFood;//指向食物的指针
    int FoodWeight;//一个食物的得分
    int score;//当前总得分
    enum dir direct;//蛇的移动方向,默认向右
    enum state Srun;//游戏状态
    int sleepTime;//蛇的移动速度,睡眠时间越短,蛇越快,默认为200
}Sinfo;

//游戏开始部分
void GameStart();
//隐藏光标
void HideCursor();
//设置光标位置决定打印内容位置
void SetPos(int x, int y);
//打印欢迎界面
void Welcome();
//打印游戏规则介绍界面
void Rules();
//打印游戏地图界面
void Map();
//打印游戏地图右侧帮助信息
void Help();

//游戏进行
void GameRun(Sinfo* snake);
//初始化蛇身以及游戏信息
void Init(Sinfo* ps);
//初始化食物
void FoodInit(Sinfo* pf);
//判断下一个坐标是不是食物
bool NextIsFood(Sinfo* snake, Snode* pfood);
//吃食物
void EatFood(Sinfo* snake, Snode* pfood);
//不是食物则正常移动
void NotEatFood(Sinfo* snake, Snode* pfood);
//游戏暂停
void SleepStatus();
//蛇的移动
void Move(Sinfo* snake);
//撞到自身
void Kill_Self(Sinfo* snake);
//撞到墙
void Kill_Wall(Sinfo* snake);

//游戏结束
void GameEnd(Sinfo* snake);

//项目文件
#define _CRT_SECURE_NO_WARNINGS 1

#include "Snake_game.h"

//隐藏光标
void HideCursor()
{
    HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
    //隐藏光标
    CONSOLE_CURSOR_INFO cursor = { 0 };
    //先获取光标信息
    GetConsoleCursorInfo(hOutput, &cursor);
    cursor.bVisible = false;//设置光标为不可见
    //再传入新的光标信息
    SetConsoleCursorInfo(hOutput, &cursor);
}

//设置光标位置
void SetPos(int x, int y)
{
    HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
    COORD pos = { x, y };
    SetConsoleCursorPosition(hOutput, pos);
}

//欢迎界面
void Welcome()
{
    system("mode con cols=105 lines=30");
    system("title 贪吃蛇");
    //定位光标
    SetPos(33, 12);
    printf("欢迎来到贪吃蛇的世界,尽情游玩吧!");
    SetPos(40, 28);
    system("pause");
    system("cls");
}

//游戏规则介绍界面
void Rules()
{
    SetPos(45, 10);
    printf("游戏规则");
    SetPos(35, 12);
    printf("使用↑↓←→键操作蛇的移动");
    SetPos(30, 13);//重新设置坐标位置防止出现文字打印覆盖
    printf("按下空格即可加速,按下左alt或右alt取消加速");
    SetPos(22, 14);
    printf("加速可以加分,加速一次+2分,最高加8分,取消加速-2分,直到减到初始值");
    SetPos(40, 28);
    system("pause");
    system("cls");
}

//打印游戏地图界面
void Map()
{
    //设置地区
    setlocale(LC_ALL, "");
    //打印地图上部分边界
    SetPos(0, 0);
    for (int i = 0; i < 29; i++)
    {
        wprintf(L"%lc", MAP);
    }
    //打印地图下部分边界
    SetPos(0, 26);
    for (int i = 0; i < 29; i++)
    {
        wprintf(L"%lc", MAP);
    }
    //打印地图左部分边界
    for (int i = 1; i <= 25; i++)
    {
        SetPos(0, i);
        wprintf(L"%lc", MAP);
    }
    //打印地图右部分边界
    for (int i = 1; i <= 25; i++)
    {
        SetPos(56, i);//注意此处是56而不是28,因为宽字符横向占两个宽度
        wprintf(L"%lc", MAP);
    }
}

//打印游戏地图右侧帮助信息
void Help()
{
    SetPos(60, 20);
    printf("不能穿墙,不能咬到自己");
    SetPos(60, 21);
    printf("用↑↓←→控制蛇的移动方向");
    SetPos(60, 22);
    printf("按下空格进入加速,按下左alt或右alt取消加速");
    SetPos(60, 23);
    printf("使用ESC退出游戏,使用P暂停游戏");
    SetPos(60, 26);
    printf("Copy Right@EPSDA");
}

//游戏开始部分
void GameStart()
{
    //隐藏光标
    HideCursor();
    //打印欢迎界面
    Welcome();
    //打印游戏规则界面
    Rules();
    //打印游戏地图界面
    Map();
    //打印游戏地图右侧帮助信息
    Help();
}

//初始化蛇身以及除食物以外的游戏信息
void Init(Sinfo* ps)
{
    //确保有蛇对象
    assert(ps);
    //优先蛇身的尾结点
    //通过头插实现蛇身整体
    Snode* cur = NULL;
    for (int i = 0; i <= 4; i++)
    {
        //创建蛇节点
        cur = (Snode*)malloc(sizeof(Snode));
        assert(cur);
        cur->x = 24 + 2 * i;
        cur->y = 5;
        cur->next = NULL;
        //将节点连起来
        if (ps->pSnake == NULL)
        {
            ps->pSnake = cur;//使游戏信息中维护蛇身的指针指向蛇尾
        }
        else
        {
            cur->next = ps->pSnake;
            ps->pSnake = cur;
        }
    }

    //打印蛇(遍历链表)
    //使用指向头结点的ps->pSnake指针
    Snode* pcur = ps->pSnake;
    setlocale(LC_ALL, "");
    while (pcur)
    {
        SetPos(pcur->x, pcur->y);
        wprintf(L"%lc", BODY);
        pcur = pcur->next;
    }

    //初始化其他信息
    ps->FoodWeight = 10;
    ps->sleepTime = 200;
    ps->score = 0;
    ps->Srun = Normal;
    ps->direct = Right;
}

//初始化食物
//食物本质也是蛇身,所以依旧可以使用蛇身结构体
void FoodInit(Sinfo* pf)
{
    assert(pf);
    //随机生成食物
    Snode* food = (Snode*)malloc(sizeof(Snode));
    assert(food);
    pf->pFood = food;
    //设置食物坐标
    //但是食物坐标不能与蛇坐标重合
create:
    do
    {
        //确保食物坐标不会超出地图范围
        pf->pFood->x = rand() % 53 + 2;
        pf->pFood->y = rand() % 24 + 1;
    } while (pf->pFood->x % 2 != 0);//确保食物的x坐标为2的倍数

    //确保食物所在位置不是蛇的位置,并且蛇身每个节点都需要比较
    Snode* cur = pf->pSnake;
    while (cur)
    {
        if (pf->pFood->x == cur->x && pf->pFood->y == cur->y)
        {
            goto create;
        }
        cur = cur->next;
    }

    //打印食物
    setlocale(LC_ALL, "");
    SetPos(pf->pFood->x, pf->pFood->y);
    wprintf(L"%lc", FOOD);
}

//判断下一个坐标是不是食物
bool NextIsFood(Sinfo* snake, Snode* pfood)
{
    assert(snake);
    assert(pfood);
    if (snake->pSnake->x == snake->pFood->x && snake->pSnake->y == snake->pFood->y)
    {
        return true;
    }
    else
    {
        return false;
    }
}

//吃食物
void EatFood(Sinfo* snake, Snode* pfood)
{
    assert(snake);
    assert(pfood);

    //是食物进行头插
    pfood->next = snake->pSnake;
    snake->pSnake = pfood;

    //打印新蛇身
    Snode* pcur = snake->pSnake;
    setlocale(LC_ALL, "");
    while (pcur)
    {
        SetPos(pcur->x, pcur->y);
        wprintf(L"%lc", BODY);
        pcur = pcur->next;
    }

    snake->score += snake->FoodWeight;
    //释放原来旧的食物
    free(snake->pFood);
    //重新创建食物
    FoodInit(snake);
}

//不是食物则正常移动
void NotEatFood(Sinfo* snake, Snode* pnext)
{
    assert(snake);
    assert(pnext);

    pnext->next = snake->pSnake;
    snake->pSnake = pnext;
    //不是食物时,蛇移动不会增加身体的长度
    Snode* pcur = snake->pSnake;
    setlocale(LC_ALL, "");
    while (pcur->next->next != NULL)
    {
        SetPos(pcur->x, pcur->y);
        wprintf(L"%lc", BODY);
        pcur = pcur->next;
    }
    //释放掉最后一个节点,防止蛇身长度增加
    SetPos(pcur->next->x, pcur->next->y);
    printf("  ");
    free(pcur->next);
    pcur->next = NULL;
}

//撞到自身
void Kill_Self(Sinfo* snake)
{
    assert(snake);

    //不可撞到自身相当于不可以撞到自身的第二个之后的所有节点
    Snode* pcur = snake->pSnake->next;
    while (pcur)
    {
        if (pcur->x == snake->pSnake->x && pcur->y == snake->pSnake->y)
        {
            snake->Srun = KillBySelf;
            return;
        }
        pcur = pcur->next;
    }
}

//撞到墙
void Kill_Wall(Sinfo* snake)
{
    assert(snake);

    //不可以撞到墙相当于头结点不可以等于墙的坐标
    Snode* pcur = snake->pSnake;
    if (pcur->x == 0 || pcur->x == 56 || pcur->y == 0 || pcur->y == 26)
    {
        snake->Srun = KillByWall;
        return;
    }
}

//蛇的移动
void Move(Sinfo* snake)
{
    //默认移动方向向右
    assert(snake);
    //创建新节点用于移动时存储新坐标
    Snode* psnake = (Snode*)malloc(sizeof(Snode));
    assert(psnake);
    psnake->next = NULL;
    switch (snake->direct)
    {
    case Up:
        psnake->x = snake->pSnake->x;
        psnake->y = snake->pSnake->y - 1;
        break;
    case Down:
        psnake->x = snake->pSnake->x;
        psnake->y = snake->pSnake->y + 1;
        break;
    case Left:
        psnake->x = snake->pSnake->x - 2;
        psnake->y = snake->pSnake->y;
        break;
    case Right:
        psnake->x = snake->pSnake->x + 2;
        psnake->y = snake->pSnake->y;
        break;
    default:
        break;
    }
    
    //移动过程中是否遇到食物
    if (NextIsFood(snake, psnake))//遇到食物
    {
        EatFood(snake, psnake);
    }
    else
    {
        NotEatFood(snake, psnake);
    }

    Kill_Self(snake);

    Kill_Wall(snake);
}

//游戏暂停
void SleepStatus()
{
    while (1)
    {
        //循环睡眠
        Sleep(200);
        //同时检测按键
        if (KEYPRESSED(0x50))
        {
            break;
        }
    }
}

//游戏过程部分
void GameRun(Sinfo* snake)
{
    //读取键盘输入控制蛇的移动
    do
    {
        //打印得分情况
        SetPos(60, 10);
        printf("一个食物%02d分", snake->FoodWeight);
        SetPos(60, 11);
        printf("当前得分:%02d", snake->score);
        //向上走,原方向不能向下
        if (KEYPRESSED(VK_UP) && snake->direct != Down)
        {
            snake->direct = Up;
        }
        //向下走,原方向不能向上
        else if (KEYPRESSED(VK_DOWN) && snake->direct != Up)
        {
            snake->direct = Down;
        }
        //向左走,原方向不能向右
        else if (KEYPRESSED(VK_LEFT) && snake->direct != Right)
        {
            snake->direct = Left;
        }
        //向右走,原方向不能向左
        else if (KEYPRESSED(VK_RIGHT) && snake->direct != Left)
        {
            snake->direct = Right;
        }
        else if (KEYPRESSED(VK_ESCAPE))
        {
            snake->Srun = Quit;
            break;
        }
        else if (KEYPRESSED(0x50))
        {
            SleepStatus();//暂停游戏
        }
        else if (KEYPRESSED(VK_SPACE))
        {
            //加速,最多加到320
            if (snake->sleepTime < 320)
            {
                snake->sleepTime += 30;
                snake->FoodWeight += 2;
            }
        }
        else if (KEYPRESSED(VK_LMENU) || KEYPRESSED(VK_RMENU))
        {
            //减速
            if (snake->sleepTime > 200)
            {
                snake->sleepTime -= 30;
                snake->FoodWeight -= 2;
            }
        }
        //蛇的移动
        Move(snake);
        Sleep(snake->sleepTime);
    } while (snake->Srun == Normal);
}

//游戏结束
void GameEnd(Sinfo* snake)
{
    assert(snake);
    //释放开辟过的空间
    SetPos(15, 12);
    switch (snake->Srun)
    {
    case Quit:
        printf("游戏退出");
        break;
    case KillByWall:
        printf("墙可不是食物嗷(头部出现大包)");
        break;
    case KillBySelf:
        printf("没有闹饥荒嗷,自己不好吃");
        break;
    }

    while (snake->pSnake)
    {
        Snode* BlockToFree = snake->pSnake;
        snake->pSnake = snake->pSnake->next;
        free(BlockToFree);
    }
    free(snake->pFood);
    snake = NULL;
}

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

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

相关文章

excel给数据库初始化/旧数据处理(自动sql拼装)

思路&#xff1a; 首先导出数据到excel编写单条数据操作的sql利用excel CONCATENATE 函数自动生成&#xff0c;每一行数据的操作sql 小技巧:对于需要套娃的字段值&#xff0c;可以加一个临时列同样使用CONCATENATE函数进行sql拼装 案例&#xff1a; 1.临时列:CONCATENATE(C2, …

HBase(docker版)简单部署和HBase shell操作实践

文章目录 说明HBase部署访问HBase Shell常见命令数据定义语言(DDL) 数据操作语言(DML)通用操作访问HBase WebUI 说明 本文适合HBase初学者快速搭建HBase环境&#xff0c;练习常见shell使用本文参考资料 《大数据技术原理和应用》&#xff08;林子雨 编著 第三版&#xff09;zh…

一文彻底搞懂redis数据结构及应用

文章目录 1. Redis介绍2.五种基本类型2.1 String字符串2.2 List列表2.3 Set集合2.4 Zset有序集合2.5 Hash散列 3. 三种基本类型3.1 Bitmap &#xff08;位存储&#xff09;3.2 HyperLogLogs&#xff08;基数统计&#xff09;3.3 geospatial (地理位置) 4. Stream详解4.1 Stream…

NTRU-Based GSW-Like FHE:Faster Blind Rotation

参考文献&#xff1a; [XZD23] Xiang, B., Zhang, J., Deng, Y., Dai, Y., Feng, D. (2023). Fast Blind Rotation for Bootstrapping FHEs. In: Handschuh, H., Lysyanskaya, A. (eds) Advances in Cryptology – CRYPTO 2023. CRYPTO 2023. Lecture Notes in Computer Scien…

C++_list

目录 一、模拟实现list 1、list的基本结构 2、迭代器封装 2.1 正向迭代器 2.2 反向迭代器 3、指定位置插入 4、指定位置删除 5、结语 前言&#xff1a; list是STL(标准模板库)中的八大容器之一&#xff0c;而STL属于C标准库的一部分&#xff0c;因此在C中可以直接使用…

实现扫码登录

扫码登录是如何实现的&#xff1f; 二维码信息里主要包括唯一的二维码ID,过期的时间&#xff0c;还有扫描状态&#xff1a;未扫描、已扫描、已失效。 扫码登录流程 用户打开网站登录页面的时候&#xff0c;浏览器会向二维码服务器发送一个获取登录二维码的请求。二维码服务器收…

雨云VPS搭建幻兽帕鲁服务器,PalWorld开服联机教程(Windows),0基础保姆级教程

雨云VPS用Windows系统搭建幻兽帕鲁私服&#xff0c;PalWorld开服联机教程&#xff0c;零基础保姆级教程&#xff0c;本教程使用一键脚本来搭建幻兽帕鲁服务端&#xff0c;并讲了如何配置游戏参数&#xff0c;如何更新服务端等。 最近这游戏挺火&#xff0c;很多人想跟朋友联机…

顶象点选验证码

要放假了好颓废。。。。 没啥事儿干&#xff0c;就把之前剩余的顶象点选系列的验证码类型看了下。 之前分享了一篇关于这个顶象的滑块的 DX算法还原_dx算法还原_逆向学习之旅-CSDN博客&#xff0c;感兴趣可以去看看。 咱们以文字点选为例&#xff1a; def get_image_arry(s…

Spring Boot如何统计一个Bean中方法的调用次数

目录 实现思路 前置条件 实现步骤 首先我们先自定义一个注解 接下来定义一个切面 需要统计方法上使用该注解 测试 实现思路 通过AOP即可实现&#xff0c;通过AOP对Bean进行代理&#xff0c;在每次执行方法前或者后进行几次计数统计。这个主要就是考虑好如何避免并发情况…

AI绘画:PhotoMaker Win11本地安装记录!

昨天介绍一个叫PhotoMaker的AI绘画开源项目。挺不错的&#xff01; 通过这个项目可以快速制作特定人脸的AI绘画作品&#xff0c;相比传统的技术效果会好很多&#xff0c;效率也高很多。 今天趁热打铁&#xff0c;本地电脑装装看&#xff0c;并且记录&#xff0c;分享一下&#…

城建档案数字化管理系统

城市建设档案数字化管理系统是指将城市建设相关档案纸质化资料转换为数字化形式&#xff0c;并通过信息技术手段进行存储、检索、管理和利用的系统。该系统旨在解决传统纸质档案管理存在的问题&#xff0c;提高档案管理的效率和准确性。 专久智能城市建设档案数字化管理系统主要…

自学C语言-7

第7章 循环控制 生活中总会有许多简单而重复的工作&#xff0c;为完成这些重复性工作&#xff0c;需要花费很多时间。使用循环语句来处理程序开发中简单、重复性的工作是最好不过的了。 本章致力于使读者了解while、do…while和for3种循环结构的特点&#xff0c;以及转移语句的…

Python第三方扩展库Matplotlib

Python第三方扩展库Matplotlib Matplotlib 是第三方库&#xff0c;不是Python安装程序自带的库&#xff0c;需要额外安装&#xff0c;它是Python的一个综合性的绘图库&#xff0c;提供了大量的绘图函数用于创建静态、动态、交互式的图形和数据可视化&#xff0c;可以帮助用户创…

Python实现时间序列分析AR定阶自回归模型(ar_select_order算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 时间序列分析中&#xff0c;AR定阶自回归模型&#xff08;AR order selection&#xff09;是指确定自回…

Springboot使用数据库连接池druid

springboot框架中可以使用druid进行数据库连接池&#xff0c;下面介绍druid在springboot中使用和参数配置介绍。 数据库连接池&#xff08;Druid&#xff09;是一种用于管理数据库连接的机制&#xff0c;其工作原理和常见使用方法如下&#xff1a; 原理&#xff1a;数据库连接…

Android 12 系统开机动画

一、查找Android系统提供的开机动画 在Android系统源码目录下输入 find ./ -name "bootanimation.zip" 如图所示 所输出的路劲即为bootanimation.zip所在路径&#xff0c;每个系统都不一样&#xff0c;建议用命令查找 二、复制到对应目录下 android12\out\target\…

vue使用mpegts.js教程

vue使用mpegts.js教程 最简单好用的H265网页播放器-mpegts.js简介特征受限性 使用步骤安装引入HTML 中添加视频标签video知识扩展 在容器里创建播放器 最简单好用的H265网页播放器-mpegts.js H265是新一代视频编码规范&#xff0c;与H264相比压缩比更高&#xff0c;同样的码率下…

Web实战丨基于Django的简单网页计数器

文章目录 写在前面Django简介主要程序运行结果系列文章写在后面 写在前面 本期内容 基于django的简单网页计数器 所需环境 pythonpycharm或vscodedjango 下载地址 https://download.csdn.net/download/m0_68111267/88795604 Django简介 Django 是一个用 Python 编写的高…

prism 10 for Mac v10.1.1.270激活版 医学绘图分析软件

GraphPad Prism 10 for Mac是一款专为科研工作者和数据分析师设计的绘图和数据可视化软件。以下是该软件的一些主要功能&#xff1a; 软件下载&#xff1a;prism 10 for Mac v10.1.1.270激活版 数据整理和导入&#xff1a;GraphPad Prism 10支持从多种数据源导入数据&#xff0…

设计与实现基于Java+MySQL的考勤发布-签到系统

课题背景 随着现代经济的迅速发展&#xff0c;电子考勤签到服务已经渗透到人们生活的方方面面&#xff0c;成为不可或缺的一项服务。在这个背景下&#xff0c;线上签到作为考勤签到的一种创新形式&#xff0c;为用户提供了便捷的操作方式&#xff0c;使得任务签到、个人签到记…