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

news2024/12/23 17:42:14

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

个人主页:我要学编程(ಥ_ಥ)-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;
	}
}

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Ansible 指定受控端使用Python的版本

最近在装Ansible&#xff0c;有一台受控端Ubuntu16的服务器&#xff0c;安装了Python2.7.12和Pyhon3.5。当用Ansible连接它时&#xff0c;显示使用的是Python3.5。最后看文档&#xff0c;发现Ansible可以在hosts的文件中指定受控服上运行的Python。 现象 受控端 查看Python版…

这些小众工作软件让你事半功倍

大家好呀&#xff01;&#x1f44b; 今天我要来种草一些不那么主流&#xff0c;但却能大大提升你工作效率的神器软件&#xff01;&#x1f31f; 如果你也像我一样&#xff0c;对那些大众化的工作工具已经审美疲劳&#xff0c;那么快来一起探索这些隐藏的宝藏吧&#xff01;&…

OpenTiny 亮相 W3C 2024春季顾问委员会会议,共话行业新趋势。

近日&#xff0c;万维网联盟&#xff08;World Wide Web Consortium&#xff0c;简称 W3C&#xff09;于4月8日-4月9日在日本召开2024年顾问委员会会议&#xff08;Advisory Committee Meeting&#xff09;。华为产业发展专家丁蔚博士及华为云 OpenTiny 项目的 Web 前端框架技术…

国产台灯哪个品牌比较好?五款国产护眼台灯品牌推荐

随着学生们重返校园&#xff0c;家长和孩子们忙于新学期的准备工作&#xff0c;眼睛健康的考量自然也在其中。这也是为何近年来护眼台灯越来越受到欢迎的原因之一。国产台灯哪个品牌比较好&#xff1f;作为一个长期近视并且日常用眼时间较长的人&#xff0c;我本人对护眼台灯有…

Springboot+Vue项目-基于Java+MySQL的网上购物商城系统(附源码+演示视频+LW)

大家好&#xff01;我是程序猿老A&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;Java毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计 &…

VisualStudio2019和2022开发Winform项目用到Devexpress组件报错不能正确加载的解决办法

1.报错1 问题简单描述&#xff1a;DevExpress.Utils.ImageCollectionStreamer 无法强制转换为 DevExpress.Utils.ImageCollectionStreamer。 原因分析&#xff1a;原项目某个组件使用的是 DevExpresss.XtraBars.v15.1版本&#xff0c;直接引用扩展控件改成引用v20.2。 解决办法…

HTML 中创建 WebSocket服务与接收webSocket发送内容

效果图 服务端 html客户端接受的消息 接下来开始实现服务端 创建server.js const WebSocket require(ws);const wss new WebSocket.Server({ port: 8877 });wss.on(connection, function connection(ws) {console.log(WebSocket connection opened.);// 每隔 5 秒发送一次…

前端学习之DOM编程案例:点名案例和秒表案例

点名 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>点名案例</title><style>*{margin: 0;padding: 0;}</style> </head> <body><div id"container">…

C语言进阶课程学习记录-函数参数的秘密

C语言进阶课程学习记录-函数参数的秘密 实验实验小结调用约定实验-求平均数实验-可变参数的函数小结 本文学习自狄泰软件学院 唐佐林老师的 C语言进阶课程&#xff0c;图片全部来源于课程PPT&#xff0c;仅用于个人学习记录 实验 #include <stdio.h>int func(int i, int…

解线性方程组——追赶法解三对角方程组 | 北太天元

一、问题描述 对于线性方程组 A x b , A ( b 1 c 1 a 2 b 2 c 2 ⋱ ⋱ ⋱ ⋱ ⋱ ⋱ a n − 1 b n − 1 c n − 1 a n b n ) , b ( f 1 f 2 ⋮ f n ) Axb,\quad A\begin{pmatrix}b_1&c_1&&&&\\a_2&b_2&c_2&&&\\&\ddots&\d…

Unity导出package

C#代码导出后为一个dll&#xff0c;原有的不同平台的库不变。 以下操作均在build PC 平台下操作。 1.在要导出的文件夹下建assembly definition (Any platform) 2.将项目文件夹下的\Library\ScriptAssemblies中的相应assembly definition的dll复制到要导出的文件夹下 3.在uni…

Linux进阶篇:CentOS7搭建NFS文件共享服务

CentOS7搭建NFS文件共享服务 一、NFS介绍 NFS(Network File System)意为网络文件系统&#xff0c;它最大的功能就是可以通过网络&#xff0c;让不同的机器不同的操作系统可以共享彼此的文件。简单的讲就是可以挂载远程主机的共享目录到本地&#xff0c;就像操作本地磁盘一样&…

适用于摩托车仪表盘的液晶驱动IC:S1D15K01

S1D15K01j是EPSON的一款适用于适用摩托车混合数字仪表盘的车规混合液晶驱动IC。随着摩托车具备的特性和功能的逐渐增多&#xff0c;需要在仪表盘显示器上显示的信息量越来越大。另一方面&#xff0c;可用于显示的空间有限&#xff0c;需要有效地显示信息。在过去&#xff0c;摩…

《深入浅出多模态》: 多模态经典模型:BLIP

🎉AI学习星球推荐: GoAI的学习社区 知识星球是一个致力于提供《机器学习 | 深度学习 | CV | NLP | 大模型 | 多模态 | AIGC 》各个最新AI方向综述、论文等成体系的学习资料,配有全面而有深度的专栏内容,包括不限于 前沿论文解读、资料共享、行业最新动态以、实践教程、求职…

CANdb++数据库

1.理解主机厂定义的信号矩阵表 2.使用CANdb制作对应矩阵表中报文和信号dbc文件。 3.新建数据库 打开candb软件&#xff0c;点击文件&#xff0c;选择创建数据库&#xff0c;并选择数据库模版&#xff0c;本次测试选择CanTemplate.dbc模版&#xff0c;选择保存在本机中的位置&…

7.机器学习-十大算法之一拉索回归(Lasso)算法原理讲解

7.机器学习-十大算法之一拉索回归&#xff08;Lasso&#xff09;算法原理讲解 一摘要二个人简介三前言四原理讲解五算法流程六代码实现6.1 坐标下降法6.2 最小角回归法 七第三方库实现7.1 scikit-learn实现&#xff08;坐标下降法&#xff09;&#xff1a;7.2 scikit-learn 实现…

idea 中导入的项目maven不自动下载依赖包

导入之后不会自动引入依赖包&#xff0c;如下图&#xff0c;external libraries 下没有依赖 解决方案&#xff1a;重新更新下maven的Local repository 即可

STM32F030K6T6,基于值线ARM的32位MCU,具有16到64KB的闪存、定时器

STM32F030K6T6&#xff0c;ST/意法&#xff0c;基于值线ARM的32位MCU具有16到64KB的闪存、定时器 ADC&#xff0c;通信接口&#xff0c;2.4-3.6 V操作 STM32F030K6T6&#xff0c;ST/意法&#xff0c;基于值线ARM的32位MCU具有16到64KB的闪存、定时器 ADC&#xff0c;通信接口&…

一目了然:ipv4和ipv6的关键区别

ipv4和ipv6是互联网协议的两种版本&#xff0c;它们在多个方面存在显著差异。以下是这两种协议的具体差异&#xff1a; 1.地址长度和地址数量 IPv4使用32位地址&#xff0c;而IPv6使用128位地址。这意味着IPv6的地址空间比IPv4大约2^96倍&#xff0c;能够支持的IP地址数量远远…

深入理解VGG网络,清晰易懂

深入理解VGG网络 VGG网络是深度学习领域中一个非常经典的卷积神经网络&#xff08;CNN&#xff09;架构&#xff0c;由牛津大学的视觉几何组&#xff08;Visual Geometry Group&#xff09;提出。它在2014年的ImageNet挑战赛中取得了第二名的好成绩&#xff0c;并且在随后的许…