文章目录
游戏实现目的
- 贪吃蛇是久负盛名的游戏,它也和俄罗斯⽅块,扫雷等游戏位列经典游戏的⾏列。
在学习完C语言与初步接触数据结构中的链表后,我们可以使⽤C语言在Windows环境的控制台中模拟实现经典⼩游戏贪吃蛇。巩固C语言语法,也可以提高大家对编程的兴趣。
知识要点
- C语言函数,枚举,结构体,动态内存管理,预处理指令,链表,Win32API等。除了Win32API,其他知识点想必大家都很熟悉了,所以我们简单介绍一下实现贪吃蛇所需要的相关Win32API函数
Win32API
- Windows 这个多作业系统除了协调应⽤程序的执⾏、分配内存、管理资源之外, 它同时也是⼀个很⼤的服务中⼼,调⽤这个服务中⼼的各种服务(每⼀种服务就是⼀个函数),可以帮应⽤程序达到开启视窗、描绘图形、使⽤周边设备等⽬的,由于这些函数服务的对象是应⽤(Application), 所以便称之为 Application Programming Interface,简称 API 函数。Win32API也就是Microsoft Windows32位平台的应⽤程序编程接⼝。
- 通俗的说就是,每一种操作系统在完成正常的操作系统该有的功能,如文件管理,内存管理等等还提供了一些接口(函数),这些函数提供给程序员,让程序员自己调用,能够实现一些自己想要的功能。这些接口就称为API。以windows为例,这些接口称为WIN32API。
控制台程序
- 前面我们提到了在Windows环境的控制台中模拟实现经典⼩游戏贪吃蛇,也就是说我们实现的贪吃蛇游戏是在控制台中进行的
- 上面这个运行起来的黑框框程序就是我们的控制台程序(以VS2019为例)
- 既然我们的游戏要在控制台中运行,那么我们就应该对控制台进行一些设置来达到我们的要求
WIN32API相关函数
- 在平常的控制台窗口里我们都能看到有一闪一闪的光标,为了游戏的界面效果我们需要将光标隐匿起来,这就要用到前面提到的win32API了
GetStdHandle
- GetStdHandle是⼀个Windows API函数。它⽤于从⼀个特定的标准设备(标准输⼊、标准输出或标准错误)中取得⼀个句柄(⽤来标识不同设备的数值),使⽤这个句柄可以操作设备。
HANDLE GetStdHandle(DWORD nStdHandle);
- 这里的Handle是一个结构体指针
- 参数为标准输⼊、标准输出或标准错误(我们要对控制台进行相关操作,所以这里的参数是标准输出)
- 实例
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结构体的指针,这个结构体包含了光标的信息
- 实例
HANDLE hOutput = NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(hOutput, &CursorInfo);
- 这个函数使我们可以获取光标的信息并对其进行修改,而这些信息都是包含在CONSOLE_CURSOR_INFO这个结构体里面的,在使用时我们要将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,如果光标不可⻅,则此成员为 false
-
在默认条件下光标是可见的,大小为四分之一个格子
-
我们可以通过修改这两个数据来对光标进行修改。
SetConsoleCursorInfo
- 设置指定控制台屏幕缓冲区的光标的⼤小和可见性
BOOL WINAPI SetConsoleCursorInfo(
HANDLE hConsoleOutput,
const CONSOLE_CURSOR_INFO *lpConsoleCursorInfo
);
- 这里的参数有两个,都是上面提到过的句柄和指向光标信息的结构体
- 实例
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
//影藏光标操作
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息
CursorInfo.bVisible = false; //隐藏控制台光标
SetConsoleCursorInfo(hOutput, &CursorInfo);//设置控制台光标状态
-
当我们了解这个函数后我们就能对控制台的光标信息进行修改了
-
如对光标大小进行修改,将正常的CurSorInfo.dwSize=25,改为CurSorInfo.dwSize=100
-
当我们进行修改后,左上角的光标已近变为一个格子的大小了
-
接下来我们将光标隐匿起来,所以我们将CurSorInfo.bVisible=false(使用false要包函头文件<stdbool.h>)即可
-
此时我们可以看见光标已经隐匿起来了
光标隐匿代码
- 此时隐匿光标的任务就完成了,我们只需要了解即可,这些函数都是windows已经提供好的,我们只需要知道如何使用就行
- 以下就是光标隐匿的代码只需要包含<windows.h>,<stdbool.h>这两个头文件即可
//控制台窗口的设置
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);//设置控制台光标状态
- 完成光标隐匿后我们再学习一个设置坐标的函数
控制台屏幕上的坐标 COORD
- COORD 是Windows API中定义的⼀个结构体,表⽰⼀个字符在控制台屏幕幕缓冲区上的坐标,坐标系
(0,0) 的原点位于缓冲区的顶部左侧单元格。
- COORD类型的声明:
typedef struct _COORD {
SHORT X;
SHORT Y;
} COORD, *PCOORD;
- 给坐标赋值:
COORD pos = { 10, 15 };
- 这里的COORD是一个结构体类型,其参数有X,Y坐标,这是windows设计好的函数。
setconsolecursorposition
- 设置指定控制台屏幕缓冲区中的光标位置,我们将想要设置的坐标信息放在COORD类型的pos中,调⽤SetConsoleCursorPosition函数将光标位置设置到指定的位置。
BOOL WINAPI SetConsoleCursorPosition(
HANDLE hConsoleOutput,
COORD pos
);
- 参数
- 第一个参数依旧是一个句柄
- 第二个参数是COORD这个结构体的变量,即我们要设置的坐标
- 实例
COORD pos = { 10, 5};
HANDLE hOutput = NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
//设置标准输出上光标的位置为pos
SetConsoleCursorPosition(hOutput, pos);
- 通过测试我们可以看到第二次打印的haha确实打印在我们设置的坐标上,而且从这里可以看到控制台的坐标的长宽并不是等大的。
坐标设置代码SetPos
- 在贪吃蛇实现过程中,我们会多次定位坐标,所以我们将上面的代码封装成一个函数SetPos
//设置光标的坐标
void SetPos(short x, short y)
{
COORD pos = { x, y };
HANDLE hOutput = NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
//设置标准输出上光标的位置为pos
SetConsoleCursorPosition(hOutput, pos);
}
getAsyncKeyState
- 我们再看最后一个贪吃蛇中最后一个相关的API函数
- 获取按键情况,GetAsyncKeyState的函数原型如下:
SHORT GetAsyncKeyState(
int vKey
);
- 将键盘上每个键的虚拟键值传递给函数,函数通过返回值来分辨按键的状态。
GetAsyncKeyState 的返回值是short类型,在上⼀次调⽤ GetAsyncKeyState 函数后,如果
返回的16位的short数据中,最⾼位是1,说明按键的状态是按下,如果最⾼是0,说明按键的状态是抬起;如果最低位被置为1则说明,该按键被按过,否则为0。
如果我们要判断⼀个键是否被按过,可以检测GetAsyncKeyState返回值的最低值是否为1.
#define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )
- 我们定义一个宏来检测对应按键是否被按过,通过让GetAsyncKeyState(vk)的返回值按位与上一个十六进制的1来判断是否按过
虚拟键码 (Winuser.h) - Win32 apps - 这就是我们用键盘控制蛇的方向的函数,键盘对应的虚拟值都在上面的虚拟键码中,我们可以按照自己的习惯来定义控制蛇移动方向的按键
setlocale函数
-
最后我们再介绍一个C语言的函数setlocale,其头文件为locale.h
-
由于我们打印的墙边,蛇身,蛇头,食物的符号是宽字符(长为1,宽为2),在C语言标准模式下是不支持的,所以我们要改为本地模式才能使图标正确打印
-
下图可以看出平常的单字符是长宽都为1的,但宽字符长为1,宽为2,在后面食物创建时也要留意这个问题
-
setlocale 函数⽤于修改当前地区,可以针对⼀个类项修改,也可以针对所有类项。setlocale 的第⼀个参数可以是前⾯说明的类项中的⼀个,那么每次只会影响⼀个类项,如果第⼀个参数是LC_ALL,就会影响所有的类项。C标准给第⼆个参数仅定义了2种可能取值:“C”(正常模式)和" "(本地模式)。在任意程序执⾏开始,都会隐藏式执⾏调⽤:
-
当程序运⾏起来后想改变地区,就只能显⽰调⽤setlocale函数。⽤" "作为第2个参数,调⽤setlocale函数就可以切换到本地模式,这种模式下程序会适应本地环境。
⽐如:切换到我们的本地模式后就⽀持 宽字符(汉字)的输出等。
char* setlocale (int category, const char* locale);
参数
- 第一个参数是要修改的类项,我们直接全部本地化就行,使用LC_ALL,详细类项点击查看
- 第二个参数只有“C”(正常模式)和" "(本地模式),""是不含空格的
- 实例
setlocale(LC_ALL, " ");//切换到本地环境
总结
- 关于贪吃蛇里相关的win32API函数就介绍完了,我们只需要了解如何使用就可以,现成的代码也已奉上,想要详细了解这些函数也可以打开对应链接查看,接下来就是贪吃蛇具体实现的讲解