C语言——贪吃蛇游戏的实现

目录

 一. 贪吃蛇的介绍

二. Win32 API

1. 控制台程序

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

3. GetStdHandle

4. GetConsoleCursorInfo

CONSOLE_CURSOR_INFO

5. SetConsoleCursorInfo

6. SetConsoleCursorPosition

封装的SetPos函数

7. GetAsyncKeyState

宏定义KEY_PRESS

三. 贪吃蛇游戏实现

1. 贪吃蛇地图

和类项

setlocale函数

打印地图

封装的墙函数

2. 设计蛇和食物

3. 游戏实现流程

1. 游戏开始-GameStart

1.打印欢迎界面

2.打印创建地图

3.创建蛇-初始化

4.创建食物

2. 游戏运行-GameRun

暂停—Pause函数

1.蛇的移动

2.下一个位置是否是食物

无食物-NoFood

判断是否是食物-NextIsFood

3.吃掉食物——EatFood

4.撞到墙——KillByWall

5.撞到自己——KillBySelf

3. 游戏结束——GameEnd

四. 展示所有全部代码


 一. 贪吃蛇的介绍

                                 

 我们都有玩过一个小游戏——贪吃蛇,贪吃蛇也是一个经典游戏。如上图所示,游戏玩法就是操控一个蛇,让它吃掉食物,每吃掉一个食物就会增加自己身体一格长度,并且保证自己不能撞到墙和自己本身,我们就可以通过这些功能,去用代码实现这些功能从而实现贪吃蛇游戏。(贪吃蛇游戏如上图所示)

我们总结一下贪吃蛇所需要的基本功能:

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

我们完成的游戏,是在控制台窗口完成。

这里设计的知识要点有:C语言函数,枚举类型,结构体,动态内存管理,预处理指令,链表(链表用于维护贪吃蛇的蛇身),Win32 API等。

二. Win32 API

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

1. 控制台程序

控制台(console)程序可能听起来名字比较陌生,其实是如图所示:

打开方式可以按Windows+R,在输入cmd。

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

mode con cols=100 lines=30

我们可以看一下对比,上图所示

设置控制台窗口的名字的指令:

title 贪吃蛇

这些操作可以调用C语言函数system执行:

注意:使用时记得包含stdlib.h头文件

#include <stdio.h>
#include <stdlib.h>
//system函数可以用来执行系统命令//

int main()
{
	//设置控制台的相关属性

	system("mode con cols=100 lines=30");  //设置大小
	system("title 贪吃蛇");                //设置名字

	system("pause");                      //也可以getchar();效果一样。防止程序结束,导致结果有问题
	return 0;
}

效果展示:

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

控制台的坐标分布是上图所示

COORD类型的声明:

typedef struct _ COORD {
        SHORT X;
        SHORT Y;
} COORD, *PCOORD;
所以给坐标赋值为:
COORD pos = { 10, 15 };  //给坐标赋值为(10,15)

注意:需要包含头文件windows.h

3. GetStdHandle

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

函数参数:

函数使用:

int main()
{
	//获得标准输出设备的句柄
	HANDLE houtput = NULL;    //HANDLE 是一个指针变量  typedef void *HANDLE
	houtput = GetStdHandle(STD_OUTPUT_HANDLE); //获得屏幕标准输出设备

    return 0;
}

4. GetConsoleCursorInfo

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

注:第二个参数是个指针,指向(CONSOLE_CURSOR_INFO)控制台光标信息的指针。

CONSOLE_CURSOR_INFO

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

其中:

  • dwSize
    游标填充的字符单元的百分比。 该值介于 1 到 100 之间。 游标外观各不相同,范围从完全填充单元到显示为单元底部的横线。
  •  bVisible
    游标的可见性。 如果游标可见,则此成员为 TRUE。

5. SetConsoleCursorInfo

//语法声明
BOOL WINAPI SetConsoleCursorInfo(
  _In_       HANDLE              hConsoleOutput,
  _In_ const CONSOLE_CURSOR_INFO *lpConsoleCursorInfo
);

第一个参数就是句柄,第二个参数就是光标信息的地址。

结合起来运用:

#include <stdio.h>
#include <stdbool.h>  //false所需
#include <windows.h>  //HANDLE等所需

int main()
{
	//获得标准输出设备的句柄
	HANDLE houtput = NULL;
	houtput = GetStdHandle(STD_OUTPUT_HANDLE);

	//定义一个光标信息的结构体
	CONSOLE_CURSOR_INFO cursor_info = { 0 };

	//获取和houtput句柄相关的控制台上的光标信息,存放在cursor_info中
	GetConsoleCursorInfo(houtput, &cursor_info);

	//修改光标的占比
	//cursor_info.dwSize = 100;

	cursor_info.bVisible = false;

	//设置和houtput句柄相关的控制台上的光标信息
	SetConsoleCursorInfo(houtput, &cursor_info);

	system("pause");
	return 0;
}

效果如下:

改占比

修改可见性

6. SetConsoleCursorPosition

设置指定控制台屏幕缓冲区中的光标位置,我们将想要设置的坐标信息放在COORD类型的pos中,调用SetConsoleCursorPosition函数将光标位置设置到指定的位置。
//语法声明
BOOL WINAPI SetConsoleCursorPosition(
  _In_ HANDLE hConsoleOutput,
  _In_ COORD  dwCursorPosition
);

第一个参数是句柄

第二个参数就是位置

#include <stdio.h>
#include <windows.h>

int main()
{
	//获得标准输出设备的句柄
	HANDLE houtput = NULL;
	houtput = GetStdHandle(STD_OUTPUT_HANDLE);

	//定位光标的位置
	COORD pos = { 10, 20 };
	SetConsoleCursorPosition(houtput, pos);

	printf("hehe\n");
	return 0;
}

效果如下:

我们把这个封装一个设置光标位置的函数

封装的SetPos函数

void SetPos(short x, short y)
{
	//获得标准输出设备的句柄
	HANDLE houtput = NULL;
	houtput = GetStdHandle(STD_OUTPUT_HANDLE);

	//定位光标的位置
	COORD pos = { x, y };
	SetConsoleCursorPosition(houtput, pos);
}

int main()
{
	
	SetPos(10, 20);
	printf("1\n");

	SetPos(10, 10);
	printf("2\n");
	getchar();
	//system("pause");
	return 0;
}

效果展示如图:

7. GetAsyncKeyState

用来获取按键情况,将键盘上每个键的虚拟键值传递给函数,函数通过返回值来分辨按键的状态。

GetAsyncKeyState 返回值是short类型,在上一次调用GetAsyncKeyState 函数后,如果返回的16位的short数据中,最高位是1,说明按键的状态是按下,如果最高是0,说明按键的状态是抬起;如果最低位被置为1则说明,该按键被按过,否则为0。 
函数原型:
SHORT GetAsyncKeyState(
 int vKey
);

虚拟键码链接:虚拟键码

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

因此我们可以定义一个宏

宏定义KEY_PRESS
//结果是1表示按过
//结果是0表示未按过

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

这个就以便于我们后面实现贪吃蛇用按键控制方向所使用。

三. 贪吃蛇游戏实现

1. 贪吃蛇地图

我们可以用自己所想要的符号来完成地图(墙)的创建,这里使用了宽字符,墙体的为□,蛇的为⚪,食物为★。

注意:普通字符占一个字节,宽字符占2个字节。

<locale.h>和类项

<locale.h>提供的函数用于控制C标准库中对于不同的地区会产生不⼀样行为的部分。
在标准中,依赖地区的部分有以下几项:
  • 数字量的格式
  • 货币量的格式
  • 字符集
  • 日期和时间的表示形式

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

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

setlocale函数

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

当地区设置为"C"时,库函数按正常⽅式执⾏,小数点是一个点。

若要改变地区,调用setlocale函数。用""作为第二个参数

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

打印地图

在此之前,需要知道宽字符打印的格式和函数

宽字符的字面量必须加上前缀“L”,否则 C 语言会把字面量当作窄字符类型处理。前缀“L”在单引号前面,表示宽字符,对应 wprintf() 的占位符为 %lc ;在双引号前面,表示宽字符串,对应 wprintf() 的占位符为 %ls
//使用格式如下

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

int main()
{
	//设置本地化
	setlocale(LC_ALL, "");

	char a = 'a';
	char b = 'b';
	printf("%c%c\n", a, b);

	wchar_t wc1 = L'测';
	wchar_t wc2 = L'试';
	wprintf(L"%lc\n", wc1);
	wprintf(L"%lc\n", wc2);
	wprintf(L"%lc\n", L'●');
	wprintf(L"%lc\n", L'★');

	return 0;
}

我们看一下结果:

ab是普通字符,以下都是宽字符,对比看出,ab两个字符所占字节才等于一个宽字符所占字节。

我们要设计一个27*27的范围,就是要设计一个27行,58列的棋盘,我们封装的函数SetPos设置光标坐标函数就用上了

封装的墙函数
#define WALL L'□'


//创建地图
void CreateMap()
{
	//上
	int i = 0;
	for (i = 0; i < 29; i++)
	{
		wprintf(L"%lc", WALL);
	}
	//下
	SetPos(0, 26);   //设置光标坐标
	for (i = 0; i < 29; i++)
	{
		wprintf(L"%lc", WALL);
	}
	//左
	for (i = 1; i <= 25; i++)
	{
		SetPos(0, i);  //设置光标坐标
		wprintf(L"%lc", WALL);
	}
	//右
	for (i = 1; i <= 25; i++)
	{
		SetPos(56, i); //设置光标坐标
		wprintf(L"%lc", WALL);
	}

}

我们的墙就创建好了

2. 设计蛇和食物

蛇身我们用链表来维护,所以蛇的每一个节点就是链表的每个节点,所以节点要记录坐标就行。

注:蛇的每个节点的x坐标必须是2的倍数,并且食物随机位置的x坐标也要为2的倍数,并且不能跟蛇身重合。

蛇的节点结构:

typedef struct SnakeNode
{
    int x;
    int y;
    struct SnakeNode* next;
}SnakeNode,*pSnakeNode;


//*pSnakeNode是创建一个结构体指针类型,也可也写成
//typedef struct SnakeNode *pSnakeNdoe;

贪吃蛇游戏需要完成管理蛇的事项:

  1. 指向蛇头的指针,以便于后续使用
  2. 指向食物节点的指针,以便找到食物
  3. 蛇的方向(↑、↓、←、→)
  4. 游戏的状态(正常运行、撞墙、撞到自己、正常结束退出)
  5. 食物的分数
  6. 总分数
  7. 每走一步休眠时间(控制蛇的速度的)

所以我们可以创建所需要的自定义类型:

//用于维护贪吃蛇的
typedef struct Snake
{
	pSnakeNode _pSnake;          //指向蛇头的指针  维护整条蛇的指针
	pSnakeNode _pFood;          //指向食物节点的指针  维护食物的指针
	enum DIRECTION _dir;       //蛇的方向
	enum GAME_STATUS _status; //游戏的状态
	int _food_weight;		 //一个食物的分数
	int _score;			    //总分数
	int _sleep_time;       //休息时间,时间越短,速度越快,时间越长,速度越慢
}Snake, * pSnake;
//用于控制蛇的方向
//蛇的方向(上、下、左、右)
enum DIRECTION
{
	UP = 1,
	DOWN,
	LEFT,
	RIGHT
};
//用于蛇的状态
//正常、撞墙、撞到自己、正常退出
enum GAME_STATUS
{
	OK,				//正常运行
	KILL_BY_WALL,   //撞墙
	KILL_BY_SELF,   //撞自己
	END_NORMAL       //正常退出
};

3. 游戏实现流程

所以游戏主逻辑是:

  • 游戏开始——完成游戏的初始化
  • 游戏运行——完成游戏运行逻辑的实现
  • 游戏结束——完成游戏结束的说明,释放所用资源

我们需要分三个文件来完成:test.c(用于完成游戏逻辑测试)、snake.c(用于完成游戏所需逻辑代码函数等)、snake.h(用于包含游戏所需头文件和声明类型和函数等)

//snake.h文件
//把所需头文件和所要声明的函数和类型等都要包含在里面,方便使用
//声明的函数要在对应的snake.c里实现
#pragma once
#include<windows.h>
#include<stdio.h>
#include<stdbool.h>
#include<stdlib.h>
#include <time.h>

#define POS_X 24
#define POS_Y 5

//宏定义判断是否按过键
#define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )

#define WALL L'□'  //墙
#define BODY L'●'  //蛇身
#define FOOD L'★' //食物

//类型的声明
//蛇的方向
enum DIRECTION
{
	UP = 1,
	DOWN,
	LEFT,
	RIGHT
};

//蛇的状态
//正常、撞墙、撞到自己、正常退出
enum GAME_STATUS
{
	OK,				//正常运行
	KILL_BY_WALL,   //撞墙
	KILL_BY_SELF,   //撞自己
	END_NORMAL       //正常退出
};

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

//typedef struct SnakeNode* pSnakeNode;

typedef struct Snake
{
	pSnakeNode _pSnake;          //指向蛇头的指针  维护整条蛇的指针
	pSnakeNode _pFood;          //指向食物节点的指针  维护食物的指针
	enum DIRECTION _dir;       //蛇的方向
	enum GAME_STATUS _status; //游戏的状态
	int _food_weight;		 //一个食物的分数
	int _score;			    //总分数
	int _sleep_time;       //休息时间,时间越短,速度越快,时间越长,速度越慢
}Snake, * pSnake;

//函数的声明
//定位
void SetPos(short x, short y);
//游戏的初始化
void GameStart(pSnake ps);
//欢迎界面和功能介绍
void WelcomeToGame();
//地图创建
void CreateMap();
//初始化蛇身
void InitSnake(pSnake ps);
//创建食物
void CreateFood(pSnake ps);
//游戏运行
void GameRun(pSnake ps);
//蛇移动-走一步
void SnakeMove(pSnake ps);
//判断下一个坐标是否是食物
int NextIsFood(pSnakeNode pn, pSnake ps);
//吃掉食物
void EatFood(pSnakeNode pn, pSnake ps);
//下一个位置不是食物
void NoFood(pSnakeNode pn, pSnake ps);
//结束
void GameEnd(pSnake ps);
//撞墙
void KillByWall(pSnake ps);
//撞自己
void KillBySelf(pSnake ps);
//test.c文件中

#include <locale.h>
#include "snake.h"


//完成的是游戏的测试逻辑
void test()
{
	int ch = 0;
	do
	{
		system("cls");
		//创建贪吃蛇
		Snake snake = { 0 };

        //开始游戏
		GameStart(&snake);

		//运行游戏
		GameRun(&snake);
		//结束游戏 - 善后工作
		GameEnd(&snake);
		SetPos(20, 15);
		printf("再来一局吗?(Y/N):");
		ch = getchar();
		while (getchar() != '\n'); //用于清理\n
	} while (ch == 'Y' || ch == 'y');
	SetPos(0, 27);

}


int main()
{
	//设置适配本地环境
	setlocale(LC_ALL, "");
	srand((unsigned int)time(NULL));  //用于rand函数随机生成食物
	test();
	return 0;
}

接下来就是实现snake.c里的函数逻辑了。

1. 游戏开始-GameStart

我们需要完成以下的:

  • 初始化游戏,先设置窗口大小,再光标隐藏。
  • 打印欢迎界面、功能介绍
  • 创建地图
  • 创建蛇-初始化
  • 创建食物
void GameStart(pSnake ps)
{
	//初始化游戏
	//0.光标隐藏,先设置窗口大小,再光标隐藏
	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); //设置控制台光标状态

	
	//1.打印环境界面
	//2.功能介绍
	WelcomeToGame();
	//3.绘制地图
	CreateMap();
	//4.创建蛇
	InitSnake(ps);
	//5.创建食物
	CreateFood(ps);
	
1.打印欢迎界面
//欢迎界面和功能介绍
void WelcomeToGame()
{
	SetPos(40, 14);
	wprintf(L"欢迎来到贪吃蛇小游戏\n");
	SetPos(42, 20);
	system("pause");
	system("cls");
	SetPos(25, 14);
	wprintf(L"用↑.↓.←.→ 来控制蛇的移动,按F3加速,F4减速\n");
	SetPos(25, 15);
	wprintf(L"加速能够得到更高的分数\n");

	SetPos(42, 20);
	system("pause");
	system("cls");
}

注:在前面我们封装了SetPos函数,要放在snake.c里。(点击目录封装SetPos函数就可以看到)

2.打印创建地图

将墙打印出来,由于宽字符要用wprintf函数,并且注意格式,坐标的计算:

上:(0,0)到(56,0)

下:(0,26)到(56,26)

左:(0,1)到(0,25)

右:(56,1)到(56,25)

注:打印地图函数也封装在前面,在目录里封装的墙函数。

3.创建蛇-初始化
  • 蛇开始的长度(如5节),每节对应链表的一个节点,每一个节点都有自己的坐标,创建这些节点存放到链表中管理,创建完后,将蛇的每一节打印在屏幕上
  • 蛇的初始位置(如(24,5))
  • 游戏状态:OK(正常运行)
  • 蛇的移动速度:200毫秒
  • 蛇的默认方向:RIGHT(右边)
  • 初始成绩:0
  • 食物的分数:10
//初始化蛇身
void InitSnake(pSnake ps)
{
	int i = 0;
	pSnakeNode cur = NULL;                             //创建一个struct SnakeNode*类型(pSnakeNode)的指针变量cur,用于存储蛇的节点
	for (i = 0; i < 5; i++)
	{
		cur = (pSnakeNode)malloc(sizeof(SnakeNode));  //申请动态空间用于存储蛇的节点
		if (cur == NULL)                              //如申请失败,结束程序
		{
			perror("InitSnake()::malloc()");
			return;
		}
		cur->next = NULL;
		cur->x = POS_X + 2 * i;  //初始化x坐标
		cur->y = POS_Y;          //初始化y坐标
		//头插法插入链表
		if (ps->_pSnake == NULL)  //空链表的时候
		{
			ps->_pSnake = cur;
		}
		else//非空
		{
			cur->next = ps->_pSnake;
			ps->_pSnake = cur;
		}
	}
	//打印蛇
	cur = ps->_pSnake;
	while (cur)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	//设置贪吃蛇的属性
	ps->_dir = RIGHT;//默认向右
	ps->_score = 0;  //默认总分0
	ps->_food_weight = 10; //食物分数10
	ps->_sleep_time = 200; //单位毫秒
	ps->_status = OK;      //正常运行
}
4.创建食物
  • 随机生成食物的坐标:x坐标必须2的倍数、食物坐标不能和蛇身重复
  • 创建食物节点,打印
//创建食物
void CreateFood(pSnake ps)
{
	int x = 0;
	int y = 0;
	//生成x是2的倍数
	//x:2---52
	//y:1---25
again:
	//要使x坐标为2的倍数,否则对不齐蛇头坐标
	do
	{
		x = rand() % 53 + 2;
		y = rand() % 25 + 1;
	} while (x % 2 != 0);
	//x和y的坐标不能和蛇的身体坐标冲突
	pSnakeNode cur = ps->_pSnake;
	while (cur)   //遍历蛇的节点
	{
		if (x == cur->x && y == cur->y)  //判断是否与蛇的坐标重复
		{
			goto again;
		}
		cur = cur->next;
	}
	//创建食物的节点
	pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (pFood == NULL)
	{
		perror("CreateFood()::malloc()");
		return;
	}
	else
	{
		pFood->x = x;
		pFood->y = y;
		pFood->next = NULL;
		SetPos(x, y);  //定位位置
		wprintf(L"%lc", FOOD);
		ps->_pFood = pFood;  //把随机的食物节点赋值到用于维护贪吃蛇的结构体成员中用于维护食物
	}
}

查看一下成果:

可以看到开始游戏已经实现,蛇要运动还需要完成GameRun

2. 游戏运行-GameRun

  • 根据游戏状态检查游戏是否继续,如果是OK,则继续,否则结束。
  • 如果游戏继续,检查按键情况,确定蛇下一步方向、是否加速减速、是否暂停或者退出游戏。
  • 我们要在(64,15)这个坐标开始,在运行期间,打印帮助信息,提示玩家。

先完成PrintHelpInfo打印帮助信息函数

//打印帮助信息
void PrintHelp()
{
	SetPos(64, 14);
	wprintf(L"%ls", L"不能穿墙,不能咬到自己");
	SetPos(64, 15);
	wprintf(L"%ls",L"用↑.↓.←.→ 来控制蛇的移动");
	SetPos(64, 16);
	wprintf(L"%ls",L"按F3加速,F4减速");
	SetPos(64, 17);
	wprintf(L"%ls", L"按ESC退出游戏,按空格暂停游戏");
}

所需虚拟键盘的罗列:

  • 上:VK_UP
  • 下:VK_DOWN
  • 左: VK_LEFT
  • 右: VK_RIGHT
  • 空格: VK_SPACE
  • ESC:VK_ESCAPE
  • F3:VK_F3
  • F4:VK_F4
蛇的方向和速度确定,就可以让蛇移动了
//游戏运行
void GameRun(pSnake ps)
{
	//打印帮助信息
	PrintHelp();
	do
	{
		//打印总分数和食物的分值
		SetPos(64, 10);
		printf("总分数:%d\n", ps->_score);
		SetPos(64, 11);
		printf("当前食物的分数:%2d\n", ps->_food_weight);  //%2d防止打印一位数时出bug
		//判断按键情况,并做出相应操作
		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->_status = END_NORMAL;
		}
		else if (KEY_PRESS(VK_F3))
		{
			//加速
			if (ps->_sleep_time > 80)
			{
				ps->_sleep_time -= 30;
				ps->_food_weight += 2;
			}
		}
		else if (KEY_PRESS(VK_F4))
		{
			//减速
			if (ps->_food_weight > 2)
			{
				ps->_sleep_time += 30;
				ps->_food_weight -= 2;
			}
		}
		SnakeMove(ps);//贪吃蛇走一步的过程
		Sleep(ps->_sleep_time);  //蛇每次之间要休眠的时间,时间短,蛇移动的就快。
	} while (ps->_status == OK);
}
暂停—Pause函数
//暂停
void Pause()
{
	while (1)
	{
		Sleep(200);
		if (KEY_PRESS(VK_SPACE))
		{
			break;
		}
	}
}
1.蛇的移动

逻辑是:先创建下一个节点,根据移动方向和蛇头的坐标,蛇移动下一个位置的坐标。

注:

  • 确定了下一个位置后,看下一个位置是否是食物(NextIsFood),是食物就做吃食物处理(EatFood),如果不是食物则做前进一步的处理(NoFood)。
  • 蛇身移动后,判断此次移动是否会造成撞墙(KillByWall)或者撞上自己蛇身(KillBySelf),从而影响游戏的状态。
//蛇移动
void SnakeMove(pSnake ps)
{
	//创建一个结点,表示蛇即将到的下一个节点
	pSnakeNode pNextNode = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (pNextNode == NULL)
	{
		perror("SnakeMove()::malloc()");
		return;
	}
	switch (ps->_dir)  //方向情况
	{
	case UP:
		pNextNode->x = ps->_pSnake->x;
		pNextNode->y = ps->_pSnake->y - 1;
		break;
	case DOWN:
		pNextNode->x = ps->_pSnake->x;
		pNextNode->y = ps->_pSnake->y + 1;
		break;
	case LEFT:
		pNextNode->x = ps->_pSnake->x - 2;
		pNextNode->y = ps->_pSnake->y;
		break;
	case RIGHT:
		pNextNode->x = ps->_pSnake->x + 2;
		pNextNode->y = ps->_pSnake->y;
		break;
	}
	//检测下一个坐标处是否是食物
	if (NextIsFood(pNextNode, ps))
	{
		EatFood(pNextNode, ps);
	}
	else
	{
		NoFood(pNextNode, ps);
	}
	//检测蛇是否撞墙
	KillByWall(ps);
	//检测蛇是否撞到自己
	KillBySelf(ps);
}

注意:x+2是要确保x坐标为2的倍数。

2.下一个位置是否是食物
无食物-NoFood

将下一个节点头插入蛇的身体,并将之前的蛇身最后一个节点打印为空格,释放掉蛇身最后一个节点。

注意:释放最后一个节点后,还要把指向最后一个节点的指针改为NULL,保证蛇尾打印可以正常结束,不会越界访问。

// 下一个位置不是食物
//pSnakeNode pn 是下一个节点的地址
//pSnake ps 维护蛇的指针
void NoFood(pSnakeNode pn, pSnake ps)
{
	//头插法
	pn->next = ps->_pSnake;
	ps->_pSnake = pn;
	//打印新的蛇的身体
	pSnakeNode cur = ps->_pSnake;
	while (cur->next->next != NULL)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	//把最后一个结点打印成空格
	SetPos(cur->next->x, cur->next->y);
	printf("  ");  //注意要两个空格,否则会出bug
	//释放最后一个结点
	free(cur->next);
	//把倒数第二个节点的地址置为NULL
	cur->next = NULL;
}
判断是否是食物-NextIsFood

只需判断下一个节点的坐标是否与所存储的食物的坐标一致就可,若是就返回真

//判断下一个坐标是否是食物
//pSnakeNode pn 是下一个节点的地址
//pSnake ps 维护蛇的指针
int NextIsFood(pSnakeNode pn, pSnake ps)
{
	return (pn->x == ps->_pFood->x) && (pn->y == ps->_pFood->y);
}
3.吃掉食物——EatFood

只需将这个节点头插到蛇身,释放节点,再把蛇打印,无需打印空格,并且总分增加,最后重新创建食物。

//吃掉食物
//pSnakeNode pn 是下一个节点的地址
//pSnake ps 维护蛇的指针
void EatFood(pSnakeNode pn, pSnake ps)
{
	//头插法
	ps->_pFood->next = ps->_pSnake;
	ps->_pSnake = ps->_pFood;
	//释放下一个位置的节点
	free(pn);
	pn = NULL;
	pSnakeNode cur = ps->_pSnake;
	//打印蛇
	while (cur)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	ps->_score += ps->_food_weight;
	//重新创建食物
	CreateFood(ps);
}
4.撞到墙——KillByWall

判断蛇头坐标是否和墙的坐标重复

//撞墙
void KillByWall(pSnake ps)
{
	if (ps->_pSnake->x == 0 || ps->_pSnake->x == 56 || ps->_pSnake->y == 0 || ps->_pSnake->y == 26)
	{
		ps->_status = KILL_BY_WALL;
	}
}
5.撞到自己——KillBySelf

判断蛇头坐标是否和蛇身体坐标重复

//撞到自己
void KillBySelf(pSnake ps)
{
	pSnakeNode cur = ps->_pSnake->next;
	while (cur)
	{
		if (cur->x == ps->_pSnake->x && cur->y == ps->_pSnake->y)
		{
			ps->_status = KILL_BY_SELF;
			break;
		}
		cur = cur->next;
	}
}

3. 游戏结束——GameEnd

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

//结束
void GameEnd(pSnake ps)
{
	SetPos(24, 12);
	switch (ps->_status)
	{
	case END_NORMAL:
		wprintf(L"您主动结束游戏\n");
		break;
	case KILL_BY_WALL:
		wprintf(L"您撞到墙上,游戏结束\n");
		break;
	case KILL_BY_SELF:
		wprintf(L"您撞到了自己,游戏结束\n");
		break;
	}
	//释放蛇身的链表
	pSnakeNode cur = ps->_pSnake;
	while (cur)
	{
		pSnakeNode del = cur;
		cur = cur->next;
		free(del);
	}
}

四. 展示所有全部代码

//test.c
#define  _CRT_SECURE_NO_WARNINGS 1

#include <locale.h>
#include "snake.h"

//完成的是游戏的测试逻辑
void test()
{
	int ch = 0;
	do
	{
		system("cls");
		//创建贪吃蛇
		Snake snake = { 0 };
		//初始化游戏
		//1. 打印环境界面
		//2. 功能介绍
		//3. 绘制地图
		//4. 创建蛇
		//5. 创建食物
		//6. 设置游戏的相关信息
		GameStart(&snake);
		//运行游戏
		GameRun(&snake);
		//结束游戏 - 善后工作
		GameEnd(&snake);
		SetPos(20, 15);
		printf("再来一局吗?(Y/N):");
		ch = getchar();
		while (getchar() != '\n');
		//system("cls");
	} while (ch == 'Y' || ch == 'y');
	SetPos(0, 27);

}

int main()
{
	//设置适配本地环境
	setlocale(LC_ALL, "");
	srand((unsigned int)time(NULL));
	test();
	return 0;
}
//snake.h
#pragma once

#include<windows.h>
#include<stdio.h>
#include<stdbool.h>
#include<stdlib.h>
#include <time.h>

#define POS_X 24
#define POS_Y 5

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

//宏定义判断是否按过键
#define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )

//类型的声明

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

//蛇的状态
//正常、撞墙、撞到自己、正常退出
enum GAME_STATUS
{
	OK,				//正常运行
	KILL_BY_WALL,   //撞墙
	KILL_BY_SELF,   //撞自己
	END_NORMAL       //正常退出
};

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

//typedef struct SnakeNode* pSnakeNode;

typedef struct Snake
{
	pSnakeNode _pSnake;          //指向蛇头的指针  维护整条蛇的指针
	pSnakeNode _pFood;          //指向食物节点的指针  维护食物的指针
	enum DIRECTION _dir;       //蛇的方向
	enum GAME_STATUS _status; //游戏的状态
	int _food_weight;		 //一个食物的分数
	int _score;			    //总分数
	int _sleep_time;       //休息时间,时间越短,速度越快,时间越长,速度越慢
}Snake, * pSnake;

//函数的声明
//定位
void SetPos(short x, short y);
//游戏的初始化
void GameStart(pSnake ps);
//欢迎界面和功能介绍
void WelcomeToGame();
//地图创建
void CreateMap();
//初始化蛇身
void InitSnake(pSnake ps);
//创建食物
void CreateFood(pSnake ps);
//游戏运行
void GameRun(pSnake ps);
//蛇移动-走一步
void SnakeMove(pSnake ps);
//判断下一个坐标是否是食物
int NextIsFood(pSnakeNode pn, pSnake ps);
//吃掉食物
void EatFood(pSnakeNode pn, pSnake ps);
//下一个位置不是食物
void NoFood(pSnakeNode pn, pSnake ps);
//结束
void GameEnd(pSnake ps);
//撞墙
void KillByWall(pSnake ps);
//撞自己
void KillBySelf(pSnake ps);
#define  _CRT_SECURE_NO_WARNINGS 1
#include "snake.h"

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

//欢迎界面和功能介绍
void WelcomeToGame()
{
	SetPos(40, 14);
	wprintf(L"欢迎来到贪吃蛇小游戏\n");
	SetPos(42, 20);
	system("pause");
	system("cls");
	SetPos(25, 14);
	wprintf(L"用↑.↓.←.→ 来控制蛇的移动,按F3加速,F4减速\n");
	SetPos(25, 15);
	wprintf(L"加速能够得到更高的分数\n");
	SetPos(42, 20);
	system("pause");
	system("cls");
}

//创建地图
void CreateMap()
{
	//上
	int i = 0;
	for (i = 0; i < 29; i++)
	{
		wprintf(L"%lc", WALL);
	}
	//下
	SetPos(0, 26);
	for (i = 0; i < 29; i++)
	{
		wprintf(L"%lc", WALL);
	}
	//左
	for (i = 1; i <= 25; i++)
	{
		SetPos(0, i);
		wprintf(L"%lc", WALL);
	}
	//右
	for (i = 1; i <= 25; i++)
	{
		SetPos(56, i);
		wprintf(L"%lc", WALL);
	}
}

//初始化蛇身
void InitSnake(pSnake ps)
{
	int i = 0;
	pSnakeNode cur = NULL;                             //创建一个struct SnakeNode*类型(pSnakeNode)的指针变量cur,用于存储蛇的节点
	for (i = 0; i < 5; i++)
	{
		cur = (pSnakeNode)malloc(sizeof(SnakeNode));  //申请动态空间用于存储蛇的节点
		if (cur == NULL)                              //如申请失败,结束程序
		{
			perror("InitSnake()::malloc()");
			return;
		}
		cur->next = NULL;
		cur->x = POS_X + 2 * i;  //初始化x坐标
		cur->y = POS_Y;          //初始化y坐标
		//头插法插入链表
		if (ps->_pSnake == NULL)  //空链表的时候
		{
			ps->_pSnake = cur;
		}
		else//非空
		{
			cur->next = ps->_pSnake;
			ps->_pSnake = cur;
		}
	}
	//打印蛇
	cur = ps->_pSnake;
	while (cur)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	//设置贪吃蛇的属性
	ps->_dir = RIGHT;//默认向右
	ps->_score = 0;
	ps->_food_weight = 10;
	ps->_sleep_time = 200; //单位毫秒
	ps->_status = OK;
}

//创建食物
void CreateFood(pSnake ps)
{
	int x = 0;
	int y = 0;
	//生成x是2的倍数
	//x:2---52
	//y:1---25
again:
	//要使x坐标为2的倍数,否则对不齐蛇头坐标
	do
	{
		x = rand() % 53 + 2;
		y = rand() % 25 + 1;
	} while (x % 2 != 0);
	//x和y的坐标不能和蛇的身体坐标冲突
	pSnakeNode cur = ps->_pSnake;
	while (cur)   //遍历蛇的节点
	{
		if (x == cur->x && y == cur->y)  //判断是否与蛇的坐标重复
		{
			goto again;
		}
		cur = cur->next;
	}
	//创建食物的节点
	pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (pFood == NULL)
	{
		perror("CreateFood()::malloc()");
		return;
	}
	else
	{
		pFood->x = x;
		pFood->y = y;
		pFood->next = NULL;
		SetPos(x, y);  //定位位置
		wprintf(L"%lc", FOOD);
		ps->_pFood = pFood;  //把随机的食物节点赋值到用于维护贪吃蛇的结构体成员中用于维护食物
	}
}

//开始游戏
void GameStart(pSnake ps)
{
	//初始化游戏
	//0.光标隐藏,先设置窗口大小,再光标隐藏
	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); //设置控制台光标状态
	//1.打印环境界面
	//2.功能介绍
	WelcomeToGame();
	//3.绘制地图
	CreateMap();
	//4.创建蛇
	InitSnake(ps);
	//5.创建食物
	CreateFood(ps);
}

//打印帮助信息
void PrintHelp()
{
	SetPos(64, 14);
	wprintf(L"%ls", L"不能穿墙,不能咬到自己");
	SetPos(64, 15);
	wprintf(L"%ls", L"用↑.↓.←.→ 来控制蛇的移动");
	SetPos(64, 16);
	wprintf(L"%ls", L"按F3加速,F4减速");
	SetPos(64, 17);
	wprintf(L"%ls", L"按ESC退出游戏,按空格暂停游戏");
	SetPos(64, 25);
	wprintf(L"%ls", L"DUST制作");
}

// 下一个位置不是食物
//pSnakeNode pn 是下一个节点的地址
//pSnake ps 维护蛇的指针
void NoFood(pSnakeNode pn, pSnake ps)
{
	//头插法
	pn->next = ps->_pSnake;
	ps->_pSnake = pn;
	//打印新的蛇的身体
	pSnakeNode cur = ps->_pSnake;
	while (cur->next->next != NULL)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	//把最后一个结点打印成空格
	SetPos(cur->next->x, cur->next->y);
	printf("  ");  //注意要两个空格,否则会出bug
	//释放最后一个结点
	free(cur->next);
	//把倒数第二个节点的地址置为NULL
	cur->next = NULL;
}

//判断下一个坐标是否是食物
//pSnakeNode pn 是下一个节点的地址
//pSnake ps 维护蛇的指针
int NextIsFood(pSnakeNode pn, pSnake ps)
{
	return (pn->x == ps->_pFood->x) && (pn->y == ps->_pFood->y);
}

//吃掉食物
//pSnakeNode pn 是下一个节点的地址
//pSnake ps 维护蛇的指针
void EatFood(pSnakeNode pn, pSnake ps)
{
	//头插法
	ps->_pFood->next = ps->_pSnake;
	ps->_pSnake = ps->_pFood;
	//释放下一个位置的节点
	free(pn);
	pn = NULL;
	pSnakeNode cur = ps->_pSnake;
	//打印蛇
	while (cur)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	ps->_score += ps->_food_weight;
	//重新创建食物
	CreateFood(ps);
}

//撞墙
void KillByWall(pSnake ps)
{
	if (ps->_pSnake->x == 0 || ps->_pSnake->x == 56 || ps->_pSnake->y == 0 || ps->_pSnake->y == 26)
	{
		ps->_status = KILL_BY_WALL;
	}
}

//撞到自己
void KillBySelf(pSnake ps)
{
	pSnakeNode cur = ps->_pSnake->next;
	while (cur)
	{
		if (cur->x == ps->_pSnake->x && cur->y == ps->_pSnake->y)
		{
			ps->_status = KILL_BY_SELF;
			break;
		}
		cur = cur->next;
	}
}

//蛇移动
void SnakeMove(pSnake ps)
{
	//创建一个结点,表示蛇即将到的下一个节点
	pSnakeNode pNextNode = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (pNextNode == NULL)
	{
		perror("SnakeMove()::malloc()");
		return;
	}
	switch (ps->_dir)  //方向情况
	{
	case UP:
		pNextNode->x = ps->_pSnake->x;
		pNextNode->y = ps->_pSnake->y - 1;
		break;
	case DOWN:
		pNextNode->x = ps->_pSnake->x;
		pNextNode->y = ps->_pSnake->y + 1;
		break;
	case LEFT:
		pNextNode->x = ps->_pSnake->x - 2;
		pNextNode->y = ps->_pSnake->y;
		break;
	case RIGHT:
		pNextNode->x = ps->_pSnake->x + 2;
		pNextNode->y = ps->_pSnake->y;
		break;
	}
	//检测下一个坐标处是否是食物
	if (NextIsFood(pNextNode, ps))
	{
		EatFood(pNextNode, ps);
	}
	else
	{
		NoFood(pNextNode, ps);
	}
	//检测蛇是否撞墙
	KillByWall(ps);
	//检测蛇是否撞到自己
	KillBySelf(ps);
}

//暂停
void Pause()
{
	while (1)
	{
		Sleep(200);
		if (KEY_PRESS(VK_SPACE))
		{
			break;
		}
	}
}

//游戏运行
void GameRun(pSnake ps)
{
	//打印帮助信息
	PrintHelp();
	do
	{
		//打印总分数和食物的分值
		SetPos(64, 10);
		printf("总分数:%d\n", ps->_score);
		SetPos(64, 11);
		printf("当前食物的分数:%2d\n", ps->_food_weight);  //%2d防止打印一位数时出bug
		//判断按键情况,并做出相应操作
		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->_status = END_NORMAL;
		}
		else if (KEY_PRESS(VK_F3))
		{
			//加速
			if (ps->_sleep_time > 80)
			{
				ps->_sleep_time -= 30;
				ps->_food_weight += 2;
			}
		}
		else if (KEY_PRESS(VK_F4))
		{
			//减速
			if (ps->_food_weight > 2)
			{
				ps->_sleep_time += 30;
				ps->_food_weight -= 2;
			}
		}
		SnakeMove(ps);//贪吃蛇走一步的过程
		Sleep(ps->_sleep_time);  //蛇每次之间要休眠的时间,时间短,蛇移动的就快。
	} while (ps->_status == OK);
}

//结束
void GameEnd(pSnake ps)
{
	SetPos(24, 12);
	switch (ps->_status)
	{
	case END_NORMAL:
		wprintf(L"您主动结束游戏\n");
		break;
	case KILL_BY_WALL:
		wprintf(L"您撞到墙上,游戏结束\n");
		break;
	case KILL_BY_SELF:
		wprintf(L"您撞到了自己,游戏结束\n");
		break;
	}
	//释放蛇身的链表
	pSnakeNode cur = ps->_pSnake;
	while (cur)
	{
		pSnakeNode del = cur;
		cur = cur->next;
		free(del);
	}
}

运行结果:

制作不易,求各位大佬三连qwq,若有问题的地方,请大佬们多多指教。

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

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

相关文章

Jackson 2.x 系列【31】Spring Boot 集成之字典回写

有道无术&#xff0c;术尚可求&#xff0c;有术无道&#xff0c;止于术。 本系列Jackson 版本 2.17.0 本系列Spring Boot 版本 3.2.4 源码地址&#xff1a;https://gitee.com/pearl-organization/study-jaskson-demo 文章目录 1. 场景描述2. 案例演示2.1 修改枚举2.2 定义注解…

【Go语言】接口类型(一)接口类型与接口的值

本文是介绍golang接口类型的第一篇&#xff0c;主要介绍接口类型与接口类型的值的相关概念。 1. 静态类型、动态类型、动态值 所谓的静态类型&#xff08;即 static type&#xff09;&#xff0c;就是变量声明的时候的类型。 var age int // int 是静态类型 var name strin…

MQ面试题

为什么要使用消息队列&#xff1f; 优点&#xff1a;解耦、异步、流量削峰 缺点&#xff1a;可用性降低、复杂性提高、一致性问题 为什么选择了RabbitMQ而不是其它的MQ&#xff1f; kafka是以吞吐量高而闻名&#xff0c;不过其数据稳定性一般&#xff0c;而且无法保证消息有…

关于Android中的限定符

很多对于Android不了解或是刚接触Android的初学者来说&#xff0c;对于Android开发中出现的例如layout-large或者drawable-xxhdpi这样的文件夹赶到困惑&#xff0c;这这文件夹到底有什么用&#xff1f;什么时候用&#xff1f;这里简单的说一下。 其实&#xff0c;在上面例子中&…

day05 51单片机-外部中断、定时器

1 外部中断——按键控制LED亮灭 1.1 需求描述 本案例通过检测SW3触发的外部中断实现P00对应LED的亮灭。 1.2 硬件设计 1.2.1 中断简介 单片机中断是一种重要的计算机编程概念,用于处理在程序执行过程中突然发生的事件或条件。这些事件可以是外部硬件触发的,如按下按钮、…

SpringBoot+vue开发记录(二)

说明&#xff1a;本篇文章的主要内容为SpringBoot开发中后端的创建 项目创建: 1. 新建项目&#xff1a; 如下&#xff0c;这样简单创建就行了&#xff0c;JDK什么的就先17&#xff0c;当然1.8也是可以的&#xff0c;后面可以改。 这样就创建好了&#xff1a; 2. pom.xml…

【面试经典 150 | 回溯】电话号码的字母组合

文章目录 写在前面Tag题目来源解题思路方法一&#xff1a;回溯 写在最后 写在前面 本专栏专注于分析与讲解【面试经典150】算法&#xff0c;两到三天更新一篇文章&#xff0c;欢迎催更…… 专栏内容以分析题目为主&#xff0c;并附带一些对于本题涉及到的数据结构等内容进行回顾…

IOTE2024第二十一届(上海)国际物联网展览会4月24日-26日开幕

交流产业信息&#xff0c;把脉发展方向&#xff0c;IOTE 国际物联网展是每年物联网行业、企业、用户交流合作的大型平台。2024年4月24-26日IOTE2024第二十一届国际物联网展•上海站&#xff0c;在上海世博展览馆开展。 本次物联网展汇聚全球超300家参展企业、3万来自工业、物流…

区块链技术与应用学习笔记(1-4节)——北大肖臻课程

目录 1. 区块链初识(课程简介&#xff09; 被过度炒作&#xff0c;落地应用有限&#xff1f; 下一代的价值互联网&#xff1f;世界上最慢的数据库&#xff1f; 2. BTC-密码学原理&#xff08;比特币&#xff09; 1)哈希 哈希函数特点 个人学习所得 2)签名 个人对于…

工业测径仪的应用场景和可靠性判断

关键字:线缆测径仪,圆棒测径仪,圆管测径仪,金属棒管测径仪,工业测径仪,智能测径仪 智能测径仪主要应用于以下领域&#xff1a; 金属加工&#xff1a;测量金属线材、棒材、管材等的直径。线缆制造&#xff1a;检测电线、电缆的直径。塑料管材生产&#xff1a;监控塑料管材的外…

Python 数组控件的使用

当一个UI窗口界面内有多个相同类型的控件&#xff0c;且这多个控件的功能都类似时&#xff0c;使用数组控件是一个非常不错的选择&#xff0c;可以大大减少代码的编写 且 代码易读性强&#xff0c;可惜的是Python好象是没有数组控件这个东东。 我们来看看以下一个界面&#xff…

前端CSS基础11(相对定位,绝对定位,固定定位,粘性定位)

前端CSS基础11&#xff08;相对定位&#xff0c;绝对定位&#xff0c;固定定位&#xff0c;粘性定位&#xff09; CSS相对定位&#xff08;position: relative;&#xff09;相对定位的参考点在哪&#xff1f; CSS绝对定位&#xff08;position: absolute&#xff09;如何设置绝…

微信小程序:6.事件

什么事事件 事件就是渲染层到逻辑层的通讯方式&#xff0c;比如提交表单&#xff0c;按钮点击都可以看作一个事件。 小程序中常用的事件 事件对象属性列表 当事件回调时&#xff0c;会收到一个事件对象event&#xff0c;他详细属性如夏表所示&#xff1a; target和curren…

yudao-cloud微服务系统系统模块+后台管理系统成功运行

&#x1f339;作者主页&#xff1a;青花锁 &#x1f339;简介&#xff1a;Java领域优质创作者&#x1f3c6;、Java微服务架构公号作者&#x1f604; &#x1f339;简历模板、学习资料、面试题库、技术互助 &#x1f339;文末获取联系方式 &#x1f4dd; 系列文章目录 第一章 芋…

【软件测试】终于有人讲明白:bug的分类和定级了!

01、bug的定义 一般是指不满足用户需求的则可以认为是bug&#xff0c;狭义指软件程序的漏洞或缺陷&#xff0c;广义指测试工程师或用户提出的软件可改进的细节、或与需求文档存在差异的功能实现等 对应三个测试目的&#xff1a; 为了发现程序的代码或业务逻辑错误 为了检查产…

《第二行代码》第二版学习笔记(6)——内容提供器

文章目录 一 运行时权限2.权限分类3 运行时申请权限 二、内容提供器1、 ContentResolver的基本用法2、现有的内容提供器3、创建自己的内容提供器2.1 创建内容提供器的步骤2.2 跨程序数据共享 内容提供器&#xff08;Content Provider&#xff09;主要用于在不同的应用程序之间实…

2024年大数据应用、智能控制与软件工程国际会议(BDAICSE2024)

2024年大数据应用、智能控制与软件工程国际会议(BDAICSE2024) 会议简介 我们诚挚邀请您参加2024年大数据应用、智能控制和软件工程国际会议&#xff08;BDAICSE2024&#xff09;。这次会议将在美丽的长沙市举行。 本次大会旨在汇聚全球大数据应用、智能控制、软件工程等领…

WebGIS

文章目录 GIS的全名是Geographic Information System&#xff0c;中文全名是地理信息系统。 它是在计算机硬、软件系统支持下&#xff0c;对整个或部分地球表层&#xff08;包括大气层&#xff09;空间中的有关地理分布数据进行采集、储存、管理、运算、分析、显示和描述的技术…

Windows搭建php文件管理服务Tiny File Manager并发布至公网可访问

文章目录 1. 前言2.Tiny File Manager网站搭建2.1.Tiny file manager下载和安装2.2 Tiny file manager网页测试2.3 内网穿透工具下载安装 3. 本地网页发布3.1 Cpolar云端设置3.2 Cpolar本地设置 4. 公网访问测试总结 1. 前言 今天&#xff0c;笔者就为大家介绍一款只有两个文件…

大数据第七天

文章目录 吐槽一下这个是怎么需要真的这么大吗? 内核错误内核软死锁&#xff08;soft lockup&#xff09;我这个cpu很高吗?大模型都说了不超过80就行了 FinBi安装FinBI下载链接安装时间比较长 吐槽一下 dbeaver 查询hive 数据信息是真的慢&#xff0c;没有一点快的方式&…