贪吃蛇是久负盛名的游戏之一,它也和俄罗斯⽅块,扫雷等游戏位列经典游戏的行列。在编程语言的教学中,我们以贪吃蛇为例,从设计到代码实现来提升大家的编程能⼒和逻辑能⼒。
在本篇讲解中,我们会看到很多陌生的知识,但是不必惊慌,你只需要了解如何使用就可以了,不必在这里大费周章,我们学习的关键是游戏编码中的编写能力和底层逻辑思维。
游戏开始前的准备工作
为了让代码的逻辑清晰可见,我们主要将代码分成三个文件(snake.h、snake.c、test.c)。
snake.h用于包含其他文件需要用到的头文件、常量、宏以及结构体和函数声明。
snake.c用于各种函数的实现。
test.c用于测试游戏。
下面我来说明一下C语言的文字环境问题。
<locale.h>
在游戏中,我们需要用到一些字符(方块、五角星、圆圈)来作为游戏的墙体、蛇的身体、和食物,这些在C语言的标准环境下是不被支持的,这⾥再简单的讲⼀下C语⾔的国际化特性相关的知识,过去C语⾔并不适合⾮英语国家(地区)使⽤。C语⾔最初假定字符都是单字节的。但是这些假定并不是在世界的任何地⽅都适⽤。后来为了使C语⾔适应国际化,C语⾔的标准中不断加⼊了国际化的⽀持。⽐如:加⼊了宽字符的类型:wchar_t 和宽字符的输⼊和输出函数,加入了<locale.h>头⽂件,其中提供了允许程序员针对特定地区(通常是国家或者说某种特定语⾔的地理区域)调整程序⾏为的函数。
拓展:
C语⾔字符默认是采⽤ASCII编码的,ASCII字符集采⽤的是单字节编码,且只使⽤了单字节中的低7位,最⾼位是没有使⽤的,可表⽰为0xxxxxxxx;可以看到,ASCII字符集共包含128个字符,在英语国家中,128个字符是基本够⽤的,但是,在其他国家语⾔中,⽐如,在法语中,字⺟上⽅有注⾳符号,它就⽆法⽤ASCII码表⽰。于是,⼀些欧洲国家就决定,利⽤字节中闲置的最⾼位编⼊新的符号。⽐如,法语中的é的编码为130(⼆进制0000010)。这样⼀来,这些欧洲国家使⽤的编码体系,可以表⽰最多256个符号。但是,这⾥⼜出现了新的问题。不同的国家有不同的字⺟,因此,哪怕它们都使⽤256个符号的编码⽅式,代表的字⺟却不⼀样。⽐如,130在法语编码中代表了é,在希伯来语编码中却代表了字⺟Gimel(),在俄语编码中⼜会代表另⼀个符号。但是不管怎样,所有这些编码⽅式中,0--127表⽰的符号是⼀样的,不⼀样的只是128--255的这⼀段。⾄于亚洲国家的⽂字,使⽤的符号就更多了,汉字就多达10万左右。⼀个字节只能表⽰256种符号,肯定是不够的,就必须使⽤多个字节表达⼀个符号。⽐如,简体中⽂常⻅的编码⽅式是GB2312,使⽤两个字节表⽰⼀个汉字,所以理论上最多可以表⽰256x256=65536个符号。
了解了C语言在不同国家的文字环境差异后我们就来了解一下如何修改C语言的环境。
locale.h的本地化
<locale.h>提供的函数用于控制c标准库中对于同地区会产生不一样的行为的部分。在标准中依赖地区的部分有以下几项:
• 数字的格式
• 货币量的格式
• 字符集
• 日期和时间的表示形式
类项
通过修改地区,程序可以改变它的⾏为来适应世界的不同区域。但地区的改变可能会影响库的许多部分,其中⼀部分可能是我们不希望修改的。所以C语⾔⽀持针对不同的类项进⾏修改,下⾯的⼀个宏,指定⼀个类项:
• LC_COLLATE:影响字符串⽐较函数 strcoll() 和 strxfrm() 。
• LC_CTYPE:影响字符处理函数的⾏为。
• LC_MONETARY:影响货币格式。
• LC_NUMERIC:影响 printf() 的数字格式。
• LC_TIME:影响时间格式 strftime() 和 wcsftime() 。
• LC_ALL:针对所有类项修改,将以上所有类别设置为给定的语⾔环境。
setlocale函数
上面的这些类项都在本函数中有说明。
char* setlocale (int category, const char* locale);
setlocale函数⽤于修改当前地区,可以针对⼀个类项修改,也可以针对所有类项。
setlocale的第⼀个参数可以是前⾯说明的类项中的⼀个,那么每次只会影响⼀个类项,如果第⼀数是LC_ALL,就会影响所有的类项。
C标准给第⼆个参数仅定义了2种可能取值:"C"(正常模式)和 " "(本地模式)。
在任意程序执行开始,都会隐藏式执行调用: setlocale(LC_ALL, "C");
当地区设置为"C"时,库函数按正常⽅式执⾏,⼩数点是⼀个点。当程序运⾏起来后想改变地区,就只能显⽰调⽤setlocale函数。⽤""作为第2个参数,调⽤setlocale 函数就可以切换到本地模式,这种模式下程序会适应本地环境。
⽐如:切换到我们的本地模式后就⽀持宽字符(汉字)的输出等。
setlocale(LC_ALL, " "); //切换到本地环境即可
我们对贪吃蛇封装一个测试函数,代码如下:
int main()
{
//设置C语言为本地环境,为了支持中文字符的打印。
setlocale(LC_ALL, "");
//游戏测试
test();
return 0;
}
封装好函数之后,我们进入到函数中的游戏环境搭建模块。
游戏环境搭建
Win32API
介绍
windows是一个多作业系统,除了协调应用程序的执行、分配内存、管理资源之外,它同时也是一个很大的服务中心,调用这个服务中心的各种服务(每一种服务就是一个函数),可以帮应用程序达到开启视窗、描绘图性、使用周边设备等目的,由于这些函数的服务对象是应用程序(Application),所以便称之为Application Programming Interface,简称API函数。Win32 API也就是Microsoft Windows32位平台的应用程序编程接口。
控制台程序
平常我们运行起来的黑框程序其实就是程序控制台
我们可以使用cmd命令来设置控制台窗口的长宽(行列):30行,100列
mode con cols=100 lines=30
同时我们还可以使用命令设置控制台窗口的名字:
title 贪吃蛇
想要使用控制台窗口执行命令,我们需要调用system函数,该函数包含在windows.h的头文件中,代码如下:
void test()
{
//设置控制台窗⼝的⻓宽:设置控制台窗⼝的⼤⼩,30⾏,100列
system("mode con cols=100 lines=30");
//设置cmd窗⼝名称
system("title 贪吃蛇");
}
学会了设置控制台窗口和cmd窗口后,我们再来解决一个光标显示问题,我们知道在输入是屏幕上总有一个光标在闪,如果我们在运行游戏中总是有这个光标就会影响我们。那下面我们就来看看如何隐藏光标。
GetStdHandle函数
GetStdHandle是⼀个WindowsAPI函数。它⽤于从⼀个特定的标准设备(标准输⼊、标准输出或标准错误)中取得⼀个句柄(⽤来标识不同设备的数值),使⽤这个句柄可以操作设备。
函数原型:
HANDLE GetStdHandle(DWORD nStdHandle);
使用方法:
HANDLE hOutput = NULL; //创建HANDLE类型的变量
//获取标准输出的句柄(⽤来标识不同设备的数值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE); //参数的用法见下图
GetConsoleCursorInfo
本函数的作用是检索有关指定控制台屏幕缓冲区的光标大小和可见性的信息。
结构体定义原型:
BOOL WINAPI GetConsoleCursorInfo(
HANDLE hConsoleOutput,
PCONSOLE_CURSOR_INFO lpConsoleCursorInfo
);PCONSOLE_CURSOR_INFO 是指向 CONSOLE_CURSOR_INFO 结构的指针,该结构接收有关控制台游标的信息。
使用方式:
HANDLE hOutput = NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息
CONSOLE_CURSOR_INFO
这个结构体包含有关控制台光标的信息。
typedef struct _CONSOLE_CURSOR_INFO {
DWORD dwSize;
BOOL bVisible;
} CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO;
• dwSize,由光标填充的字符单元格的百