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  再来一局,不言弃!!


 一、《贪吃蛇》游戏介绍

        也许正在看的你玩过一款名为《贪吃蛇》的小游戏,《贪食蛇》中玩家控制一条不断移动的蛇,在屏幕上吃掉出现的食物。每吃掉一个食物,蛇的身体就会变长。游戏的目标是尽可能长时间地生存下去,同时避免蛇头撞到自己的身体或屏幕边缘。玩家需要灵活操作,利用策略在有限的空间内避免碰撞,挑战高分。

        通过对经典的《贪吃蛇》的简单介绍,可以知道这篇文章中实现的贪吃蛇选哟实现以下的几点:

  1. 通过按方向键上下左右,来改变蛇的移动方向
  2. 通过按F3键实现蛇的加速行进,按F4键可以降低蛇的移速。
  3. 按空格键可实现暂停,暂停后按任意键继续游戏。
  4. 按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函数来生成随机数,不过生成的随机数也需要具备如下特点:

  1. x坐标的值为2~54,y坐标点值为1~25(不能在墙壁上或墙壁外)
  2. 必须为2的倍数(保证蛇可以吃到食物)
  3. 不能和蛇身的坐标重合

        确定好以上条件后,我们就可以将食物打印在地图上了。

//创建食物
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');
}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于深度学习的图片识别系统(下)

文章目录 前言1.任务描述2.模型搭建3.代码解释3.1模型加载3.2加载数据3.3模型权重的保存3.4学习率3.5过拟合3.6训练模型3.7调试检查 4.结果分析5. 完整代码结语 前言 书接上回&#xff0c;我们已经完成数据预处理部分的内容&#xff0c;后续仍需要对表格进行裁剪&#xff0c;此…

再学:区块链基础与合约初探 EVM与GAS机制

目录 1.区块链是什么 2.remix ​3.账户​ ​4.以太坊三种交易​ 5.EVM 6.以太坊客户端节点 ​7.Gas费用 8.区块链浏览器 1.区块链是什么 只需要检验根节点 Merkel根是否有更改&#xff0c;就不用检查每个交易是否有更改。方便很多。 2.remix 3.账户 如果交易失败的话&…

Java 中装饰者模式与策略模式在埋点系统中的应用

前言 在软件开发中&#xff0c;装饰者模式和策略模式是两种常用的设计模式&#xff0c;它们在特定的业务场景下能够发挥巨大的作用。本文将通过一个实际的埋点系统案例&#xff0c;探讨如何在 Java 中运用装饰者模式和策略模式&#xff0c;以及如何结合工厂方法模式来优化代码…

HCIP_NOTE03_网络组成

网络组成 LAN MAN WAN 园区网 企业或机构内部的网络,分大中小型 行业园:企业园网 校园网 政务园 商业园 三层交换机 数据大量交换的局域网内部,转发效率高,有简单的路由功能 路由器 进出口网络,适用于复杂的网络环境,选路需求 无线网 信号传输稳定性差---- 电磁波易受干…

简记_单片机硬件最小系统设计

以STM32为例&#xff1a; 一、电源 1.1、数字电源 IO电源&#xff1a;VDD、VSS&#xff1a;1.8~3.6V&#xff0c;常用3.3V&#xff0c;去耦电容1 x 10u N x 100n &#xff1b; 内核电源&#xff1a;内嵌的稳压器输出&#xff1a;1.2V&#xff0c;给内核、存储器、数字外设…

32.[前端开发-JavaScript基础]Day09-元素操作-window滚动-事件处理-事件委托

JavasScript事件处理 1 认识事件处理 认识事件(Event) 常见的事件列表 认识事件流 2 事件冒泡捕获 事件冒泡和事件捕获 事件捕获和冒泡的过程 3 事件对象event 事件对象 event常见的属性和方法 事件处理中的this 4 EventTarget使用 EventTarget类 5 事件委托模式 事件委托&am…

LeetCode hot 100 每日一题(15)——48.旋转图像

这是一道难度为中等的题目&#xff0c;让我们来看看题目描述&#xff1a; 给定一个 n n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。 你必须在 原地 旋转图像&#xff0c;这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。 提示…

图灵300题-21~40-笔记002

图灵300题 图灵面试题视频&#xff1a;https://www.bilibili.com/video/BV17z421B7rB?spm_id_from333.788.videopod.episodes&vd_sourcebe7914db0accdc2315623a7ad0709b85&p20。 本文是学习笔记&#xff0c;如果需要面试没有时间阅读原博文&#xff0c;可以快速浏览笔…

09_从经典论文入手Seq2Seq架构

Sequence to Sequence 架构 Paper链接 Sequence to Sequence Learning with Neural Networks B站课程ShusenWang 核心思想 关键的改进点 In this paper, we show that a straightforward application of the Long Short-Term Memory (LSTM) architecture [16] can solve …

大疆上云api介绍

概述 目前对于 DJI 无人机接入第三方云平台,主要是基于 MSDK 开发定制 App,然后自己定义私有上云通信协议连接到云平台中。这样对于核心业务是开发云平台,无人机只是其中一个接入硬件设备的开发者来说,重新基于 MSDK 开发 App 工作量大、成本高,同时还需要花很多精力在无人…

3、孪生网络/连体网络(Siamese Network)

目的&#xff1a; 用Siamese Network (孪生网络) 解决Few-shot learning (小样本学习)。 Siamese Network并不是Meta Learning最好的方法&#xff0c; 但是通过学习Siamese Network&#xff0c;非常有助于理解其他Meta Learning算法。 这里介绍了两种方法&#xff1a;Siame…

OpenCV图像拼接(7)根据权重图对源图像进行归一化处理函数normalizeUsingWeightMap()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 cv::detail::normalizeUsingWeightMap 是 OpenCV 中用于图像拼接细节处理的一个函数。它根据权重图对源图像进行归一化处理&#xff0c;通常用于…

卷积神经网络 - AlexNet各层详解

AlexNet的层次化设计&#xff0c;使得 AlexNet 能够逐层提取从简单边缘到复杂图形的特征&#xff0c;同时结合归一化、池化和 Dropout 技术&#xff0c;有效提升了训练速度和泛化能力&#xff0c;成为推动深度学习发展的重要里程碑。本文我们来理解AlexNet各层的参数设置以及对…

【设计模式】工厂模式

首先了解一下什么是工厂方法模式&#xff1f; 工厂方法模式&#xff08;Factory Method Pattern&#xff09;是一种创建型设计模式&#xff0c;它提供了一种方法来封装对象的创建逻辑。具体来说&#xff0c;它通过定义一个创建对象的接口&#xff08;即工厂方法&#xff09;&a…

centos 7 部署FTP 服务用shell 脚本搭建

#!/bin/bash# 检查是否以root身份运行脚本 if [ "$EUID" -ne 0 ]; thenecho "请以root身份运行此脚本。"exit 1 fi# 安装vsftpd yum install -y vsftpd# 启动vsftpd服务并设置开机自启 systemctl start vsftpd systemctl enable vsftpd# 配置防火墙以允许F…

基于Spring Boot的个性化商铺系统的设计与实现(LW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…

AI(DeepSeek、ChatGPT)、Python、ArcGIS Pro多技术融合下的空间数据分析、建模与科研绘图及论文写作

人工智能&#xff08;AI&#xff09;与ArcGIS Pro的结合&#xff0c;为空间数据处理和分析开辟了前所未有的创新路径。AI通过强大的数据挖掘、深度学习及自动化能力&#xff0c;可高效处理海量、多源、异构的空间数据&#xff0c;极大提升了分析效率与决策支持能力。而ArcGIS P…

2025最新3个wordpress好用的主题

红色大气的wordpress企业主题&#xff0c;适合服务行业的公司搭建企业官方网站使用。是一款专为中小企业和个人开发者设计的WordPress主题&#xff0c;旨在提供专业的网站构建解决方案。 通过此WordPress主题&#xff0c;用户可以轻松创建和维护一个专业的企业网站&#xff0c…

Spring AI Alibaba AudioModel使用

一、AudioModel简介 1、AudioModel 当前&#xff0c;Spring AI Alibaba 支持以下两种通义语音模型的适配&#xff0c;分别是&#xff1a; 文本生成语音 SpeechModel&#xff0c;对应于 OpenAI 的 Text-To-Speech (TTS) API录音文件生成文字 DashScopeAudioTranscriptionMode…

时隔多年,终于给它换了皮肤,并正式起了名字

时隔多年&#xff0c;终于更新了直播推流软件UI&#xff0c;并正式命名为FlashEncoder。软件仍使用MFC框架&#xff0c;重绘了所有用到的控件&#xff0c;可以有效保证软件性能&#xff0c;也便于后续进一步优化。 下载地址&#xff1a;https://download.csdn.net/download/Xi…