当前位置: 首页 > news >正文

【C语言】项目实践-贪吃蛇小游戏(Windows环境的控制台下)

一.游戏要实现基本的功能:

• 贪吃蛇地图绘制

• 蛇吃食物的功能 (上、下、左、右方向键控制蛇的动作)

• 蛇撞墙死亡

• 蛇撞自身死亡

• 计算得分

• 蛇身加速、减速

• 暂停游戏

二.技术要点

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

三.补充知识:Win32 API

1.Win32 API介绍

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

2.控制台程序

平常我们运行起来的命令提示符(黑框框程序)其实就是控制台程序,

我们可以使用cmd命令来设置控制台窗口的长宽:设置控制台窗口的大小,lines来设置30行,cols来设置100列

mode con cols=100 lines=30

也可以通过命令设置控制台窗口的名字

title 贪吃蛇


这些能在控制台窗口执行的命令,也可以调用C语言函数system(需要包含头文件<windows.h>)来执行。

#include<windows.h>int main()
{//设置控制台窗口的⻓宽:设置控制台窗口的⼤小,30行,100列system("mode con cols=100 lines=30");//设置cmd窗口名称system("title 贪吃蛇");return 0;
} 0;
(因相关命令知识较多,本文仅根据游戏需要处使用相关命令,诸如换行、改标题,不会具体讲解某个命令,感兴趣的读者可自行了解)
参考:mode命令
参考:title命令

3.控制台屏幕上的坐标COORD

正常我们运行程序输出信息,仅仅通过‘\n’来实现不同行当输出,并且输出默认其实位置时是都是定格开始,但是如果我们要创建墙体,在特定出打印相关信息,我们就需要能够将光标移动到对应位置。这就涉及到窗体的坐标系了。

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

但需要注意的是控制台屏幕上的坐标COORD的x,y的一个大小并不是一一对应关系,由于宽窄字符的区别,x的一个单位长度设置的较小,在长度上,y的一个单位差不多相当于x的2个单位。

COORD的声明:

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

给坐标值赋值:

1 COORD pos = { 10, 15 };

4.GetStdHandle

在知道坐标系的概念后,我们还不能直接的去操控光标的移动,就像我们访问一些博物馆,机密的地方时,我们需要递交身份证或者一些凭证来获得访问权限一样,为了能够对光标进行操作,我们也需要凭证来获得权限。

GetStdHandle是⼀个Windows API函数。它用于从⼀个特定的标准设备(标准输入、标准输出或标准错误)中取得⼀个句柄(用来标识不同设备的数值),这个句柄就相当于凭证,通过句柄我们可以操作对应设备。

GetStdHandle的返回值就是对应设备的句柄,形参通过接受STD_INPUT_HANDLE、 STD_OUTPUT_HANDLE、STD_ERROR_HANDLE三个值来获得输入输出及错误设备的句柄

1 HANDLE GetStdHandle(DWORD nStdHandle);
HANDLE hOutput = NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
含义
STD_INPUT_HANDLE((DWORD)-10)标准输入设备。 最初,这是输入缓冲区 CONIN$ 的控制台
STD_OUTPUT_HANDLE((DWORD)-11)标准输出设备。 最初,这是活动控制台屏幕缓冲区 CONOUT$
STD_ERROR_HANDLE((DWORD)-12)标准错误设备。 最初,这是活动控制台屏幕缓冲区 CONOUT$

5.GetConsoleCursorInfo

在凭证后,我们有了对光标操作的权限,之后要实现对光标的一些操作,我们就要找到存放光标信息的箱子,将原有的信息按照我们想要的进行修改,然后将修改的箱子在放回去。

GetConsoleCursorInfo 是检索有关指定控制台屏幕缓冲区的光标大小和可见性的信息的函数

BOOL WINAPI GetConsoleCursorInfo(
HANDLE hConsoleOutput,
PCONSOLE_CURSOR_INFO lpConsoleCursorInfo
);
PCONSOLE_CURSOR_INFO 是指向 CONSOLE_CURSOR_INFO 结构的指针,该结构接收有关主机游标
(光标)的信息
HANDLE hOutput = NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息

它接受两个参数,首先我们需要获得输出设备的句柄,然后我们要创建CONSOLE_CURSOR_INFO类型的箱子用来存放GetConsoleCursorInfo获得的光标信息。

6.CONSOLE_CURSOR_INFO

CONSOLE_CURSOR_INFO 这个结构体,包含有关控制台光标的信息

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

dwSize是由光标填充的字符单元格的百分比。 此值介于1到100之间。 改变dwsize光标外观会变化,范围从完全填充单元格到单元底部的水平线条。

• bVisible是表示游标的可见性。 如果光标可见,则此成员为 TRUE,否则为FLASE(注意需要包含<stdbool.h>头文件)。

7.SetConsoleCursorInfo

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

#include<windows.h>
#include<stdbool.h>
int main()
{// 获得标准输出设备的句柄HANDLE houtput = NULL;houtput = GetStdHandle(STD_OUTPUT_HANDLE);// 定义一个光标信息的结构体CONSOLE_CURSOR_INFO cursor_info = {0};// 获取和 houtput 句柄相关的控制台上的光标信息,存放在 cursor_info 中GetConsoleCursorInfo(houtput, &cursor_info);// 修改光标的占比cursor_info.dwSize = 100;// 设置和 houtput 句柄相关的控制台上的光标信息SetConsoleCursorInfo(houtput, &cursor_info);system("pause");return 0;
}

#include<windows.h>
#include<stdbool.h>
int main()
{// 获得标准输出设备的句柄HANDLE houtput = NULL;houtput = GetStdHandle(STD_OUTPUT_HANDLE);// 定义一个光标信息的结构体CONSOLE_CURSOR_INFO cursor_info = {0};// 获取和 houtput 句柄相关的控制台上的光标信息,存放在 cursor_info 中GetConsoleCursorInfo(houtput, &cursor_info);cursor_info.bVisible = false;// 设置和 houtput 句柄相关的控制台上的光标信息SetConsoleCursorInfo(houtput, &cursor_info);system("pause");return 0;
}

8.SetConsoleCursorPosition

设置指定控制台屏幕缓冲区中的光标位置,我们将想要设置的坐标信息放在COORD类型的pos中,调用SetConsoleCursorPosition函数将光标位置设置到指定的位置。

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

8.1贪吃蛇游戏光标定位

如果直接使用SetConsoleCursorPosition,步骤过于繁琐因此,我们将上述步骤专门分装成用于光标定位的函数。

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

  9.GetAsyncKeyState

在贪吃蛇游戏中,我们会通过按上下左右键来改变方向,通过按F3,F4改变速度等,因此我们需要能够检测键是否被按过因为本游戏是按一次键相应作出变换,因此,我们不考虑一直按键的情况。

为了区分对应的键及相关信息的传递,我们规定的虚拟键值这一概念,对应键有对应的整型值(16进制形式),注意这不要与ASCII值混淆。

参考:虚拟键码 (Winuser.h) - Win32 apps

GetAsyncKeyState就是这个用途的函数,函数原型如下:
SHORT GetAsyncKeyState(
int vKey
)

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

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

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

实例:检测数字键:

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


9.1贪吃蛇游戏按键识别:

在贪吃蛇游戏中,我们可以通过GetAsyncKeyState返回值按位与1的值是1还是0来判断键是否被按过,为了方便,在这里笔者定义了一个宏命令。

1 #define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )

四.补充知识:C语言的国际化与本地化

1.C语言国际化与本土化的由来

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

但是,我们发现我们无法直接打印出来,这是为什么呢?因为这类字符属于宽字符。而因为字符属于窄字符。

普通的字符是占⼀个字节的,这类宽字符是占用2个字节。过去C语言并不适合非英语国家(地区)使用。C语言最初假定字符都是单字节的。但是这些假定并不是在世界的任何地方都适用。

C语言字符默认是采用ASCII编码的,ASCII字符集采用的是单字节编码,且只使用了单字节中的低7位,最高位是没有使用的,可表示为0xxxxxxxx;可以看到,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>头文件,其中提供了允许程序员针对特定地区(通常是国家或者说某种特定语言的地理区域)调整程序行为的函数

2.<locale.h>本地化

<locale.h>提供的函数用于控制C标准库中对于不同的地区会产生不⼀样行为的部分,使用该头文文件会自动检测系统使用的地区、时间等。 在标准中,依赖地区的部分有以下几项:

• 数字量的格式

• 货币量的格式

• 字符集

• 日期和时间的表示形式

实验代码:

#include <stdio.h>      /* printf */
#include <time.h>       /* time_t, struct tm, time, localtime, strftime */
#include <locale.h>     /* struct lconv, setlocale, localeconv */int main()
{time_t rawtime;struct tm* timeinfo;char buffer[80];struct lconv* lc;time(&rawtime);timeinfo = localtime(&rawtime);int twice = 0;do {printf("Locale is: %s\n", setlocale(LC_ALL, NULL));strftime(buffer, 80, "%c", timeinfo);printf("Date is: %s\n", buffer);lc = localeconv();printf("Currency symbol is: %s\n-\n", lc->currency_symbol);setlocale(LC_ALL, "");} while (!twice++);return 0;
}

3.类项

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

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

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

• LC_MONETARY:影响货币格式。

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

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

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

参考:每个类项

4.setlocale

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

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

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

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

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

setlocale(LC_ALL, "C");

当地区设置为"C"时,库函数按正常方式执行,小数点是⼀个点。

当程序运行起来后想改变地区,就只能显示调用setlocale函数。用" "作为第2个参数,调用setlocale函数就可以切换到本地模式,这种模式下程序会适应本地环境。

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

1 setlocale(LC_ALL, " ");

4.宽字符的打印:wprintf

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

宽字符的字面量必须加上前缀“L”,否则 C 语言会把字面量当作窄字符类型处理。前缀“L”在单引号前面,表示宽字符,对应 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;
}

附:读者可能会疑惑我们平时使用的printf()不是也可以打印宽字符汉字吗,两个函数的打印有什么区别吗?

printf()打印的汉字在中国大陆常见的是 GB2312、GBK 或者 UTF-8 编码。这个编码通常由系统的 locale 设置决定。

wprintf()打印的汉字通常采用的编码方式是宽字符编码,比如 UTF-16 或者 UTF-32。因此打印宽字符时选用wprintf()会更好

五.贪吃蛇游戏实现:

1.规划思路如下:

#include"snake.h"void test()
{int ch = 0;srand((unsigned int)time(NULL));//通过rand产生随机坐标来产生随机食物的效果do{snake sk = { 0 };GameStart(&sk);GameRun(&sk);GameEnd(&sk);SetPos(25, 15);printf("您是否再来一局?(Y/N):");ch = getchar();while (getchar() != '\n');} while (ch == 'Y'|| ch == 'y');}int main()
{setlocale(LC_ALL, "");//修改当前地区为本地模式,为了支持中文宽字符及特殊字符的打印test();//测试逻辑SetPos(0, 27);//将光标移动至地图下方,不会破坏地图return 0;
}

2.游戏主逻辑:

程序开始就设置程序支持本地模式,然后进入游戏的主逻辑。主逻辑分为3个过程:

• 游戏开始(GameStart)完成游戏的初始化

• 游戏运行(GameRun)完成游戏运行逻辑的实现

• 游戏结束(GameEnd)完成游戏结束的说明,实现资源释放

2.1游戏开始(GameStart)

这个模块完成游戏的初始化任务:

• 控制台窗口大小的设置

• 控制台窗口名字的设置

• ⿏标光标的隐藏

• 打印欢迎界面

• 创建地图

• 初始化蛇

• 创建第⼀个食物

void GameStart(psnake ps)
{WelcomeToGame();//设置窗口名称大小、隐藏光标,打印欢迎界面CreatMap();//打印欢迎界面InitSnake(ps);//初始化蛇及其相关信息CreateFood(ps);//创造第一个食物}
2.1.1打印欢迎界面
2.1.1.1.窗口设置

由于本项目是通过控制台窗口实现的,但是win系统升级之类缘故,C语言开发环境调用的窗口,使用的初始设置无法很好的实现我们想要的效果,因此,这里要先更改一下设置。首先运行程序,弹出黑框,然后鼠标移动到如下区域右键,点击设置。

将默认终端应用程序改为windows控制台主机。

更改完成后,如果对颜色不太满意,可以右键弹出窗口,点击设置,在颜色中调整窗体,在这里,笔者为了演示效果,调成了灰色。

在游戏正式开始之前,我们可以做⼀些功能提醒,这里我们可以先设置窗体大小,名称,需要注意的是我们除了创建地图之外,地图旁边还需留有空间显示游戏相关信息及提示。因此笔者设计窗体

行30,列100.地图大小是实现⼀个棋盘26行,58列的棋盘(行和列可以根据自己的情况修改)。

	//设置控制台窗口的大小,30行,100列//mode为DOS命令system("mode con cols=100 lines=30");//设置cmd窗口名称system("title 贪吃蛇");//获取标准输出的句柄(用来标识不同设备的数值)HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);//影藏光标操作CONSOLE_CURSOR_INFO CursorInfo;GetConsoleCursorInfo(houtput, &CursorInfo);//获取控制台光标信息CursorInfo.bVisible = false;//隐藏控制台光标SetConsoleCursorInfo(houtput, &CursorInfo);//设置控制台光标状态

void WelcomeToGame()
{//SetPos调整光标位置SetPos(40, 15);printf("欢迎来到贪吃蛇小游戏!");SetPos(40, 25);system("pause");system("cls");SetPos(25, 12);printf("用↑ . ↓ . ← . → 分别控制蛇的移动, F3为加速,F4为减速\n");SetPos(25, 13);printf("加速将能得到更高的分数。\n");SetPos(40, 25);system("pause");system("cls");
}
void SetPos(short x, short y)
{COORD pos = { x,y };HANDLE houtput = NULL;houtput = GetStdHandle(STD_OUTPUT_HANDLE);SetConsoleCursorPosition(houtput, pos);}

在上述代码中,我们使用pause指令,实现程序不退出的效果即屏幕上”请按任意键退出“,如果不加上这句换或者其他相似效果的语句,程序直接退出,我们是无法看到窗体标题的改变的,同时为了实现换界面的效果,我们可以在pause语句后加上cls清屏指令。

2.1.2.创建地图

创建地图就是将墙打印出来,因为是宽字符打印,所以使用wprintf函数,打印格式串前使用L

打印地图的关键是要算好坐标,才能在想要的位置打印墙体。为了方便修改,如同蛇身、食物、

墙等特殊字符。笔者同一使用使用预处理指令定义。墙体定义如下:

#define WALL L'□'

坐标设定如下:上:(0,0)到(56,0) 下:(0,26)到(56,26) 左:(0,1)到(0,25) 右:(56,1)到(56,25)


创建地图函数CreateMap:

void CreatMap()
{int i = 0;SetPos(0,0);//宽字符一块墙体站X两个单位大小//上(0,0)-(56, 0)for(i = 0;i < 58; i = i + 2){wprintf(L"%c", WALL);}SetPos(0, 26);//下(0,26)-(56, 26)for (i = 0; i < 58; i = i + 2){wprintf(L"%c", WALL);}//左//x是0,y从0开始增⻓for (i = 0; i < 26; i++){SetPos(0, i);wprintf(L"%c", WALL);}//x是56,y从0开始增⻓for (i = 0; i < 26; i++){SetPos(56, i);wprintf(L"%c", WALL);}
}
2.1.3.蛇身创建及初始化蛇身
a.蛇身节点

在游戏运行的过程中,蛇每次吃⼀个食物,蛇的身体就会变长⼀节,如果我们使用链表存储蛇的信息,那么蛇的每⼀节其实就是链表的每个节点。每个节点只要记录好蛇身节点在地图上的坐标就行,然后根据坐标移动光标打印对应节点就行,同样的对于食物来说也是这样,我们只要创建结构体存储对应的位置信息就行了,所以蛇节点(食物节点)结构如下:

蛇身打印的宽字符:

 #define BODY L'●'
b.整条蛇及相关信息的维护

x,y就是对应一节身体的位置。这里我们还创建指向一节身体的指针方便调用。同时,在本项目中与蛇有关的信息还有诸如蛇的状态(撞墙、吃到自己、按ESC结束)、移动速度、蛇前进的方向、得分、食物分数、等等,如我们分开存放这些信息,就会显得非常杂乱、不利于我们后期游戏的维护,因此我们这里运用面向对象的思想,创建一个结构体,将这些与蛇有关的信息集中存放在一起。结构体如下:

typedef struct Snake
{pSnakeNode _Psnake;//维护整条蛇的指针,指向代表蛇头的节点pSnakeNode _Pfood;//维护⻝物的指针enum DIRECTION _Dir;//蛇头的方向默认是向右enum STATE _State;//游戏状态int _Score;//当前获得分数int _FoodScore;//默认每个⻝物10分int _SleepTime;//每走一步休眠时间
}snake,*psnake;
c.蛇的速度

需要特别说明的是,蛇的速度的实现跟读者想象的不同,如果将游戏运行,我们发现蛇是走一步顿一步的,其实我们并不是让蛇真的有个速度动起来,我们只是让玩家觉得它动起来了,蛇身移动的前进是依靠打印字符与空格实现的,一系列的连续动作最终也不过是通过不断循环实现的,如我们直接运行程序,那么依赖打印实现的蛇身体的移动,必然是迅速均衡的,因此如果想实现蛇存在移动速度的感觉,我们必须使得程序运行速度上存在差异,因此在这里,我们使用休眠Sleep指令,改变程序的运行,赋予蛇速度,休眠时间短,速度就快,休眠时间长,速度就慢

d.蛇的方向

方向仅用来判断,用枚举存放不同方向就行

//方向
enum DIRECTION
{UP = 1,DOWN,LEFT,RIGHT
};
e.蛇的状态(游戏状态)

状态仅用来判断,用枚举存放不同状态就行

//游戏状态
enum STATE
{OK,//正常运行END_NORMAL,//撞墙KILL_BY_WALL,//咬到自己KILL_BY_SELF,//正常结束
};
d.初始化

蛇最开始长度设为5节,每节对应链表的⼀个节点,蛇身的每⼀个节点都有自己的坐标。

创建5个节点,然后将每个节点存放在链表中进行管理。创建完蛇身后,从一个固定的位置出发,访问节点存储的坐标位置信息,将蛇的每⼀节打印在屏幕上。

• 蛇的初始位置从 (24,5) 开始。

注意:因为墙体为宽字符,从(0,0)坐标开始打印,X轴方向上一个墙体为站两个单位长度,所以蛇的每个节点的x坐标必须是2个倍数,否则可能会出现蛇的⼀个节点有⼀半出现在墙体中,另外⼀般在墙外的现象,坐标不好对齐。

再设置当前游戏的状态,蛇移动的速度,默认的方向,初始成绩,每个食物的分数。

• 游戏状态是:OK

• 蛇的移动速度:200毫秒

• 蛇的默认方向:RIGHT

• 初始成绩:0

• 每个食物的分数:10

初始化蛇⾝函数:InitSnake

void InitSnake(psnake ps)
{pSnakeNode cur = NULL;int i = 0;//创建蛇身节点,并初始化坐标//头插法:创建新的头插入原链表中,并将新节点作为新蛇头for(i = 0;i < 5;i++){//创建蛇身的节点cur = (pSnakeNode)malloc(sizeof(SnakeNode));if (NULL == cur){perror("InitSnake:malloc()");exit(1);}//设置坐标cur->next = NULL;cur->x = POS_X + i*2;cur->y = POS_Y;//头插法if (ps->_Psnake == NULL){ps->_Psnake = cur;}else{cur->next = ps->_Psnake;ps->_Psnake = cur;}}//打印蛇的身体:改变光标位置,在对应光标位置打印身体字符cur = ps->_Psnake;while(cur){SetPos(cur->x,cur->y);wprintf(L"%c",BODY);cur = cur->next;}//初始化贪吃蛇数据ps->_Dir = RIGHT;ps->_FoodScore = 10;ps->_Score = 0;ps->_SleepTime = 200;ps->_State = OK;
}

2.1.4.创建第⼀个食物

• 先随机生成食物的坐标:食物必须生成在墙内,且不能生成在蛇身上(循环遍历蛇身节点,根据坐标是否相等判断,因为,蛇头不可能吃到蛇头,只需从蛇头之后开始循环即可。),因此食物的节点坐标x必须在2~54内,y坐标必须在1~25之内(rand生成是按0~52,0~24在加上数来实现),同时这个食物坐标的生成必须是随机不定的,对此我们可以使用rand函数与srand函数。

• x坐标必须是2的倍数:如果食物的x坐标不是2的倍数,就会出现蛇一半碰到食物,能一半未碰到食物,但是这碰到的食物是宽字符比窄字符宽出的那部分,这部分是多打印的,我们蛇的节点并未存储相关坐标信息,因此不好判断坐标是否相等蛇是否吃到食物,因此食物X坐标必须是2的倍数

• 创建食物节点,打印食物

CreateFood:

void CreateFood(psnake ps)
{int x = 0;int y = 0;
again://产生的x坐标应该是2的倍数,这样才可能和蛇头坐标对⻬do{x = rand() % 53 + 2;y = rand() % 25 + 1;} while (x % 2 != 0);pSnakeNode cur = ps->_Psnake; // 获取指向蛇头的指针//⻝物不能和蛇身冲突while(cur){if(cur->x == x && cur->y == y){goto again;//goto语法,如果⻝物和蛇身冲突,则重新生成坐标}cur = cur->next;}pSnakeNode pfood = (pSnakeNode)malloc(sizeof(SnakeNode));//创建⻝物if (NULL == pfood){perror("CreateFood:malloc()");exit(1);}pfood->x = x;//创键食物后,在维护贪吃蛇相关信息的结构体内更新游戏信息,方便维护管理pfood->y = y;SetPos(x, y);wprintf(L"%c", FOOD);ps->_Pfood = pfood;
}

2.2.游戏运行(GameRun)

void GameRun(psnake ps)
{do{//打印右侧帮助信息PrintHelpInfo(ps);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))//检测是否按过F3,按过则游戏加速{if (ps->_SleepTime >= 50){ps->_SleepTime -= 30;//游戏加速就是休眠时间变短ps->_FoodScore += 2;//游戏加速,每个食物能获得分数上涨}}else if (KEY_PRESS(VK_F4))//检测是否按过F4,按过则游戏减速{if (ps->_SleepTime < 350){ps->_SleepTime += 30;//游戏减速就是休眠时间变长ps->_FoodScore -= 2;//游戏减速,每个食物能获得分数下降}if (ps->_SleepTime == 350)//蛇能减到的最低速度{ps->_FoodScore = 1;}}else if (KEY_PRESS(VK_ESCAPE))//检测是否按过ESC键,按过则游戏正常结束{ps->_State = END_NORMAL;//更新蛇的状态break;}//蛇每次⼀定之间要休眠的时间,时间短,蛇移动速度就快Sleep(ps->_SleepTime);SnakeMove(ps);//蛇移动一步} while (ps->_State == OK);//判断蛇的状态是否正常进行游戏
}
2.2.1帮助信息及得分打印

游戏运行期间,右侧打印帮助信息,提示玩家,坐标开始位置(64, 15)

PrintHelpInfo:
void PrintHelpInfo(psnake ps)
{//打印提示信息SetPos(64, 10);printf("得分: %2d", ps->_Score);SetPos(64, 11);printf("每个食物得分: %2d", ps->_FoodScore);SetPos(64, 14);printf("游戏说明:");SetPos(64,15);printf("不能穿墙,不能咬到自己");SetPos(64, 16);printf("用↑ . ↓ . ← . → 分别控制蛇的移动");SetPos(64, 17);printf("F3为加速,F4为减速");SetPos(64, 18);printf("ESC:退出游戏。Space:暂停游戏。");
}
2.2.2.方向、状态、速度判断(按键检测)

根据游戏状态检查游戏是否继续,如果是状态是OK,游戏继续,否则游戏结束。

如果游戏继续,就是根据前面介绍虚拟键值相关信息检测按键情况,确定蛇下一步的方向,或者是否加速减速,是否暂停或者退出游戏。

需要的虚拟按键的罗列:

• 上:VK_UP

• 下:VK_DOWN

• 左:VK_LEFT

• 右:VK_RIGHT

• 空格:VK_SPACE

• ESC:VK_ESCAPE

• F3:VK_F3

• F4:VK_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_SPACE))//检测是否按过空格,按过则游戏暂停{pause();}else if (KEY_PRESS(VK_F3))//检测是否按过F3,按过则游戏加速{if(ps->_SleepTime >= 50){ps->_SleepTime -= 30;//游戏加速就是休眠时间变短ps->_FoodScore += 2;//游戏加速,每个食物能获得分数上涨}}else if (KEY_PRESS(VK_F4))//检测是否按过F4,按过则游戏减速{if (ps->_SleepTime < 350){ps->_SleepTime += 30;//游戏减速就是休眠时间变长ps->_FoodScore -= 2;//游戏减速,每个食物能获得分数下降}if(ps->_SleepTime == 350)//蛇能减到的最低速度{ps->_FoodScore = 1;}}else if (KEY_PRESS(VK_ESCAPE))//检测是否按过ESC键,按过则游戏正常结束{ps->_State = END_NORMAL;//更新蛇的状态break;}
a.Pause
void pause()//按一次空格,游戏暂停
{while(1)//通过死循环的休眠程序达到暂停的效果{Sleep(200);if(KEY_PRESS(VK_SPACE)){break;///再按一次空格,游戏继续}}
}
b.KEY_PRESS

检测按键状态,笔者封装了⼀个宏
#define KEY_PRESS(VK) (GetAsyncKeyState(VK) & 1 ? 1 : 0)
2.2.3蛇身移动(SnakeMove)
2.2.3.1.SnakeMove:

蛇身移动是通过先创建下一个节点,根据移动方向和蛇头的坐标,获得下⼀个步位置上节点的坐标,之后看下一个位置是否是食物(NextIsFood),是食物就做吃食处理(EatFood),将食物的节点直接作为新蛇头,并销毁之前创建新节点,如果不是食物则做前进⼀步的处理(NoFood),将创建节点作为新蛇头插入原链表,循环遍历蛇身,打印蛇身字符,但是在原来蛇尾处应该打印两个空字符消除屏幕上原蛇尾,否则,蛇身会越来越长,最后再释放原来的尾节点。

除此之外,蛇身移动后,还应该判断此次移动是否会造成撞墙(KillByWall)或者撞上蛇身(KillBySelf),从而影响游戏的状态,导致游戏结束。

void SnakeMove(psnake ps)
{//创建下⼀个节点pSnakeNode cur = (pSnakeNode)malloc(sizeof(SnakeNode));if (NULL == cur){perror("InitSnake:malloc()");exit(1);}//确定下一个节点的坐标,下一个节点的坐标根据,蛇头的坐标和方向确定switch(ps->_Dir){case UP:cur->x = ps->_Psnake->x;cur->y = ps->_Psnake->y - 1;break;case DOWN:cur->x = ps->_Psnake->x;cur->y = ps->_Psnake->y + 1;break;case LEFT:cur->x = ps->_Psnake->x - 2;cur->y = ps->_Psnake->y;break;case RIGHT:cur->x = ps->_Psnake->x + 2;cur->y = ps->_Psnake->y;break;}//如果下一个位置就是⻝物if(NextIsFood(ps,cur)){EatFood(ps,cur);}else//如果没有⻝物{NotFood(ps, cur);}KillByWall(ps);KillBySelf(ps);
}
a.NextIsFood
//pSnakeNode pn 是下一个节点的地址
//pSnake ps 维护蛇的指针
int NextIsFood(psnake ps,pSnakeNode pn)
{return (ps->_Pfood->x == pn->x && ps->_Pfood->y == pn->y);
}
b.EatFood
//pSnakeNode pn 是下一个节点的地址
//pSnake ps 维护蛇的指针
void EatFood(psnake ps, pSnakeNode pn)
{//头插法:将属于食物的节点插入到原链表中,作为新的蛇头ps->_Pfood->next = ps->_Psnake;ps->_Psnake = ps->_Pfood;pSnakeNode cur = ps->_Psnake;//打印蛇while(cur){SetPos(cur->x,cur->y);wprintf(L"%c",BODY);cur = cur->next;}ps->_Score += ps->_FoodScore;free(pn);pn = NULL;CreateFood(ps);//吃掉食物后再创建一个食物
}
c.NotFood

将下⼀个节点头插入蛇的身体,并将之前蛇身最后⼀个节点打印为空格,释放掉蛇身的最后⼀个节点

需要注意的是:释放最后⼀个结点后,还得将指向在最后⼀个结点的指针改为NULL,保证蛇尾打印可以正常结束,不会越界访问。

//pSnakeNode pn 是下一节点的地址
//pSnake ps 维护蛇的指针
void NotFood(psnake ps, pSnakeNode pn)
{//头插法:将创建的节点插入到原链表中,作为新蛇头   pn->next = ps->_Psnake;ps->_Psnake = pn;pSnakeNode cur = ps->_Psnake;//打印蛇while(cur->next->next){SetPos(cur->x, cur->y);wprintf(L"%c", BODY);cur = cur->next;}//最后⼀个位置打印空格,然后释放节点,将最后节点的next置为NULL否则会越界访问SetPos(cur->next->x, cur->next->y);printf("  ");free(cur->next);cur->next = NULL;
}
d.KillByWall

判断蛇头的坐标是否和墙的坐标冲突

//pSnake ps 维护蛇的指针
void KillByWall(psnake ps)
{if(ps->_Psnake->x == 0 || ps->_Psnake->x == 56 || ps->_Psnake->y == 0 || ps->_Psnake->y == 25){ps->_State = KILL_BY_WALL;}
}
e.KillBySelf

判断蛇头的坐标是否和蛇身体的坐标冲突

//pSnake ps 维护蛇的指针
void KillBySelf(psnake ps)
{pSnakeNode cur = ps->_Psnake->next;while(cur){if((ps->_Psnake->x ==  cur->x) && (ps->_Psnake->y == cur->y)){ps->_State = KILL_BY_SELF;return;}cur = cur->next;}
}

2.3游戏结束


游戏状态不再是OK(游戏继续)的时候,要告知游戏结束的原因,并且释放蛇身节点。

void GameEnd(psnake ps)
{pSnakeNode cur = ps->_Psnake;SetPos(24, 12);switch(ps->_State){case END_NORMAL:printf("您退出了游戏,游戏结束!");break;case KILL_BY_SELF:printf("您吃到了自己,游戏结束!");break;case KILL_BY_WALL:printf("您撞到了墙,游戏结束!");break;}//释放蛇身的节点while (cur){pSnakeNode next = cur->next;free(cur);cur = next;}}

2.4重来一局相关问题

正常一场游戏不管怎么样,一运行必然是直接开始游戏,因此笔者这里采用do..while结构来实现这个效果,而游戏结束后程序应该询问玩家是否再玩一局,这时以输入Y/或N为重开的标志,考虑到玩家可能出于手误或其他原因出现;”大小写不分“、”多个输入“等多种问题,scanf使用不安全,因此笔者这里采用getchar()来接受玩家的输入值,同时判断重来的条件也大小写都可以。

但是需要注意的是getchar函数是自动的从输入缓冲区读取字符的,因此假设当上一句我们输入Y

,我们是会回车键确定输入的,但是回车键按过的同时,输入缓冲区是会存入一个'\n',因此下一句getchar()是不会等玩家输入的,它会直接读取‘\n’,同时又相当于按了回车键确定输入,因此游戏会出现直接结束的Bug.

对此我们通过while (getchar() != '\n');循环读取来清空缓冲区的输入,就可避免这个问题,同属由于getchar一次只读一个字符,我们还解决了一次输入多个字符可能造成的问题。

最后我们在循环开头加上清屏指令,清楚可能的残留。

void test()
{int ch = 0;srand((unsigned int)time(NULL));//通过rand产生随机坐标来产生随机食物的效果do{system("cls");snake sk = { 0 };GameStart(&sk);GameRun(&sk);GameEnd(&sk);SetPos(25, 15);//使询问是否重来的句子位置美观一点printf("您是否再来一局?(Y/N):");ch = getchar();while (getchar() != '\n');} while (ch == 'Y'|| ch == 'y');
}

六:参考代码

1.snake.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#define WALL L'□'
#define BODY L'■'
#define FOOD L'★'//蛇的初始位置#define POS_X 24
#define POS_Y 5
#define KEY_PRESS(VK) (GetAsyncKeyState(VK) & 1 ? 1 : 0)
#include<stdio.h>
#include<stdlib.h>
#include<windows.h>
#include<locale.h>
#include<stdbool.h>
#include<time.h>//方向
enum DIRECTION
{UP = 1,DOWN,LEFT,RIGHT
};//游戏状态
enum STATE
{OK,//正常运行END_NORMAL,//撞墙KILL_BY_WALL,//咬到自己KILL_BY_SELF,//正常结束
};//蛇身节点typedef struct SnakeNode
{short x;short y;struct SnakeNode* next;
}SnakeNode, * pSnakeNode;typedef struct Snake
{pSnakeNode _Psnake;//维护整条蛇的指针pSnakeNode _Pfood;//维护⻝物的指针enum DIRECTION _Dir;//蛇头的方向默认是向右enum STATE _State;//游戏状态int _Score;//当前获得分数int _FoodScore;//默认每个⻝物10分int _SleepTime;//每走一步休眠时间
}snake,*psnake;//设置光标的坐标void SetPos(short x, short y);//游戏初始化void GameStart(psnake ps);//欢迎界面void WelcomeToGame();//创建地图void CreatMap();//创建⻝物void CreateFood(psnake ps);//初始化蛇void InitSnake(psnake ps);//游戏运行过程void GameRun(psnake ps);//打印帮助信息void PrintHelpInfo(psnake ps);//蛇的移动void SnakeMove(psnake ps);//下一个节点是⻝物int NextIsFood(psnake ps, pSnakeNode pn);//吃⻝物void EatFood(psnake ps, pSnakeNode pn);//不吃⻝物void NotFood(psnake ps, pSnakeNode pn);//暂停响应void pause();//撞墙检测void KillByWall(psnake ps);//撞自身检测void KillBySelf(psnake ps);//游戏结束void GameEnd(psnake ps);

2.snake.c

#include"snake.h"//设置光标的坐标void SetPos(short x, short y)
{COORD pos = { x,y };HANDLE houtput = NULL;//获取标准输出的句柄(用来标识不同设备的数值)houtput = GetStdHandle(STD_OUTPUT_HANDLE);//设置标准输出上光标的位置为posSetConsoleCursorPosition(houtput, pos);}void GameStart(psnake ps)
{WelcomeToGame();//设置窗口名称大小、隐藏光标,打印欢迎界面CreatMap();//打印欢迎界面InitSnake(ps);//初始化蛇及其相关信息CreateFood(ps);//创造第一个食物}void GameRun(psnake ps)
{do{//打印右侧帮助信息PrintHelpInfo(ps);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))//检测是否按过F3,按过则游戏加速{if (ps->_SleepTime >= 50){ps->_SleepTime -= 30;//游戏加速就是休眠时间变短ps->_FoodScore += 2;//游戏加速,每个食物能获得分数上涨}}else if (KEY_PRESS(VK_F4))//检测是否按过F4,按过则游戏减速{if (ps->_SleepTime < 350){ps->_SleepTime += 30;//游戏减速就是休眠时间变长ps->_FoodScore -= 2;//游戏减速,每个食物能获得分数下降}if (ps->_SleepTime == 350)//蛇能减到的最低速度{ps->_FoodScore = 1;}}else if (KEY_PRESS(VK_ESCAPE))//检测是否按过ESC键,按过则游戏正常结束{ps->_State = END_NORMAL;//更新蛇的状态break;}//蛇每次⼀定之间要休眠的时间,时间短,蛇移动速度就快Sleep(ps->_SleepTime);SnakeMove(ps);//蛇移动一步} while (ps->_State == OK);//判断蛇的状态是否正常进行游戏
}void GameEnd(psnake ps)
{pSnakeNode cur = ps->_Psnake;SetPos(24, 12);switch(ps->_State){case END_NORMAL:printf("您退出了游戏,游戏结束!");break;case KILL_BY_SELF:printf("您吃到了自己,游戏结束!");break;case KILL_BY_WALL:printf("您撞到了墙,游戏结束!");break;}//释放蛇身的节点while (cur){pSnakeNode next = cur->next;free(cur);cur = next;}}void SnakeMove(psnake ps)
{//创建下⼀个节点pSnakeNode cur = (pSnakeNode)malloc(sizeof(SnakeNode));if (NULL == cur){perror("InitSnake:malloc()");exit(1);}//确定下一个节点的坐标,下一个节点的坐标根据,蛇头的坐标和方向确定switch(ps->_Dir){case UP:cur->x = ps->_Psnake->x;cur->y = ps->_Psnake->y - 1;break;case DOWN:cur->x = ps->_Psnake->x;cur->y = ps->_Psnake->y + 1;break;case LEFT:cur->x = ps->_Psnake->x - 2;cur->y = ps->_Psnake->y;break;case RIGHT:cur->x = ps->_Psnake->x + 2;cur->y = ps->_Psnake->y;break;}//如果下一个位置就是⻝物if(NextIsFood(ps,cur)){EatFood(ps,cur);}else//如果没有⻝物{NotFood(ps, cur);}KillByWall(ps);KillBySelf(ps);
}//pSnakeNode pn 是下一个节点的地址
//pSnake ps 维护蛇的指针
int NextIsFood(psnake ps,pSnakeNode pn)
{return (ps->_Pfood->x == pn->x && ps->_Pfood->y == pn->y);
}//pSnakeNode pn 是下一个节点的地址
//pSnake ps 维护蛇的指针
void EatFood(psnake ps, pSnakeNode pn)
{//头插法:将属于食物的节点插入到原链表中,作为新的蛇头ps->_Pfood->next = ps->_Psnake;ps->_Psnake = ps->_Pfood;pSnakeNode cur = ps->_Psnake;//打印蛇while(cur){SetPos(cur->x,cur->y);wprintf(L"%c",BODY);cur = cur->next;}ps->_Score += ps->_FoodScore;free(pn);pn = NULL;CreateFood(ps);//吃掉食物后再创建一个食物
}//pSnakeNode pn 是下一节点的地址
//pSnake ps 维护蛇的指针
void NotFood(psnake ps, pSnakeNode pn)
{//头插法:将创建的节点插入到原链表中,作为新蛇头   pn->next = ps->_Psnake;ps->_Psnake = pn;pSnakeNode cur = ps->_Psnake;//打印蛇while(cur->next->next){SetPos(cur->x, cur->y);wprintf(L"%c", BODY);cur = cur->next;}//最后⼀个位置打印空格,然后释放节点SetPos(cur->next->x, cur->next->y);printf("  ");free(cur->next);cur->next = NULL;
}void WelcomeToGame()
{//设置控制台窗口的大小,30行,100列//mode为DOS命令system("mode con cols=100 lines=30");//设置cmd窗口名称system("title 贪吃蛇");//获取标准输出的句柄(用来标识不同设备的数值)HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);//影藏光标操作CONSOLE_CURSOR_INFO CursorInfo;GetConsoleCursorInfo(houtput, &CursorInfo);//获取控制台光标信息CursorInfo.bVisible = false;//隐藏控制台光标SetConsoleCursorInfo(houtput, &CursorInfo);//设置控制台光标状态//SetPos调整光标位置SetPos(40, 15);printf("欢迎来到贪吃蛇小游戏!");SetPos(40, 25);//让按任意键继续的出现的位置好看点system("pause");system("cls");SetPos(25, 12);printf("用↑ . ↓ . ← . → 分别控制蛇的移动, F3为加速,F4为减速\n");SetPos(25, 13);printf("加速将能得到更高的分数。\n");SetPos(40, 25);//让按任意键继续的出现的位置好看点system("pause");system("cls");
}void CreatMap()
{int i = 0;SetPos(0,0);//宽字符一块墙体站X两个单位大小//上(0,0)-(56, 0)for(i = 0;i < 58; i = i + 2){wprintf(L"%c", WALL);}SetPos(0, 26);//下(0,26)-(56, 26)for (i = 0; i < 58; i = i + 2){wprintf(L"%c", WALL);}//左//x是0,y从0开始增⻓for (i = 0; i < 26; i++){SetPos(0, i);wprintf(L"%c", WALL);}//x是56,y从0开始增⻓for (i = 0; i < 26; i++){SetPos(56, i);wprintf(L"%c", WALL);}
}void InitSnake(psnake ps)
{pSnakeNode cur = NULL;int i = 0;//创建蛇身节点,并初始化坐标//头插法:创建新的头插入原链表中,并将新节点作为新蛇头for(i = 0;i < 5;i++){//创建蛇身的节点cur = (pSnakeNode)malloc(sizeof(SnakeNode));if (NULL == cur){perror("InitSnake:malloc()");exit(1);}//设置坐标cur->next = NULL;cur->x = POS_X + i*2;cur->y = POS_Y;//头插法if (ps->_Psnake == NULL){ps->_Psnake = cur;}else{cur->next = ps->_Psnake;ps->_Psnake = cur;}}//打印蛇的身体:改变光标位置,在对应光标位置打印身体字符cur = ps->_Psnake;while(cur){SetPos(cur->x,cur->y);wprintf(L"%c",BODY);cur = cur->next;}//初始化贪吃蛇数据ps->_Dir = RIGHT;ps->_FoodScore = 10;ps->_Score = 0;ps->_SleepTime = 200;ps->_State = OK;
}void CreateFood(psnake ps)
{int x = 0;int y = 0;
again://产生的x坐标应该是2的倍数,这样才可能和蛇头坐标对⻬do{x = rand() % 53 + 2;y = rand() % 25 + 1;} while (x % 2 != 0);pSnakeNode cur = ps->_Psnake; // 获取指向蛇头的指针//⻝物不能和蛇身冲突while(cur){if(cur->x == x && cur->y == y){goto again;}cur = cur->next;}pSnakeNode pfood = (pSnakeNode)malloc(sizeof(SnakeNode));//创建⻝物if (NULL == pfood){perror("CreateFood:malloc()");exit(1);}pfood->x = x;pfood->y = y;SetPos(x, y);wprintf(L"%c", FOOD);ps->_Pfood = pfood;
}void PrintHelpInfo(psnake ps)
{//打印提示信息SetPos(64, 10);printf("得分: %2d", ps->_Score);SetPos(64, 11);printf("每个食物得分: %2d", ps->_FoodScore);SetPos(64, 14);printf("游戏说明:");SetPos(64,15);printf("不能穿墙,不能咬到自己");SetPos(64, 16);printf("用↑ . ↓ . ← . → 分别控制蛇的移动");SetPos(64, 17);printf("F3为加速,F4为减速");SetPos(64, 18);printf("ESC:退出游戏。Space:暂停游戏。");
}void pause()//按一次空格,游戏暂停
{while(1)//通过死循环的休眠程序达到暂停的效果{Sleep(200);if(KEY_PRESS(VK_SPACE)){break;///再按一次空格,游戏继续}}
}
//pSnake ps 维护蛇的指针
void KillByWall(psnake ps)
{if(ps->_Psnake->x == 0 || ps->_Psnake->x == 56 || ps->_Psnake->y == 0 || ps->_Psnake->y == 25){ps->_State = KILL_BY_WALL;}
}//pSnake ps 维护蛇的指针
void KillBySelf(psnake ps)
{pSnakeNode cur = ps->_Psnake->next;while(cur){if((ps->_Psnake->x ==  cur->x) && (ps->_Psnake->y == cur->y)){ps->_State = KILL_BY_SELF;return;}cur = cur->next;}
}

3.test.c

#include"snake.h"void test()
{int ch = 0;srand((unsigned int)time(NULL));//通过rand产生随机坐标来产生随机食物的效果do{system("cls");snake sk = { 0 };GameStart(&sk);GameRun(&sk);GameEnd(&sk);SetPos(25, 15);printf("您是否再来一局?(Y/N):");ch = getchar();while (getchar() != '\n');} while (ch == 'Y'|| ch == 'y');
}int main()
{setlocale(LC_ALL, "");//修改当前地区为本地模式,为了支持中文宽字符及特殊字符的打印test();//测试逻辑SetPos(0, 27);//将光标移动至地图下方,不会破坏地图return 0;
}

  ​​​​​​​七​​​​​​​:附录参考:汉字字符集编码查询;中文字符集编码:GB2312、BIG5、GBK、GB18030、Unicode
 

相关文章:

【C语言】项目实践-贪吃蛇小游戏(Windows环境的控制台下)

一.游戏要实现基本的功能&#xff1a; • 贪吃蛇地图绘制 • 蛇吃食物的功能 &#xff08;上、下、左、右方向键控制蛇的动作&#xff09; • 蛇撞墙死亡 • 蛇撞自身死亡 • 计算得分 • 蛇身加速、减速 • 暂停游戏 二.技术要点 C语言函数、枚举、结构体、动态内存管…...

在做题中学习(50):搜索插入位置

35. 搜索插入位置 - 力扣&#xff08;LeetCode&#xff09; 解法&#xff1a;二分查找 思路&#xff1a;题目是有序的&#xff0c;时间复杂度O(logN),二分没跑了&#xff0c;题目说如果找不到target&#xff0c;返回它应该被插入位置的下标&#xff0c;所以可以分析一下示例2&…...

【mysql】mysql单表查询、多表查询、分组查询、子查询等案例详细解析

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…...

【Gateway远程开发】0.5GB of free space is necessary to run the IDE.

【Gateway远程开发】0.5GB of free space is necessary to run the IDE. 报错 0.5GB of free space is necessary to run the IDE. Make sure that there’s enough space in following paths: /root/.cache/JetBrains /root/.config/JetBrains 原因 下面两个路径的空间不…...

普通组件的注册-局部注册和全局注册

目录 一、局部注册和全局注册-概述 二、局部注册的使用示例 三、全局注册的使用示例 一、局部注册和全局注册-概述 组件注册有两种方式&#xff1a; 局部注册&#xff1a;只能在注册的组件内使用。使用方法&#xff1a;创建.vue文件&#xff0c;在使用的组件内导入并注册。…...

Apache Dubbo知识点表格总结

Dubbo是一个高性能的Java RPC框架&#xff0c;它提供了一系列的功能来支持分布式系统的开发。通常用于微服务之间的服务调用&#xff0c;顺便提一下也是用于微服务之间调用的OpenFeign&#xff0c;OpenFeign是Spring Cloud体系中的一个声明式HTTP客户端&#xff0c;用于简化HTT…...

电路板/硬件---器件

电阻 电阻作用 电阻在电路中扮演着重要的角色&#xff0c;其作用包括&#xff1a; 限制电流&#xff1a;电阻通过阻碍电子流动的自由而限制电流。这是电阻最基本的功能之一。根据欧姆定律&#xff0c;电流与电阻成正比&#xff0c;电阻越大&#xff0c;通过电阻的电流就越小。…...

STC15W1K16S和VC6.0串口通讯收发测试实例

/********************************************* STC USB 串口板 2014 4 7 20:12 发送接收数据 使用STC串口调试助手通讯正常&#xff0c;L161 **********************************************/ #include "reg51.h" #include "intrins.h" #define…...

Python程序设计 函数(三)

练习十一 函数 第1关&#xff1a; 一元二次方程的根 定义一个函数qg&#xff0c;输入一元二次方程的系数a,b,c 当判别式大于0&#xff0c;返回1和两个根 当判别式等于0&#xff0c;返回0和两个根 当判别式小于0&#xff0c;访问-1和两个根 在主程序中&#xff0c;根据函数返回…...

linux之ssh

SSH远程连接协议 SSH远程管理 定义 SSH&#xff08;Secure Shell &#xff09;是一种安全通道协议&#xff0c;主要用来实现字符界面的远程的登录、远程复制等功能。 SSH协议对通信双方的数据传输进行了加密处理&#xff0c;其中包括用户登录时输入的用户口令。因此SSH协议具…...

excel如何将多列数据转换为一列?

这个数据整理借用数据透视表也可以做到&#xff1a; 1.先将数据源的表头补齐&#xff0c;“姓名” 2.点击插入选项卡&#xff0c;数据透视表&#xff0c;在弹出对话框中&#xff0c;数据透视位置选择 现有工作表&#xff0c;&#xff08;实际使用时新建也没有问题&#xff09;…...

【Java 刷题记录】前缀和

前缀和 25. 一维前缀和 示例1&#xff1a; 输入&#xff1a; 3 2 1 2 4 1 2 2 3输出&#xff1a; 3 6import java.util.Scanner;// 注意类名必须为 Main, 不要有任何 package xxx 信息 public class Main {public static void main(String[] args) {Scanner in new Scanner(S…...

NVIDIA: RULER新测量方法让大模型现形

1 引言 最近在人工智能系统工程和语言模型设计方面的进展已经实现了语言模型上下文长度的高效扩展。以前的工作通常采用合成任务,如密钥检索和大海捞针来评估长上下文语言模型(LMs)。然而,这些评估在不同工作中使用不一致,仅揭示了检索能力,无法衡量其他形式的长上下文理解。 …...

2024数学-微积分和线性代数/本科研究生专业考试/考研/论文/重点公式考点汇总/最难公式投票

## 整体公式汇总列表 http://www.deepnlp.org/equation/category/math #### 微积分 ## 几何级数http://www.deepnlp.org/equation/arithmetic-and-geometric-progressions ## 级数收敛http://www.deepnlp.org/equation/convergence-of-series ## 二项式展开 http://www.dee…...

代码随想录训练营Day33(贪心算法):Leetcode1005、134、135(难得有一天能完全独立做出题目)

Leetcode1005: 题目描述&#xff1a; 给你一个整数数组 nums 和一个整数 k &#xff0c;按以下方法修改该数组&#xff1a; 选择某个下标 i 并将 nums[i] 替换为 -nums[i] 。 重复这个过程恰好 k 次。可以多次选择同一个下标 i 。 以这种方式修改数组后&#xff0c;返回数…...

Flutter笔记:Widgets Easier组件库(12)使用消息吐丝(Notify Toasts)

Flutter笔记 Widgets Easier组件库&#xff08;12&#xff09;使用消息吐丝&#xff08;Notify Toasts&#xff09; - 文章信息 - Author: 李俊才 (jcLee95) Visit me at CSDN: https://jclee95.blog.csdn.netMy WebSite&#xff1a;http://thispage.tech/Email: 29114848416…...

从《春色寄情人》学习如何面对死亡

经典台词&#xff0c;很震撼又很实用&#xff0c;记录一下。 ❤️01 有的时候好人不长命百岁&#xff0c;是因为老天爷觉得他们太累&#xff0c;让他们提前休息了&#xff01; ❤️02 跟我们亲近的人离世&#xff0c;有可能是老天给我们发的信号&#xff0c;提醒我们&#xff…...

使用moveit控制机械臂

在这篇博客中&#xff0c;我们将详细探讨如何利用Python和Robot Operating System&#xff08;ROS&#xff09;配合MoveIt! 控制机械臂执行精确的抓取任务。机械臂技术在工业自动化、医疗服务以及研究领域扮演着越来越关键的角色。本文将通过介绍安装必要的软件、编写控制脚本以…...

Mysql报错红温集锦(一)(ipynb配置、pymysql登录、密码带@、to_sql如何加速、触发器SIGNAL阻止插入数据)

一、jupyter notebook无法使用%sql来添加sql代码 可能原因&#xff1a; 1、没装jupyter和notebook库、没装ipython-sql库 pip install jupyter notebook ipython-sql 另外如果是vscode的话还需要安装一些相关的插件 2、没load_ext %load_ext sql 3、没正确的登录到mysql…...

ASP.NET Core SignalR 配置与集成测试究极指南

这篇文章也可以在我的博客中查看 前言 哥们最近都在埋头苦干&#xff0c;沉默是金&#xff0c;有一段时间没更新博客了。然而今儿SignalR集成测试实属是给我整破防了。虽说SignalR是.NET官方维护的实时通信库&#xff0c;已经开发了有十几年&#xff0c;甚至已经编入至了core…...

JENKINS 安装,学习运维从这里开始

Download and deployJenkins – an open source automation server which enables developers around the world to reliably build, test, and deploy their softwarehttps://www.jenkins.io/download/首先点击上面。下载Jenkins 为了学习&#xff0c;从windows开始&#x…...

大语言模型从Scaling Laws到MoE

1、摩尔定律和伸缩法则 摩尔定律&#xff08;Moores law&#xff09;是由英特尔&#xff08;Intel&#xff09;创始人之一戈登摩尔提出的。其内容为&#xff1a;集成电路上可容纳的晶体管数目&#xff0c;约每隔两年便会增加一倍&#xff1b;而经常被引用的“18个月”&#xf…...

四级英语翻译随堂笔记

降维表达&#xff1a;中译英&#xff0c;英译英 没有强调主语&#xff0c;没有说明主语&#xff1a;用被动 但如果实在不行&#xff0c;再增添主语 不会就不翻译&#xff0c;不要乱翻译 以xxx为背景&#xff1a;against the backdrop of the xxx eg:against the backdrop of…...

Nacos支持的配置格式及其在微服务架构中的应用

今天&#xff0c;我想和大家探讨一下Nacos这一重要的微服务组件&#xff0c;特别是它所支持的配置格式以及这些格式在微服务架构中的应用。 一、Nacos简介 Nacos是阿里巴巴开源的一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。它提供了服务发现、配置管理…...

2024年华为OD机试真题-小明找位置-(C++)-OD统一考试(C卷D卷)

题目描述: 小朋友出操,按学号从小到大排成一列;小明来迟了,请你给小明出个主意,让他尽快找到他应该排的位置。 算法复杂度要求不高于nLog(n);学号为整数类型,队列规模<=10000; 输入描述: 1、第一行:输入已排成队列的小朋友的学号(正整数),以”,”隔开; …...

机器人系统ros2内部接口介绍

内部 ROS 接口是公共 C API &#xff0c;供创建客户端库或添加新的底层中间件的开发人员使用&#xff0c;但不适合典型 ROS 用户使用。 ROS客户端库提供大多数 ROS 用户熟悉的面向用户的API&#xff0c;并且可能采用多种编程语言。 内部API架构概述 内部接口主要有两个&#x…...

跟随Facebook的足迹:社交媒体背后的探索之旅

在当今数字化时代&#xff0c;社交媒体已经成为了人们日常生活中不可或缺的一部分。而在这庞大的社交媒体网络中&#xff0c;Facebook作为其中的巨头&#xff0c;一直在引领着潮流。从创立之初的一个大学社交网络到如今的全球性平台&#xff0c;Facebook的发展历程承载了无数故…...

面试题分享之Java并发篇

注意&#xff1a;文章若有错误的地方&#xff0c;欢迎评论区里面指正 &#x1f36d; 系列文章目录 面试题分享之Java集合篇&#xff08;三&#xff09; 面试题分享之Java集合篇&#xff08;二&#xff09; 面试题分享之Java基础篇&#xff08;三&#xff09; 前言 今天给小…...

bpmn-js 多实例配置MultiInstanceLoopCharacteristics实现或签会签

使用bpmn-js流程图开发过程中会遇到会签和或签的问题,这个时候我们就需要使用多实例配置来实现BPMN 2.0的配置实现了,多实例任务,是从流程编辑概念之初也就是Activiti时期就存在的一个方式。所谓的多实例任务也就是字面意思,一个任务由多个人完成,常见于我们的审批流程的或…...

【gpedit.msc】组策略编辑器的安装,针对windows家庭版,没有此功能

创建一个记事本文件然后放入以下内容 echo offpushd "%~dp0"dir /b %systemroot%\Windows\servicing\Packages\Microsoft-Windows-GroupPolicy-ClientExtensions-Package~3*.mum >gp.txtdir /b %systemroot%\servicing\Packages\Microsoft-Windows-GroupPolicy-…...

带EXCEL附件邮件发送相关代码

1.查看生成的邮件 2.1 非面向对象的方式&#xff08;demo直接copy即可&#xff09; ​ REPORT Z12. DATA: IT_DOCUMENT_DATA TYPE SODOCCHGI1,IT_CONTENT_TEXT TYPE STANDARD TABLE OF SOLISTI1 WITH HEADER LINE,IT_PACKING_LIST TYPE TABLE OF SOPCKLSTI1 WITH HEADER LIN…...

【算法作业】均分卡牌,购买股票

问题描述 John 有两个孩子&#xff0c;在 John病逝后&#xff0c;留下了一组价值不一定相同的魔卡&#xff0c; 现在要求你设计一种策略&#xff0c;帮John的经管人将John的这些遗产分给他的两个孩子&#xff0c;使得他们获得的遗产差异最小&#xff08;每张魔卡不能分拆&#…...

python作业

题目 分析 步骤&#xff1a; 判断先画空格还是数字 当有n层时&#xff0c;第i层有多少个空格第i层的起始数字是几&#xff0c;结尾是几&#xff0c;即数字取值范围当有n层时&#xff0c;第i层有多少个数字 代码 模式A n int(input("请输入行数:")) for i in range(…...

【Linux的文件篇章 - 管道文件】

Linux学习笔记---013 Linux的管道文件1、进程间通信1.1、进程为什么要通信&#xff1f;1.2、进程如何通信&#xff1f;1.3、进程通信的方式&#xff1f; 2、匿名管道2.1、理解一种现象2.2、基本概念和管道原理 3、管道的使用3.1、代码样例3.2、如何使用管道通信呢&#xff1f;3…...

C# 局部静态函数,封闭方法中的最佳选择

C# 局部静态函数&#xff0c;封闭方法中的最佳选择 简介特性 应用场景辅助计算递归与尾递归优化筛选与过滤操作查找与映射操作 生命周期静态局部函数 vs 普通局部函数性能封装性可读性 简介 C# 局部静态函数&#xff08;Local Static Functions&#xff09;是一种函数作用域内…...

【MySQL】MySQL 8.4.0 长期支持版(LTS)安装

就在2024年 “5.1” 节前&#xff0c;MySQL官方发布了8.4.0长期支持版&#xff08;LTS - Long Term Support&#xff09;。根据官方提供的文档&#xff0c;在本地虚拟机进行安装测试。 安装、配置和启动过程记录如下&#xff1a; 第一步&#xff0c;上传到安装包&#xff08;my…...

nest中的ORM

在 Nest.js 中执行 SQL 查询通常涉及使用 TypeORM 或 Sequelize 这样的 ORM&#xff08;对象-关系映射&#xff09;库。这些库使得在 Nest.js 应用程序中连接和操作 SQL 数据库变得更加简单和直观。 以下是一个使用 TypeORM 在 Nest.js 中执行 SQL 查询的示例代码&#xff1a;…...

TCP(Transmission Control Protocol,传输控制协议)如何保证数据的完整性?

TCP&#xff08;Transmission Control Protocol&#xff0c;传输控制协议&#xff09;通过一系列机制来保证数据传输的可靠性和无错性&#xff0c;这些机制主要包括&#xff1a; 校验和&#xff1a;TCP报文段包含一个校验和字段&#xff0c;用于检测数据在传输过程中是否出错。…...

Numpy库介绍

NumPy&#xff08;Numerical Python的缩写&#xff09;是Python中用于科学计算的一个强大的库。它提供了高性能的多维数组对象&#xff08;即ndarray&#xff09;、用于处理这些数组的工具以及用于数学函数操作的函数。让我为你介绍一下它的一些主要功能&#xff1a; 1. 多维数…...

临时有事无法及时签字盖章?试试用契约锁设置“代理人”

遇到“领导休假中、在开重要会议、外出考察或者主任医生手术中等”一段时间内不方便或者无法及时签字盖章的情况怎么办&#xff1f;业务推进不了只能干等&#xff1f; 契约锁电子签及印控平台支持印章、签名“临时授权”、“代理签署”&#xff0c;实现指定人、指定时间段、指定…...

数据库权限管理

1.查看系统级权限&#xff08;global level) Select * from mysql.user\G; 2.查看数据库中所有表的权限 Select * from mysql.db\G 3.远程连接数据库 第一步在有数据库服务上的主机上&#xff1a;授权 grant all on *.* to root192.168.40.83 identified by Zxy20234; 第…...

如何创建一个 Django 应用并连接到数据库

简介 Django 是一个用 Python 编写的免费开源的 Web 框架。这个工具支持可扩展性、可重用性和快速开发。 在本教程中&#xff0c;您将学习如何为一个博客网站建立与 MySQL 数据库的初始基础。这将涉及使用 django-admin 创建博客 Web 应用程序的骨架结构&#xff0c;创建 MyS…...

【算法刷题day44】Leetcode:518. 零钱兑换 II、377. 组合总和 Ⅳ

文章目录 Leetcode 518. 零钱兑换 II解题思路代码总结 Leetcode 377. 组合总和 Ⅳ解题思路代码总结 草稿图网站 java的Deque Leetcode 518. 零钱兑换 II 题目&#xff1a;518. 零钱兑换 II 解析&#xff1a;代码随想录解析 解题思路 先遍历物品&#xff0c;再遍历背包。 代码…...

『51单片机』AT24C02[IIC总线]

存储器的介绍 ⒈ROM的功能⇢ROM的数据在程序运行的时候是不容改变的&#xff0c;除非你再次烧写程序&#xff0c;他就会改变&#xff0c;就像我们的书本&#xff0c;印上去就改不了了&#xff0c;除非再次印刷&#xff0c;这个就是ROM的原理。 注→在后面发展的ROM是可以可写可…...

Jenkins与Rancher的配合使用

Jenkins和Rancher是两个常用的DevOps工具&#xff0c;可以很好地配合使用来实现持续集成和持续部署。 Jenkins是一个开源的自动化构建工具&#xff0c;可以实现自动化的代码构建、测试和部署等一系列操作。可以通过Jenkins来触发构建任务&#xff0c;例如从代码仓库中拉取最新的…...

GIS入门,常用的多边形平滑曲线算法介绍和JavaScript的多边形平滑曲线算法库chaikin-smooth的实现原理和使用

前言 本章介绍一下常用的多边形平滑曲线算法及其使用案例。 多边形平滑算法通常用于图形处理或计算机图形学中,以使线条或曲线在连接处平滑过渡,而不出现明显的棱角或断裂。多边形平滑算法有多种实现方法,其中一些常见的有下面几种: 贝塞尔曲线插值(Bezier Curve Interpo…...

气膜体育馆内部的采光效果如何?—轻空间

气膜体育馆内部的采光效果如何&#xff1f;这是许多人对这种创新建筑的一个关键关注点。 首先&#xff0c;气膜体育馆的采光性非常好。阳光透过屋顶时以漫射光的方式进入室内&#xff0c;这种透射方式使得室内的光线柔和而均匀。从内部观察&#xff0c;整个屋顶就像一个连续的明…...

矩阵的对称正定性判决(复习)

文章目录 本科学的数学知识忘的太快了 如何判断一个实矩阵是否是对称正定 在线性代数中&#xff0c;一个实对称矩阵是否为正定可以通过以下方法判断&#xff1a; 对称性&#xff1a; 首先&#xff0c;确认矩阵是否对称&#xff0c;即矩阵的转置是否等于其本身。 特征值检查&…...

网络安全之DHCP详解

DHCP&#xff1a;Dynamic Host Configration Protocol 动态主机配置协议 某一协议的数据是基于UDP封装的&#xff0c;当它想确保自己的可靠性时&#xff0c;这个协议要么选确认重传机制&#xff0c;要么选周期性传输。 DHCP是确认重传&#xff0c;【UDP|DHCP】,当DHCP分配完地…...

【Proteus】LED呼吸灯 直流电机调速

1.LED呼吸灯 #include <REGX51.H> sbit LEDP2^0; void delay(unsigned int t) {while(t--); } void main() {unsigned char time,i;while(1){for(time0;time<100;time){for(i0;i<20;i){LED0;delay(time);LED1;delay(100-time);}}for(time100;time>0;time--){fo…...

PWM 什么是PWM?

1. 什么是PWM&#xff1f; PWM是Pulse Width Modulation的缩写&#xff0c;中文是脉冲宽度调制。 是利用微处理器的数字输出来对模拟电路进行控制的一种技术。 2. 面积等效原理 2.1. 什么是面积等效原理&#xff1f; 冲量相等而形状不同的窄脉冲施加在惯性环节上时&#xf…...

【吊打面试官系列】Java高并发篇 - 创建线程的有哪些方式?

大家好&#xff0c;我是锋哥。今天分享关于 【创建线程的有哪些方式&#xff1f;】面试题&#xff0c;希望对大家有帮助&#xff1b; 创建线程的有哪些方式&#xff1f; 1、继承 Thread 类创建线程类 2、通过 Runnable 接口创建线程类 3、通过 Callable 和 Future 创建线程 …...

面了科大讯飞 NLP 算法岗,被疯狂拷打。。。

节前&#xff0c;我们组织了一场算法岗技术&面试讨论会&#xff0c;邀请了一些互联网大厂朋友、今年参加社招和校招面试的同学。 针对大模型技术趋势、大模型落地项目经验分享、新手如何入门算法岗、该如何准备面试攻略、面试常考点等热门话题进行了深入的讨论。 总结链接…...

笔记本电脑忘记开机密码怎么办?不需重装系统,重置开机密码

笔记本电脑忘记开机密码&#xff0c;重置开机密码方法 这里记录个方法&#xff0c;亲测有效。我的电脑版本是Windows11&#xff0c;21H2。 步骤1&#xff1a;打开疑难解答界面 方式1&#xff1a;按住Shift键&#xff0c;然后重启电脑。我电脑使用这个方法无效。 方式2&#…...

小程序|锁定查询功能如何使用?

学生或家长想要实现自己查询完成后&#xff0c;任何人都无法再次查询&#xff0c;老师应该如何设置&#xff1f;易查分的【锁定查询功能】就可实现&#xff0c;下面教大家如何使用吧。 &#x1f4cc;使用教程 &#x1f512;锁定查询功能介绍 ✅学生或家长自主锁定&#xff1a;开…...

Git使用(4):分支管理

一、新建分支 首先选择Git -> Branches... 然后选择 New Branch&#xff0c;输入新分支名称&#xff0c;例如dev。 可以看到右下角显示已经切换到新建的dev分支了。 push到远程仓库&#xff0c;可以看到新添加的分支。 二、切换分支与合并分支 为了演示合并分支&#xff0c…...