小游戏贪吃蛇的实现之C语言版

找往期文章包括但不限于本期文章中不懂的知识点:

个人主页:我要学编程(ಥ_ಥ)-CSDN博客

所属专栏:C语言

目录

游戏前期准备:

设置控制台相关的信息 

GetStdHandle

GetConsoleCursorInfo 

SetConsoleCursorInfo

SetConsoleCursorPosition

GetAsyncKeyState

贪吃蛇游戏设计与分析 

本地化

地图,食物和蛇身的设计 

GameStart()—— 游戏的初始化

打印欢迎界面 

绘制贪吃蛇地图 

初始化贪吃蛇

初始化食物

GameRun()——游戏的运行

打印右侧的帮助信息

贪吃蛇的相关运行信息 

GameOver()——游戏的结束(善后工作) 

贪吃蛇源码 


使用C语言在Windows环境的控制台中模拟实现经典小游戏贪吃蛇。

游戏前期准备:

 本次实现贪吃蛇会使用到的一些Win32 API知识,接下来我们就学习一下。

背景介绍

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

设置控制台相关的信息 

平常我们运行起来的黑框程序其实就是控制台程序(如下图所示)。

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

//格式:  列        行
mode con cols=100 lines=30

注意:

1. 列和行在赋值时,不能带有空格。例如:cols = 100,这就是不行的,没有影响到控制台的大小。

2. 使用这个命令之前,需要把这个控制台改为让Windows决定或者Windows 控制台主机

演示:

改变VS编译器的控制台

3. 使用system函数所需要包含的头文件既可以是stdlib.h,也可以是Windows.h(不分大小写的,因此可以使用"windows.h"、"WINDOWS.H"或者"Windows.h"等形式来引用该头文件。不过,一般约定使用"Windows.h"的形式来引用该头文件,以保持代码的一致性和可读性。) 

下面就来使用这个来改变控制台的大小。

从上面的结果来看:行列对应不一致。没错,一行的宽度是一列的宽度的二倍

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

//格式:
title 要修改的名字

注意:在更改之后要观察到的话,就不能让程序运行结束,也就是说只能在程序运行期间才能够观察的到。

 控制台屏幕上的坐标COORD

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

COORD类型的声明:

typedef struct _COORD {
    SHORT X;//短整型
    SHORT Y;
} COORD, *PCOORD;

给坐标赋值:

COORD pos = { 10, 15 };

GetStdHandle

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

标准输入是指键盘,标准输出和标准错误是指屏幕。

这个句柄就类似一个遥控器,可以通过句柄来操作标准设备。而我们想要操作标准设备也得通过GetStdHandle这个函数来获得句柄。再通过句柄来操作。

HANDLE GetStdHandle(DWORD nStdHandle);

例如:

HANDLE hOutput = NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);

GetConsoleCursorInfo 

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

BOOL WINAPI GetConsoleCursorInfo(
 HANDLE hConsoleOutput,
 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 = {0};
//获取控制台光标信息
GetConsoleCursorInfo(hOutput, &CursorInfo);

SetConsoleCursorInfo

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

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

举例:

//获取标准输出的句柄
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
//定义一个存放光标信息的结构体
CONSOLE_CURSOR_INFO CursorInfo = {0};
//获取控制台光标信息存放到这个结构体中
GetConsoleCursorInfo(hOutput, &CursorInfo);
//隐藏控制台光标
CursorInfo.bVisible = false; 
//把控制台光标大小调到最大
CursorInfo.dwSize = 100;
//设置控制台光标状态(按照上面的设置调)
SetConsoleCursorInfo(hOutput, &CursorInfo);

SetConsoleCursorPosition

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

BOOL WINAPI SetConsoleCursorPosition(
    HANDLE hConsoleOutput,
    COORD pos
);

举例:

//改变光标的位置
COORD pos = { 10, 5};
HANDLE hOutput = NULL;
//获取标准输出的句柄
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
//设置标准输出上光标的位置为pos
SetConsoleCursorPosition(hOutput, pos);

注意:这个pos的位置设置有可能不一定会成功。 

GetAsyncKeyState

获取按键情况,GetAsyncKeyState的函数原型如下:

SHORT GetAsyncKeyState(int vKey);

将键盘上每个键的虚拟键值传递给函数,函数通过返回值来分辨按键的状态。GetAsyncKeyState 的返回值是short类型,在上一次调用 GetAsyncKeyState 函数后,如果返回的16位的short数据中,最高位是1,说明按键的状态是按下,如果最高是0,说明按键的状态是抬起;如果最低位被置为1则说明,该按键被按过,否则为0。 如果我们要判断一个键是否被按过,可以检测GetAsyncKeyState返回值的最低位是否为1 。下面是虚拟键代码虚拟键码 (Winuser.h) - Win32 apps | Microsoft Learn虚拟键码

举例:检测数字键是否被摁过。(字母键上面的数字键)

//判断一个键是否被摁过
//如果被摁过结果就是1,否则就是0
#define KEY_PRESS(vk) ((GetAsyncKeyState(vk)&0x1)?1:0)
int main()
{
	while (1)
	{
		if (KEY_PRESS(0x30))
		{
			printf("%d\n", 0);
		}
		else if (KEY_PRESS(0x31))
		{
			printf("%d\n", 1);
		}
		else if (KEY_PRESS(0x32))
		{
			printf("%d\n", 2);
		}
		else if (KEY_PRESS(0x33))
		{
			printf("%d\n", 3);
		}
		else if (KEY_PRESS(0x34))
		{
			printf("%d\n", 4);
		}
		else if (KEY_PRESS(0x35))
		{
			printf("%d\n", 5);
		}
		else if (KEY_PRESS(0x36))
		{
			printf("%d\n", 6);
		}
		else if (KEY_PRESS(0x37))
		{
			printf("%d\n", 7);
		}
		else if (KEY_PRESS(0x38))
		{
			printf("%d\n", 8);
		}
		else if (KEY_PRESS(0x39))
		{
			printf("%d\n", 9);
		}
	}
}

贪吃蛇游戏设计与分析 

实现基本的功能:

• 贪吃蛇地图绘制

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

• 蛇撞墙死亡

• 蛇撞自身死亡

• 计算得分

• 蛇身加速、减速

• 暂停游戏

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

普通的字符是占一个字节的,这类宽字符是占用2个字节。 

加入了宽字符的类型 wchar_t 和宽字符的输入和输出函数,加入了<locale.h>头文件,其中提供了允许程序员针对特定地区(通常是国家或者说某种特定语言的地理区域)调整程序行为的函数。

<locale.h>本地化

setlocale函数原型:

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

类项

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

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

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

• LC_MONETARY:影响货币格式。

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

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

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

setlocale 函数用于修改当前地区,可以针对一个类项修改,也可以针对所有类项。 setlocale 的第一个参数可以是前面说明的类项中的一个,那么每次只会影响一个类项,如果第一个参数是LC_ALL,就会影响所有的类项。 C标准给第二个参数仅定义了2种可能取值:"C"(正常模式)和" "(本地模式)。 在任意程序执行开始,都会隐藏式执行调用:

setlocale(LC_ALL, "C");

当地区设置为"C"时,库函数按正常方式执行,小数点是一个点。 当程序运行起来后想改变地区,就只能显示调用setlocale函数。用" "作为第2个参数,调用setlocale 函数就可以切换到本地模式,这种模式下程序会适应本地环境。比如:切换到我们的本地模式后就支持宽字符(汉字)的输出等。 

setlocale(LC_ALL, "");//切换到本地环境(注意这里双引号里不能有空格)

那如果想在屏幕上打印宽字符,怎么打印呢? 宽字符的字面量必须加上前缀“L”,否则 C 语言会把字面量当作窄字符类型处理。前缀“L”在单引号前面,表示宽字符,对应 wprintf() 的占位符为 %lc ;在双引号前面,表示宽字符串,对应 wprintf() 的占位符为 %ls 。

举例:

#include <stdio.h>
#include <locale.h>
int main()
{
    //切换到本地环境
    setlocale(LC_ALL, "");
    wprintf(L"%s\n", L"我要学编程");

    wchar_t wc1 = L'我';
    wchar_t wc2 = L'要';
    wchar_t wc3 = L'学';
    wchar_t wc4 = L'编';
    wchar_t wc5 = L'程';
    wprintf(L"%lc", wc1);
    wprintf(L"%lc", wc2);
    wprintf(L"%lc", wc3);
    wprintf(L"%lc", wc4);
    wprintf(L"%lc", wc5);
    return 0;
}

地图,食物和蛇身的设计 

我们假设实现一个棋盘27行,58列的棋盘,再围绕地图画出墙。

由于1行的宽度是一列宽度的二倍,就可以按照上面的样式绘制地图。这些都用宽字符来打印。

初始化状态:假设蛇的长度是5,蛇身的每个节点是●,在固定的一个坐标处,比如(24, 5)处开始出现蛇,连续5个节点。 注意:蛇的每个节点的x坐标必须是2个倍数,否则可能会出现蛇的一个节点有一半出现在墙体中, 另外一半在墙外的现象。 关于食物,就是在墙体内随机生成一个坐标(x坐标必须是2的倍数),坐标不能和蛇的身体重合,然后打印★。食物的x坐标也要是2的倍数。

在游戏运行的过程中,蛇每次吃一个食物,蛇的身体就会变长一节,如果我们使用链表存储蛇的信 息,那么蛇的每一节其实就是链表的一个节点。每个节点只要记录好蛇身节点在地图上的坐标就行, 所以蛇节点结构如下: 

//蛇身的节点类型
typedef struct SnakeNode
{
	//坐标
	int x;
	int y;
	//指向下一个节点的指针
	struct SnakeNode* next;
}SnakeNode, * pSnakeNode;

还得创建一些变量:指向蛇头的指针,初始时蛇的速度,蛇的方向,食物,食物分数,总分,贪吃蛇的状态。但是这些都比较麻烦,我们就创建一个贪吃蛇的结构体来管理这些变量。

//贪吃蛇
typedef struct Snake
{
	pSnakeNode _pSnake;//指向蛇头的指针
	pSnakeNode _pFood;//指向食物的指针
	DIRECTION _dir;//蛇的方向
	GAME_STATE _state;//游戏的运行状态
	int _FoodWeight;//一个食物的分数
	int _score;//总分数
	int _SleepTime;//休眠时间,时间越短,速度越快,时间越长,速度越慢
}Snake, * pSnake;

蛇的方向有四种:上,下,左,右。我们就可以枚举出来。

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

游戏的运行状态:正常运行,正常退出,撞墙死亡,撞到自己死亡。

//游戏的状态
typedef enum GAME_STATE
{
	OK,//正常运行
	KILL_BY_WALL,//撞墙
	KILL_BY_SELF,//撞到自己
	END_NORMAL//正常退出
}GAME_STATE;

接下来就是正式的游戏设计。

首先分装三个大的函数。

GameStart()—— 游戏的初始化

初始化的内容:1,打印欢迎界面  2,绘制贪吃蛇地图  3,初始化贪吃蛇和食物 。

打印欢迎界面 

首先得分装一个函数用来定位坐标。

//定位光标
void SetPos(short x, short y)
{
	HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);//获取标准输出的句柄
	COORD pos = { x, y };
	SetConsoleCursorPosition(houtput, pos);//设置光标的位置
}

system("pause")是一个用于暂停控制台的函数。它会在控制台输出一个提示信息,等待用户按下任意键后才会继续执行程序。

system("pause");

由上面的界面切换到下面这个界面,就需要用到一个清理控制台界面的函数。 

system("cls");//用于清理当前控制台的界面所有信息

打印欢迎界面:

//打印欢迎界面
void WelcomToGame()
{
	//首先得定位光标
	SetPos(39, 10);
	printf("欢迎来到贪吃蛇小游戏\n");
	SetPos(40, 15);
	system("pause");//暂停
	system("cls");//清理屏幕

	SetPos(28, 10);
	printf("用↑.↓.←.→ 来控制蛇的移动!摁F3加速!摁F4减速!\n");
	SetPos(38, 11);
	printf("游戏即将开始,请做好准备!\n");
	SetPos(40, 15);
	system("pause");
	system("cls");
}

绘制贪吃蛇地图 

上面这个就是我们要绘制的地图。值得一提的是:这个方块是宽字符,而使用wprintf来打印宽字符就得先将C语言环境转化到本地环境。至于后面的打印,就是通过定位来循环打印。

先打印上下两行,再打印左右两列。

上一行的坐标是(2*i,0),i 的范围是0~28。  下一行的坐标是(2*i,25),i 的范围是0~28。 

左一列的坐标是(0,i),i 的范围是1~25。      右一列的坐标是(56,i),i 的范围是1~25。

注意:

1. 一行的打印就相当于是打印了一列中的一个,因此用总列数-2就是我们要打印的列坐标。

2. 列在打印时,需要先定位好坐标。因为打印的顺序是默认从左到右的;而我们是要实现从上到下的打印。

#define WALL L'□'


//绘制地图
void CreatMap()
{
	//打印上体墙
	for (int i = 0; i <= 28; i++)
	{
		wprintf(L"%lc", WALL);
	}
	SetPos(0, 26);//定位到下体墙的位置
	//打印下体墙
	for (int i = 0; i <= 28; i++)
	{
		wprintf(L"%lc", WALL);
	}
	//打印左体墙
	for (int i = 1; i <= 25; i++)
	{
		SetPos(0, i);
		wprintf(L"%lc", WALL);//用换行不行,因此加不加换行无所谓
	}
	//打印右体墙
	for (int i = 1; i <= 25; i++)
	{
		SetPos(56, i);
		wprintf(L"%lc", WALL);//用换行不行,因此加不加换行无所谓
	}
}

初始化贪吃蛇

既然要初始化蛇,首先就得有蛇,而蛇是用节点串起来的。因此我们只要创建5个节点,并且传入我们想要的坐标(我们想要蛇出现在哪个位置,这个位置最好是固定的,这里也是采用固定的位置。),最后把这些节点串起来就行了。

//创建一条蛇
pSnakeNode pcur = NULL;
pSnakeNode prev = NULL;
for (int i = 0; i < 5; i++)
{
	pSnakeNode tmp = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (tmp == NULL)
	{
		perror("InitSnake():malloc:");
		return;
	}
	pcur = tmp;
	pcur->x = POS_X + 2 * i;
	pcur->y = POS_Y;
	pcur->next = NULL;
	if (ps->_pSnake == NULL)
	{
		//直接插入即可
		ps->_pSnake = pcur;
	}
	else
	{
		//尾插
		if (prev != NULL)
			prev->next = pcur;
	}
	prev = pcur;
}

打印蛇(打印整个链表)

#define BODY L'●'
#define HEAD L'◆'


//开始在控制台上打印蛇
int count = 0;
while (pcur)
{
	SetPos(pcur->x, pcur->y);//定位
	if (count == 0)
	{
		count++;
		wprintf(L"%lc", HEAD);//打印蛇头
	}
	else
	{
		wprintf(L"%lc", BODY);//打印蛇身
	}
	pcur = pcur->next;
}

设置贪吃蛇的属性。

//设置贪吃蛇的属性
ps->_dir = LEFT;//初始时蛇的方向向左
ps->_FoodWeight = 50;//一个食物50分
ps->_score = 0;//总分为0
ps->_SleepTime = 200;//单位是毫秒
ps->_state = OK;//正常运行

 因为这里的蛇头是在最左边,所以这个蛇的初始方向不能是向右走,除此之外都可以。

初始化食物

初始化食物其实就是创建一个食物并且打印出来。

这个食物为了能够被蛇给吃掉,x坐标也必须是2的倍数,并且这个食物应该是要随机生成的,还要在这个墙体中。 

	int x = 0;
	int y = 0;
	//随机创建食物(食物的x坐标必须是2的倍数,因此要判断)
again:
	do
	{
        //为了食物出现在墙内
		x = rand() % 53 + 2;
		y = rand() % 25 + 1;

	} while (x % 2);
	//食物的坐标不能和蛇身冲突
	pSnakeNode pcur = ps->_pSnake;
	//开始寻找看看是否与蛇身冲突
	while (pcur)
	{
		if (pcur->x == x && pcur->y == y)
		{
			goto again;//如果冲突了,就要回炉重造
		}
		pcur = pcur->next;
	}
	//开始创建食物的节点
	pSnakeNode tmp = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (tmp == NULL)
	{
		perror("CreatFood():malloc:");
		return;
	}
	tmp->x = x;
	tmp->y = y;
	tmp->next = NULL;
	ps->_pFood = tmp;

打印食物

//打印食物
SetPos(x, y);
wprintf(L"%lc", FOOD);

GameRun()——游戏的运行

1,打印帮助手册  2,贪吃蛇的相关运行信息  3,判断贪吃蛇是否死亡

上图就是游戏运行时的界面。

打印右侧的帮助信息

//打印帮助信息
void PrintHelpInfo()
{
	SetPos(68, 10);
	printf("小提示:");
	SetPos(68, 13);
	printf("不能穿墙,不能咬到自己!");
	SetPos(68, 14);
	printf("用↑.↓.←.→ 来控制蛇的移动!");
	SetPos(68, 15);
	printf("摁F3加速!摁F4减速!");
	SetPos(68, 16);
	printf("加速将增加单个食物的分数!");
	SetPos(68, 17);
	printf("减速将减少单个食物的分数!");
	SetPos(68, 18);
	printf("摁Esc退出游戏!摁空格暂停游戏!");
}

贪吃蛇的相关运行信息 

贪吃蛇要运行起来,就得需要我们摁键来实现贪吃蛇的走动。所以接下来就是判断哪个键是否摁过来判断蛇的走向。而只要是蛇的状态不等于OK时,此时就不需要再走了。

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


//接下来就是通过按键来判断贪吃蛇的运行状态
do
{
	//打印分数显示
	SetPos(68, 7);
	printf("当前总分数:%08d", ps->_score);
	SetPos(68, 8);
	printf("当前食物分数:%02d", ps->_FoodWeight);
	//判断摁了什么键,根据键来判断要执行的命令
	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_ESCAPE))
	{
		//退出游戏
		ps->_state = END_NORMAL;
	}
	else if (KEY_PRESS(VK_F3))
	{
		//加速(减少休眠时间)
		if (ps->_SleepTime > 80)//设置为4档速度
		{
			ps->_SleepTime -= 30;
			ps->_FoodWeight += 10;
		}
	}
	else if (KEY_PRESS(VK_F4))
	{
		//减速
		if (ps->_FoodWeight > 10)//设置为4档速度
		{
			ps->_SleepTime += 20;
			ps->_FoodWeight -= 10;
		}
	}
	//蛇开始走
	//走一步,就休息一下
	SnakeMove(ps);//蛇走一步的过程
	Sleep(ps->_SleepTime);
	//检测是否撞墙
	KillByWall(ps);
	//检测是否撞到自己
	KillBySelf(ps);
    //通过总分数来判断游戏是否结束
    if (ps->_score > 30000)
    {
	    SetPos(20, 13);
	    printf("恭喜你!成功通关!");
    }
} while (ps->_state == OK);//只有蛇的状态正常才走

暂停的实现就只需要系统一直处于休眠状态。 

void Pause()
{
	while (1)
	{
		//休眠200毫秒
		Sleep(200);
		//这个只能放到休眠的后面
		if (KEY_PRESS(VK_SPACE))
		{
			break;
		}
	}
}

当进入这个暂停函数就需要休眠,即使再次摁了空格键,也得先休眠一下。

蛇在走的时候,就是根据我们摁的键位来判断蛇头应该出现在哪个地方。再通过链接蛇头的下一个位置和蛇身以及释放蛇身的尾节点。还有一个小细节:如果蛇头下一个位置是食物的话,就要吃掉食物,并且再创建一个食物,而如果不是食物的话,就只需要按照上面的步骤走就行了。因此就得先判断是否为食物。

//蛇走一步的过程
void SnakeMove(pSnake ps)
{
	//创建一个节点来存放蛇要走的下一个节点
	pSnakeNode next = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (next == NULL)
	{
		perror("SnakeMove():malloc:");
		return;
	}
	//根据方向来判断蛇是怎么走的
	switch (ps->_dir)
	{
	case UP:
		next->x = ps->_pSnake->x;
		next->y = ps->_pSnake->y - 1;
		break;
	case DOWN:
		next->x = ps->_pSnake->x;
		next->y = ps->_pSnake->y + 1;
		break;
	case LEFT:
		next->x = ps->_pSnake->x - 2;
		next->y = ps->_pSnake->y;
		break;
	case RIGHT:
		next->x = ps->_pSnake->x + 2;
		next->y = ps->_pSnake->y;
		break;
	}
	//判断蛇走的下一个节点是不是食物
	if (NextIsFood(next, ps))
	{
        //是食物就吃掉食物
		EatFood(next, ps);
	}
	else
	{
        //不是就不吃
		NoFood(next, ps);
	}
}

下一个位置是食物

//下一个位置是食物,就可以吃掉
void EatFood(pSnakeNode next, pSnake ps)
{
	//把这个节点(就是食物节点)头插到蛇身就行
	ps->_pFood->next = ps->_pSnake;
	ps->_pSnake = ps->_pFood;
	//释放掉这个节点(因为创建了两个节点:一个食物节点,一个蛇头的下一个节点)
	free(next);
	next = NULL;
	//打印蛇身
	pSnakeNode pcur = ps->_pSnake;
	int count = 0;
	while (pcur)
	{
		SetPos(pcur->x, pcur->y);//定位
		if (count == 0)
		{
			count++;
			wprintf(L"%lc", HEAD);//打印蛇头
		}
		else
		{
			wprintf(L"%lc", BODY);//打印蛇身
		}
		pcur = pcur->next;
	}
    //分数的增加
	ps->_score += ps->_FoodWeight;
	//重新创建食物
	CreatFood(ps);
}

下一个位置不是食物

//下一个位置不是食物
void NoFood(pSnakeNode next, pSnake ps)
{
	//把下一个位置的节点头插到蛇身
	next->next = ps->_pSnake;
	ps->_pSnake = next;
	//把蛇身最后一个节点的空间释放掉,顺便打印蛇身
	pSnakeNode pcur = ps->_pSnake;
	int count = 0;
	while (pcur->next->next != NULL)
	{
		//遍历时,可以直接打印
		SetPos(pcur->x, pcur->y);
		if (count == 0)
		{
			count++;
			wprintf(L"%lc", HEAD);
		}
		else
		{
			wprintf(L"%lc", BODY);
		}
		pcur = pcur->next;
	}
	//把最后一个节点的位置打印成空格
	SetPos(pcur->next->x, pcur->next->y);
	printf("  ");
	free(pcur->next);
	pcur->next = NULL;
}

如果不把蛇身的尾节点的位置打印成空格,那么上一次的痕迹就不会被消除。 会导致蛇身一直变长。  走一步,再休息两百毫秒,可以让我们有时间来判断贪吃蛇下一步需怎么走,如果不休息就会直接撞墙。

检测是否撞墙只需要检测蛇头是否撞墙,因为蛇身的每一个节点是重复执行蛇头的操作。蛇头不撞墙那么蛇身就没有机会撞墙,如果蛇头撞墙,那么就说明这个蛇撞墙了。

//检测是否撞墙
void KillByWall(pSnake ps)
{
	//只要判断蛇头是否碰到墙就可以了
	if (ps->_pSnake->x == 0 || ps->_pSnake->x == 56
		|| ps->_pSnake->y == 0 || ps->_pSnake->y == 26)
	{
		ps->_state = KILL_BY_WALL;//改变蛇的状态即可 
	}
}

检测蛇是否撞到自身就只需要检测蛇头和蛇身的某一个节点是否重合就行了。

//检测是否撞到自己
void KillBySelf(pSnake ps)
{
	//只要判断蛇头是否碰到自己的蛇身
	pSnakeNode pcur = ps->_pSnake->next;
	while (pcur)
	{
		if (pcur->x == ps->_pSnake->x && pcur->y == ps->_pSnake->y)
		{
			ps->_state = KILL_BY_SELF;//改变蛇的状态即可
			break;
		}
		pcur = pcur->next;
	}
}

GameOver()——游戏的结束(善后工作) 

善后也就是把蛇身的节点释放掉,并告诉玩家游戏结束的原因。

//结束游戏(善后工作)
void GameOver(pSnake ps)
{
	SetPos(20, 13);
	switch (ps->_state)
	{
	case KILL_BY_WALL:
		printf("很遗憾!撞墙死亡!");
		break;
	case KILL_BY_SELF:
		printf("很遗憾!撞到自己死亡!");
		break;
	case END_NORMAL:
		printf("玩家主动结束游戏!");
		break;
	}
	//释放蛇身链表
	pSnakeNode prev = ps->_pSnake;
	pSnakeNode pcur = ps->_pSnake;
	while (pcur)
	{
		prev = pcur->next;
		free(pcur);
		pcur = prev;
	}
}

贪吃蛇源码 

 Snake.h

#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
#include <locale.h>
#include <stdbool.h>
#include <time.h>

#define POS_X 24
#define POS_Y 5

#define WALL L'□'
#define BODY L'●'
#define FOOD L'★'
#define HEAD L'◆'

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

//游戏的状态
typedef enum GAME_STATE
{
	OK,//正常运行
	KILL_BY_WALL,//撞墙
	KILL_BY_SELF,//撞到自己
	END_NORMAL//正常退出
}GAME_STATE;

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

//蛇身的节点类型
typedef struct SnakeNode
{
	//坐标
	int x;
	int y;
	//指向下一个节点的指针
	struct SnakeNode* next;
}SnakeNode, * pSnakeNode;


//贪吃蛇
typedef struct Snake
{
	pSnakeNode _pSnake;//指向蛇头的指针
	pSnakeNode _pFood;//指向食物的指针
	DIRECTION _dir;//蛇的方向
	GAME_STATE _state;//游戏的运行状态
	int _FoodWeight;//一个食物的分数
	int _score;//总分数
	int _SleepTime;//休眠时间,时间越短,速度越快,时间越长,速度越慢
}Snake, * pSnake;


//定位光标
void SetPos(short x, short y);

//初始化游戏
void GameStart(pSnake ps);

//打印欢迎界面
void WelcomToGame();

//绘制地图
void CreatMap();

//初始化蛇
void InitSnake(pSnake ps);

//创建食物
void CreatFood(pSnake ps);

//游戏运行
void GameRun(pSnake ps);

//蛇走一步的过程
void SnakeMove(pSnake ps);

//判断蛇要走的下一个节点是否为食物
int NextIsFood(pSnakeNode next, pSnake ps);

//下一个位置是食物,就可以吃掉
void EatFood(pSnakeNode next, pSnake ps);

//下一个位置不是食物
void NoFood(pSnakeNode next, pSnake ps);

//检测是否撞墙
void KillByWall(pSnake ps);

//检测是否撞到自己
void KillBySelf(pSnake ps); 

//结束游戏(善后工作)
void GameOver(pSnake ps);

Snake.c

#include "Snake.h"


//定位光标
void SetPos(short x, short y)
{
	HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);//获取标准输出的句柄
	COORD pos = { x, y };
	SetConsoleCursorPosition(houtput, pos);//设置光标的位置
}


//打印欢迎界面
void WelcomToGame()
{
	//首先得定位光标
	SetPos(39, 10);
	printf("欢迎来到贪吃蛇小游戏\n");
	SetPos(40, 15);
	system("pause");//暂停
	system("cls");//清理屏幕

	SetPos(28, 10);
	printf("用↑.↓.←.→ 来控制蛇的移动!摁F3加速!摁F4减速!\n");
	SetPos(38, 11);
	printf("游戏即将开始,请做好准备!\n");
	SetPos(40, 15);
	system("pause");
	system("cls");
}


//绘制地图
void CreatMap()
{
	//打印上体墙
	for (int i = 0; i <= 28; i++)
	{
		wprintf(L"%lc", WALL);
	}
	SetPos(0, 26);//定位到下体墙的位置
	//打印下体墙
	for (int i = 0; i <= 28; i++)
	{
		wprintf(L"%lc", WALL);
	}
	//打印左体墙
	for (int i = 1; i <= 25; i++)
	{
		SetPos(0, i);
		wprintf(L"%lc", WALL);
	}
	//打印右体墙
	for (int i = 1; i <= 25; i++)
	{
		SetPos(56, i);
		wprintf(L"%lc", WALL);
	}
}


//初始化蛇
void InitSnake(pSnake ps)
{
	//创建一条蛇
	pSnakeNode pcur = NULL;
	pSnakeNode prev = NULL;
	for (int i = 0; i < 5; i++)
	{
		pSnakeNode tmp = (pSnakeNode)malloc(sizeof(SnakeNode));
		if (tmp == NULL)
		{
			perror("InitSnake():malloc:");
			return;
		}
		pcur = tmp;
		pcur->x = POS_X + 2 * i;
		pcur->y = POS_Y;
		pcur->next = NULL;
		if (ps->_pSnake == NULL)
		{
			//直接插入即可
			ps->_pSnake = pcur;
		}
		else
		{
			//尾插
			if (prev != NULL)
				prev->next = pcur;
		}
		prev = pcur;
	}
	pcur = ps->_pSnake;
	//开始在控制台上打印蛇
	int count = 0;
	while (pcur)
	{
		SetPos(pcur->x, pcur->y);//定位
		if (count == 0)
		{
			count++;
			wprintf(L"%lc", HEAD);//打印蛇头
		}
		else
		{
			wprintf(L"%lc", BODY);//打印蛇身
		}
		pcur = pcur->next;
	}
	//设置贪吃蛇的属性
	ps->_dir = LEFT;//初始时蛇的方向向左
	ps->_FoodWeight = 50;//一个食物50分
	ps->_score = 0;//总分为0
	ps->_SleepTime = 200;//单位是毫秒
	ps->_state = OK;//正常运行
}


//创建食物
void CreatFood(pSnake ps)
{
	int x = 0;
	int y = 0;
	//随机创建食物(食物的x坐标必须是2的倍数,因此要判断)
again:
	do
	{
		x = rand() % 53 + 2;
		y = rand() % 25 + 1;
	} while (x % 2);
	//食物的坐标不能和蛇身冲突
	pSnakeNode pcur = ps->_pSnake;
	//开始寻找看看是否与蛇身冲突
	while (pcur)
	{
		if (pcur->x == x && pcur->y == y)
		{
			goto again;
		}
		pcur = pcur->next;
	}
	//开始创建食物的节点
	pSnakeNode tmp = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (tmp == NULL)
	{
		perror("CreatFood():malloc:");
		return;
	}
	tmp->x = x;
	tmp->y = y;
	tmp->next = NULL;
	ps->_pFood = tmp;
	//打印食物
	SetPos(x, y);
	wprintf(L"%lc", FOOD);
}


//初始化游戏
void GameStart(pSnake ps)
{
	//设置窗口大小以及名字
	system("mode con cols=100 lines=30");
	system("title 贪吃蛇");
	//隐藏光标信息,为了后续打印
	HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);//获取标准输出的句柄
	CONSOLE_CURSOR_INFO CursorInfo;
	GetConsoleCursorInfo(houtput, &CursorInfo);//获取光标信息
	CursorInfo.bVisible = false;//隐藏光标
	SetConsoleCursorInfo(houtput, &CursorInfo);//设置光标状态

	//打印欢迎界面和功能介绍
	WelcomToGame();
	//绘制地图
	CreatMap();
	//初始化蛇
	InitSnake(ps);
	//创建食物
	CreatFood(ps);
}


//打印帮助信息
void PrintHelpInfo()
{
	SetPos(68, 10);
	printf("小提示:");
	SetPos(68, 13);
	printf("不能穿墙,不能咬到自己!");
	SetPos(68, 14);
	printf("用↑.↓.←.→ 来控制蛇的移动!");
	SetPos(68, 15);
	printf("摁F3加速!摁F4减速!");
	SetPos(68, 16);
	printf("加速将增加单个食物的分数!");
	SetPos(68, 17);
	printf("减速将减少单个食物的分数!");
	SetPos(68, 18);
	printf("摁Esc退出游戏!摁空格暂停游戏!");
}


void Pause()
{
	while (1)
	{
		//休眠200毫秒
		Sleep(200);
		//这个只能放到休眠的后面
		if (KEY_PRESS(VK_SPACE))
		{
			break;
		}
	}
}


//判断蛇要走的下一个节点是否为食物
int NextIsFood(pSnakeNode next, pSnake ps)
{
	return ((next->x == ps->_pFood->x) && (next->y == ps->_pFood->y));
}


//下一个位置是食物,就可以吃掉
void EatFood(pSnakeNode next, pSnake ps)
{
	//把这个节点(就是食物节点)头插到蛇身就行
	ps->_pFood->next = ps->_pSnake;
	ps->_pSnake = ps->_pFood;
	//释放掉这个节点(因为有两个节点)
	free(next);
	next = NULL;
	//打印蛇身
	pSnakeNode pcur = ps->_pSnake;
	int count = 0;
	while (pcur)
	{
		SetPos(pcur->x, pcur->y);//定位
		if (count == 0)
		{
			count++;
			wprintf(L"%lc", HEAD);//打印蛇头
		}
		else
		{
			wprintf(L"%lc", BODY);//打印蛇身
		}
		pcur = pcur->next;
	}
	ps->_score += ps->_FoodWeight;
	//重新创建食物
	CreatFood(ps);
}


//下一个位置不是食物
void NoFood(pSnakeNode next, pSnake ps)
{
	//把下一个位置的节点头插到蛇身
	next->next = ps->_pSnake;
	ps->_pSnake = next;
	//把蛇身最后一个节点的空间释放掉,顺便打印蛇身
	pSnakeNode pcur = ps->_pSnake;
	int count = 0;
	while (pcur->next->next != NULL)
	{
		//遍历时,可以直接打印
		SetPos(pcur->x, pcur->y);
		if (count == 0)
		{
			count++;
			wprintf(L"%lc", HEAD);
		}
		else
		{
			wprintf(L"%lc", BODY);
		}
		pcur = pcur->next;
	}
	//把最后一个节点的位置打印成空格
	SetPos(pcur->next->x, pcur->next->y);
	printf("  ");
	free(pcur->next);
	pcur->next = NULL;
}



//蛇走一步的过程
void SnakeMove(pSnake ps)
{
	//创建一个节点来存放蛇要走的下一个节点
	pSnakeNode next = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (next == NULL)
	{
		perror("SnakeMove():malloc:");
		return;
	}
	//根据方向来判断蛇是怎么走的
	switch (ps->_dir)
	{
	case UP:
		next->x = ps->_pSnake->x;
		next->y = ps->_pSnake->y - 1;
		break;
	case DOWN:
		next->x = ps->_pSnake->x;
		next->y = ps->_pSnake->y + 1;
		break;
	case LEFT:
		next->x = ps->_pSnake->x - 2;
		next->y = ps->_pSnake->y;
		break;
	case RIGHT:
		next->x = ps->_pSnake->x + 2;
		next->y = ps->_pSnake->y;
		break;
	}
	//判断蛇走的下一个节点是不是食物
	if (NextIsFood(next, ps))
	{
		EatFood(next, ps);
	}
	else
	{
		NoFood(next, ps);
	}
}


//检测是否撞墙
void KillByWall(pSnake ps)
{
	//只要判断蛇头是否碰到墙就可以了
	if (ps->_pSnake->x == 0 || ps->_pSnake->x == 56
		|| ps->_pSnake->y == 0 || ps->_pSnake->y == 26)
	{
		ps->_state = KILL_BY_WALL; 
	}
}


//检测是否撞到自己
void KillBySelf(pSnake ps)
{
	//只要判断蛇头是否碰到自己的蛇身
	pSnakeNode pcur = ps->_pSnake->next;
	while (pcur)
	{
		if (pcur->x == ps->_pSnake->x && pcur->y == ps->_pSnake->y)
		{
			ps->_state = KILL_BY_SELF;
			break;
		}
		pcur = pcur->next;
	}
}


//游戏运行
void GameRun(pSnake ps)
{
	//先打印帮助信息
	PrintHelpInfo();
	//接下来就是通过按键来判断贪吃蛇的运行状态
	do
	{
		//打印分数显示
		SetPos(68, 7);
		printf("当前总分数:%05d", ps->_score);
		SetPos(68, 8);
		printf("当前食物分数:%02d", ps->_FoodWeight);
		//判断摁了什么键,根据键来判断要执行的命令
		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_ESCAPE))
		{
			//退出游戏
			ps->_state = END_NORMAL;
		}
		else if (KEY_PRESS(VK_F3))
		{
			//加速(减少休眠时间)
			if (ps->_SleepTime > 80)//设置为4档速度
			{
				ps->_SleepTime -= 30;
				ps->_FoodWeight += 10;
			}
		}
		else if (KEY_PRESS(VK_F4))
		{
			//减速
			if (ps->_FoodWeight > 10)
			{
				ps->_SleepTime += 20;
				ps->_FoodWeight -= 10;
			}
		}
		//蛇开始走
		//走一步,就休息一下
		SnakeMove(ps);//蛇走一步的过程
		Sleep(ps->_SleepTime);
		//检测是否撞墙
		KillByWall(ps);
		//检测是否撞到自己
		KillBySelf(ps);
		if (ps->_score > 30000)
		{
			SetPos(20, 13);
			printf("恭喜你!成功通关!");
			break;
		}
	} while (ps->_state == OK);
}


//结束游戏(善后工作)
void GameOver(pSnake ps)
{
	SetPos(20, 13);
	switch (ps->_state)
	{
	case KILL_BY_WALL:
		printf("很遗憾!撞墙死亡!");
		break;
	case KILL_BY_SELF:
		printf("很遗憾!撞到自己死亡!");
		break;
	case END_NORMAL:
		printf("玩家主动结束游戏!");
		break;
	}
	//释放蛇身链表
	pSnakeNode prev = ps->_pSnake;
	pSnakeNode pcur = ps->_pSnake;
	while (pcur)
	{
		prev = pcur->next;
		free(pcur);
		pcur = prev;
	}
}

 好啦!本期贪吃蛇游戏的学习之旅到此结束了!我们下一期再一起学习吧!

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

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

相关文章

VSCode插件开发学习

一、环境准备 0、参考文档&#xff1a;VS Code插件创作中文开发文档 1、大于18版本的nodejs 2、安装Yeoman和VS Code Extension Generator&#xff1a; npm install -g yo generator-code 3、生成脚手架 yo code 选择内容&#xff1a; ? What type of extension do yo…

GPT-3.5 Turbo 的 temperature 设置为 0 就是贪婪解码?

&#x1f349; CSDN 叶庭云&#xff1a;https://yetingyun.blog.csdn.net/ 将 GPT-3.5 Turbo 的 temperature 设置为 0 通常意味着采用贪婪解码&#xff08;greedy decoding&#xff09;策略。在贪婪解码中&#xff0c;模型在每一步生成文本时选择概率最高的词元&#xff0c;从…

微调Llama3实践并基于Llama3构建心理咨询EmoLLM

Llama3 Xtuner微调Llama3 EmoLLM 心理咨询师

LabVIEW多设备控制与数据采集系统

LabVIEW多设备控制与数据采集系统 随着科技的进步&#xff0c;自动化测试与控制系统在工业、科研等领域的应用越来越广泛。开发了一种基于LabVIEW平台开发的多设备控制与数据采集系统&#xff0c;旨在解决多设备手动设置复杂、多路数据显示不直观、数据存储不便等问题。通过RS…

基于STM32的蓝牙小车的Proteus仿真(虚拟串口模拟)

文章目录 一、前言二、仿真图1.要求2.思路3.画图3.1 电源部分3.2 超声波测距部分3.3 电机驱动部分3.4 按键部分3.5 蓝牙部分3.6 显示屏部分3.7 整体 4.仿真5.软件 三、总结 一、前言 proteus本身并不支持蓝牙仿真&#xff0c;这里我采用虚拟串口的方式来模拟蓝牙控制。 这里给…

42岁TVB男艺人曾靠刘德华贴钱出道,苦熬10年终上位

张颕康在无线&#xff08;TVB&#xff09;电视打滚多年&#xff0c;近年在《逆天奇案》第一、二辑凭扎实演技为人留下印象。他还是圈中出名的「爱妻号」&#xff0c;日前在访问期间&#xff0c;张颕康三句不离多谢太太。 较年长的观众或会记得&#xff0c;张颕康初出道以「刘德…

kettle从入门到精通 第五十三课 ETL之kettle MQTT/RabbitMQ consumer实战

1、上一节课我们学习了MQTT producer 生产者步骤&#xff0c;MQTT consumer消费者步骤。该步骤可以从支持MRQTT协议的中间件获取数据&#xff0c;该步骤和kafka consumer 一样可以处理实时数据交互&#xff0c;如下图所示&#xff1a; 2、双击步骤打开MQTT consumer 配置窗口&a…

Today At Apple Notes

文章目录 2024.04.15 Phone15 入门2024.04.20 ipad 绘画 & 图片管理recreate 软件 绘画图片管理 官网&#xff1a; https://www.apple.com/today/Apple 亚洲第一大商店&#xff1a;Apple 静安零售店现已在上海开幕 2024.04.15 Phone15 入门 听课地点&#xff1a;上海区静…

如何对图片进行压缩和缩放

在手机像素越来越高的时代&#xff0c;照片的体积也在不断地膨胀&#xff0c;大部分情况下我们是不需要这么大的图片的&#xff0c;这个时候我们就需要对图片进行压缩或者缩放了&#xff0c;今天教大家如何缩小图片体积 打开智游剪辑&#xff08;官网: zyjj.cc&#xff09;&…

二维前缀和与差分

前言 延续前面所讲的一维前缀和以及差分&#xff0c;现在来写写二维前缀和与差分 主要这个画图就比前面的一维前缀和与差分复杂一点&#xff0c;不过大体思路是一样的 一维和二维的主要思路在于一维是只针对对一行一列&#xff0c;而二维是针对与一个矩阵的 好吧&#xff0…

OpenStack 常见模块详解

目录 一、OpenStack 架构 二、控制台 Dashboard 三、身份认证服务 Keystone 1&#xff09;用户&#xff08;user&#xff09; 2&#xff09;项目&#xff08;project&#xff09; 3&#xff09;角色&#xff08;role&#xff09; 4&#xff09;服务&#xff08;serv…

JavaCard学习笔记: CAP Component 之 Class Component

文章目录 整体结构tag和size字段signature_pool_length和signature_pooltype_descriptor结构导入类型编码导入项签名示例导入类导入数组导入远程方法 interfaces[]interface_info结构flagsinteface_countsuperinterfacesinterface_name class_info_compact classes[]结构flagsi…

wasm 系列之 WebAssembly 和 emscripten 暴力上手

wasm 是什么&#xff1f; wasm 是 WebAssembly 的缩写。wasm 不是传统意义上的汇编语言&#xff0c;而是一种编译的中间字节码&#xff0c;可以在浏览器和其他 wasm runtime 上运行非 JavaScript 类型的语言&#xff0c;只要能被编译成 wasm&#xff0c;譬如 kotlin/wasm、Rus…

Linux嵌入式驱动开发-阻塞IO与非阻塞IO

文章目录 阻塞与非阻塞访问简介阻塞访问的实现等待队列等待队列头等待队列项从等待队列头添加/移除等待队列项等待唤醒等待事件API 非阻塞访问的实现轮询poll 函数原型可以返回的资源状态 阻塞与非阻塞访问简介 **IO&#xff1a;**Input/Output&#xff0c;也就是输入/输出&am…

2024mac苹果电脑如何清理磁盘空间?用什么软件最好

苹果电脑已成为我们日常生活和工作不可或缺的一部分。随着时间的推移&#xff0c;不论是办公文档、个人照片还是各式各样的应用程序&#xff0c;都会逐渐积累&#xff0c;导致电脑的磁盘空间日益紧张。对于用户来说&#xff0c;苹果电脑如何清理磁盘空间&#xff0c;以保持设备…

iOS 全平台矢量动画库:体积小巧、功能丰富 | 开源日报 No.227

airbnb/lottie-ios Stars: 24k License: NOASSERTION lottie-ios 是一个用于在 iOS 平台上本地渲染 After Effects 矢量动画的库。 该项目主要功能、关键特性、核心优势包括&#xff1a; 跨平台支持&#xff1a;可在 iOS, macOS, tvOS, visionOS, Android 和 Web 上使用实时渲…

07节-51单片机-矩阵键盘

文章目录 1矩阵键盘原理2.扫描的概念3.弱上拉4.实战-实现矩阵键盘对应按钮按下显示对应值4.1配置代码模板 5.键盘锁 1矩阵键盘原理 在键盘中按键数量较多时&#xff0c;为了减少I/O口的占用&#xff0c;通常将按键排列成矩阵形式 采用逐行或逐列的“扫描”&#xff0c;就可以读…

前程贷v6.5系统测试报告

1.引言部分 1&#xff0e;1 项目背景 本测试报告的具体编写目的&#xff0c;指出预期的读者范围。(3-4句) 项目描述 &#xff08;项目内容&#xff0c;用户需求&#xff09; 本测试报告为**&#xff08;系统名称&#xff09;**系统测试报告&#xff1b;本报告目的在于总结测试…

【JavaEE初阶系列】——数据链路层以太网以及Mac地址

目录 &#x1f6a9;认识以太网 &#x1f6a9;以太网帧格式 &#x1f6a9;IP地址和Mac地址各自的用途 &#x1f6a9;认识以太网 "以太网"不是一种具体的网络&#xff0c;而是一种技术标准&#xff1b;既包含了数据链路层的内容&#xff0c;也包含了一些物理层的内…

在ios设备上运行Unity Profiler

久违了朋友们。 最近基于Unity 2021.3 和AR Foundation开发了个应用&#xff0c;需要在ipad上实际运行时查看程序的各项指标功耗。 于是乎&#xff0c;我尝试跟随者官方教程来实时调试&#xff0c;现在附上一些心得。 按照官方的三步走&#xff0c;Build and Run理论上会自动…