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

 一. 贪吃蛇的介绍

                                 

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

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

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

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

这里设计的知识要点有: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 *HANDLEhoutput = 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;  //默认总分0ps->_food_weight = 10; //食物分数10ps->_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);//把倒数第二个节点的地址置为NULLcur->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);//把倒数第二个节点的地址置为NULLcur->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,若有问题的地方,请大佬们多多指教。

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

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

相关文章

Ubuntu系统安装Anaconda

1. 下载Anconda安装包 1.1 wget命令下载 当然还可以去清华大学开源软件镜像站&#xff1a;Index of /anaconda/archive/ | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror&#xff0c;下载各种版本的Anaconda。 wget下载命令如下&#xff1a; 我这里下载的是2024.02…

二百三十三、Flume——Flume采集JSON文件到Kafka,再用Flume采集Kafka数据到HDFS中

一、目的 由于使用了新的Kafka协议&#xff0c;因为根据新的协议推送模拟数据到Kafka中&#xff0c;再Flume采集Kafka数据到HDFS中 二、技术选型 &#xff08;一&#xff09;Kettle工具 准备使用Kettle的JSON input控件和Kafka producer控件&#xff0c;但是搞了1天没搞定&…

OSPF的LSA详解

一、什么是LSA&#xff1f;LSA作用&#xff1f; 在OSPF协议中&#xff0c;LSA全称链路状态通告&#xff0c;主要由LSA头部信息&#xff08;LSA摘要&#xff09;和链路状态组成。部分LSA只有LSA头部信息&#xff0c;无链路状态信息。使用LSA来传递路由信息和拓扑信息&#xff0c…

【STM32F4】STM32CUMX相关环境配置

一、环境配置 我们需要以下两个软件 &#xff08;一&#xff09;keil5 最正统&#xff0c;最经典的嵌入式MCU开发环境。 该环境的配置可以看看之前的文章 所需文件如下&#xff1a; 当时配置的是STC8H的环境&#xff0c;现在基于此&#xff0c;重新给STM32配置环境。能让STC…

运营商三要素核验接口-手机实名验证API

运营商三要素核验接口是一种API&#xff08;Application Programming Interface&#xff0c;应用程序编程接口&#xff09;&#xff0c;主要用于通过互联网技术对接通信运营商的实名制数据库&#xff0c;以验证用户提供的手机号码、身份证号码、姓名这三项关键信息&#xff08;…

Python | Leetcode Python题解之第37题解数独

题目&#xff1a; 题解&#xff1a; class Solution:def solveSudoku(self, board: List[List[str]]) -> None:def dfs(pos: int):nonlocal validif pos len(spaces):valid Truereturni, j spaces[pos]for digit in range(9):if line[i][digit] column[j][digit] bloc…

jmeter 指定QPS压测接口

文章目录 jmeter 指定QPS压测接口更换语言为中文创建测试任务新建线程组右键线程组&#xff0c;新建http request&#xff0c;填写要你要压测的接口地址、参数如果需要自定义请求头&#xff0c;添加一个Http头信息管理器要查看结果和QPS统计数据&#xff0c;给上门的http请求添…

算法库应用-有序单链表插入节点

学习源头: 模仿贺利坚老师单链表排序文章浏览阅读5.9k次。  本文针对数据结构基础系列网络课程(2)&#xff1a;线性表中第11课时单链表应用举例。例&#xff1a;拆分单链表 &#xff08;linklist.h是单链表“算法库”中的头文件&#xff0c;详情单击链接…&#xff09;//本程…

VForm3的文件上传方式

更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码&#xff1a; https://gitee.com/nbacheng/ruoyi-nbcio 演示地址&#xff1a;RuoYi-Nbcio后台管理系统 http://122.227.135.243:9666/ 更多nbcio-boot功能请看演示系统 gitee源代码地址 后端代码&#xff1a…

第三届 SWCTF-Web 部分 WP

写在前面 题目主要涉及的是前端 php 内容知识&#xff0c;仅以本篇博客记录自己 Web 出题的奇思妙想。 Copyright © [2024] [Myon⁶]. All rights reserved. 目录 1、HTTP 2、再见了晚星 3、myon123_easy_php 4、baby_P0P 5、LOGIN!!! 1、HTTP 首页文件默认就是 ind…

《大话西游2》本人收集的十二个单机版游戏,有详细的视频架设教程,云盘下载

《大话西游2》是一款经典的大型多人在线角色扮演游戏&#xff0c;也是一款国风经典的游戏。 有能力的可以架设个外网&#xff0c;让大家一起玩。 《大话西游2》本人收集的十二个单机版游戏&#xff0c;有详细的视频架设教程&#xff0c;值得收藏 下载地址&#xff1a; 链接&…

半导体制造工艺之分类浅述

半导体制造工艺分为逻辑制程(也叫逻辑工艺)和特殊制程(也叫特色工艺)。 1、逻辑工艺概述 随着集成电路行业沿着摩尔定律不断发展,晶体管数量增加的同时,工艺节点不断缩小。先进逻辑工艺是相对的概念,2005年全球先进逻辑工艺的工艺节点在65/55纳米,现在则变为3纳米。中…

人人可拥有刘强东同款数字人分身!

每个人都可以拥有东哥同款数字人分身直播间进行直播带货&#xff0c;怎样克隆自己的数字人形象&#xff1f; 青否数字人克隆源码的克隆效果媲美真人&#xff1a; 仅需将真人录制的2-6分钟视频上传至克隆端后台&#xff0c;系统便会自动启动自动克隆。3-5小时后&#xff0c;即可…

学习微服务nacos遇到的问题

在学习微服务注册到nacos的时候&#xff0c;所有过程都正确了&#xff0c;注册也成功了&#xff0c;但是访问不了调用的地址报错出现问题。 一、引入依赖 在cloud-demo父工程的pom文件中的<dependencyManagement>中引入SpringCloudAlibaba的依赖 1、springboot <pa…

森林消防装备:高压消防接力水泵/恒峰智慧科技

在广袤无垠的森林中&#xff0c;每一份绿色都是大自然赋予我们的宝贵财富。然而&#xff0c;这些美丽的绿色也可能因为一场突如其来的火灾而被瞬间吞噬。为了保护这片生命的绿洲&#xff0c;我们需要一种高效、可靠的消防装备——高压消防接力水泵。 这款森林消防装备采用本田汽…

常规文件怎么做成二维码?扫描二维码就能在线看文件

现在可以将文件做成活码二维码之后&#xff0c;通过扫描二维码的方式来查看文件内容&#xff0c;部分二维码也可以扫码下载文件&#xff0c;从而实现文件在其他人之间的快速传播。 文件二维码的制作原理是将文件上传到云端之后&#xff0c;生成单独的链接转换成二维码&#xf…

皮带跑偏AI巡检系统 砂石、煤矿、物流场景下的皮带跑偏自动检测

在工业生产中&#xff0c;皮带传动系统被广泛应用于输送、运输和生产线等领域。然而&#xff0c;皮带跑偏是一个普遍存在且隐患严重的问题。一旦皮带跑偏&#xff0c;不仅会造成设备损坏、生产中断&#xff0c;还可能引发严重的安全事故&#xff0c;甚至导致人员伤亡。目前常见…

vue3项目使用<img :src=““ />动态加载图片

分享一下使用<img :src"" />动态加载图片时遇到的问题以及解决方法。 下面是部分页面代码&#xff0c;这里我使用了<img :src"itemc.headUrl" />来动态加载图片 这时遇到了问题&#xff0c;因为这里的itemc.headUrl是图片的相对路径&#xff…

P450焕新而来,室内外两用+路径规划+YOLO点击跟踪,算力高达100TOPS

Prometheus 450&#xff08;简称P450&#xff09;是一款室内室外两用的中型轴距&#xff08;410mm&#xff09;无人机&#xff0c;基于F450基础飞行平台&#xff0c;搭载感知传感器二维平面激光雷达&#xff0c;双目深度相机等&#xff0c;配合软件Prometheus自主无人机系统和S…

Java学习笔记29(泛型)

1.泛型 ArrayList<Dog> arrayList new ArrayList<Dog>(); //1.当我们ArrayList<Dog>表示存放到ArrayList集合中的元素是Dog类 //2.如果编译器发现添加的类型&#xff0c;不满足要求&#xff0c;就会报错 //3.在便利的时候&#xff0c;可以直接取出Dog类型而…