【C语言】贪吃蛇项目(1) - 部分Win32 API详解 及 贪吃蛇项目思路

文章目录


一、贪吃蛇项目需要实现的基本功能

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

二、Win32 API介绍

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

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

2.1 控制台

我们这次的贪吃蛇项目需要用到控制台来运行,所以在什么空项目调用时应该出现的时调试控制台,就像下图这样

如果出现的不是 调试控制台 ,就右键调试栏,选择 属性 后找到终端,将 默认终端应用程序 改成 让Windows决定 然后确定,重新调试就行了

2.2 部分控制台命令及调用函数

mode 和 title 命令

我们平常调试出来的黑框其实就是控制台,对于这个控制台我们可以改变长宽,也可以改变名字,这就要用到 modetitle 命令,但是在调试时我们需要借用 system 来调用它们

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

	//getchar(); 停止运行
	system("pause");//程序暂停
	return 0;
}

这里最后需要放上 getchar()system("pause") 来暂停程序,否则因为程序运行好就停止了,无法向控制台一样,直接更改控制台名字。调试结果如下

COORD 命令

COORD 是Windows API中定义的一个结构体,表示一个字符在控制台屏幕幕缓冲区上的坐标,坐标系(0,0)的原点位于缓冲区的顶部左侧单元格。关于控制台的坐标轴大家可以看成下图

这是 COORD 命令的声明,里面包含了坐标的x和y坐标,需要注意的是这里调用需要添加头文件 <Windows.h>

c 复制代码
typedef struct _COORD {
	SHORT X;
	SHORT Y;
} COORD, * PCOORD;

GetStdHandle(获取数据)

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

c 复制代码
//函数声明
HANDLE GetStdHandle(DWORD nStdHandle);

利用这个调用函数,我们可以设定某点的坐标,比如这样

c 复制代码
int main()
{
	//获得标准输出设备的句柄
	HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);
	return 0;
}

我们观察 HANDlE ,发现它是重命名 void * 的一个指针变量,所以当我们不打算给某点一个确定的坐标值时,可以将它设为 NULL

GetConsoleCursorInfo(获取光标数据)

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

c 复制代码
	//声明
	BOOL WINAPI GetConsoleCursorInfo(
		HANDLE hConsoleOutput,
		PCONSOLE_CURSOR_INFO lpConsoleCursorInfo
	);
	//PCONSOLE_CURSOR_INFO 是指向 CONSOLE_CURSOR_INFO 结构的指针,该结构接收有关主机游标

关于 CONSOLE_CURSOR_INFO ,这个结构体,包含有关控制台光标的信息,它里面的内容是这样

c 复制代码
typedef struct _CONSOLE_CURSOR_INFO {
	DWORD dwSize;
	BOOL bVisible;
} CONSOLE_CURSOR_INFO, * PCONSOLE_CURSOR_INFO;
  • dwSize,由光标填充的字符单元格的百分比。此值介于1到100之间。光标外观会变化,范围从完全填充单元格到单元底部的水平线条。
  • bVisible,游标的可见性。如果光标可见,则此成员为 TRUE。我们也可以设置为 FALSE 让它变得不可见

举个例子,这样我们就可以获取和houtput句柄相关的控制台上的光标信息,存放在 cursor_info 中了

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

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

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

	return 0;
}

SetConsoleCursorInfo (设置光标属性)

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

c 复制代码
BOOL WINAPI SetConsoleCursorInfo(
	HANDLE hConsoleOutput,
	const CONSOLE_CURSOR_INFO* lpConsoleCursorInfo
);

比如我们将光标的填充变成100(默认为25)

c 复制代码
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;

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

	system("pause");
	return 0;
}

我们可以发现确实发生了变化

既然这样,我们就可以设计一个封装方法来改变光标的位置,就不用每次改变光标位置时都写这么都行了

c 复制代码
void set_pos(int x, int y)
{
	//获得标准输出的设备的句柄
	HANDLE houtput = NULL;
	houtput = GetStdHandle(STD_OUTPUT_HANDLE);

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

int main()
{
	set_pos(10, 20);
	printf("hehe\n");

	set_pos(10, 10);
	printf("hehe\n");

	return 0;
}

GetAsyncKeyState(获取按键情况)

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

关于虚拟键码,大家可以点击这个链接查看 link:虚拟键码

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

如果我们要判断一个键是否被按过,可以检测GetAsyncKeyState返回值的最低值是否为1。即将它与1按位与得出结果

因此我们可以设计出这样一个宏来实现它

c 复制代码
#define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )
//VK即按键的虚拟键码

中间一列就是虚拟键码

讲到这里差不多就是贪吃蛇项目所需的所有API了,但是控制台所拥有的API函数远不止如此,有兴趣的朋友可以点开下面链接 link:API函数


三、贪吃蛇项目游戏设计和分析

大家看,如果不进行处理我们地图根据坐标和宽度、高度打印出来就会是这样

很明显高大于宽,这样我们就不好确定贪吃蛇的每个节点,也不方便创建,

所以就需要用到宽字符

3.1 本地化中宽字符的概念及应用

简单来说,C语言设计时只考虑了英语,所以最开始只有8位二进制来存储数据,但是相对于中文的十万汉字等基数极大的语言就非常不够用,因此 本地化 应运而生,宽字符 就是其中之一。

关于本地化,也进行一个简单介绍

<locale.h>提供的函数用于控制C标准库中对于不同的地区会产生不一样行为的部分。在标准中,依赖地区的部分有以下几项:

  • 数字量的格式
  • 货币量的格式
  • 字符集
  • 日期和时间的表示形式

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

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

更多: link

为了修改当前地区我们需要用到 setlocale函数

c 复制代码
char* setlocale (int category, const char* locale);

setlocale 的第一个参数可以是前面说明的类项中的一个,那么每次只会影响一个类项,如果第一个参数是LC ALL,就会影响所有的类项。

C标准给第二个参数仅定义了2种可能取值:"C"(正常模式)和""(本地模式)。也可以用"NULL"获取当前的默认模式

宽字符的打印

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

这样就能打印出来了

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

3.2 蛇身及食物等初始化结构设计

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

关于蛇身我们只需要确定蛇头的位置坐标,然后利用链表就可以找到后面的蛇身直至蛇尾

不仅如此,我们对整条蛇还需要进行一个封装,来确定方向、状态等

所以我们先利用枚举罗列方向和状态

c 复制代码
//蛇的方向
enum DIRECTION
{
	UP = 1,
	DOWN,
	LEFT,
	RIGHT
};

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

然后再进行整条蛇的封装,为了方便去下一个节点的坐标及对整条蛇的实参进行改变,我们使用指针来重命名各个结构体

c 复制代码
//蛇身的节点类型
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 _score;//总成绩
	int _sleep_time;//休息时间,时间越短,速度越快,时间越长,速度越慢
}Snake, * pSnake;

由此一来蛇的基本结构就设置完毕了

3.3 游戏流程设计

整体流程放在下面,具体游戏实现请看下回分解😊

相关推荐
网易独家音乐人Mike Zhou1 小时前
【卡尔曼滤波】数据预测Prediction观测器的理论推导及应用 C语言、Python实现(Kalman Filter)
c语言·python·单片机·物联网·算法·嵌入式·iot
搬砖的小码农_Sky4 小时前
C语言:数组
c语言·数据结构
PegasusYu4 小时前
STM32CUBEIDE FreeRTOS操作教程(九):eventgroup事件标志组
stm32·教程·rtos·stm32cubeide·free-rtos·eventgroup·时间标志组
先鱼鲨生6 小时前
数据结构——栈、队列
数据结构
一念之坤6 小时前
零基础学Python之数据结构 -- 01篇
数据结构·python
IT 青年6 小时前
数据结构 (1)基本概念和术语
数据结构·算法
熬夜学编程的小王6 小时前
【初阶数据结构篇】双向链表的实现(赋源码)
数据结构·c++·链表·双向链表
liujjjiyun7 小时前
小R的随机播放顺序
数据结构·c++·算法
ahadee8 小时前
蓝桥杯每日真题 - 第19天
c语言·vscode·算法·蓝桥杯
Theliars8 小时前
C语言之字符串
c语言·开发语言