感谢大佬的光临各位,希望和大家一起进步,望得到你的三连,互三支持,一起进步
个人主页:LaNzikinh-CSDN博客
文章目录
- 前言
- 一、Win32 API
- 二、地图的绘制和初始化
- 总结
前言
贪吃蛇(也叫做贪食蛇)游戏是一款休闲益智类游戏,有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的使用,后面在讲解贪吃蛇的实现