C语言贪吃蛇实现
When the night gets dark,remember that the Sun is also a star.
当夜幕降临时,请记住太阳也是一颗星星。
————《去月球海滩篇》
目录
文章目录
一、《贪吃蛇》游戏介绍
二、WIN32部分接口简单介绍
2.1 控制台窗口大小设置
2.2 命令行窗口的名称的变更
2.3 vs平台下的调试窗口的设置
2.4 隐藏光标
2.5 控制光标的位置
2.6 获取键盘的值的情况
2.7 字符问题
三、贪吃蛇的实现
3.1 游戏开始前的准备 GameStart函数
3.1.1 设置好控制台界面的行和列,隐藏控制台光标
3.1.2 打印欢迎界面
3.1.3 绘制地图
3.1.4 初始化蛇身
3.1.5 创建食物
3.2 游戏运行 GameRun函数
3.2.1 打印帮助信息
3.2.2 判断玩家按下的按键
3.2.3 蛇的移动
3.2.3.1 NextFood
3.2.3.2 EatFood
3.2.3.3 Kill_Wall
3.2.3.4 Kill_Self
3.2.4 .Sleep函数、循环移动
3.3 贪吃蛇游戏结束的善后
3.4 再来一局,不言弃!!
一、《贪吃蛇》游戏介绍
也许正在看的你玩过一款名为《贪吃蛇》的小游戏,《贪食蛇》中玩家控制一条不断移动的蛇,在屏幕上吃掉出现的食物。每吃掉一个食物,蛇的身体就会变长。游戏的目标是尽可能长时间地生存下去,同时避免蛇头撞到自己的身体或屏幕边缘。玩家需要灵活操作,利用策略在有限的空间内避免碰撞,挑战高分。
通过对经典的《贪吃蛇》的简单介绍,可以知道这篇文章中实现的贪吃蛇选哟实现以下的几点:
- 通过按方向键上下左右,来改变蛇的移动方向
- 通过按F3键实现蛇的加速行进,按F4键可以降低蛇的移速。
- 按空格键可实现暂停,暂停后按任意键继续游戏。
- 按Esc键可直接退出游戏。
除此之外,本游戏还拥有计分系统,可保存玩家的历史最高记录。和食物分值显示,蛇的移速的不同可以改变食物的分值。
二、WIN32部分接口简单介绍
在贪吃蛇的实现过程当中,会使用部分WIN32的部分接口,此处简单介绍贪吃蛇的实现过程当中会使用到的API的功能以及简单说明:
2.1 控制台窗口大小设置
mode con cols=100 lines=30 #控制窗口,cols为行长度,lines为列行数
按住Win键+R键,打开Windows命令运行框,输入:cmd---------打开Windows控制台命令窗口,输入该指令,就可以调整窗口的大小了,效果如下:
回车运行后,控制台窗口的大小被修改了,通过观察窗口的属性值与我们设定一致
2.2 命令行窗口的名称的变更
命令行窗口的名称的变更,可以如下通过命令的方式来更改:
title 贪吃蛇#更改命令行窗口的名称
效果如下:
2.3 vs平台下的调试窗口的设置
在C语言中,需要使用system接口来改变终端 窗口的大小 以及 窗口名称,使用system接口需要包含 stdlib.h 头文件,例如下面代码:
#include<stdio.h>
#include<stdlib.h>//使用system接口的头文件int main()
{system("title 贪吃蛇");//将命令行窗口的名字更改为需要的名字system("mode con cols=100 lines=30");//设置命令行窗口的大小return 0;
}
2.4 隐藏光标
终端可以看作尾一个坐标系,左上角为坐标原点,向右为x轴,向下位y轴,如下图所示:
在windows窗口上描述一个坐标需要使用一个windows API中定义的一个结构体 COORD,表示一个字符在控制台屏幕缓冲区上的坐标,在C语言中,我们需要包含 windows.h 头文件才能使用,使用实例如下:
#include<stdio.h>
#include<windows.h>//调用该api需要的头文件
#include<stdlib.h>int main()
{COORD pos = { 10, 10 };//使用第一个参数为行,第二参数为列return 0;
}
为了实现光标隐藏,我们需要先调用 GetStdHandle 函数来获取标准输出句柄,使用这个句柄可以操作设备。
HANDLE output = NULL;//HANDLE为结构体指针类型
//获取标准输出句柄来表示不同设备的数值
output = GetStdHandle(STD_OUTPUT_HANDLE);
标准输出通常是指控制台窗口中打印文本的位置。通过使用GetStdHandle函数,可以获取表示标准输出的句柄,以便可以对其进行操作。函数的参数STD_OUTPUT_HANDLE是一个宏,它表示标准输出句柄的常量值。通过将此参数传递给GetStdHandle函数,可以获得标准输出的句柄。获取标准输出句柄后,可以使用其他Windows API函数来更改控制台的输出属性、位置和文本颜色等,以实现更复杂的控制台输出。
要隐藏光标,需要先获得一个光标信息,上面我们已经获取了标准输出相关设备的句柄,接下来我们创建 CONSOLE_CORSOR_INFO 结构体对象(接收有关主机光标信息的结构体),再调用 GetConsoleCursorInfo 函数来获得光标信息:
#include<stdio.h>
#include<windows.h>//调用win32 api所需要的头文件int main()
{HANDLE output = NULL;//获取标准输出句柄来表示不同设备的数值output = GetStdHandle(STD_OUTPUT_HANDLE);CONSOLE_CURSOR_INFO cursor_info;GetConsoleCursorInfo(output, &cursor_info);//获取光标的信息return 0;
}
CONSOLE_CURSOR_INFO这个结构体包含了控制台光标信息:
typedef struct _CONSOLE_CURSOLE_INFO {DWORD dwSize;BOOL bVisible;
}CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO;
- dwSize 参数,由光标填充的字符单元格的百分比。值范围为1到100。光标外观会变化,范围从完全填充单元格到单元底部的水平线条。
- bVisible 参数,设置光标的可见性,默认尾true;如果光标不可见,设置为false。
我们调用结构体的第二个参数设置为false(注意:C语言要包含 stdbool.h 头文件才能使用布尔类型)
GetConsoleCursorInfo 函数介绍
然后再调用 SetConsoleCursorInfo 函数来设置更改的光标信息。
#include<stdio.h>
#include<stdbool.h>
#include<windows.h>int main()
{HANDLE output = NULL;//获取标准输出句柄来表示不同设备的数值output = GetStdHandle(STD_OUTPUT_HANDLE);CONSOLE_CURSOR_INFO cursor_info;GetConsoleCursorInfo(output, &cursor_info);//获取光标的信息cursor_info.bVisible = false;SetConsoleCursorInfo(output, &cursor_info);//设置更改信息int ch = getchar();putchar(ch);return 0;
}
使用getchar putchar来输入输出信息,检测是否隐藏光标成功
下面是光标未被隐藏的结果,输入字符或者数字后,末尾会有一个光标在闪烁:
2.5 控制光标的位置
设置终端光标输出位置,我们首先要获取想要输出位置的坐标,上面我们介绍了COORD结构体,用来设置位置坐标。获取完坐标之后,我们可以调用 SetConsoleCorsorPosition 函数将光标位置设置到获取的坐标位置
BOOL SetConsoleCorsorPosition{HANDLE output;//句柄COORD pos;//位置
};
通过这个接口就可以将光标输出的信息放在想要的位置上了:
#include<stdio.h>
#include<stdbool.h>
#include<windows.h>int main()
{HANDLE output = NULL;//获取标准输出句柄来表示不同设备的数值output = GetStdHandle(STD_OUTPUT_HANDLE);COORD pos = { 20, 20 };SetConsoleCursorPosition(output, pos);int ch = getchar();putchar(ch);return 0;
}
考虑到贪吃蛇中会频繁的改变贯标的位置,不妨把这个接口封装为一个函数,来完成光标定位的功能:
//定位光标位置
void SearchLocal(int x,int y) {//获取控制台的句柄,控制权限,HANDLE 结构体指针HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);COORD pos = { x,y };// 获取设备句柄,传入坐标COORDSetConsoleCursorPosition(handle, pos);
}
2.6 获取键盘的值的情况
控制贪吃蛇的移动,加速或者减速,我们都是通过键盘按下对应的按键进行控制,那系统是如何知道我们按下的按键是哪一个呢?
使用 GetAsyncKeyState 函数来获取按键情况,此函数函数原型如下:
SHORT GetAsyncKeyState(int vKey);
将键盘上的键值传给函数,通过函数返回值来判断按键的状态。GetAsyncKeyState 返回值是short类型,在上一次调用此函数后,如果返回的16位的short数据中,最高位是1,说明按键的状态是按下,如果最高位是0,说明按键的状态是抬起;如果最低位被置为1,则说明该按键被按过,否则位0。
如果我们要判断按键是否被按过,只需要判断返回值最低值是否为1即可,我们可以按位与上0x1来获取最低位的值,那么我们就可这样来编写函数:
//检测按键是否被按过,间返回的虚拟值按位与上1,相同为1,相异为0,进行检测
//封装为一个宏
#define KEY_PRESS(vk) (GetAsyncKeyState(vk)&0x1?1:0)
前面提出的系统是如何判断我们按下的按键爱是哪一个的疑问,我们可以通过虚拟键码来判断是不同按键的不同状态,这样就可以实现一些按键响应的功能了。 Virtual-Key 代码 (Winuser.h) - Win32 apps | Microsoft Learnhttps://learn.microsoft.com/zh-cn/windows/win32/inputdev/virtual-key-codes
本文贪吃蛇中需要用到的虚拟键值如下:
2.7 字符问题
我们在打印蛇身和墙体的时候,总不能用数字或者字符来表示吧。所以需要用到特殊字符——宽字符。宽字符的长度为2字节,因为不同地区的语言不同,计算机中描述的方式也不太一样,普通的单字节字符并不适合我们的地区,因此C语言加入了宽字符(字符类型:wchar_t 需要包含 locale.h 头文件)允许程序员针对特定地区调整程序行为函数。
类项: 通过修改地区,程序可以改变它的行为来适应世界的不同区域。但是地区改变可能会影响库的许多部分,其中一部分可能是我们不希望修改的,所以C语言针对不同类型的类项进行修改,下面的一个宏指定一个类项:
- LC_COLLATE:影响字符串比较函数
- LC_CTYPE:影响字符处理函数行为
- LC_MONETARY:影响货币格式
- LC_NUMERIC:影响printf()函数
- LC_TIME:影响时间格式
- LC_ALL:针对所有类项修改,将以上所有类别设定为给定的语言环境
使用 setlocale 函数修改类项:
char* setlocale(int category, const char* locale);
函数的第一个参数可以是前面说明的类项中的一个,那么每次只会影响一个类项,如果第一个参数是LC_ALL,就会影响所有类项。C标准给第二个参数定义了2种可能取值:“C”(正常模式)和“”(本地模式) 在任意程序执行开始,默认隐式调用:
setlocale(LC_ALL, "C");//我们需要切换到本地环境输出字符,所以:
setlocale(LC_ALL, " ");//这种模式下程序会适应本地环境,切换后程序就支持宽字符的打印了
想要打印宽字符也是与普通打印不同的,宽字符字面量前必须加上L,否则C语言就会将其当为窄字符,且占位符应当为"%lc",和"%ls",才可正常打印宽字符。此处打印宽字符用到了wprintf函数,这里简单介绍一下:
wprintf 是C语言中用于宽字符输出的函数,它类似于 printf,但是用于处理宽字符(wchar_t)和宽字符串(wchar_t*)。这个函数定义在 wchar.h 头文件中,并且可以用来将格式化的数据打印到标准输出。
格式说明符:
在 wprintf 函数中,格式说明符的使用与 printf 函数相似。例如,%lc 用于宽字符,%ls 用于宽字符串。与 printf 不同的是,wprintf 需要使用 L 前缀来指定宽字符串字面量
#include<stdio.h>
#include<locale.h>int main()
{setlocale(LC_ALL, "");wchar_t ch[] = L"你好哇";wprintf(L"%ls\n", ch);return 0;
}
运行结果如下:
三、贪吃蛇的实现
贪吃蛇流程简述:贪吃蛇游戏分为三个大步骤,游戏开始,游戏运行,游戏结束,我们分别用三个函数来完成。
//贪吃蛇游戏界面初始化
GameStart(&snake);
//贪吃蛇游戏运行
GameRun(&snake);
//贪吃蛇游戏结束的善后
GameEnd(&snake);
在设计游戏之前,我们需要先写出蛇的相关信息,例如:蛇移动的方向,蛇的状态,食物的分数,当前游戏总分等......我们将这些信息用结构体和枚举列出:
//贪吃蛇游戏状态
enum GAMSTATUS {OK=1, //正常运行EXIT, //按ESC键退出KILL_BY_WALL, //撞墙了!KILL_BY_SELF //咬到自己了!
};//贪吃蛇行走方向
enum SNAKEWAY {UP = 1, //前进DOWN, //向下LEFT, //左拐RIGHT //右拐
};//贪吃蛇,蛇身节点的定义
typedef struct SnakeNode {//节点的位置int x;int y;//下一个蛇身节点struct SnakeNode* next;
}SnakeNode;//整局游戏贪吃蛇的维护
typedef struct Snake {//贪吃蛇,蛇头指针,方便找到贪吃蛇的位置/维护整条蛇的指针SnakeNode* SnakePhead;//指向食物的指针SnakeNode* FoodNode;//玩家当前得分int Scour;//每个食物的得分int FoofScour;//蛇休眠的时间,休眠的时间越短,蛇的速度越快,休眠的时间月长,蛇的速度越慢int SleepTime;//贪吃蛇游戏状态enum GAMSTATUS Status;//贪吃蛇行进的方向enum SNAKEWAY way;
}Snake;
3.1 游戏开始前的准备 GameStart函数
下面是GameStart函数:
void GameStart(Snake* ps) {//设定控制台屏幕大小system("mode con cols=100 lines=30");//设置控制台标题system("title 贪吃蛇");//设定光标,进行隐藏//获取控制台句柄HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);//创建结构体来接收控制台光标的数据//有两个参数:dwSize 光标占空比; bVisible 光标可见度 默认trueCONSOLE_CURSOR_INFO cursor_info = { 0 };GetConsoleCursorInfo(handle, &cursor_info);//隐藏光标cursor_info.bVisible = false;//将修改的光标属性值传给控制台进行控制,参数类型跟GetConsoleCursorInfo一样SetConsoleCursorInfo(handle, &cursor_info);//游戏开始欢迎界面welcome();//绘制地图GameMap();//贪吃蛇初始化InitSnake(ps);//创建食物CreatFood(ps);
}
接下来我们来逐一来了解一下GameStaert的答题流程,以及它实现了哪些功能。
3.1.1 设置好控制台界面的行和列,隐藏控制台光标
在这个大块中,我们首先需要设置好控制台界面的行和列,隐藏控制台光标:
3.1.2 打印欢迎界面
其次打印欢迎界面,用SearchLocal函数来找到合适的位置,使界面变得美观,期间使用了system("cls")来进行清屏操作:
//初始化贪吃蛇界面
void welcome() {//将光标定位到界面的中间位置SearchLocal(35, 10);printf("欢迎来到贪吃蛇小游戏\n");SearchLocal(38, 20);system("pause");system("cls");//功能介绍SearchLocal(15, 10);printf("用↑ . ↓ . ← . → 来控制蛇的移动,F3为加速按键,F4为减速\n");SearchLocal(35, 12);printf("加速能够获得更高的分数!!!!");SearchLocal(38, 20);system("pause");system("cls");
}
界面展示如下:
3.1.3 绘制地图
接着,绘制地图,墙壁的范围在控制台窗口的范围如图所示:
如果想在控制台的窗口中指定位置输出信息,我们需要得知该位置的坐标,所以⾸先介绍⼀下控制台窗口的坐标知识。
在控制台窗口中,行和列的大小也是不一样的:
可以看到,在控制台窗口中,行明显要比列更大,那在我们打印墙壁时,就需要用到宽字符:'□',宽字符与普通字符不同,一个宽字符的大小是两个字节,并且一个宽字符在窗口中占一行两列(两个坐标的大小),而一个普通字符的大小是一个字节,一个普通字符在窗口中占一行一列(一个坐标的大小),打印出来会更加美观。
这里我们把墙壁的行列定义为宏,后面可以根据自己的需要修改墙壁的大小,本文用到的是27行,58列的墙壁
#define ROW 26 //游戏区行数
#define COL 56 //游戏区列数
这里列的偏移值每次加2,是因为宽字符的x轴的密度与y轴的密度不一致,而设置每次偏移2,这样打印下一个宽字符的时候不会重叠
//绘制地图
void GameMap() {//上SearchLocal(0, 0);for (int i = 0; i < COL; i += 2) {wprintf(L"%c", WALL);}//左for (int i = 1; i < ROW; i++) {SearchLocal(0, i);wprintf(L"%lc", WALL);}//右for (int i = 1; i < ROW; i++) {SearchLocal(COL-1, i);wprintf(L"%lc", WALL);}//下SearchLocal(0, ROW-1);for (int i = 0; i < COL; i += 2) {wprintf(L"%lc", WALL);}
}
3.1.4 初始化蛇身
在这里,得用到数据结构中的链表,每个蛇身视为一个节点(游戏开始时有5个节点),将这5个节点的坐标相邻(利用头插法初始化),依次打印(最后要初始化一下蛇的属性):
初始蛇头的位置:
#define Origin_x 24 // 初始蛇头的x坐标
#define Origin_y 5 // 初始蛇头的y坐标
//打印蛇身
void SnakePrint(Snake* ps) {SnakeNode* pcur = ps->SnakePhead;while (pcur) {SearchLocal(pcur->x, pcur->y);wprintf(L"%lc", BODY);pcur = pcur->next;}
}//初始化蛇
void InitSnake(Snake*ps) {//创建5个蛇身节点SnakeNode* pcur = NULL;for (int i = 0; i < 5; i++) {pcur = (SnakeNode*)malloc(sizeof(SnakeNode));if (pcur == NULL) {perror("malloc");return;//exit(1)}//头插pcur->x = Origin_x+2*i;pcur->y = Origin_y;pcur->next = NULL;if (ps->SnakePhead == NULL) { ps->SnakePhead = pcur;}else {pcur->next = ps->SnakePhead;ps->SnakePhead = pcur;}}//打印蛇身SnakePrint(ps);//初始化ps->Scour = 0;ps->FoodNode = NULL;ps->FoofScour = 10;ps->SleepTime = 200;ps->Status = OK;ps->way = RIGHT;
}
3.1.5 创建食物
将食物也当成一个普通的SnakeNode节点,因为食物也具有和蛇身节点相同的属性,只不过它的next指针永远为空,而它的x和y坐标是随机生成的,所以这里我们需要用到rand函数来生成随机数,不过生成的随机数也需要具备如下特点:
- x坐标的值为2~54,y坐标点值为1~25(不能在墙壁上或墙壁外)
- 必须为2的倍数(保证蛇可以吃到食物)
- 不能和蛇身的坐标重合
确定好以上条件后,我们就可以将食物打印在地图上了。
//创建食物
void CreatFood(Snake* ps) {//随机生成食物的坐标(x,y)//1.不能超过墙,且不能重叠 x:2~54 -- 52+2 y:1~24 -- 23+1//2.食物不能与蛇身重叠,遍历蛇身//创建食物节点SnakeNode* FoodNode = (SnakeNode*)malloc(sizeof(SnakeNode));if (FoodNode == NULL) {perror("creat FoodNode fail");return;}int x = 0;int y = 0;again:do {x = rand() % (COL-4) + 2;y = rand() % (ROW-2) + 1;} while (x % 2 != 0); //1.不能超过墙,且不能重叠//2.食物不能与蛇身重叠,遍历蛇身SnakeNode* pcur = ps->SnakePhead;while (pcur) {if (x == pcur->x && y == pcur->y) {goto again;}pcur = pcur->next;}//真正创建了属于食物的坐标FoodNode->x = x;FoodNode->y = y;FoodNode->next = NULL;ps->FoodNode = FoodNode;//打印食物SearchLocal(x,y);wprintf(L"%lc", FOOD);
}
由于食物是随机生成的(在蛇可爬行区域内),而食物有几率生成到蛇的身上,所以每生成一个食物节点后,得遍历整条蛇;如果重叠,就重新生成一个食物,直到不重叠为止。此处用到了again:,这里简略的介绍一下:
在 C 语言中, again: 不是一个关键字或预定义标识符,它只是一个标识符的名称,可以用作标签(label)。 在 C 语言中,标签通常用于在嵌套循环或 switch 语句中进行无条件跳转,从而提高代码的可读性和灵活性。 例如,使用 again: 标签可以在循环中使用 goto 语句来实现无限循环。 但是,使用 goto 语句会使代码变得难以理解和维护,因此应该谨慎使用。
3.2 游戏运行 GameRun函数
下面是 GameRun函数:
//贪吃蛇游戏运行
void GameRun(Snake* ps) {//打印操作介绍PrintOption();do{//打印当前得分情况SearchLocal(65, 11);printf("当前得分:%d",ps->Scour);SearchLocal(78, 11);printf("当前食物分值:%02d", ps->FoofScour); //每次移动分值可能变化放到循环中//检测按键是否按过if (KEY_PRESS(VK_UP)&& ps->way !=DOWN) {ps->way = UP;}else if(KEY_PRESS(VK_DOWN) && ps->way != UP) {ps->way = DOWN;}else if (KEY_PRESS(VK_LEFT) && ps->way != RIGHT) {ps->way = LEFT;}else if (KEY_PRESS(VK_RIGHT) && ps->way != LEFT) {ps->way = RIGHT;}else if (KEY_PRESS(VK_ESCAPE)) {ps->way = EXIT;break;}//空格进入暂停,死循环睡眠,在次按下空格进行游戏else if (KEY_PRESS(VK_SPACE)) {pause();}//F3加速,睡眠时间减少,食物分值增加,限制条件睡眠时间不为负数else if (KEY_PRESS(VK_F3)) {//时间不能为负,每次-30,至多减到20;if (ps->SleepTime > 20) {ps->SleepTime -= 30;ps->FoofScour += 2;}}//F4减速,睡眠时间增加,食物分值减少,限制条件食物分值不为0else if (KEY_PRESS(VK_F4)) {if (ps->FoofScour > 2) {ps->SleepTime += 30;ps->FoofScour -= 2;}}//贪吃蛇开始移动SnakeMove(ps);//睡眠一下Sleep(ps->SleepTime);} while (ps->Status == OK);
}
3.2.1 打印帮助信息
游戏开始后的帮助信息进行打印:
将光标移到指定位置(如果更改墙体的大小,帮助信息的位置光标需要做对应的修改,还有如果超出了窗口大小,可以调整窗口的大小),打印即可:
//打印帮助信息
void PrintOption() {SearchLocal(63, 17);printf("用↑ . ↓ . ← . → 来控制蛇的移动");SearchLocal(63, 18);printf("space(空格)为暂停/回到游戏按键");SearchLocal(68, 19);printf("F3为加速按键,F4为减速"); SearchLocal(72, 20);printf("ESC为退出按键");SearchLocal(63, 21);printf("注意:不能撞墙,不能咬到自己!!!");
}
3.2.2 判断玩家按下的按键
接下来,玩家会通过按方向键(↑ . ↓ . ← . →)以及F3、F4来控制蛇的移动,我们就需要用到我们上面所定义好的宏,传入各个键的虚拟键值,利用if else语句来判断玩家所按下的键,但是需要注意的是:如果蛇正在向下移动,则蛇的移动方向不能突然变为向上,否则会撞到自己(即向下移动时,按上方向键无效),左右上方向时同理:
//休眠函数
void pause() {while (1) {Sleep(200);if (KEY_PRESS(VK_ESCAPE)) {break;}}
}
3.2.3 蛇的移动
可以把下一步要移动到的坐标当做一个SnakeNode节点,malloc出一个新节点(这里命名为pnext),再根据蛇的方向设置这个节点的x,y值,接着,我们需要判断移动到的下一个坐标是不是食物,如果是食物则头插进蛇体中(不要忘记释放掉pnext节点(因为食物节点和pnext的位置是重复的));如果不是食物,也需要将pnext节点头插到蛇体中,要将最后一个节点用" "进行覆盖,不让这个节点出现在屏幕上。然后接着判断这次移动是否撞到了墙或者自己。
//贪吃蛇开始移动
void SnakeMove(Snake*ps){//创建指针记录蛇移动的下一个位置SnakeNode* pnext = (SnakeNode*)malloc(sizeof(SnakeNode));if (pnext == NULL) {perror("SnakeMove::malloc");return;}pnext->next = NULL;//记录蛇移动的下一个位置switch (ps->way){case UP:pnext->x = ps->SnakePhead->x;pnext->y = ps->SnakePhead->y - 1;break;case DOWN:pnext->x = ps->SnakePhead->x;pnext->y = ps->SnakePhead->y + 1;break;case LEFT:pnext->x = ps->SnakePhead->x-2;pnext->y = ps->SnakePhead->y;break;case RIGHT:pnext->x = ps->SnakePhead->x + 2;pnext->y = ps->SnakePhead->y;break;}//判断下一个位置是否为食物//是食物,吃掉 --- 头插if (NextFood(ps,pnext)) {EatFood(ps, pnext);}//不是食物,头插删除尾节点else {NotEatFood(ps, pnext);}//打印蛇身SnakePrint(ps);//检测是否穿墙Kill_Wall(ps);//检测是否咬到自己Kill_Self(ps);
}
3.2.3.1 NextFood
判断下一个节点的坐标是否与食物的节点的坐标一致,是则返回1;反之返回0。
//下一个位置是否为食物;
int NextFood(Snake* ps, SnakeNode* pnext) {if (pnext->x == ps->FoodNode->x && pnext->y == ps->FoodNode->y) {return 1;}else{return 0;}
}
3.2.3.2 EatFood
//吃食物
void EatFood(Snake* ps, SnakeNode* pnext) {pnext->next = ps->SnakePhead;ps->SnakePhead = pnext;//得分增加ps->Scour += ps->FoofScour;//释放旧的食物free(ps->FoodNode);ps->FoodNode = NULL;//重新创建食物CreatFood(ps);
}
3.2.3.3 Kill_Wall
//检测是否穿墙
void Kill_Wall(Snake* ps) {if (ps->SnakePhead->x == 0 ||ps->SnakePhead->x == (COL-1) ||ps->SnakePhead->y == 0 ||ps->SnakePhead->y == (ROW-1)) {ps->Status = KILL_BY_WALL;}
}
3.2.3.4 Kill_Self
//检测是否咬到自己
void Kill_Self(Snake* ps) {SnakeNode* pcur = ps->SnakePhead->next;while (pcur) {if (ps->SnakePhead->x == pcur->x && ps->SnakePhead->y == pcur->y) {ps->Status = KILL_BY_SELF;return;}pcur = pcur->next;}
}
3.2.4 .Sleep函数、循环移动
需要一段时间来间断一下蛇体的移动,并判断蛇的状态进行循环:
此处用到了Sleep函数,依旧简略的介绍一番:
Sleep()
函数是 Windows 平台下的一个 API,定义在windows.h
头文件中。其功能是挂起(暂停)当前线程的执行一段时间。输入参数是毫秒数。
3.3 贪吃蛇游戏结束的善后
打印出提示信息告诉玩家游戏失败的原因,最后,因为蛇身的节点是我们动态malloc开辟的,为了避免内存泄露,我们需要一一free掉。
//贪吃蛇游戏结束的善后
void GameEnd(Snake* ps) {SearchLocal(15, 10);//打印结束状态switch (ps->Status) {case EXIT:printf("正常退出,游戏结束了");break;case KILL_BY_WALL:printf("很遗憾,游戏结束,你撞到墙了!!!");break;case KILL_BY_SELF:printf("不小心咬到自己了,游戏结束了!!!");break;}//释放空间SnakeNode* pcur = ps->SnakePhead;SnakeNode* del = NULL;while (pcur) {del = pcur->next;free(pcur);pcur = del;}free(ps->FoodNode);ps = NULL;}
3.4 再来一局,不言弃!!
游戏失败后,为了使游戏更完整,设置循环提示玩家是否再来一局,这里用do while循环来实现:
void snakegame() {int ch = 0;do {//创建贪吃蛇Snake snake = { 0 };//贪吃蛇游戏界面初始化GameStart(&snake);//贪吃蛇游戏运行GameRun(&snake);//贪吃蛇游戏结束的善后GameEnd(&snake);SearchLocal(15, 8);printf("要再来一局吗?? Y/N:");ch = getchar();getchar();// 清理\nSearchLocal(0, 27);} while (ch == 'Y' || ch == 'y');
}
相关文章:
C语言贪吃蛇实现
When the night gets dark,remember that the Sun is also a star. 当夜幕降临时,请记住太阳也是一颗星星。 ————《去月球海滩篇》 目录 文章目录 一、《贪吃蛇》游戏介绍 二、WIN32部分接口简单介绍 2.1 控制台窗口大小设置 2.2 命令行窗口的名称的变更 2…...
基于数据挖掘的网络入侵检测关键技术研究
标题:基于数据挖掘的网络入侵检测关键技术研究 内容:1.摘要 随着互联网的迅速发展,网络安全问题日益严峻,网络入侵行为对个人、企业和国家的信息安全构成了巨大威胁。本文的目的是研究基于数据挖掘的网络入侵检测关键技术,以提高网络入侵检测…...
git上传大文件到远程仓库中
git 上传大文件报错 上传大文件文件到远程仓库上面,出现错误(gitee-100M,github-50M) remote: error: File: f422c55c723a183a1944cbec840c0171042c8251 135 MB, exceeds 100.00 MB. 意思是单个文件超过100M导致上传失败。 安装LFS curl…...
计算机网络基础之三种交换技术及其性能分析
一. 交换技术基础 1. 三种交换技术 电路交换:用于电话网络报文交换:用于电报网络分组交换:用于现代计算机网络 2. 人类历史上的通信网络 #mermaid-svg-AeGvrkUbCkicFOIo {font-family:"trebuchet ms",verdana,arial,sans-serif;…...
ANYmal Parkour: Learning Agile Navigation for Quadrupedal Robots
ANYmal Parkour: Learning Agile Navigation for Quadrupedal Robots 研究动机解决方案技术路线感知模块运动模块导航模块补充 实验结果 ANYmal Parkour: Learning Agile Navigation for Quadrupedal Robots 研究动机 行走控制器不能依赖于稳定和周期性的步态,而必…...
【AI学习笔记】AI造神时代的潘式理论与智能进化
背景前摇: 周会分享选题,决定选择这篇华为蓝军部长潘少钦先生所著的文章,原题目为《AI如此强大,我是否要改行?》。选择这篇文章的理由是,其不仅有充实扎实的AI基础知识作为铺垫,更具有独特鲜明…...
CVE-2021-45232未授权接口练习笔记
CVE-2021-45232 是 Apache APISIX Dashboard 中的一个严重权限漏洞,类似于攻击者无需密码即可拿到整个网关系统的“万能钥匙”。攻击者利用此漏洞,可直接操控网关流量转发规则,甚至远程执行代码,引发服务器沦陷。 默认账户密码导致…...
远场分量(平面波角谱)与倏逝波
远场分量(平面波角谱)与倏逝波的详细解释 在光学和电磁学中,远场分量(平面波角谱)和倏逝波是描述光场传播特性的两个核心概念,尤其在衍射理论、近场光学和超分辨成像中至关重要。以下是它们的物理意义、数…...
修改Flutter工程中Android项目minSdkVersion配置
Flutter项目开发过程中,根据模板自动生成.android项目,其中app>build.gradle中minSdkVersion的值是19,但是依赖了一个三方库,它的Android sdk 最小版本只支持到21,运行报错如下: 我们可以手动修改.andro…...
后端返回了 xlsx 文件流,前端怎么下载处理
当后端返回一个 .xlsx 文件流时,前端可以通过 JavaScript 处理这个文件流并触发浏览器下载。 实现步骤 发送请求获取文件流: 使用 fetch 或 axios 等工具向后端发送请求,确保响应类型设置为 blob(二进制数据流)。 创建…...
js中async+await+promise的用法及常见问题总结
文章目录 概况asyncawaitPromise总结常见问题 概况 在ts/js中,async 和 await 是用于简化异步操作的关键字,一般与Promise联用(不理解Promise可以看一下这篇《JS中Promise用法(简要说明)》)。它们的核心作…...
单纯形法之大M法
1. 问题背景与标准化 在求解某些线性规划问题时,往往难以直接找到初始的基本可行解。特别是当约束中存在等式或 “≥” 类型的不等式时,我们需要引入人工变量来构造一个初始可行解。 考虑如下标准形式问题(假设为最大化问题)&am…...
一个数组分为两个sum相等的数组
vector,问是否可以拆成两部分,使其两部分的总和相同,用代码写一下 #include <iostream> #include <vector>using namespace std;bool canPartition(vector<int>& nums) {int sum 0;for (int num : nums) {sum num;…...
Socket如何实现客户端和服务器间的通信
使用Socket实现客户端和服务器间的通信 Socket是一种网络编程接口,广泛用于实现客户端和服务器之间的通信。在网络应用程序中,Socket提供了一种简单而强大的机制来建立和管理网络连接。本文将详细介绍如何使用Python的Socket模块来实现基本的客户端和服…...
崖山数据库(YashanDB)部署全流程详解
文章目录 引言 第1部分:环境准备 服务器要求 初始环境调整 第2部分:yasboot工具介绍 yasboot核心功能 yasboot进程架构 第3部分:YashanDB安装步骤 创建安装用户 目录规划 命令行安装流程 步骤1:生成配置文件 步骤2&a…...
07_JavaScript函数作用域_递归
目录 一、作用域(重点) 二、变量的使用规则 (重点) 2.1 访问规则 2.2 赋值规则 三、递归函数 (难点) 了解 四、对象 4.1 对象的创建 一、作用域(重点) 什么是作用域 ? 作用…...
基于大模型预测的初治菌阳肺结核诊疗方案研究报告
目录 一、引言 1.1 研究背景与意义 1.2 研究目的 二、初治菌阳肺结核概述 2.1 疾病定义与病理机制 2.2 流行病学特征 2.3 传统诊疗方法与局限性 三、大模型在初治菌阳肺结核预测中的应用原理 3.1 大模型技术简介 3.2 数据收集与预处理 3.3 模型构建与训练 3.4 模型…...
C# Modbus TCP/IP学习记录
Modbus协议中,角色分为主站(Mater)、从站(Slave);数据类型分为线圈(Coil)、离散输入(Input)、保持寄存器(HoldingRegister)、输入寄存…...
斜线、短横、空格,三种分隔日期的优雅解析(Python | DeepSeek)
标准日期解析操作,str.replace链式如灵蛇蜿蜒,三元表达式像空灵仙家妙法。 笔记模板由python脚本于2025-03-25 22:32:24创建,本篇笔记适合三元表达式、字符串操作修习的coder翻阅。 【学习的细节是欢悦的历程】 博客的核心价值:在…...
Skynet 中 snlua 服务启动整体流程分析
前言: 在 Skynet 中,Lua 扮演了极其重要的角色。Skynet 大多数业务逻辑都跑在一个个 Lua 服务里,而能够将 Lua 环境嵌入到 Skynet 框架下,并与 Skynet 消息调度机制完美结合,正是 snlua 服务所承担的核心功能。 本文将…...
RWA代币化崛起中的香港机遇:数字金融新枢纽的破局之道
引言:全球资产代币化浪潮中的香港坐标 在2025年全球金融数字化重构的关键节点,RWA(现实世界资产代币化)市场以年均740%的增速重塑价值流动规则。香港凭借独特的政策创新、跨境枢纽优势及庞大的资产储备,正从传统金融中…...
Docker Compose介绍
基本概念 Docker-Compose是Docker官方的开源项目,负责实现对docker容器集群的快速编排。 可以这么理解,docker compose是docker提出的一个工具软件,可以管理多个docker容器组成一个应用,只需要编写一个YAML格式的配置文件docker…...
【LeetCode 题解】算法:15.三数之和
一、问题描述 在 LeetCode 上有这样一道经典的算法题,题目要求给定一个整数数组 nums,找出所有不重复的三元组 [nums[i], nums[j], nums[k]],需要满足以下两个条件: 三个元素的索引互不相同,即 i ! j,i ! …...
springboot使用阿里限流框架-sentinel
当前项目源码 控制台下载 启动bin中的看板服务:账号密码:sentinel/sentinel 官方文档地址 项目引入依赖 <!-- sentinel注解支持 --> <dependency><groupId>com.alibaba.csp</groupId><artifactId>sentinel-annotation-aspectj<…...
Ubuntu20.04系统安装IsaacSim4.5与IsaacLab环境
Introduction 今天用自己的Ubuntu20.04系统安装最新更新的IsaacSim 4.5 与 最新版的IsaacLab遇到了问题,且这个问题组里师兄之前也遇到了,我的解决方法是从头来过,所及记录一下。 IsaacSim现在支持pip安装,但是Ubuntu20.04的pip…...
【大模型】数字人 EchoMimicV2 的环境配置和使用
一、EchoMimicV2的简单介绍 EchoMimicV2是EchoMimicV1的升级版本,旨在通过音频驱动生成更加自然和流畅的半身人类动画。它能够让用户仅通过简单的音频输入,生成与声音内容相匹配的动画效果,使虚拟人物看起来更加生动。 作者:由蚂蚁…...
导入 Excel 规则批量修改或删除 PDF 文档内容
需要对 PDF 文档内容进行修改的时候,通常我们会需要借助一些专业的工具来帮我们完成。那我们如果需要修改的 PDF 文档较多的时候,有什么方法可以帮我们实现批量操作呢?今天这篇文章就给大家介绍一下当我们需要批量修改多个 PDF 文档的时候&am…...
2.Excel :快速填充和拆分重组
一 案例1:快速填充 电子邮件中包含每个人的人名,现在要提取电子邮件中的姓名到名字列。 方法1:将 Nancy 复制到单元格后,邮件会高亮,然后输入A的时候系统就会知道提取名字了。 补充:如果第三个位置输入错误…...
python tkinter 开发蓍草占卜系统
1. 项目概述 1.1 简介 蓍草占卜是中国传统的占卜方法,用于演算六十四卦。本系统通过现代编程技术,将传统的蓍草占卜方法数字化,提供一个准确、便捷的占卜工具。 蓍草占卜,作为中国古代的一种传统占卜方法,承载着深厚…...
综合小实验之电视机
综合小实验1 一、实验拓扑图 二、实验要求 按照图示配置IP地址;按照图示区域划分配置对应的动态路由协议;在R7上配置dhcp服务器,能够让pc可以获取IP地址;将所有环回⼝宣告进ospf中,将环回⼝7宣告进rip中,…...
全排列|| 分发饼干 摆动序列
1.给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。 示例 1: 输入:nums [1,1,2] 输出: [[1,1,2], [1,2,1], [2,1,1]] 示例 2: 输入:nums [1,2,3] 输出:[[1,2,3…...
【开源宝藏】用 JavaScript 手写一个丝滑的打字机动画效果
你当前项目实现了一个非常丝滑的 打字机文字效果动画,使用的是自定义的 typical.js 脚本。下面我将给出一份逐步拆解的中文教程,帮你或其他初学者快速上手并自定义这个打字效果。 ✨ 最终效果 打开页面后,中央会逐字显示: Hello…...
推荐1款简洁、小巧的实用收音机软件,支持手机和电脑
聊一聊 没想到现在还有人喜欢听广播。 我一直以为听广播必须要用那种小广播机才可以。 原来手机或电脑上也是可以的。 今天给大家分享一款可以在电脑和手机上听广播的软件。 软件介绍 龙卷风收音机 电台广播收音机分电脑和手机两个版本。 电脑端无需安装,下载…...
64. MfgTool烧写工具详解
一、MfgTool工具简介 1、mfgtool是NXP官方做的向I.MX系列烧写系统的软件,运行在windows下。可以烧写uboot.imx、zImage、dtb,rootfs。通过USB烧写。 Mfgtool里面默认存放了NXP官方开发板的系统文件, 2、基本原理 向开发板烧系统分两部分&am…...
3、孪生网络/连体网络(Siamese Network)
目的: 用Siamese Network (孪生网络) 解决Few-shot learning (小样本学习)。 Siamese Network并不是Meta Learning最好的方法, 但是通过学习Siamese Network,非常有助于理解其他Meta Learning算法。 这里介绍了两种方法:Siamese Network (孪生网络)、Trplet Loss Siam…...
前端学习笔记--CSS
HTMLCSSJavaScript 》 结构 表现 交互 如何学习 1.CSS是什么 2.CSS怎么用? 3.CSS选择器(重点,难点) 4.美化网页(文字,阴影,超链接,列表,渐变。。。) 5…...
开个坑记录一下树莓派4B部署yolo的一些问题
问题一:操作系统与内核信息 这个问题困扰了我一天半,下载的时候显示的信息是aar64的系统,但是这并无意味着一个问题,那就是你的操作系统是64位的。需要采用如下的指令查看: getconf LONG_BIT 我在树莓派得出来的操作…...
1.1 结构体与类对象在List中使用区别
一、问题的起源如下的代码是错误的,无法编译通过 struct Point {public int X;public int Y; }List<Point> points new List<Point> { new Point { X 1, Y 2 } }; points[0].X 10; // 编译错误!无法修改副本的字段 二、原因分析 在C#中&…...
详细说明windows系统函数::SetUnhandledExceptionFilter(ExceptionFilter)
::SetUnhandledExceptionFilter(ExceptionFilter); 是 Windows 编程中用于设置顶层未处理异常过滤器的关键 API 调用。它属于 Windows 结构化异常处理(SEH, Structured Exception Handling)机制的一部分,主要用于捕获那些未被程序内部处理的异…...
力扣刷题39. 组合总和
39. 组合总和 - 力扣(LeetCode) 需要定义一个index变量用来记录访问数组的下标,每次递归进行传参,在搜索过程中,因为为了避免重复数据,而且允许一个元素的重复出现,传入index时传入当前遍历的i…...
正弦函数的连续傅里叶变换正弦序列的DTFT
正弦序列 时域 x [ n ] sin ( ω 0 n ) x[n] \sin(\omega_0 n) x[n]sin(ω0n)频域 X ( e j ω ) j π 2 [ δ ( ω − ω 0 ) − δ ( ω ω 0 ) ] X({\rm e}^{{\rm j}\omega}) \frac{{\rm j}\pi}{2} \left[ \delta(\omega - \omega_0) - \delta(\omega \omega_0…...
winstart.wsf 病毒清理大作战
0x00 背景 发现感染了winstart.wsf 病毒如何清理。 0x01 现象 遍历Users下每个目录以及C:\和C:\Windows\Temp 2个目录写入病毒文件。 C:\Users\Administrator\AppData\Local\Temp\winstart.wsf C:\Users\Administrator\AppData\Roaming\Microsoft\Windows\Start Menu\Program…...
leetcode 20.有效括号
20. 有效的括号 - 力扣(LeetCode) class Solution:def isValid(self, s: str) -> bool:stack []for i in s :if i in ((,{,[ ):stack.append(i)elif i in () ):# 这种情况是 栈弹出元素为空时候 ,右半部分的括号多出来一些 比如&#x…...
Leetcode刷题笔记1 图论part07
卡码网 53 寻宝 prim算法 prim算法核心就是三步,称为prim三部曲: 第一步,选距离生成树最近节点第二步,最近节点加入生成树第三步,更新非生成树节点到生成树的距离(即更新minDist数组) def p…...
unittest自动化测试实战
🍅 点击文末小卡片,免费获取软件测试全套资料,资料在手,涨薪更快 为什么要学习unittest 按照测试阶段来划分,可以将测试分为单元测试、集成测试、系统测试和验收测试。单元测试是指对软件中的最小可测试单元在与程…...
flask,示例及解释
1) from flask import Flask, render_templateapp Flask(__name__)app.route(/) def index():return render_template(m1index.html)app.route(/get_type) def get_type():return ["语文", "数学"]if __name__ __main__:app.run(host0.0.0.0…...
电机倍频曲线的一些奇异特性-原因分析及应用
这里对感应电机倍频曲线的特征进行了说明,然后将其特性用于电机转差率和工况的测量。先给出可以直接利用的结论: 电机的工况和转差率谱线会体现为5x,7x谱线调制在基频附近。两条调制过携带s信息的谱线距离基频谱线的距离。 与真实转速相对同步转速的频差…...
基于Ebay拍卖网站成交价格的影响因素分析
摘要:近些年来网上拍卖的不断地发展,网上购物慢慢变成了大家普遍接受的购物方式。因此关于网上拍卖的研究日益成为很多人研究的重点。 影响拍卖网站价格的因素很多,但很少有人分得清楚哪些因素才是比较重要的因素,因此对价格因素分析&#x…...
详解图卷积网络
文章目录 GCN/RGCN图卷积网络概述--运作原理**1. GCN(Graph Convolutional Network,图卷积网络)****1.1 核心思想****1.2 公式****1.3 特点****1.4 总结** **2. RGCN(Relational Graph Convolutional Network,关系型图…...
Java 8-17核心特性全景解析之Java9、10
Java 9 核心特性解析 Java 9在2017年9月发布,带来了模块系统等重大变革,是Java平台现代化的重要一步。 模块系统 (Project Jigsaw) 特性概述 模块系统是Java 9最重要的特性,旨在解决Java平台和应用程序的可伸缩性问题,提供更好…...