贪吃蛇(上)Win32API

发布于:2024-05-05 ⋅ 阅读:(36) ⋅ 点赞:(0)

感谢大佬的光临各位,希望和大家一起进步,望得到你的三连,互三支持,一起进步

个人主页LaNzikinh-CSDN博客

文章目录


前言

贪吃蛇(也叫做贪食蛇)游戏是一款休闲益智类游戏,有PC和手机等多平台版本。既简单又耐玩。该游戏通过控制蛇头方向吃蛋,从而使得蛇变得越来越长,使用C语言在Windows环境的控制台中模拟实现经典小游戏贪吃蛇。


在实现这次贪吃蛇小游戏的时候,我们需要应用到一些关于Win32 API的一些知识,所以我们把贪吃蛇游戏的实现分为上下两篇来讲,商品,我们主要就是讲解Win32 API的知识

一.Win32 API

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

Programming Interface,简称 API 函数。WIN32 API也就是Microsoft Windows 32位平台的应用程序编程接口。

控制台程序

我们平常运行起来的黑框,他就是控制台程序,但是其实很多人都不是用的控制台程序,用的是终端,这两个是有区别的,控制台程序,它可以利用一些命令来操控终端就不可以啊,这次的贪吃蛇游戏就必须要应用控制台程序,如果还是终端操控的话,可以在设置里调回控制台程序

然后我们接下来讲一些控制指令

1.1窗口与命名

在说之前我们还要引入一个函数system 函数可以用来执行系统的命令,我们之前写的关机指令就是用的这个,这个在一些游戏项目的实现上,非常有用。

设置窗口mode con cols=100 lines=30,title 贪吃蛇重命名,这两个就是很简单的命令,利用system,可以用来改变控制台窗口的大小和控制台的名字

//system 函数可以用来执行系统的命令
int main()
{
  //设置控制台的相关属性
	system("mode con cols=100 lines=30");
	system("title 贪吃蛇");
	getchar();
	//system("pause");

	return 0;
}

用之前记得停一下,不然看不到效果,可以用getchar获取字符来停,也可以直接用命令去暂停

1.2控制台屏幕上的坐标COORD

其实,控制台可以看成一个数轴,居然是一个数轴的话,那么数轴里面每个点就是坐标,控制台有一个特性就是竖着的坐标是横着坐标的两倍,所以说我们要完成这个游戏的话,我们肯定要想办法得知每个点的坐标,计算机中专门定义了一个关于这个坐标的结构体,我们直接用就可以了。

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

int main()
{
	COORD pos1 = { 0,0 };
	COORD pos2 = { 20,30 };

	return 0;
}

1.3 GetStdHandleGetStdHandle 函数 - Windows Console | Microsoft Learn

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

你可以把它抽象的理解成,如果我要炒饭,我炒饭的锅子前我先要获得一个句柄,这样我才能进行炒,我干什么事情之前都需要先引用下这个函数

int main()
{
	HANDLE hOutput = NULL;
	//获取标准输出的句柄
	hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
return 0;
}

这个主要是和别的函数绑定一起用,我们来看下一个

1.4GetConsoleCursorInfoGetConsoleCursorInfo 函数 - Windows Console | Microsoft Learn

这个他主要是可以获取句柄相关的控制台上的光标信息,就是我通过这个函数,我可以获取到一个光标的信息,那我要怎么拉到这个光标的信息,这里这个系统已经提前给我们定义了一个关于光标的结构体CONSOLE_CURSOR_INFO

typedef struct _CONSOLE_CURSOR_INFO {
 DWORD dwSize;
 BOOL bVisible;
} CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO;
int main()
{
	//获取标准输出的句柄
	HANDLE hOutput = NULL;
	hOutput = GetStdHandle(STD_OUTPUT_HANDLE);

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

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

	printf("%d", CursorInfo.dwSize);
	//不可以直接改,因为只看到了台屏幕缓冲区的光标⼤⼩和可⻅性的信息
	return 0;
}

这时候我们会打印出一个结果25是什么意思呢?

dwSize:由光标填充的字符单元格的百分比。 此值介于1到100之间。 光标外观会变化,范围从完全填充单元格到单元底部的水平线条。 bVisible:游标的可见性。 如果光标可见,则此成员为TRUE。

因为贪吃蛇这个游戏总体下来肯定是不需要看见光标的,所以为了游戏的美观,我们要将光标进行隐藏,但是不可以直接改,因为只看到了台屏幕缓冲区的光标大小和可见性的信息所以。

1.5SetConsoleCursorInfoSetConsoleCursorInfo 函数 - Windows Console | Microsoft Learn

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

int main()
{
	//获取标准输出的句柄
	HANDLE hOutput = NULL;
	hOutput = GetStdHandle(STD_OUTPUT_HANDLE);

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

	//获取和hOutput句柄相关的控制台上的光标信息,存放在CursorInfo去
	GetConsoleCursorInfo(hOutput, &CursorInfo);
	
	//修改光标信息
	CursorInfo.bVisible = false;

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

	return 0;
}

这样就完美的将光标隐藏了

1.6SetConsoleCursorPosition SetConsoleCursorPosition 函数 - Windows Console | Microsoft Learn

光标除了被隐藏,那么我们可不可以把光标设置到指定位置去呢?比如说我的贪吃蛇游戏标题我要把它放到中间去,那么我该如何去做呢?

开始首先我们先必须获得标准输出的句柄,然后我就要定位光标的位置先输入,我需要光标的位置,再把光标位置和句柄传入到这个函数中就可以了

int main()
{
	//获取标准输出的句柄
	HANDLE houtput = NULL;
	houtput = GetStdHandle(STD_OUTPUT_HANDLE);
	//定位光标的位置
	COORD pos= { 10,30 };
	SetConsoleCursorPosition(houtput, pos);	
	return 0;
}

但是在后续我们写贪吃蛇的时候,我们需要大量定位,我们不可能每次都去获取句柄在定位光标,所以我们可以直接把它包装成一个函数,需要的时候就直接调用

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

int main()
{
	//获取标准输出的句柄
	HANDLE houtput = NULL;
	houtput = GetStdHandle(STD_OUTPUT_HANDLE);
	//定位光标的位置
	/*COORD pos= { 10,30 };
	SetConsoleCursorPosition(houtput, pos);*/
	SetPos(10, 30);
	
	return 0;
}

1.7GetAsyncKeyStategetAsyncKeyState 函数 (winuser.h) - Win32 apps | Microsoft Learn

我们在玩贪吃蛇小游戏的时候,键盘点击左贪吃蛇就会向左转,他是如何感应到的呢?就和这个函数有关,这个函数它可以判断一个键是否被摁过,我们键盘上的每个键其实都有一个虚拟键代码

虚拟键码 (Winuser.h) - Win32 apps | Microsoft Learn

我们可以通过这个函数和虚拟间代码来判断一个键是是否按过没有,将键盘上每个键的虚拟键值传递给函数,函数通过返回值来分辨按键的状态。

如果我们要判断一个键是否被按过,可以检测GetAsyncKeyState返回值的最低值是否为1.那这样子的话,我们就可以通过一个宏来直接定义

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

这也是后面会用到的

然后贪吃蛇关于Win32 API的内容就没有了

二.地图的绘制和初始化

我先看看我们这次完成什么

2.1宽字符的打印

在开始前,我们还需要讲一下宽字符的打印,如图三可得地图周围的墙食物还有蛇这些符号都是宽字符,那在四中我们该如何打印宽字符呢?宽字符之所以较宽字符,是因为它跟C语言最初假定的字符不一样C语言最初假定的字符都是单字节的宽字符,他是占两个字节的过去,C语言并不适合非英语地方国家使用,所以关于宽字符的打印和C语言的国际化特性是有关系的,这里我就不多讲了,我们这里要引入一个头文件的概念就可以了

<locale.h>本地化 提供的函数用于控制C标准库中对于不同的地区会产生不一样行为的部分。

然后用里面的setlocale 函数,setlocale 函数用于修改当前地区,可以针对一个类项修改,也可以针对所有类项。setlocale 的第一个参数可以是前⾯说明的类项中的一个,那么每次只会影响一个类项,如果第一个参 数是LC_ALL,就会影响所有的类项。 C标准给第⼆个参数仅定义了2种可能取值:"C"(正常模式)和" "(本地模式)。

宽字符的打印那如果想在屏幕上打印宽字符,怎么打印呢?

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

2.2初始化

我们开始先把游戏进行初始化,贪吃蛇的整个身体,我们肯定要用链表的形式来构建吃一个食物,就头插一下链表,所以我们先创造一个蛇的节点的结构体,肯定要有坐标,还有指向下一个节点的指针。

//蛇身的节点类型
typedef struct SnakeNode
{
	//坐标
	int x;
	int y;
	//指向下一个节点的指针
	struct SnakeNode* next;
}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;

蛇的方向和游戏状态,我们有两个枚举变量,方向很好理解就是上下左右

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

状态的划分好几种,比如说正常,那就是蛇没有事撞墙撞到墙撞到自己,还有正常退出游戏这几种

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

2.3地图的绘制

地图的绘制之前,我们首先要打印前面两页东西,欢迎来到贪吃蛇小游戏和游戏规则的讲解,我们直接用下标定位函数来定位注意,我现在已经切换模式了,所以我的打印要用宽字符去打印,欢迎来到游戏之后直接清屏幕,这样子就可以做到一页两页的效果了。欢迎来到游戏之后直接清屏幕

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

先用#define来定义一个墙的宽字符

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

}

总结

这是主要讲解了Win32 API的使用,后面在讲解贪吃蛇的实现


网站公告

今日签到

点亮在社区的每一天
去签到