贪吃蛇详解

Win32 API介绍:

在写贪吃蛇这款游戏时需要用到一些有关Win32 API的知识, 接下来我会将设计到的知识点列举并讲解:

首先我们先了解一下Win32 API是什么,Windows这个多作业系统除了协调应⽤程序的执⾏、分配内存、管理资源之外,它同时也是⼀个很⼤ 的服务中⼼,调⽤这个服务中⼼的各种服务(每⼀种服务就是⼀个函数),可以帮应⽤程序达到开启 视窗、描绘图形、使⽤周边设备等⽬的,由于这些函数服务的对象是应⽤程序(Application),所以便 称之为Application Programming Interface,简称API函数。WIN32 API也就是Microsoft Windows  32位平台的应⽤程序编程接⼝。

控制台程序:

控制台就是我们在运行代码时出现的那个窗口:

有些人的可能是下面这张图的样子:

这个是无法实现贪吃蛇的,我们需要进行设置上的修改,改成第一个图的样子即可,具体步骤如下:

在更改完设置后我们就可以尝试对控制台进行一些操作了,比如说我们在玩贪吃蛇游戏的时候需要一个大小合适的游戏窗口,并且将控制台的标题改成“贪吃蛇”,这样会让我们写出来的游戏更加完美,接下来我来介绍一下这些命令:

1.mode命令:

(参考链接mode | Microsoft Learn )

代码:
system("mode con cols=30 lines=30");
演示效果:

我们把列和行的大小都设置为30,这样只是让效果更加明显,在实际使用时需要我们不断去尝试运行去找到一个合适的大小。

2.title命令

参考链接(title | Microsoft Learn)

代码:
    system("title 贪吃蛇");
演示效果:

 这里我使用了“pause”命令,它会使代码暂停下来,如果不加这个命令的话,我们不会看到效果,因为在程序运行结束时,title也会结束,我们不妨看一下没有这个命令时的效果:

 

控制台屏幕上的坐标COORD:

参考链接(COORD 结构 - Windows Console | Microsoft Learn)

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

COORD的类型:

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

控制台其实是有坐标的,但它会与我们平常认识的有些许不同,如下图所示:

需要注意的是x和y坐标的单位长度是不一样的,大概就是1 :2的比例,如下图所示:

在打印东西的时候,总会在光标处打印,打印一下光标就会向后移动一下,在我们实现贪吃蛇的时候会需要在特定的位置进行打印,那就需要将光标放在指定的位置,也就是上面所讲的坐标,接下来我就介绍一下如何实现在指定位置打印的功能。

 GetStdHandle 函数:

参考链接(GetStdHandle 函数 - Windows Console | Microsoft Learn)

仔细观察运行窗口会发现有个光标在一直闪烁,设想一下,在使用贪吃蛇时总会有一个光标在闪来闪去,这会非常影响游戏体验,那我们就要想办法将光标隐藏掉。

怎么隐藏呢?这就需要我们获得光标的一个控制权,那光标的控制权又在哪里,这就需要我们设想一下炒菜的这么一个情景,在颠锅的时候我们需要手持锅的把手,再回到代码的世界里,控制台就是这口大锅,GetStdHandle就是获得控制台控制权的一个函数,这里我们将这种控制权叫做句柄

有了这样的思考,这个函数的定义也就易于理解了。

GetStdHandle是⼀个Windows  API函数。它⽤于从⼀个特定的标准设备(标准输⼊、标准输出或标 准错误)中取得⼀个句柄(⽤来标识不同设备的数值),使⽤这个句柄可以操作设备。

函数声明:(并不需要我们自己声明,写出来只是为了便于理解)

不难看出函数的返回类型是HANDLE,它是一个指向句柄的指针,如果获取失败,就会返回空指针;

HANDLE GetStdHandle(DWORD nStdHandle);

函数的参数只有一个,这种类型的参数只有三种,只需选择需要的然后copy到代码中即可。

示例:

HANDLE hOutput = NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值) 
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
if (hOutput == NULL)//如果获取失败,直接退出程序并打印错误信息
{
    perror("GetStdHandle");
    exit(1);
}

 GetConsoleCursorInfo 函数:

参考链接(GetConsoleCursorInfo 函数 - Windows Console | Microsoft Learn)

既然已经拿到句柄,我们就可以进行操作了,怎么操作呢?这还需要借助一个函数。

GetConsoleCursorInfo的作用就是检索有关指定控制台屏幕缓冲区的光标⼤⼩和可⻅性的信息。

语法:

可见函数有两个参数,一个就是我们使用 GetStdHandle获得的句柄,

BOOL WINAPI GetConsoleCursorInfo(
  _In_  HANDLE               hConsoleOutput,
  _Out_ PCONSOLE_CURSOR_INFO lpConsoleCursorInfo
);
PCONSOLE_CURSOR_INFO 是指向 CONSOLE_CURSOR_INFO 结构的指针,该结构接收有关主机游标

另一个是是指向 CONSOLE_CURSOR_INFO 结构的指针,该结构接收有关主机游标,它包含两个类型的变量,一个是由游标填充的字符单元的百分比,另一个是光标的显示与隐藏。

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

示例:

HANDLE hOutput = NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值) 
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息 
CursorInfo.bVisible = false;

我们来看一下效果:发现光标并没有消失,要想实现对光标的设置,还需要调用一个函数。

 SetConsoleCursorInfo函数:

参考链接(SetConsoleCursorInfo 函数 - Windows Console | Microsoft Learn)

设置指定控制台屏幕缓冲区的光标的⼤⼩和可⻅性。

语法:SetConsoleCursorInfo函数的参数与GetConsoleCursorInfo 函数的参数类型相同 。

BOOL WINAPI SetConsoleCursorInfo(
  _In_       HANDLE              hConsoleOutput,
  _In_ const CONSOLE_CURSOR_INFO *lpConsoleCursorInfo
);

示例:

HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
//影藏光标操作 
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息 
CursorInfo.bVisible = false; //隐藏控制台光标 
SetConsoleCursorInfo(hOutput, &CursorInfo);//设置控制台光标状态 

效果图:

 SetConsoleCursorPosition 函数:

参考链接(SetConsoleCursorPosition 函数 - Windows Console | Microsoft Learn)

在隐藏好光标后,就可以尝试将光标放置到指定坐标上了,这就需要借助SetConsoleCursorPosition 函数。

语法:参数有两个,分别是获得的句柄和创建的COORD类型的变量。

BOOL WINAPI SetConsoleCursorPosition(
  _In_ HANDLE hConsoleOutput,
  _In_ COORD  dwCursorPosition
);

示例:

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

效果图:

学到这里,我们就可以尝试去封装一个函数来控制光标的位置,具体代码如下: 

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

这样我们就可以通过调用该函数并传入想要设置的坐标就可以实现这一功能。

GetAsyncKeyState函数:

参考链接(getAsyncKeyState 函数 (winuser.h) - Win32 apps | Microsoft Learn)

思考一下,在玩贪吃蛇游戏的时候,我们会通过按相应的按键来控制蛇的移动,这种功能是如何实现的?接下来就介绍一下实现这个功能的函数。

GetAsyncKeyState函数会获取按键情况,我们需要知道的一点是键盘上的所有按键都有一个专属的虚拟值,将键盘上每个键的虚拟键值传递给函数,函数通过返回值来分辨按键的状态。GetAsyncKeyState 的返回值是short类型,在上⼀次调⽤ GetAsyncKeyState 函数后,如果 返回的16位的short数据中,最⾼位是1,说明按键的状态是按下,如果最⾼是0,说明按键的状态是抬 起;如果最低位被置为1则说明,该按键被按过,否则为0。 

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

语法:

SHORT GetAsyncKeyState(
 int vKey
);

示例:

这里我用了一个宏定义,宏的内容就是( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 ),0x1其实就等价于十进制中的1,写成二进制为0000 0000 0000 0000 0000 0000 0000 0001,与1进行&得到的是最后一位是否是1。这样可以让代码看起来更加简洁。

#include <stdio.h>
#include <windows.h>
#define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )

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;
}

效果演示:

我们按键盘上的数字,按几控制台上就会打印几。

接下来我们步入正题,讲解一下贪吃蛇游戏的实现思路。

 贪吃蛇游戏设计与分析:

联想一下我们自己玩游戏的时候,刚进入游戏它会打印一个欢迎界面,然后显示游戏规则,最后进入游戏,贪吃蛇的速度和方向通过我们的控制在一定的区域内吃食物,蛇头碰到自己或者撞到墙会直接结束游戏,游戏期间还可以加速和减速,加速吃到的食物分数会更高,减速吃到的食物分数会低一点,游戏期间可以暂停,直接退出。

那我们就以这个思路来写代码:(接下来的功能实现我都会以函数的形式进行书写,最终会有完整代码)

游戏运行前数据的初始化:

0.设置窗口的大小并隐藏光标:

这里就使用我们上面所讲的对光标的状态设置和控制台设置的知识点,写起来也会相对轻松一点。

代码:

//0.设置窗口的大小并隐藏光标
void Std_set()
{
	system("mode con cols=100 lines=30");
	system("title 贪吃蛇");
	HANDLE houtput = NULL;
	houtput = GetStdHandle(STD_OUTPUT_HANDLE);
	CONSOLE_CURSOR_INFO con;
	GetConsoleCursorInfo(houtput, &con);
	con.bVisible = false;
	SetConsoleCursorInfo(houtput, &con);
}

1.欢迎界面的打印:

只需要我们将光标放到合适的位置打印”欢迎进入贪吃蛇小游戏“,如果不进行位置调整 会很难看。大家可以对比一下:

显然是第二种比较美观,而位置的设置完全可以调用我们前面封装的函数,那么我们的欢迎界面也就完成了。

代码:

#include<stdio.h>

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

int main()
{
    SetPos(35, 11);
    printf("欢迎来到贪吃蛇小游戏。\n");
    system("pause");
    return 0;
}

2.规则说明界面的打印:

操作与欢迎界面的打印一模一样,只需要更换一下打印的内容即可,在这我还是进行一个演示并放置参考代码。

效果演示:

代码:

//2.规则说明界面
void Print_rule()
{
    Set_Con(15, 10);
    printf("请用↑. ↓. ←. →控制蛇移动的方向,F3为加速,F4为减速,加速会获得更高的分数。\n\n\n\n\n\n\n");
    system("pause");
    //system("cls");
}

设想一下,贪吃蛇的速度和方向通过我们的控制在一定的区域内吃食物,蛇头碰到自己或者撞到墙会直接结束游戏。

根据设想,我们就会有这么一个代码:

//游戏的初始化
void Set_Game(Snake* ppsnake)
{
	//0.设置窗口的大小并隐藏光标
	Std_set();
	//1.欢迎界面
	Print_wel();
	//2.规则说明界面
	Print_rule();
	//3.绘制地图
	Print_wall();
	//4.绘制蛇
	Print_snake(ppsnake);
	//5.绘制食物
	Print_food(ppsnake);
}

接下来就是地图的绘制

3.地图的绘制:

这里地图的大小就由我规定了,如有需要,可以自行更改。

地图其实也就是我们的墙体,但是墙体我想要用特殊的图案进行绘制,比如效果图是这样的:

 这个图案可以在输入法中寻找,以下是具体步骤:

那我们进行实操:

代码:

//3.绘制地图
void Print_wall()
{
    for (int i = 0; i < 29; i++)
    {
        wprintf(L"%lc", L'□');
    }
    Set_Con(0, 26);
    for (int i = 0; i < 29; i++)
    {
        wprintf(L"%lc", L'□');
    }
    for (int i = 1; i < 26; i++)
    {
        Set_Con(0, i);
        wprintf(L"%lc", L'□');
    }
    for (int i = 1; i < 26; i++)
    {
        Set_Con(56, i);
        wprintf(L"%lc", L'□');
    }
}

效果演示: 

发现和我们预想的不太一样,这就涉及到另一个知识:

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

宽字符的字⾯量必须加上前缀“L”,否则C语⾔会把字⾯量当作窄字符类型处理。前缀“L”在单引 号前⾯,表⽰宽字符,对应 wprintf() 的占位符为 %lc ;在双引号前⾯,表⽰宽字符串,对应 wprintf() 的占位符为 %ls 。

代码:

int main()
{
    //适配到本地
    setlocale(LC_ALL, "");
    wchar_t ch = L'□';
    wprintf(L"%lc", ch);
    return 0;
}

本地化设置:

这⾥再简单的讲⼀下C语⾔的国际化特性相关的知识,过去C语⾔并不适合⾮英语国家(地区)使⽤。 C语⾔最初假定字符都是单字节的。但是这些假定并不是在世界的任何地⽅都适⽤。

C语⾔字符默认是采⽤ASCII编码的,ASCII字符集采⽤的是单字节编码,且只使⽤了单字节中的低7 位,最⾼位是没有使⽤的,可表⽰为0xxxxxxxx;可以看到,ASCII字符集共包含128个字符,在英语 国家中,128个字符是基本够⽤的,但是,在其他国家语⾔中,⽐如,在法语中,字⺟上⽅有注⾳符 号,它就⽆法⽤ASCII码表⽰。于是,⼀些欧洲国家就决定,利⽤字节中闲置的最⾼位编⼊新的符 号。⽐如,法语中的é的编码为130(⼆进制10000010)。这样⼀来,这些欧洲国家使⽤的编码体 系,可以表⽰最多256个符号。但是,这⾥⼜出现了新的问题。不同的国家有不同的字⺟,因此,哪 怕它们都使⽤256个符号的编码⽅式,代表的字⺟却不⼀样。⽐如,130在法语编码中代表了é,在希 伯来语编码中却代表了字⺟Gimel,在俄语编码中⼜会代表另⼀个符号。但是不管怎样,所有这 些编码⽅式中,0--127表⽰的符号是⼀样的,不⼀样的只是128--255的这⼀段。 ⾄于亚洲国家的⽂字,使⽤的符号就更多了,汉字就多达10万左右。⼀个字节只能表⽰256种符号, 肯定是不够的,就必须使⽤多个字节表达⼀个符号。⽐如,简体中⽂常⻅的编码⽅式是GB2312,使 ⽤两个字节表⽰⼀个汉字,所以理论上最多可以表⽰256x256=65536个符号。在俄语编码中⼜会代表另⼀个符号。但是不管怎样,所有这 些编码⽅式中,0--127表⽰的符号是⼀样的,不⼀样的只是128--255的这⼀段。 ⾄于亚洲国家的⽂字,使⽤的符号就更多了,汉字就多达10万左右。⼀个字节只能表⽰256种符号, 肯定是不够的,就必须使⽤多个字节表达⼀个符号。⽐如,简体中⽂常⻅的编码⽅式是GB2312,使 ⽤两个字节表⽰⼀个汉字,所以理论上最多可以表⽰256x256=65536个符号。

后来为了使C语⾔适应国际化,C语⾔的标准中不断加⼊了国际化的⽀持。⽐如:加⼊了宽字符的类型 wchar_t 和宽字符的输⼊和输出函数,加⼊了头⽂件,其中提供了允许程序员针对特定 地区(通常是国家或者说某种特定语⾔的地理区域)调整程序⾏为的函数。

在游戏地图上,我们打印墙体使⽤宽字符:□,打印蛇使⽤宽字符●,打印⻝物使⽤宽字符◆ 普通的字符是占⼀个字节的,这类宽字符是占⽤2个字节。

<locale.h>提供的函数⽤于控制C标准库中对于不同的地区会产⽣不⼀样⾏为的部分。

 在标准中,依赖地区的部分

有以下⼏项:

• 数字量的格式

• 货币量的格式

• 字符集

• ⽇期和时间的表⽰形式

类项:

通过修改地区,程序可以改变它的⾏为来适应世界的不同区域。但地区的改变可能会影响库的许多部 分,其中⼀部分可能是我们不希望修改的。

所以C语⾔⽀持针对不同的类项进⾏修改,下⾯的⼀个宏, 指定⼀个类项:

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

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

 • LC_MONETARY:影响货币格式。

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

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

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

setlocale函数:

参考链接(setlocale - C++ Reference)

语法:

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

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

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

了解之后就可以直接投入使用了:

代码:

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


//3.绘制地图
void Print_wall()
{
    for (int i = 0; i < 29; i++)
    {
        wprintf(L"%lc", L'□');
    }
    Set_Con(0, 26);
    for (int i = 0; i < 29; i++)
    {
        wprintf(L"%lc", L'□');
    }
    for (int i = 1; i < 26; i++)
    {
        Set_Con(0, i);
        wprintf(L"%lc", L'□');
    }
    for (int i = 1; i < 26; i++)
    {
        Set_Con(56, i);
        wprintf(L"%lc", L'□');
    }
}


int main()
{
    //适配到本地
    setlocale(LC_ALL, "");
    Print_wall();
    //system("pause");
    return 0;
}

效果演示:

观察到最后一行没有打印出来,其实不然,只是下面的文字将其遮挡住了,只需加一个”pause“命令即可看到效果:

 4.绘制蛇:

这里会涉及到链表的知识,在前面的文章中有讲过,那蛇其实就是一个链表而已,每个结点里面储存着各自结点的x, y坐标,用于蛇身的打印,需要注意的是需要将蛇头的结点储存起来,以便找到整条蛇。

所以我们需要创建一个蛇的结点类型,代码如下:

//蛇身的结点
typedef struct SnakeNode
{
	//结点的坐标
	short x;
	short y;
	//指向下一个结点
	struct SnakeNode* next;
}snakeNode;

假设我们将蛇身的长度初始为5,增加结点的时候采用尾插的方式,蛇头的坐标为(24,5),

那么初始化蛇身的代码就可以是如下:

//4.绘制蛇
void Print_snake(Snake* ppsnake) 
{
	snakeNode* cur = NULL;
	for (int i = 0; i < 5; i++)
	{
		cur = (snakeNode*)malloc(sizeof(snakeNode));
		if (cur == NULL)
		{
			perror("malloc");
			exit(1);
		}
		cur->x = 24 + 2 * i;
		cur->y = 5;
		cur->next = NULL;
		//尾插
		if (ppsnake->psnake == NULL)
		{
			ppsnake->psnake = cur;
		}
		else
		{
			cur->next = ppsnake->psnake;
			ppsnake->psnake = cur;
		}
	}
	cur = ppsnake->psnake;
	//打印蛇身
	while (cur)
	{
		Set_Con(cur->x, cur->y);
		wprintf(L"%lc", L'●');
	    cur = cur->next;
	}
	

}

当然,我们在绘制蛇的时候可以顺便将整个游戏的数据初始化一下,比如说游戏状态,蛇的初始方向,吃一个食物获得的分数,获得的总分数,蛇的速度(也就是两次打印之间的时间间隔)。这就需要我们写一些枚举和结构体,具体如下:

//蛇的方向
typedef enum DIRECTION {
	UP = 1,
	DOWN,
	LEFT,
	RIGHT,
}DIRECTION;

//游戏状态
typedef enum GAME_STATUS
{
	//正常运行
	 REGULAR= 1, 
	//撞墙
	OVER_BY_WALL,
	//撞到自己
	OVER_BY_SELF,
	//正常退出
	OVER_NORMAL
}GAME_STATUS;


//贪吃蛇
typedef struct Snake
{
	//蛇的头结点
	snakeNode* psnake;
	//食物
	snakeNode* pfood;
	//蛇的方向
	DIRECTION dir;
	//蛇的速度
	int sleep_time;//时间和速度成反比
	//一个食物的分数
	int foof_weight;
	//总分数
	int score;
	//游戏状态
	GAME_STATUS status;
}Snake;

绘制蛇的代码优化:

//4.绘制蛇
void Print_snake(Snake* ppsnake) 
{
	snakeNode* cur = NULL;
	for (int i = 0; i < 5; i++)
	{
		cur = (snakeNode*)malloc(sizeof(snakeNode));
		if (cur == NULL)
		{
			perror("malloc");
			exit(1);
		}
		cur->x = 24 + 2 * i;
		cur->y = 5;
		cur->next = NULL;
		//尾插
		if (ppsnake->psnake == NULL)
		{
			ppsnake->psnake = cur;
		}
		else
		{
			cur->next = ppsnake->psnake;
			ppsnake->psnake = cur;
		}
	}
	cur = ppsnake->psnake;
	//打印蛇身
	while (cur)
	{
		Set_Con(cur->x, cur->y);
		wprintf(L"%lc", L'●');
	    cur = cur->next;
	}
	//设置食物分数
	ppsnake->foof_weight = 10;
	//总分
	ppsnake->score = 0;
	//速度
	ppsnake->sleep_time = 200;
	//初始方向
	ppsnake->dir = RIGHT;
	//游戏状态
	ppsnake->status = REGULAR;

}

 

5.绘制食物:

食物的结点类型与蛇的相同,只不过只有一个结点,但是该结点的坐标不能与蛇身相同并且坐标是随机的,关于产生随机值这个知识在前面扫雷的实现里有讲,所以这里就不细说了。

代码:

//5.绘制食物
void Print_food(Snake* ppsnake)
{
	int x = 0;
	int y = 0;
again:
	do
	{
		x = rand() % 53 + 2;
		y = rand() % 25 + 1;
	} while (x % 2 != 0);
	snakeNode* cur = ppsnake->psnake;
	while (cur)
	{
		if (cur->x == x && cur->y == y)
		{
			goto again;
		}
		cur = cur->next;
	}
	snakeNode*  p = (snakeNode*)malloc(sizeof(snakeNode));
	if (p == NULL)
	{
		perror("malloc");
		return;
	}
	p->x = x;
	p->y = y;
	p->next = NULL;
	Set_Con(x, y);
	wprintf(L"%lc", L'◆');
	ppsnake->pfood = p;
}

 游戏运行:

0.实现按键判断及反应:

将要判断的按键的虚拟值传入写的宏中即可,判断后进行相应的反应。

代码:

//暂停游戏
void PAUSE()
{
	while (1)
	{
		Sleep(100);
		if (KEY_PASS(VK_SPACE))
		{
			break;
		}
	}
}


void StartGame(Snake* ppsnake)
{
	do {
		if (KEY_PASS(VK_UP) && ppsnake->dir != DOWN)//向上
		{
			ppsnake->dir = UP;
		}
		else if (KEY_PASS(VK_DOWN) && ppsnake->dir != UP)//向下
		{
			ppsnake->dir = DOWN;
		}
		else if (KEY_PASS(VK_LEFT) && ppsnake->dir != RIGHT)//向左
		{
			ppsnake->dir = LEFT;
		}
		else if (KEY_PASS(VK_RIGHT) && ppsnake->dir != LEFT)//向右
		{
			ppsnake->dir = RIGHT;
		}
		else if (KEY_PASS(VK_SPACE))//暂停,也就是写一个死循环,永远处于休眠状态
		{
			PAUSE();
		}
		else if (KEY_PASS(VK_ESCAPE))//正常退出游戏
		{
			ppsnake->status = OVER_NORMAL;
		}
		else if (KEY_PASS(VK_F3))//加速
		{
            //对加速次数进行限制,防止蛇出现瞬移的情况
			if (ppsnake->foof_weight < 18)
			{
				ppsnake->sleep_time -= 30;
				ppsnake->foof_weight += 2;
			}
		}
		else if (KEY_PASS(VK_F4))//减速
		{
            //与加速相同,要做次数限制
			if (ppsnake->foof_weight > 2)
			{
				ppsnake->sleep_time += 30;
				ppsnake->foof_weight -= 2;
			}
		}
        Snakemove(ppsnake);//移动蛇
		Sleep(ppsnake->sleep_time);//等价于蛇的速度
	} while (ppsnake->status == REGULAR);
}

1.帮助信息的打印: 

想要达到一个这样的效果:

我们只需要找到 合适的坐标,接着打印一下就可以了,可以把这个函数在游戏运行里调用,因为总分和每个食物的分数是要进行更新的。

添加此功能后的代码:

//暂停游戏
void PAUSE()
{
	while (1)
	{
		Sleep(100);
		if (KEY_PASS(VK_SPACE))
		{
			break;
		}
	}
}

//打印帮助信息
void Print_help()
{
	Set_Con(64, 14);
	printf("按ESC退出游戏");
	Set_Con(64, 15);
	printf("按F3加速,按F4减速");
}


void StartGame(Snake* ppsnake)
{
	Print_help();
	do {
        Set_Con(64, 12);
		printf("总分数:%d", ppsnake->score);
		Set_Con(64, 13);
		printf("当前每个食物的分数:%02d", ppsnake->foof_weight);
		if (KEY_PASS(VK_UP) && ppsnake->dir != DOWN)//向上
		{
			ppsnake->dir = UP;
		}
		else if (KEY_PASS(VK_DOWN) && ppsnake->dir != UP)//向下
		{
			ppsnake->dir = DOWN;
		}
		else if (KEY_PASS(VK_LEFT) && ppsnake->dir != RIGHT)//向左
		{
			ppsnake->dir = LEFT;
		}
		else if (KEY_PASS(VK_RIGHT) && ppsnake->dir != LEFT)//向右
		{
			ppsnake->dir = RIGHT;
		}
		else if (KEY_PASS(VK_SPACE))//暂停,也就是写一个死循环,永远处于休眠状态
		{
			PAUSE();
		}
		else if (KEY_PASS(VK_ESCAPE))//正常退出游戏
		{
			ppsnake->status = OVER_NORMAL;
		}
		else if (KEY_PASS(VK_F3))//加速
		{
            //对加速次数进行限制,防止蛇出现瞬移的情况
			if (ppsnake->foof_weight < 18)
			{
				ppsnake->sleep_time -= 30;
				ppsnake->foof_weight += 2;
			}
		}
		else if (KEY_PASS(VK_F4))//减速
		{
            //与加速相同,要做次数限制
			if (ppsnake->foof_weight > 2)
			{
				ppsnake->sleep_time += 30;
				ppsnake->foof_weight -= 2;
			}
		}
        Snakemove(ppsnake);//移动蛇
		Sleep(ppsnake->sleep_time);//等价于蛇的速度
	} while (ppsnake->status == REGULAR);
}

接下来实现蛇的移动 ,蛇的移动其实就是每隔一定时间重新打印蛇,只不过这个时间很短,人的肉眼难以分辨罢了,所以我们只需要着力去实现Snakemove函数。

2.蛇的移动:

实现该函数的思路大概就是去创建一个新的结点,这个结点储存着蛇头的下一个位置结点,需要注意的一点是,防止蛇头只有一半接触到食物,效果如下,所以我们需要蛇身x坐标一直是2的倍数,接下来开始写代码。

 

代码:

//蛇的移动
void Snakemove(Snake* pp)
{
	snakeNode* pnew = (snakeNode*)malloc(sizeof(snakeNode));
	if (pnew == NULL)
	{
		perror("malloc");
		return;
	}
	switch (pp->dir)
	{
	case UP:
		pnew->x = pp->psnake->x;
		pnew->y = pp->psnake->y - 1;
		break;
	case DOWN:
		pnew->x = pp->psnake->x;
		pnew->y = pp->psnake->y + 1;
		break;
	case LEFT:
		pnew->x = pp->psnake->x - 2;
		pnew->y = pp->psnake->y;
		break;
	case RIGHT:
		pnew->x = pp->psnake->x + 2;
		pnew->y = pp->psnake->y;
		break;
	}
	
}

 

 3.判断下一个位置是否是食物:

在蛇移动的过程中,下一个结点可能会是食物,所以我们要做出相应的反应,大纲如下:

//蛇的移动
void Snakemove(Snake* pp)
{
	snakeNode* pnew = (snakeNode*)malloc(sizeof(snakeNode));
	if (pnew == NULL)
	{
		perror("malloc");
		return;
	}
	switch (pp->dir)
	{
	case UP:
		pnew->x = pp->psnake->x;
		pnew->y = pp->psnake->y - 1;
		break;
	case DOWN:
		pnew->x = pp->psnake->x;
		pnew->y = pp->psnake->y + 1;
		break;
	case LEFT:
		pnew->x = pp->psnake->x - 2;
		pnew->y = pp->psnake->y;
		break;
	case RIGHT:
		pnew->x = pp->psnake->x + 2;
		pnew->y = pp->psnake->y;
		break;
	}
	//判断下一个结点是不是食物
	if (IF_FOOF(pnew, pp))
	{
		//是食物:
		EatFood(pnew, pp);
		pnew = NULL;
	}
	else
	{
		//不是食物:
		NoFood(pnew, pp);
	}
	
}

接下来我们来实现EatFood和NoFood这两个函数,只需要判断蛇移动的下一个结点的x,y坐标是否相同即可,所以在传参的时候需要将蛇头的下一个结点传过去。

EatFood函数:

如果下一个结点是食物,可以直接将其化作新的蛇头,正好蛇的长度加一,重新打印蛇身,在吃掉食物的同时,我们要再创建一个新的食物,并且在总分中加上食物的分数,具体代码如下:

//是食物:
void EatFood(snakeNode* pnew, Snake* pp)
{
	//把新的结点加到蛇身上
	pp->pfood->next = pp->psnake;
	pp->psnake = pp->pfood;
	snakeNode* cur = pp->psnake;
	//释放新的结点
	free(pnew);
	pnew = NULL;
	//重新打印蛇身
	while (cur != NULL)
	{
		Set_Con(cur->x, cur->y);
		wprintf(L"%lc", L'●');
		cur = cur->next;
	}
	pp->score += pp->foof_weight;
	//创建新的食物:
	Print_food(pp);
}

 

NoFood函数:

如果下一个结点不是食物,我们需要把新的结点加到蛇的身上,让其成为新的蛇头,并且重新打印蛇身,需要注意的是原来蛇的尾巴需要释放掉,不然蛇会越来越长,不过在释放之前需要用到蛇的尾部结点去将原来尾部的位置用两个空格遮盖住,具体代码如下:

//不是食物:
void NoFood(snakeNode* pnew, Snake* pp)
{
	//把新的结点加到蛇身上
	pnew->next = pp->psnake;
	pp->psnake = pnew;
	snakeNode* cur = pp->psnake;
	//重新打印蛇身
	while (cur->next->next != NULL)
	{
		Set_Con(cur->x, cur->y);
		wprintf(L"%lc", L'●');
		cur = cur->next;
	}
	Set_Con(cur->next->x, cur->next->y);
	printf("  ");
	//释放尾部结点
	free(cur->next);
	cur->next = NULL;
}

 判断完下一个结点是不是食物后还要判断是否撞墙和撞到自己,大纲如下:

//蛇的移动
void Snakemove(Snake* pp)
{
	snakeNode* pnew = (snakeNode*)malloc(sizeof(snakeNode));
	if (pnew == NULL)
	{
		perror("malloc");
		return;
	}
	switch (pp->dir)
	{
	case UP:
		pnew->x = pp->psnake->x;
		pnew->y = pp->psnake->y - 1;
		break;
	case DOWN:
		pnew->x = pp->psnake->x;
		pnew->y = pp->psnake->y + 1;
		break;
	case LEFT:
		pnew->x = pp->psnake->x - 2;
		pnew->y = pp->psnake->y;
		break;
	case RIGHT:
		pnew->x = pp->psnake->x + 2;
		pnew->y = pp->psnake->y;
		break;
	}
	//判断下一个结点是不是食物
	if (IF_FOOF(pnew, pp))
	{
		//是食物:
		EatFood(pnew, pp);
		pnew = NULL;
	}
	else
	{
		//不是食物:
		NoFood(pnew, pp);
	}
	//撞到墙
	KillByWall(pp);
	//撞到自己
	KillBySelf(pp);
}

4.是否撞墙和撞到自己

接下来我们尝试去实现KillByWall和KillBySelf函数:

KillByWall函数

只需判断蛇头是否超出我们前面绘制地图时给出的范围,如果超过了就修改游戏状态即可,具体代码如下:

//撞到墙
void KillByWall(Snake* pp)
{
	if (pp->psnake->x == 0 || pp->psnake->x == 56 || pp->psnake->y == 0 || pp->psnake->y == 26)
	{
		pp->status = OVER_BY_WALL;
	}
}

 

KillBySelf函数:

将除蛇头以外的蛇身的每一个结点与蛇头的x,y坐标进行对比即可,具体代码如下:

//撞到自己
void KillBySelf(Snake* pp)
{
	snakeNode* cur = pp->psnake->next;
	while (cur)
	{
		if (cur->x == pp->psnake->x && cur->y == pp->psnake->y)
		{
			pp->status = OVER_BY_SELF;
			break;
		}
		cur = cur->next;
	}
}

到这里游戏的运行就已经完成了,但是我们还要对游戏结束后进行善后工作。

5.游戏结束善后工作:

首先要提示玩家游戏为什么结束,然后释放蛇的结点,具体代码如下:

//结束游戏
void EndGame(Snake* pp)
{
	switch (pp->status)
	{
	case OVER_BY_SELF:  
		printf("撞到自己了,本局游戏结束!!!");
		break;
	case OVER_BY_WALL:
		printf("撞到墙了,本局游戏结束!!!");
		break;
	case OVER_NORMAL:
		printf("本局游戏已正常退出!!!");
		break;
	}
	//释放结点
	snakeNode* cur = pp->psnake;
	while (cur)
	{
		snakeNode* del = cur;
		cur = cur->next;
		free(del);
	}
}

 

 细节补充:

在传参上我们需要注意因为我们在实现功能的时候对结构体中的内容进行了修改,所以要传结构体指针。

我们还可以在增加一些细节,比如使用文件操作来储存历史最高纪录,这方面的知识在通讯录的实现中讲过,感兴趣可以去看一下。

游戏所有代码: 

到这里已经将所有功能都实现了,现在就将他们的合体展示出来,分为三个文件:

snake.h:

#pragma once
#include<Windows.h>
#include<stdio.h>
#include<stdbool.h>
#include<locale.h>
#include<stdlib.h>
#include<time.h>
#define KEY_PASS(vk) ((GetAsyncKeyState(vk)&1)?1:0)


//蛇身的结点
typedef struct SnakeNode
{
	//结点的坐标
	short x;
	short y;
	//指向下一个结点
	struct SnakeNode* next;
}snakeNode;

//蛇的方向
typedef enum DIRECTION {
	UP = 1,
	DOWN,
	LEFT,
	RIGHT,
}DIRECTION;

//游戏状态
typedef enum GAME_STATUS
{
	//正常运行
	 REGULAR= 1, 
	//撞墙
	OVER_BY_WALL,
	//撞到自己
	OVER_BY_SELF,
	//正常退出
	OVER_NORMAL
}GAME_STATUS;


//贪吃蛇
typedef struct Snake
{
	//蛇的头结点
	snakeNode* psnake;
	//食物
	snakeNode* pfood;
	//蛇的方向
	DIRECTION dir;
	//蛇的速度
	int sleep_time;//时间和速度成反比
	//一个食物的分数
	int foof_weight;
	//总分数
	int score;
	//游戏状态
	GAME_STATUS status;
}Snake;


//游戏的初始化
void Set_Game(Snake* ppsnake);
//光标位置的设置
void Set_Con(int x, int y);
//0.设置窗口的大小并隐藏光标
void Std_set();
//1.欢迎界面
void Print_wel();
//2.规则说明界面
void Print_rule();
//3.绘制地图
void Print_wall();
//4.绘制蛇
void Print_snake(Snake* ppsnake);
//5.绘制食物
void Print_food(Snake* ppsnake);
//开始游戏
void StartGame(Snake* snake);
//判断下一个结点是不是食物
int IF_FOOF(snakeNode* pnew, Snake* pp);
//是食物:
void EatFood(snakeNode* pnew,Snake* pp);
//不是食物:
void NoFood(snakeNode* pnew, Snake* pp);
//撞到墙
void KillByWall(Snake* pp);
//撞到自己
void KillBySelf(Snake* pp);
//结束游戏
void EndGame(Snake* pp);



snake.c:

#include"snake.h"

//游戏的初始化
void Set_Game(Snake* ppsnake)
{
	//0.设置窗口的大小并隐藏光标
	Std_set();
	//1.欢迎界面
	Print_wel();
	//2.规则说明界面
	Print_rule();
	//3.绘制地图
	Print_wall();
	//4.绘制蛇
	Print_snake(ppsnake);
	//5.绘制食物
	Print_food(ppsnake);
}

//光标位置的设置
void Set_Con(int x, int y)
{
	HANDLE output = NULL;
	output = GetStdHandle(STD_OUTPUT_HANDLE);
	COORD coor = { x, y };
	SetConsoleCursorPosition(output, coor);
}


//0.设置窗口的大小并隐藏光标
void Std_set()
{
	system("mode con cols=100 lines=30");
	system("title 贪吃蛇");
	HANDLE houtput = NULL;
	houtput = GetStdHandle(STD_OUTPUT_HANDLE);
	CONSOLE_CURSOR_INFO con;
	GetConsoleCursorInfo(houtput, &con);
	con.bVisible = false;
	SetConsoleCursorInfo(houtput, &con);
}

//1.欢迎界面
void Print_wel()
{
	Set_Con(40, 12);
	printf("欢迎来到贪吃蛇小游戏!!!\n\n\n\n");
	system("pause");
	system("cls");
}

//2.规则说明界面
void Print_rule()
{
	Set_Con(15, 10);
	printf("请用↑. ↓. ←. →控制蛇移动的方向,F3为加速,F4为减速,加速会获得更高的分数。\n\n\n\n\n\n\n");
	system("pause");
	system("cls");
}

//3.绘制地图
void Print_wall()
{
	for (int i = 0; i < 29; i++)
	{
		wprintf(L"%lc", L'□');
	}
	Set_Con(0, 26);
	for (int i = 0; i < 29; i++)
	{
		wprintf(L"%lc", L'□');
	}
	for (int i = 1; i < 26; i++)
	{
		Set_Con(0, i);
		wprintf(L"%lc", L'□');
	}
	for (int i = 1; i < 26; i++)
	{
		Set_Con(56, i);
		wprintf(L"%lc", L'□');
	}
}

//4.绘制蛇
void Print_snake(Snake* ppsnake) 
{
	snakeNode* cur = NULL;
	for (int i = 0; i < 5; i++)
	{
		cur = (snakeNode*)malloc(sizeof(snakeNode));
		if (cur == NULL)
		{
			perror("malloc");
			exit(1);
		}
		cur->x = 24 + 2 * i;
		cur->y = 5;
		cur->next = NULL;
		//尾插
		if (ppsnake->psnake == NULL)
		{
			ppsnake->psnake = cur;
		}
		else
		{
			cur->next = ppsnake->psnake;
			ppsnake->psnake = cur;
		}
	}
	cur = ppsnake->psnake;
	//打印蛇身
	while (cur)
	{
		Set_Con(cur->x, cur->y);
		wprintf(L"%lc", L'●');
	    cur = cur->next;
	}
	//设置食物分数
	ppsnake->foof_weight = 10;
	//总分
	ppsnake->score = 0;
	//速度
	ppsnake->sleep_time = 200;
	//初始方向
	ppsnake->dir = RIGHT;
	//游戏状态
	ppsnake->status = REGULAR;

}

//5.绘制食物
void Print_food(Snake* ppsnake)
{
	int x = 0;
	int y = 0;
again:
	do
	{
		x = rand() % 53 + 2;
		y = rand() % 25 + 1;
	} while (x % 2 != 0);
	snakeNode* cur = ppsnake->psnake;
	while (cur)
	{
		if (cur->x == x && cur->y == y)
		{
			goto again;
		}
		cur = cur->next;
	}
	snakeNode*  p = (snakeNode*)malloc(sizeof(snakeNode));
	if (p == NULL)
	{
		perror("malloc");
		return;
	}
	p->x = x;
	p->y = y;
	p->next = NULL;
	Set_Con(x, y);
	wprintf(L"%lc", L'◆');
	ppsnake->pfood = p;
}

//打印帮助信息
void Print_help()
{
	Set_Con(64, 14);
	printf("按ESC退出游戏");
	Set_Con(64, 15);
	printf("按F3加速,按F4减速");
}

//暂停游戏
void PAUSE()
{
	while (1)
	{
		Sleep(100);
		if (KEY_PASS(VK_SPACE))
		{
			break;
		}
	}
}

//判断下一个结点是不是食物
int IF_FOOF(snakeNode* pnew, Snake* pp)
{
	return (pp->pfood->x == pnew->x && pp->pfood->y == pnew->y);
}

//是食物:
void EatFood(snakeNode* pnew, Snake* pp)
{
	//把新的结点加到蛇身上
	pp->pfood->next = pp->psnake;
	pp->psnake = pp->pfood;
	snakeNode* cur = pp->psnake;
	//释放新的结点
	free(pnew);
	pnew = NULL;
	//重新打印蛇身
	while (cur != NULL)
	{
		Set_Con(cur->x, cur->y);
		wprintf(L"%lc", L'●');
		cur = cur->next;
	}
	pp->score += pp->foof_weight;
	//创建新的食物:
	Print_food(pp);
}

//不是食物:
void NoFood(snakeNode* pnew, Snake* pp)
{
	//把新的结点加到蛇身上
	pnew->next = pp->psnake;
	pp->psnake = pnew;
	snakeNode* cur = pp->psnake;
	//重新打印蛇身
	while (cur->next->next != NULL)
	{
		Set_Con(cur->x, cur->y);
		wprintf(L"%lc", L'●');
		cur = cur->next;
	}
	Set_Con(cur->next->x, cur->next->y);
	printf("  ");
	//释放尾部结点
	free(cur->next);
	cur->next = NULL;
}

//撞到墙
void KillByWall(Snake* pp)
{
	if (pp->psnake->x == 0 || pp->psnake->x == 56 || pp->psnake->y == 0 || pp->psnake->y == 26)
	{
		pp->status = OVER_BY_WALL;
	}
}

//撞到自己
void KillBySelf(Snake* pp)
{
	snakeNode* cur = pp->psnake->next;
	while (cur)
	{
		if (cur->x == pp->psnake->x && cur->y == pp->psnake->y)
		{
			pp->status = OVER_BY_SELF;
			break;
		}
		cur = cur->next;
	}
}


//蛇的移动
void Snakemove(Snake* pp)
{
	snakeNode* pnew = (snakeNode*)malloc(sizeof(snakeNode));
	if (pnew == NULL)
	{
		perror("malloc");
		return;
	}
	switch (pp->dir)
	{
	case UP:
		pnew->x = pp->psnake->x;
		pnew->y = pp->psnake->y - 1;
		break;
	case DOWN:
		pnew->x = pp->psnake->x;
		pnew->y = pp->psnake->y + 1;
		break;
	case LEFT:
		pnew->x = pp->psnake->x - 2;
		pnew->y = pp->psnake->y;
		break;
	case RIGHT:
		pnew->x = pp->psnake->x + 2;
		pnew->y = pp->psnake->y;
		break;
	}
	//判断下一个结点是不是食物
	if (IF_FOOF(pnew, pp))
	{
		//是食物:
		EatFood(pnew, pp);
		pnew = NULL;
	}
	else
	{
		//不是食物:
		NoFood(pnew, pp);
	}
	//撞到墙
	KillByWall(pp);
	//撞到自己
	KillBySelf(pp);
}


//开始游戏
void StartGame(Snake* ppsnake)
{
	Print_help();
	do {
		Set_Con(64, 12);
		printf("总分数:%d", ppsnake->score);
		Set_Con(64, 13);
		printf("当前每个食物的分数:%02d", ppsnake->foof_weight);
		if (KEY_PASS(VK_UP) && ppsnake->dir != DOWN)//向上
		{
			ppsnake->dir = UP;
		}
		else if (KEY_PASS(VK_DOWN) && ppsnake->dir != UP)//向下
		{
			ppsnake->dir = DOWN;
		}
		else if (KEY_PASS(VK_LEFT) && ppsnake->dir != RIGHT)//向左
		{
			ppsnake->dir = LEFT;
		}
		else if (KEY_PASS(VK_RIGHT) && ppsnake->dir != LEFT)//向右
		{
			ppsnake->dir = RIGHT;
		}
		else if (KEY_PASS(VK_SPACE))//暂停
		{
			PAUSE();
		}
		else if (KEY_PASS(VK_ESCAPE))//正常退出游戏
		{
			ppsnake->status = OVER_NORMAL;
		}
		else if (KEY_PASS(VK_F3))//加速
		{
			if (ppsnake->foof_weight < 18)
			{
				ppsnake->sleep_time -= 30;
				ppsnake->foof_weight += 2;
			}
		}
		else if (KEY_PASS(VK_F4))//减速
		{
			if (ppsnake->foof_weight > 2)
			{
				ppsnake->sleep_time += 30;
				ppsnake->foof_weight -= 2;
			}
		}
		Snakemove(ppsnake);
		Sleep(ppsnake->sleep_time);
	} while (ppsnake->status == REGULAR);
}

//结束游戏
void EndGame(Snake* pp)
{
	switch (pp->status)
	{
	case OVER_BY_SELF:  
		printf("撞到自己了,本局游戏结束!!!");
		break;
	case OVER_BY_WALL:
		printf("撞到墙了,本局游戏结束!!!");
		break;
	case OVER_NORMAL:
		printf("本局游戏已正常退出!!!");
		break;
	}
	//释放结点
	snakeNode* cur = pp->psnake;
	while (cur)
	{
		snakeNode* del = cur;
		cur = cur->next;
		free(del);
	}
}

test.c:


#include"snake.h"

void test1()
{
	char ch = 0;
	do {

		system("cls");
		//创建贪吃蛇
		Snake snake = { 0 };

		//初始化游戏

		//0.设置窗口的大小并隐藏光标
		//1.欢迎界面
		//2.规则说明界面
		//3.绘制地图
		//4.绘制蛇
		//5.绘制食物
		//6.设置游戏的相关信息
		Set_Game(&snake);

		//开始游戏
		StartGame(&snake);
		//结束游戏
		EndGame(&snake);
		Set_Con(20, 15);
		printf("再来一局?(Y/N):");
		ch = getchar();
		while (getchar() != '\n');
	} while (ch == 'Y' || ch == 'y');
	Set_Con(0, 27);
}


int main()
{
	//适配到本地
	setlocale(LC_ALL, "");

	srand((unsigned int)time(NULL));
	//测试游戏
	test1();
	return 0;
}


 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Unity射线实现碰撞检测(不需要rigbody组件)

使用physic.CapsulCast&#xff08;&#xff09;&#xff1b; 前面3个参数生成一个胶囊体&#xff0c; 向着发射方向&#xff0c;发射出一串的胶囊&#xff08;没有最大距离&#xff09; 有最大距离&#xff0c;可以节约性能开销。 physic.CapsulCast&#xff08;&#xff0…

类的六个构造函数相关干货

构造函数 特点 1.名字与类名相同 2.无返回值 3.对象实例化的时候编译器自动调用这个函数 4.构造函数可以重载&#xff08;无参构造函数&#xff0c;拷贝构造等&#xff09; 5.如果类中没有显式定义构造函数&#xff08;深拷贝&#xff09;&#xff0c;则编译器会自动生成一个…

IP地址查询API接口怎么对接

IP地址查询API接口又叫IP归属地信息查询API接口&#xff0c;指的是根据IP地址查询归属地定位信息&#xff0c;包含国家、省、市、街道和运营商、区号、邮编、坐标等信息。那么IP地址查询API接口该怎么对接呢&#xff1f; 首先我们找到一家有做IP归属地信息查询API接口的服务商…

Python程序设计教案

文章目录&#xff1a; 一&#xff1a;软件环境安装 第一个软件&#xff1a;pycharm 第二个软件&#xff1a;thonny 第三个软件&#xff1a;IDIE&#xff08;自带的集成开发环境&#xff09; 二&#xff1a;相关 1.规范 2.关键字 3.Ascll码表 三&#xff1a;语法基础…

【学习】如何高效地进行集成测试

在软件开发的过程中&#xff0c;测试环节至关重要。而在这其中&#xff0c;集成测试更是保证软件质量的关键步骤之一。本文将探讨如何高效地进行集成测试&#xff0c;以确保软件的稳定性和可靠性。 一、什么是集成测试 集成测试是指在单元测试的基础上&#xff0c;将模块按照设…

JavaScript进阶知识点及案例(续)

编程思想 面向过程介绍 面向过程就是分析出解决问题所需要的步骤&#xff0c;然后用函数把这些步骤一步一步的实现&#xff0c;使用的时候再一个一个的依次调用就可以了 面向对象介绍 面向对象是把事务分解成为一个个对象&#xff0c;然后由对象之间分工与合作面向对象是以…

word导出或另存为pdf图片不清晰问题解决方案

问题描述&#xff1a; 使用word 2019导出pdf时图片不清晰&#xff0c;即使我已经在“选项 → \to →高级 → \to →图片大小和质量 → \to →不压缩文件中的图像 ”选项卡中关闭掉了图片压缩依然无效。 解决方案&#xff1a; 利用word foxit pdf 软件打印的方案转pdf。 &…

Linux加强篇-存储结构与管理硬盘(三)

目录 ⛳️推荐 磁盘容量配额 VDO虚拟数据优化 软硬方式链接 ⛳️推荐 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站 磁盘容量配额 使用磁盘容量配额服务来限制某位用户或某个用户组针…

Java 笔记 07:包机制,JavaDoc 文档的生成方式,Math 工具类,以及字符串连接符相关内容

一、前言 记录时间 [2024-04-25] 系列文章简摘&#xff1a; Java 笔记 01&#xff1a;Java 概述&#xff0c;MarkDown 常用语法整理 Java 笔记 04&#xff1a;Java 数据类型基础&#xff0c;数据类型转换&#xff0c;及其相关场景拓展 Java 笔记 05&#xff1a;变量和常量相关知…

第55篇:创建Nios II工程之Hello_World<一>

Q&#xff1a;本期我们开始介绍创建Platform Designer系统&#xff0c;并设计基于Nios II Processor的Hello_world工程。 A&#xff1a;设计流程和实验原理&#xff1a;需要用到的IP组件有Clock Source、Nios II Processor、On-Chip Memory、JTAG UART和System ID外设。Nios I…

Golang | Leetcode Golang题解之第48题旋转图像

题目&#xff1a; 题解&#xff1a; func rotate(matrix [][]int) {n : len(matrix)// 水平翻转for i : 0; i < n/2; i {matrix[i], matrix[n-1-i] matrix[n-1-i], matrix[i]}// 主对角线翻转for i : 0; i < n; i {for j : 0; j < i; j {matrix[i][j], matrix[j][i]…

2024采用JSP的酒店客房管理系统源代码+毕业设计论文+开题报告+答辩PPT

点击下载源码 摘 要 计算机技术发展至今已走过了半个多世纪之久&#xff0c;现在各个阶层、各个领域都使用着计算机&#xff0c;在这个快节奏的时代中它已经成为了社会生活的必需品。它的出现是现代社会进步&#xff0c;科技发展的标志。同时现代化的酒店组织庞大&#xff0c;…

C++: IO流

目录 1、C语言输入输出 流的概念&#xff1a; 2、CIO流 3、C文件IO流 1、C语言输入输出 C语言中我们用到的最频繁的输入输出方式就是scanf () 与 printf() 。 scanf(): 从标准输入设备 ( 键 盘 ) 读取数据&#xff0c;并将值存放在变量中 。 printf(): 将指定的文…

新能源汽车小米su7

小米su7汽车 function init() {const container document.querySelector( #container );camera new THREE.PerspectiveCamera( 20, window.innerWidth / window.innerHeight, 1, 50000 );camera.position.set( 0, 700, 7000 );scene new THREE.Scene();scene.background ne…

深入浅出 Spring Boot 3.x:从原理到实战,全面解锁 Java 后端开发新潮流

&#x1f482; 个人网站:【 摸鱼游戏】【神级代码资源网站】【工具大全】&#x1f91f; 一站式轻松构建小程序、Web网站、移动应用&#xff1a;&#x1f449;注册地址&#x1f91f; 基于Web端打造的&#xff1a;&#x1f449;轻量化工具创作平台&#x1f485; 想寻找共同学习交…

一分钟教会你使用51cto网课视频下载工具下载51cto视频

想要学习技术知识&#xff0c;提升自己的职业能力&#xff0c;51cto是一个绝佳的选择。然而&#xff0c;有时候我们可能无法随时在线观看这些精彩的视频课程。别担心&#xff01;我将在一分钟内教您如何使用51cto视频下载工具&#xff0c;将这些宝贵的学习资源下载到您的设备上…

物联网鸿蒙实训解决方案

一、建设背景 在数字化浪潮汹涌的时代&#xff0c;华为鸿蒙系统以其前瞻的技术视野和创新的开发理念&#xff0c;成为了引领行业发展的风向标。 据华为开发者大会2023&#xff08;HDC. Together&#xff09;公布的数据&#xff0c;鸿蒙生态系统展现出了强劲的发展动力&#x…

钡铼IOy系列模块在无人值守智能仓库中的成功运用,提升仓储物流效率

随着科技的不断发展&#xff0c;无人值守智能仓库正成为现代物流行业的一个重要趋势。在这个快节奏的时代&#xff0c;提升仓储物流效率是企业追求的目标之一。钡铼IOy系列模块为无人值守智能仓库的成功运作提供了关键支持。本文将探讨钡铼IOy系列模块在无人值守智能仓库中的应…

子域名如何启用HTTPS,有免费泛域名SSL证书吗

如今HTTPS已经成为了网站标配&#xff0c;然而&#xff0c;对于一些刚刚起步的网站或是个人博客而言&#xff0c;如何自动跳转到HTTPS&#xff0c;以及免费SSL证书的获取&#xff0c;可能还是一个需要解决的问题。下面就来详细解答这两个问题。 我们需要明确HTTPS与SSL之间的关…

OpenAI 和 Moderna 合作,推进 mRNA 医学

&#x1f349; CSDN 叶庭云&#xff1a;https://yetingyun.blog.csdn.net/ 一、关于 Moderna Moderna 是 mRNA 医学领域的佼佼者&#xff0c;其通过不断推动 mRNA 技术的发展&#xff0c;正在重塑药物的制造方式&#xff0c;并深刻地改变我们治疗和预防疾病的方法。凭借在科学、…