C语言:项目实践(贪吃蛇)

前言:
相信大家都玩过贪吃蛇这款游戏吧,贪吃蛇是久负盛名的游戏,它也和俄罗斯方块,扫雷等游戏位列经典游戏的行列,那贪吃蛇到底是怎么实现的呢?
今天,我就用C语言带着大家一起来实现一下这款游戏,从设计到代码的实现可以帮助我们提升编程能力和逻辑能力,项目适合: C语言已经学完,有一定的代码能力,初步接触数据结构中的链表,因为贪吃蛇是基于链表来实现的。
这里是我们在实现贪吃蛇过程中必须要使用到的一些知识点:C语⾔函数、枚举、结构体、动态内存管理、预处理指令、链表、Win32 API等,至于Win32 API没听过也不要紧,我们下面就会详细讲解到它的使用方法。

目录

  • 一、游戏最终目标
  • 二、Win32 API介绍
    • 1.Win32 API
    • 2.控制台程序
    • 3.控制台屏幕上的坐标COORD
    • 4.GetStdHandle
    • 5.GetConsoleCursorInfo
    • 6.SetConsoleCursorInfo
    • 7.SetConsoleCursorPosition
    • 8.GetAsyncKeyState
  • 三、贪吃蛇游戏设计与分析
    • 1.地图
    • 2.setlocale函数
    • 3.宽字符的打印
    • 4.地图坐标
    • 5.蛇身和食物
    • 6.数据结构设计
    • 7.游戏流程设计
    • 8.核心逻辑实现分析
  • 四、游戏开始(GameStart)
    • 1.初始化游戏主逻辑
    • 2.打印欢迎界面
    • 3.创建地图
    • 4.创建蛇身
    • 5.创建第一个食物
  • 五、游戏运行(GameRun)
    • 1.打印帮助信息(PrintHelpInfo)
    • 2.蛇身移动(SnakeMove)
    • 3.判断下一个节点是否是食物(NextIsFood)
    • 4.吃食物(EatFood)
    • 5.不是食物(NoFood)
    • 6.撞到墙(KillByWall)
    • 7.撞到自己(KillBySelf)
    • 8.游戏结束(GameEnd)
  • 六、完整代码实现

一、游戏最终目标

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

  • 贪吃蛇地图绘制
  • 蛇吃食物的功能 (上、下、左、右方向键控制蛇的动作)
  • 蛇撞墙死亡
  • 蛇撞自身死亡
  • 计算得分
  • 蛇身加速、减速
  • 暂停游戏
  • 正常退出游戏

游戏效果演示

---

二、Win32 API介绍

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

1.Win32 API

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

2.控制台程序

平常我们运行起来的⿊框程序其实就是控制台程序。
我们可以使用cmd命令来设置控制台窗口的长宽:设置控制台窗⼝的大小,30行,100列。

mode con cols=100 lines=30

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

title 贪吃蛇

在这里插入图片描述
参考:title命令
这些能在控制台窗口执行的命令,也可以调用C语言函数system来执行。例如:

#include <stdio.h>
int main()
{
	//设置控制台窗⼝的⻓宽:设置控制台窗⼝的⼤⼩,30⾏,100列
	system("mode con cols=100 lines=30");
	//设置cmd窗⼝名称
	system("title 贪吃蛇");
	return 0;
}

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

COORD 是Windows API中定义的一个结构体,表示一个字符在控制台屏幕幕缓冲区上的坐标,坐标系(0,0) 的原点位于缓冲区的顶部左侧单元格。
在这里插入图片描述
COORD类型的声明:

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

给坐标赋值:

COORD pos = { 10, 15 };

4.GetStdHandle

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

实例:

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

5.GetConsoleCursorInfo

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

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

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

typedef struct _CONSOLE_CURSOR_INFO {
	DWORD dwSize;
	BOOL  bVisible;
} CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO;
  • dwSize,由光标填充的字符单元格的百分比。 此值介于1到100之间。 光标外观会变化,范围从完全填充单元格到单元底部的水平线条。
  • bVisible,游标的可见性。 如果光标可见,则此成员为 TRUE。
CursorInfo.bVisible = false; //隐藏控制台光标

6.SetConsoleCursorInfo

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

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

7.SetConsoleCursorPosition

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

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

SetPos:封装一个设置光标位置的函数

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

8.GetAsyncKeyState

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

SHORT GetAsyncKeyState(
	int vKey
);

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

#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");
		}
	}
	return 0;
}

三、贪吃蛇游戏设计与分析

1.地图

我们最终的贪吃蛇大纲要是这个样子,那我们的地图如何布置呢?
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

这里不得不讲一下控制台窗口的一些知识,如果想在控制台的窗口中指定位置输出信息,我们得知道该位置的坐标,所以首先介绍一下控制台窗口的坐标知识。
控制台窗口的坐标横向的是X轴,从左向右依次增长,纵向是Y轴,从上到下依次增长。
在这里插入图片描述
在游戏地图上,我们打印墙体使用宽字符:〓,打印蛇使用宽字符:㊣,打印⻝物使用宽字符:◆,普通的字符是占一个字节的,这类宽字符是占用2个字节。
这里再简单的讲一下C语言的国际化特性相关的知识,过去C语言并不适合非英语国家(地区)使用。C语言最初假定字符都是单字节的。但是这些假定并不是在世界的任何地方都适用。
后来为了使C语言适应国际化,C语言的标准中不断加入了国际化支持。比如:加入了宽字符的类型wchar_t 和宽字符的输入和输出函数,加入了<locale.h>头文件,其中提供了允许程序员针对特定地区(通常是国家或者说某种特定语言的地理区域)调整程序行为的函数。

2.setlocale函数

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

setlocale 函数用于修改当前地区,可以针对一个类项修改,也可以针对所有类项。C语言支持针对不同的类项进行修改,下面的一个宏,
指定一个类项:

  • LC_COLLATE:影响字符串比较函数 strcoll()strxfrm()
  • LC_CTYPE:影响字符处理函数的行为。
  • LC_MONETARY:影响货币格式。
  • LC_NUMERIC:影响 printf() 的数字格式。
  • LC_TIME:影响时间格式 strftime()wcsftime()
  • LC_ALL :针对所有类项修改,将以上所有类别设置为给定的语言环境。

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

setlocale(LC_ALL, "C");

当程序运行起来后想改变地区,就只能调用setlocale函数。用" "作为第2个参数,调用setlocale函数就可以切换到本地模式,这种模式下程序会适应本地环境。
比如:切换到我们的本地模式后就支持宽字符(汉字)的输出等。

setlocale(LC_ALL, " ");//切换到本地环境

3.宽字符的打印

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

#include <stdio.h>
#include<locale.h>
int main() {
	setlocale(LC_ALL, "");
	wchar_t ch1 = L'●';
	wchar_t ch2 = L'大';
	wchar_t ch3 = L'佬';
	wchar_t ch4 = L'★';
	printf("%c%c\n", 'a', 'b');
	wprintf(L"%lc\n", ch1);
	wprintf(L"%lc\n", ch2);
	wprintf(L"%lc\n", ch3);
	wprintf(L"%lc\n", ch4);
	return 0;
}

输出结果:
在这里插入图片描述
从输出的结果来看,我们发现一个普通字符占一个字符的位置,但是打印一个汉字字符,占用2个字符的位置,那么我们如果要在贪吃蛇中使用宽字符,就得处理好地图上坐标的计算。
普通字符和宽字符打印出宽度的展示如下:
在这里插入图片描述

4.地图坐标

我们假设实现一个棋盘27行,58列的棋盘(行和列可以根据自己的情况修改),再围绕地图画出墙,
如下:
在这里插入图片描述

5.蛇身和食物

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

在这里插入图片描述

6.数据结构设计

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

//定义蛇身的节点
typedef struct snakenode
{
	//坐标
	int x;
	int y;
	struct snakenode* next;//指向下一个节点的指针
}Snakenode, * Psnakenode;

要管理整条贪吃蛇,我们再封装一个Snake的结构来维护整条贪吃蛇:

//维护蛇的相关信息
typedef struct snake
{
	Psnakenode _snake;  //指向蛇头的指针
	Psnakenode _Food;   //指向食物节点的指针
	enum Dir _dir;      //蛇的方向
	enum Status _status;//蛇的状态
	int _sleep;         //每⾛⼀步休眠时间
	int _Allgrade;      //游戏当前获得分数
	int _grade;         //默认每个⻝物10分
}Snake, * Psnake;

蛇的方向,可以一一列举,使用枚举

//方向
enum Dir
{
	UP,
	DOWN,
	LEFT,
	RIGHT
};

游戏状态,可以一一列举,使用枚举

//游戏状态
enum Status
{
	OK,
	KILL_BYWALL,//撞到墙
	KILL_BYSELF,//撞到自己
	END_NORMAL  //正常退出
};

7.游戏流程设计

在这里插入图片描述

8.核心逻辑实现分析

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

  • 游戏开始(GameStart)完成游戏的初始化
  • 游戏运行(GameRun)完成游戏运行逻辑的实现
  • 游戏结束(GameEnd)完成游戏结束的说明,实现资源释放
#include "Snake.h"
void snakeTest()
{
	char ch;
	do
	{
		system("cls");
		//创建蛇
		Snake snake;
		snake._snake = NULL;
		//初始化游戏
		GameStart(&snake);
		//运行游戏
		GameRun(&snake);
		//结束游戏,游戏的善后
		GameEnd(&snake);
		Setpos(24, 14);
		wprintf(L"再来一局吗?(Y/N)");
		ch = getchar();
		getchar();
	} while (ch == 'Y' || ch == 'y');
	Setpos(0, 27);
}
int main()
{
	//修改当前地区为本地模式,为了⽀持中⽂宽字符的打印
	setlocale(LC_ALL, "");
	//生成随机数函数的声明
	srand((unsigned int)time(NULL));
	//测试逻辑
	snakeTest();
	return 0;
}

四、游戏开始(GameStart)

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

  • 控制台窗口大小的设置
  • 控制台窗口名字的设置
  • ⿏标光标的隐藏
  • 打印欢迎界面
  • 创建地图
  • 初始化第蛇
  • 创建第一个⻝物

1.初始化游戏主逻辑

void GameStart(Psnake Ps)
{
	//设置控制台相关属性
	system("mode con cols=110 lines=30");
	system("title 贪吃蛇");
	//隐藏光标
	//获取标准输出的句柄
	HANDLE houtput = NULL;
	houtput = GetStdHandle(STD_OUTPUT_HANDLE);
	//创建光标结构体
	CONSOLE_CURSOR_INFO cursor_info = { 0 };
	//获取控制台光标信息
	GetConsoleCursorInfo(houtput, &cursor_info);
	//修改光标信息
	cursor_info.bVisible = false;
	//设置光标信息
	SetConsoleCursorInfo(houtput, &cursor_info);
	//颜色设置
	void Color(int c)
	{
		SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), c);
		//SetConsoleTextAttribute为Win32 API函数
	}
	//定位光标
	void Setpos(short x, short y)
	{
		//获取标准输出的句柄
		HANDLE houtput = NULL;
		houtput = GetStdHandle(STD_OUTPUT_HANDLE);
		//定位控制台光标位置
		COORD pos = { x,y };
		SetConsoleCursorPosition(houtput, pos);
	}
	//打印欢迎界面
	WelcomePrint();
	//创建地图
	Createmap();
	//创建蛇身
	CreateSnakenode(Ps);
	//创建食物
	Createfood(Ps);
	//设置蛇的信息
	Ps->_Allgrade = 0;
	Ps->_grade = 10;
	Ps->_dir = RIGHT;
	Ps->_sleep = 200;
	Ps->_status = OK;
}

2.打印欢迎界面

在游戏正式开始之前,做一些功能提醒

void WelcomePrint()
{
	Setpos(40, 10);
	wprintf(L"欢迎来到贪吃蛇小游戏");
	Setpos(43, 18);//让按任意键继续的出现的位置好看点
	system("pause");
	Setpos(33, 10);
	wprintf(L"按 ↑ ↓ ← → 控制蛇的移动,F3加速,F4减速");
	Setpos(33, 11);
	wprintf(L"按ESC退出游戏,按空格暂停游戏");
	Setpos(43, 18);
	system("pause");
	system("cls");
}

3.创建地图

创建地图就是将墙打印出来,因为是宽字符打印,所有使用wprintf函数,打印格式串前使用L,打印地图的关键是要算好坐标,才能在想要的位置打印墙体。
墙体打印的宽字符:

#define WALL L'〓'

易错点:就是坐标的计算
上:(0,0)到(56,0)
下:(0,26)到(56,26)
左:(0,1)到(0,25)
右:(56,1)到(56,25)
创建地图函数CreateMap

void CreateMap()
{
	Color(6);//设置墙的颜色为土黄色
	//上(0,0)-(56, 0)
	for (int i = 0; i < 29; i++)
	{
		wprintf(L"%lc", WALL);
	}
	//下(0,26)-(56, 26)
	Setpos(0, 26);
	for (int i = 0; i < 29; i++)
	{
		wprintf(L"%lc", WALL);
	}
	//左,x是0,y从1开始增⻓
	for (int i = 1; i < 26; i++)
	{
		Setpos(0, i);
		wprintf(L"%lc", WALL);
	}
	//右,x是56,y从1开始增⻓
	for (int i = 1; i < 26; i++)
	{
		Setpos(56, i);
		wprintf(L"%lc", WALL);
	}
}

4.创建蛇身

蛇最开始长度为5节,每节对应链表的一个节点,蛇身的每一个节点都有自己的坐标,创建5个节点,然后将每个节点存放在链表中进行管理,创建完蛇身后,将蛇的每一节打印在屏幕上。
蛇的初始位置从 (24,5) 开始。
蛇⾝打印的宽字符:

#define BODY L'㊣'

创建蛇⾝函数:CreateSnakenode

void CreateSnakenode(Psnake Ps)
{
	for (int i = 0; i < 5; i++)
	{
	    //创建蛇身的节点
		Psnakenode pcur = (Psnakenode)malloc(sizeof(Snakenode));
		if (pcur == NULL)
		{
			perror("SetSnakenode::malloc");
			return;
		}
		//设置坐标
		pcur->x = 24 + i * 2;
		pcur->y = 5;
		pcur->next = NULL;
		if (Ps->_snake == NULL)
		{
			//为空
			Ps->_snake = pcur;
		}
		else
		{
			//不为空,使用头插法
			pcur->next = Ps->_snake;
			Ps->_snake = pcur;
		}
	}
	//打印蛇身
	Psnakenode prev = Ps->_snake;
	while (prev)
	{
		Setpos(prev->x, prev->y);
		wprintf(L"%lc", BODY);
		prev = prev->next;
	}
}

5.创建第一个食物

先随机生成食物的坐标,食物的x坐标必须是2的倍数,食物的坐标不能和蛇身每个节点的坐标重复,然后创建食物节点,打印食物。
食物打印的宽字符:

#define FOOD L'◆'

创建食物的函数:CreateFood

void CreateFood(Psnake Ps)
{
	int x = 0, y = 0;
qu:
	do
	{
		x = rand() % 53 + 2;
		y = rand() % 25 + 1;
	} while (x % 2 != 0);//生成的x坐标必须是2的倍数
	Psnakenode pcur = Ps->_snake;
	while (pcur)
	{
		//生成的食物不能和蛇的身体重合
		if (pcur->x == x && pcur->y == y)
			goto qu;//坐标与蛇身重合就重新生成坐标
		pcur = pcur->next;
	}
	Setpos(x, y);
	Color(223);//食物颜色设置为粉色
	wprintf(L"%lc", FOOD);
	Ps->_Food = (Psnakenode)malloc(sizeof(Snakenode));//给食物申请一个节点
	if (Ps->_Food == NULL)
	{
		perror("Set_food::malloc");
		return;
	}
	Ps->_Food->x = x;
	Ps->_Food->y = y;
}

五、游戏运行(GameRun)

游戏运行期间,右侧打印帮助信息,提示玩家,坐标开始位置(62, 9)。
根据游戏状态检查游戏是否继续,如果是状态是OK,游戏继续,否则游戏结束。
如果游戏继续,就是检测按键情况,确定蛇下一步的方向,或者是否加速减速,是否暂停或者退出游戏。
需要的虚拟按键的罗列:

  • 上:VK_UP
  • 下:VK_DOWN
  • 左:VK_LEFT
  • 右:VK_RIGHT
  • 空格:VK_SPACE
  • ESC:VK_ESCAPE
  • F3:VK_F3
  • F4:VK_F4

检测按键状态,我们封装了⼀个宏

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

确定了蛇的方向和速度,蛇就可以移动了。

void GameRun(Psnake Ps)
{
	do
	{
		//打印帮助信息
		PrintHelpInfo(Ps);
		if (KEY_PRESS(VK_UP) && Ps->_dir != DOWN)
			Ps->_dir = UP;
		else if (KEY_PRESS(VK_DOWN) && Ps->_dir != UP)
			Ps->_dir = DOWN;
		else if (KEY_PRESS(VK_LEFT) && Ps->_dir != RIGHT)
			Ps->_dir = LEFT;
		else if (KEY_PRESS(VK_RIGHT) && Ps->_dir != LEFT)
			Ps->_dir = RIGHT;
		else if (KEY_PRESS(VK_SPACE))
		{
			//暂停游戏
			Stop();
		}
		else if (KEY_PRESS(VK_ESCAPE))
		{
			//正常退出游戏
			Ps->_status = END_NORMAL;
		}
		else if (KEY_PRESS(VK_F3))
		{
			//加速
			if (Ps->_sleep > 80)
			{
				Ps->_sleep -= 40;
				Ps->_Allgrade += 2;
			}
		}
		else if (KEY_PRESS(VK_F4))
		{
			//减速
			if (Ps->_sleep >= 200 && Ps->_sleep <= 320)
			{
				Ps->_sleep += 40;
				Ps->_Allgrade -= 2;
			}
		}
		//蛇走一步
		SnakeMove(Ps);
		//撞到墙
		Kill_ByWall(Ps);
		//撞到自己
		Kill_BySelf(Ps);
		Sleep(Ps->_sleep);
	} while (Ps->_status == OK);
}

1.打印帮助信息(PrintHelpInfo)

void PrintHelpInfo(Psnake Ps)
{
	//打印帮助信息
	Setpos(62, 9);
	printf("当前蛇的总分数为:%2d", Ps->_Allgrade);
	Setpos(62, 10);
	printf("当前食物的分数为:%2d", Ps->_grade);
	Color(7);//设置打印信息颜色
	Setpos(62, 14);
	wprintf(L"不能撞墙,不能撞到自己");
	Setpos(62, 15);
	wprintf(L"按 ↑ ↓ ← → 控制蛇的移动,F3加速,F4减速");
	Setpos(62, 16);
	wprintf(L"按ESC退出游戏,按空格暂停游戏");
}

2.蛇身移动(SnakeMove)

先创建下一个节点,根据移动方向和蛇头的坐标,蛇移动到下一个位置的坐标。
确定了下一个位置后,看下一个位置是否是食物(NextIsFood),是食物就做吃食物处理(EatFood),如果不是食物则做前进一步的处理(NoFood)。
蛇身移动后,判断此次移动是否会造成撞墙(KillByWall)或者撞上自己蛇身(KillBySelf),从而影响游戏的状态。

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->_snake->x;
		next->y = Ps->_snake->y - 1;
		break;
	case DOWN:
		next->x = Ps->_snake->x;
		next->y = Ps->_snake->y + 1;
		break;
	case LEFT:
		next->x = Ps->_snake->x - 2;
		next->y = Ps->_snake->y;
		break;
	case RIGHT:
		next->x = Ps->_snake->x + 2;
		next->y = Ps->_snake->y;
		break;
	}
	//判断下一个节点是否是食物
	int ret = NextIsFood(next, Ps);
	if (ret)
	{
		//吃掉食物
		EatFood(next, Ps);
		Ps->_Allgrade += Ps->_grade;
	}
	else
	{
		//不是食物
		NotFood(next, Ps);
	}
}

3.判断下一个节点是否是食物(NextIsFood)

//Psnakenode next指向下一个节点的指针
//Psnake Ps维护蛇的指针
int NextIsFood(Psnakenode next, Psnake Ps)
{
	return (next->x == Ps->_Food->x && next->y == Ps->_Food->y)
}

4.吃食物(EatFood)

//Psnakenode next指向下一个节点的指针
//Psnake Ps维护蛇的指针
void EatFood(Psnakenode next, Psnake Ps)
{
	//让食物的节点成为新的蛇头
	Ps->_Food->next = Ps->_snake;
	Ps->_snake = Ps->_Food;
	//释放next节点
	free(next);
	//打印蛇身
	Psnakenode pcur = Ps->_snake;
	while (pcur)
	{
		Setpos(pcur->x, pcur->y);
		wprintf(L"%lc", BODY);
		pcur = pcur->next;
	}
	//创建新食物
	CreateFood(Ps);
}

5.不是食物(NoFood)

将下一个节点头插入蛇的身体,并将之前蛇身最后一个节点打印为空格,释放掉蛇身的最后一个节点。
易错点:这里最容易错误的是,释放最后一个结点后,还得将倒数第二个节点的next指针改为NULL,保证蛇尾打印可以正常结束,不会越界访问。

//Psnakenode next指向下一个节点的指针
//Psnake Ps维护蛇的指针
void NoFood(Psnakenode next, Psnake Ps)
{
	//头插法
	next->next = Ps->_snake;
	Ps->_snake = next;
	Psnakenode pcur = Ps->_snake;
	while (pcur->next->next != NULL)
	{
		pcur = pcur->next;
	}
	//把最后一个节点打印成空格
	Setpos(pcur->next->x, pcur->next->y);
	printf("  ");
	//释放最后一个节点
	free(pcur->next);
	//将倒数第二个节点的next指针置为空
	pcur->next = NULL;
	//打印蛇身
	pcur = Ps->_snake;
	//设置蛇身颜色
	Color(12);
	while (pcur)
	{
		Setpos(pcur->x, pcur->y);
		wprintf(L"%lc", BODY);
		pcur = pcur->next;
	}
}

6.撞到墙(KillByWall)

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

//Psnake Ps维护蛇的指针
void KillByWall(Psnake Ps)
{
	if (Ps->_snake->x == 0 
	|| Ps->_snake->x == 56 
	|| Ps->_snake->y == 0 
	|| Ps->_snake->y == 26)
		Ps->_status = KILL_BYWALL;
}

7.撞到自己(KillBySelf)

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

void KillBySelf(Psnake Ps)
{
	Psnakenode pcur = Ps->_snake->next;
	while (pcur)//遍历蛇头之后的节点
	{
		//当有一个节点和头节点重合,就说明撞到了自己
		if (pcur->x == Ps->_snake->x && pcur->y == Ps->_snake->y)
			Ps->_status = KILL_BYSELF;//改变蛇的状态
		pcur = pcur->next;
	}
}

8.游戏结束(GameEnd)

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

void GameEnd(Psnake Ps)
{
	Setpos(22, 13);
	switch (Ps->_status)
	{
	case KILL_BYSELF:
		wprintf(L"您撞到了自己,游戏结束");
		break;
	case KILL_BYWALL:
		wprintf(L"您撞到墙了,游戏结束");
		break;
	case END_NORMAL:
		wprintf(L"您主动退出游戏,游戏结束");
		break;
	}
	//释放蛇身节点
	Psnakenode pcur = Ps->_snake;
	while (pcur)
	{
		Psnakenode next = pcur->next;
		free(pcur);
		pcur = next;
	}
}

六、完整代码实现

分3个文件实现
Snake.h

#pragma once
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <locale.h>
#include <windows.h>
#include <stdbool.h>
#define WALL L'〓'
#define BODY L'㊣'
#define FOOD L'◆'
enum Dir
{
	UP,
	DOWN,
	LEFT,
	RIGHT
};
enum Status
{
	OK,
	KILL_BYWALL,//撞到墙
	KILL_BYSELF,//撞到自己
	END_NORMAL  //正常退出
};
//定义蛇身的节点
typedef struct snakenode
{
	//坐标
	int x;
	int y;
	struct snakenode* next;//指向下一个节点的指针
}Snakenode, * Psnakenode;
//维护蛇的相关信息
typedef struct snake
{
	Psnakenode _snake;  //指向蛇头的指针
	Psnakenode _Food;   //指向食物节点的指针
	enum Dir _dir;      //蛇的方向
	enum Status _status;//蛇的状态
	int _sleep;         //休眠的时间越长,速度越慢,休眠的时间越短,速度越快
	int _Allgrade;      //游戏当前获得分数
	int _grade;         //默认每个⻝物10分
}Snake, * Psnake;
//初始化游戏
void GameStart(Psnake Ps);
//设置控制台相关属性
void SetConsole();
//颜色设置
void Color(int c);
//定位光标
void Setpos(short x, short y);
//打印欢迎界面
void WelcomePrint();
//创建地图
void CreateMap();
//创建蛇身
void CreateSnakenode(Psnake Ps);
//创建食物
void CreateFood(Psnake Ps);
//运行游戏
void GameRun(Psnake Ps);
//打印帮助信息
void PrintHelpInfo(Psnake Ps);
//检测蛇是否撞到墙
void KillByWall(Psnake Ps);
//检测蛇是否撞到自己
void KillBySelf(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 GameEnd(Psnake Ps);

Snake.c

#include "Snake.h"
//1.设置控制台相关属性
void SetConsole()
{
	system("mode con cols=110 lines=30");
	system("title 贪吃蛇");
	//隐藏光标
	//获取标准输出的句柄
	HANDLE houtput = NULL;
	houtput = GetStdHandle(STD_OUTPUT_HANDLE);
	//创建光标结构体
	CONSOLE_CURSOR_INFO cursor_info = { 0 };
	//获取控制台光标信息
	GetConsoleCursorInfo(houtput, &cursor_info);
	//修改光标信息
	cursor_info.bVisible = false;
	//设置光标信息
	SetConsoleCursorInfo(houtput, &cursor_info);
}
//2.颜色设置
void Color(int c)
{
	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), c);
	//SetConsoleTextAttribute为Win32 API函数
}
//3.定位光标
void Setpos(short x, short y)
{
	//获取标准输出的句柄
	HANDLE houtput = NULL;
	houtput = GetStdHandle(STD_OUTPUT_HANDLE);
	//定位控制台光标位置
	COORD pos = { x,y };
	SetConsoleCursorPosition(houtput, pos);
}
//4.打印欢迎界面
void WelcomePrint()
{
	Setpos(40, 10);
	wprintf(L"欢迎来到贪吃蛇小游戏");
	Setpos(43, 18);
	system("pause");
	Setpos(33, 10);
	wprintf(L"按 ↑ ↓ ← → 控制蛇的移动,F3加速,F4减速");
	Setpos(33, 11);
	wprintf(L"按ESC退出游戏,按空格暂停游戏");
	Setpos(43, 18);
	system("pause");
	system("cls");
}
//5.创建地图
void CreateMap()
{
	Color(6);//设置墙的颜色为土黄色
	//上(0,0)-(56, 0)
	for (int i = 0; i < 29; i++)
	{
		wprintf(L"%lc", WALL);
	}
	//下(0,26)-(56, 26)
	Setpos(0, 26);
	for (int i = 0; i < 29; i++)
	{
		wprintf(L"%lc", WALL);
	}
	//左,x是0,y从1开始增⻓
	for (int i = 1; i < 26; i++)
	{
		Setpos(0, i);
		wprintf(L"%lc", WALL);
	}
	//右,x是56,y从1开始增⻓
	for (int i = 1; i < 26; i++)
	{
		Setpos(56, i);
		wprintf(L"%lc", WALL);
	}
}
//6.创建蛇身
void CreateSnakenode(Psnake Ps)
{
	for (int i = 0; i < 5; i++)
	{
		Psnakenode pcur = (Psnakenode)malloc(sizeof(Snakenode));
		if (pcur == NULL)
		{
			perror("SetSnakenode::malloc");
			return;
		}
		pcur->x = 24 + i * 2;
		pcur->y = 5;
		pcur->next = NULL;
		if (Ps->_snake == NULL)
		{
			//为空
			Ps->_snake = pcur;
		}
		else
		{
			//不为空
			pcur->next = Ps->_snake;
			Ps->_snake = pcur;
		}
	}
	//打印蛇身
	Psnakenode prev = Ps->_snake;
	while (prev)
	{
		Setpos(prev->x, prev->y);
		wprintf(L"%lc", BODY);
		prev = prev->next;
	}
}
//7.创建食物
void CreateFood(Psnake Ps)
{
	int x = 0, y = 0;
qu:
	do
	{
		x = rand() % 53 + 2;
		y = rand() % 25 + 1;
	} while (x % 2 != 0);//生成的x坐标必须是2的倍数
	Psnakenode pcur = Ps->_snake;
	while (pcur)
	{
		//生成的食物不能和蛇的身体重合
		if (pcur->x == x && pcur->y == y)
			goto qu;//坐标与蛇身重合就重新生成坐标
		pcur = pcur->next;
	}
	Setpos(x, y);
	Color(223);//食物颜色设置为粉色
	wprintf(L"%lc", FOOD);
	Ps->_Food = (Psnakenode)malloc(sizeof(Snakenode));//给食物申请一个节点
	if (Ps->_Food == NULL)
	{
		perror("Set_food::malloc");
		return;
	}
	Ps->_Food->x = x;
	Ps->_Food->y = y;
}
//初始化游戏
void GameStart(Psnake Ps)
{
	//设置控制台相关属性
	SetConsole();
	//打印欢迎界面
	WelcomePrint();
	//创建地图
	CreateMap();
	//创建蛇身
	CreateSnakenode(Ps);
	//创建食物
	CreateFood(Ps);
	//设置蛇的信息
	Ps->_Allgrade = 0;
	Ps->_grade = 10;
	Ps->_dir = RIGHT;
	Ps->_sleep = 200;
	Ps->_status = OK;
}
#define KEY_PRESS(VK) ((GetAsyncKeyState(VK))&1?1:0)
//暂停游戏
void Stop()
{
	while (1)
	{
		Sleep(200);
		if (KEY_PRESS(VK_SPACE))//当检测到空格键被按过就退出循环
			break;
	}
}
//撞到墙
void KillByWall(Psnake Ps)
{
	if (Ps->_snake->x == 0 || Ps->_snake->x == 56 || Ps->_snake->y == 0 || Ps->_snake->y == 26)
		Ps->_status = KILL_BYWALL;
}
//撞到自己
void KillBySelf(Psnake Ps)
{
	Psnakenode pcur = Ps->_snake->next;
	while (pcur)//遍历蛇头之后的节点
	{
		if (pcur->x == Ps->_snake->x && pcur->y == Ps->_snake->y)//当有一个节点和头节点重合,就说明撞到了自己
			Ps->_status = KILL_BYSELF;//改变蛇的状态
		pcur = pcur->next;
	}
}
//判断下一个节点是否是食物
int NextIsFood(Psnakenode next, Psnake Ps)
{
	return (next->x == Ps->_Food->x && next->y == Ps->_Food->y);
}
//吃掉食物
void EatFood(Psnakenode next, Psnake Ps)
{
	//让食物的节点成为新的蛇头
	Ps->_Food->next = Ps->_snake;
	Ps->_snake = Ps->_Food;
	//释放next节点
	free(next);
	//打印蛇身
	Psnakenode pcur = Ps->_snake;
	while (pcur)
	{
		Setpos(pcur->x, pcur->y);
		wprintf(L"%lc", BODY);
		pcur = pcur->next;
	}
	//创建新食物
	CreateFood(Ps);
}
//不是食物
void NoFood(Psnakenode next, Psnake Ps)
{
	next->next = Ps->_snake;
	Ps->_snake = next;
	Psnakenode pcur = Ps->_snake;
	while (pcur->next->next != NULL)
	{
		pcur = pcur->next;
	}
	//把最后一个节点打印成空格
	Setpos(pcur->next->x, pcur->next->y);
	printf("  ");
	//释放最后一个节点
	free(pcur->next);
	//将倒数第二个节点的next指针置为空
	pcur->next = NULL;
	//打印蛇身
	pcur = Ps->_snake;
	//设置蛇身颜色
	Color(12);
	while (pcur)
	{
		Setpos(pcur->x, pcur->y);
		wprintf(L"%lc", BODY);
		pcur = pcur->next;
	}
}
//蛇走一步
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->_snake->x;
		next->y = Ps->_snake->y - 1;
		break;
	case DOWN:
		next->x = Ps->_snake->x;
		next->y = Ps->_snake->y + 1;
		break;
	case LEFT:
		next->x = Ps->_snake->x - 2;
		next->y = Ps->_snake->y;
		break;
	case RIGHT:
		next->x = Ps->_snake->x + 2;
		next->y = Ps->_snake->y;
		break;
	}
	//判断下一个节点是否是食物

	int ret = NextIsFood(next, Ps);
	if (ret)
	{
		//吃掉食物
		EatFood(next, Ps);
		Ps->_Allgrade += Ps->_grade;
		//next = NULL;
	}
	//不是食物
	else
	{
		NoFood(next, Ps);
	}
}
//打印帮助信息
void PrintHelpInfo(Psnake Ps)
{
	//打印帮助信息
	Setpos(62, 9);
	printf("当前蛇的总分数为:%2d", Ps->_Allgrade);
	Setpos(62, 10);
	printf("当前食物的分数为:%2d", Ps->_grade);
	Color(7);//设置打印信息颜色
	Setpos(62, 14);
	wprintf(L"不能撞墙,不能撞到自己");
	Setpos(62, 15);
	wprintf(L"按 ↑ ↓ ← → 控制蛇的移动,F3加速,F4减速");
	Setpos(62, 16);
	wprintf(L"按ESC退出游戏,按空格暂停游戏");
}
//运行游戏
void GameRun(Psnake Ps)
{
	do
	{
		//打印帮助信息
		PrintHelpInfo(Ps);
		if (KEY_PRESS(VK_UP) && Ps->_dir != DOWN)
			Ps->_dir = UP;
		else if (KEY_PRESS(VK_DOWN) && Ps->_dir != UP)
			Ps->_dir = DOWN;
		else if (KEY_PRESS(VK_LEFT) && Ps->_dir != RIGHT)
			Ps->_dir = LEFT;
		else if (KEY_PRESS(VK_RIGHT) && Ps->_dir != LEFT)
			Ps->_dir = RIGHT;
		else if (KEY_PRESS(VK_SPACE))
		{
			//暂停游戏
			Stop();
		}
		else if (KEY_PRESS(VK_ESCAPE))
		{
			//正常退出游戏
			Ps->_status = END_NORMAL;
		}
		else if (KEY_PRESS(VK_F3))
		{
			//加速
			if (Ps->_sleep > 80)
			{
				Ps->_sleep -= 40;
				Ps->_Allgrade += 2;
			}
		}
		else if (KEY_PRESS(VK_F4))
		{
			//减速
			if (Ps->_sleep >= 200 && Ps->_sleep <= 320)
			{
				Ps->_sleep += 40;
				Ps->_Allgrade -= 2;
			}
		}
		//蛇走一步
		SnakeMove(Ps);
		//撞到墙
		KillByWall(Ps);
		//撞到自己
		KillBySelf(Ps);
		Sleep(Ps->_sleep);
	} while (Ps->_status == OK);
}
//结束游戏,游戏的善后
void GameEnd(Psnake Ps)
{
	Setpos(22, 13);
	switch (Ps->_status)
	{
	case KILL_BYSELF:
		wprintf(L"您撞到了自己,游戏结束");
		break;
	case KILL_BYWALL:
		wprintf(L"您撞到墙了,游戏结束");
		break;
	case END_NORMAL:
		wprintf(L"您主动退出游戏,游戏结束");
		break;
	}
	//释放蛇身节点
	Psnakenode pcur = Ps->_snake;
	while (pcur)
	{
		Psnakenode next = pcur->next;
		free(pcur);
		pcur = next;
	}
}

text.c

#include "Snake.h"
void snakeTest()
{
	char ch;
	do
	{
		system("cls");
		//创建蛇
		Snake snake;
		snake._snake = NULL;
		//初始化游戏
		GameStart(&snake);
		//运行游戏
		GameRun(&snake);
		//结束游戏,游戏的善后
		GameEnd(&snake);
		Setpos(24, 14);
		wprintf(L"再来一局吗?(Y/N)");
		ch = getchar();
		getchar();
	} while (ch == 'Y' || ch == 'y');
	Setpos(0, 27);
}
int main()
{
	//修改当前地区为本地模式,为了⽀持中⽂宽字符的打印
	setlocale(LC_ALL, "");
	//生成随机数函数的声明
	srand((unsigned int)time(NULL));
	//测试逻辑
	snakeTest();
	return 0;
}

第一次写这么长的博客,希望能给大家带来帮助呀😜!如果感觉还不错的话,麻烦一键三连哟!创作不易,谢谢宝子们!😽

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

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

相关文章

Golang Colly爬取图片gorm存储数据

语言:Golang 库:Iris/Colly/gorm 运行结果 text/html; charset=utf-8 It is image 20240429222029_0_0.jpg Saved file: images\20240429222029_0_0.jpg text/html; charset=utf-8 It is image 20240429222030_1_0.jpg Saved file: images\20240429222030_1_0.jpg It is ima…

String类1⃣️

目录 预备知识 1.string成员函数 1.string() 2.string (const char* s); 3.string (size_t n, char c); 4.string (const string& str);&#xff08;拷贝构造&#xff09; 2.string类对象的容量操作 1.size length 2.max_size 3.resize 4.capacity 5.empty 6…

【leetcode面试经典150题】78.二叉树中的最大路径和(C++)

【leetcode面试经典150题】专栏系列将为准备暑期实习生以及秋招的同学们提高在面试时的经典面试算法题的思路和想法。本专栏将以一题多解和精简算法思路为主&#xff0c;题解使用C语言。&#xff08;若有使用其他语言的同学也可了解题解思路&#xff0c;本质上语法内容一致&…

元数据管理在态势感知系统的应用

在当今信息爆炸的时代&#xff0c;数据量呈指数级增长&#xff0c;如何高效地管理和利用这些数据成为了各行各业所面临的重要问题。在网络安全领域&#xff0c;态势感知系统作为一种重要的安全防御工具&#xff0c;承担着及时发现、分析和应对安全威胁的重任。 然而&#xff0c…

网络层 --- IP协议

目录 1. 前置性认识 2. IP协议 3. IP协议头格式 3.1. 4位版本 3.2. 4位首部长度 3.3. 8位服务类型 3.4. 16位总长度 3.5. 8位生存时间 TTL 3.6. 8位协议 3.7. 16位首部检验和 3.8. 32位源IP和32位目的IP 4. 分片问题 4.1. 为什么要分片 4.2. 分片是什么 4.2.1. …

助力企业部署国产云原生数据库 XSKY星辰天合与云猿生完成产品互兼容认证

近日&#xff0c;北京星辰天合科技股份有限公司&#xff08;简称&#xff1a;XSKY 星辰天合&#xff09;与杭州云猿生数据有限公司&#xff08;简称“云猿生”&#xff09;完成了产品互兼容认证&#xff0c;星辰天合企业级分布式统一数据平台 XEDP 与云猿生的开源数据库管控平台…

JAVA系列 小白入门参考资料 继承

目录 1. 为什么需要继承 2. 继承的概念 3. 继承的语法 4. 父类成员访问 4.1 子类中访问父类的成员变量 1. 子类和父类不存在同名成员变量 2. 子类和父类成员变量同名 4.2 子类中访问父类的成员方法 1. 成员方法名字不同 2. 成员方法名字相同 ​5. super关键字 …

《ElementPlus 与 ElementUI 差异集合》el-dialog 显示属性有差异

ElementPlus 用属性 v-model ElementUI 用属性 visible 其实也是 Vue2/Vue3 的差异&#xff1a;v-model 指令在组件上的使用已经被重新设计&#xff0c;替换掉了 v-bind.sync

自己手写了一个大模型RAG项目-05.基于知识库的大模型问答

大家好&#xff0c;我是程序锅。 github上的代码封装程度高&#xff0c;不利于小白学习入门。 常规的大模型RAG框架有langchain等&#xff0c;但是langchain等框架源码理解困难&#xff0c;debug源码上手难度大。 因此&#xff0c;我写了一个人人都能看懂、人人都能修改的大…

力扣HOT100 - 79. 单词搜索

解题思路&#xff1a; 深度优先搜索&#xff08;DFS&#xff09; 剪枝。 class Solution {public boolean exist(char[][] board, String word) {char[] words word.toCharArray();for(int i 0; i < board.length; i) {for(int j 0; j < board[0].length; j) {if (df…

Springboot+MybatisPlus入门案例(postman测试)

一、项目框架 pom.xml依赖 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apac…

微软如何打造数字零售力航母系列科普04 - 微软联合Adobe在微软365应用程序中工作时推出新的生成式AI功能

微软和Adobe正在合作&#xff0c;将情境营销见解和工作流程引入微软Copilot&#xff0c;以提供生成的人工智能功能&#xff0c;使营销人员和营销团队能够在自然的工作流程中实现更多目标。 这些新的集成功能将在生产力和协作工具&#xff08;如Outlook、Teams和Word&#xff0…

【保姆级教程】用IDEA2023版本给RuoYi-Vue添加子模块

文章目录 前言添加子模块新建子模块新建子模块界面&#xff1f;新建子模块界面&#xff01; 修改pom依赖配置RuoYiApplication添加测试接口配置接口权限测试 前言 若依前后端分离框架能够极大方便当前开发任务&#xff0c;并且使用的技术栈也相当丰富&#xff0c;但是目前只提…

【HarmonyOS4学习笔记】《HarmonyOS4+NEXT星河版入门到企业级实战教程》课程学习笔记(六)

课程地址&#xff1a; 黑马程序员HarmonyOS4NEXT星河版入门到企业级实战教程&#xff0c;一套精通鸿蒙应用开发 &#xff08;本篇笔记对应课程第 12 - 13节&#xff09; P12《11.ArkUI组件-循环控制》 forEach() 方法的使用方式&#xff1a; 在预览界面点击红框的按钮&#xf…

煤矿综合自动化智能监控系统

系统概述 建设煤矿井上下工业环网、工业数据集成平台、排水、供电、运输、通风、压风、瓦斯抽放、采掘、智能洗煤厂等智能自动化控制系统&#xff0c;利用多种软硬件接口(OPC协议、驱动通讯、数据库、文本文件、DDE/NETDDE、子网等)&#xff0c;构建全矿井统一、稳定、高效的数…

vue2 实现echarts图表进入可视区域后再加载动画,以及 使用了resize之后,动画失效问题解决

Intersection Observer API 是一个现代的浏览器 API&#xff0c;用于监测一个或多个目标元素与其祖先元素或视窗&#xff08;viewport&#xff09;之间的交叉状态&#xff08;intersection&#xff09;的变化。它可以有效地监听元素是否进入或离开可视区域&#xff0c;从而实现…

使用OkHttp 缓存 API 调用提高Android应用性能

使用OkHttp 缓存 API 调用提高Android应用性能 坦率地说&#xff0c;我们都遇到过这样的情况——焦急地刷新应用&#xff0c;看着加载图标不停地旋转&#xff0c;等待那个至关重要的 API 响应。这样的等待我们已经是炉火纯青了&#xff0c;是吧&#xff1f;手指有节奏地轻敲屏…

记录些RAG-Fusion、Agent、NL2SQL的问题

RAG-Fusion RAG-Fusion 的实现原理 Query Duplication with a Twist&#xff1a;用 LLM 根据用户的 Query 生成几个相关的但不同的 Queries。Vector Search Unleashed&#xff1a;对原 Query 和生成的 Queries 都进行向量&#xff08;或者其他方式的&#xff09;搜索。Intell…

基于Springboot的滑雪场管理系统(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的滑雪场管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&a…

摩根大通推出创新工具 FlowMind,引领金融自动化新变革

近日&#xff0c;摩根大通人工智能研究部推出了一款极具创新性的工具——FlowMind&#xff0c;为金融行业带来了全新的工作模式和效率提升。 FlowMind 能够自动化金融工作流程&#xff0c;在信贷审批、风险评估、合规监测等重要任务中发挥着关键作用。它利用 GPT 自动生成工作…