C语言·贪吃蛇游戏(上)

1. 游戏任务

        使用C语言在Windows环境的控制台中模拟实现小游戏贪吃蛇

        游戏中要包含以下功能:

        1. 贪吃蛇地图绘制

        2. 贪吃蛇上下左右移动和吃食物

        3. 蛇撞墙,或撞到自身死亡

        4. 计算得分

        5. 蛇身加速、减速

        6. 暂停游戏

2. Win32 API 介绍

        Windows是一种多作业的操作系统(同时进行多个任务进程),它除了协调应用程序的执行、分配内存、管理资源之外。它还是一个很大的服务中心,服务中心有很多函数接口,我们可以通过调用这些函数或者说是服务,来帮助应用程序达到开启视窗、描绘图形、使用周边设备等功能。由于这些服务的对象是应用程序,所以我们把这些服务称为 Application Programming Interface (应用程序编程接口) 简称API函数

2.1 控制台程序(Console)

        首先操作系统是win11的朋友们要注意了,我们运行程序的时候弹出的那个黑色窗口不是控制台,而是win11新提供的终端窗口,在终端窗口中是不能实现控制台程序中的一些功能的

C语言·贪吃蛇游戏(上)_第1张图片

        上面这个窗口就是终端窗口,下面我们讲解如何改成控制台窗口

C语言·贪吃蛇游戏(上)_第2张图片

        鼠标放到下箭头上,然后选择设置

C语言·贪吃蛇游戏(上)_第3张图片

        在启动中选择Windows控制台主机,并保存

C语言·贪吃蛇游戏(上)_第4张图片

        下次再运行起来的就是控制台窗口了

        下面我们介绍两个控制台程序命令:

2.1.1 设置控制台的大小

                        mode con cols=100 lines=30

C语言·贪吃蛇游戏(上)_第5张图片

        当我们把这段命令敲到cmd里头之后就会发现窗口的大小改变了

C语言·贪吃蛇游戏(上)_第6张图片

        cols 控制的是列,lines 控制的是行,现在我们就可以根据喜好控制游戏窗口的大小了

2.1.2 设置控制台的名字

                        title 贪吃蛇

C语言·贪吃蛇游戏(上)_第7张图片

        现在可以注意到,控制台窗口的名字变成了贪吃蛇

2.1.3 system()函数

        上面我们是在cmd中进行的操作,那么我们如何把这些操作写进C程序中呢,这时就用到了system函数,system函数就相当于帮你把内容输入到控制台中了,这个函数需要引用头文件

                                int system (const char* command);

        官网链接:system - C++ Reference

C语言·贪吃蛇游戏(上)_第8张图片

        上面我们展示了一下使用的效果,我们将窗口大小和名字都修改了,但是我在最后输入了一个pause暂停的语句,这是因为如果不暂停的话程序就直接结束了,紧接着刚刚输入的这些命令就失效了,那我们就看不到效果了。

2.2 控制台屏幕上的坐标 COORD

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

C语言·贪吃蛇游戏(上)_第9张图片

        COORD的类型声明差不多长这个样子:

                        C语言·贪吃蛇游戏(上)_第10张图片

        为啥说差不多呢,因为在真正的声明中short是大写的,因为它前面给重命名了,但是其实这些我们都不必关注。

        下面说一下如何使用这个结构体,首先要引用头文件,然后搞一个 COORD 类型变量赋值就行

                C语言·贪吃蛇游戏(上)_第11张图片

        

2.3 控制台的操作以及光标控制

2.3.1 GetStdHandle

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

                HANDLE GetStdHandle( DWORD nStdHandle );

        官网资料:GetStdHandle 函数 - Windows Console | Microsoft Learn

        这个参数的类型 DWORD 看起来很迷,但其实这个参数就3种输入情况

C语言·贪吃蛇游戏(上)_第12张图片

        本节我们主要是用这个函数来获取控制台的标准输出句柄,以此来控制控制台上输出的东西,其实说白了就是把光标隐藏掉,因为如果不隐藏的话,光标在那里一直闪,很影响游戏画面的美观性。

        当我们使用这个函数的时候要先定义一个HANDLE类型的参数,其实HANDLE就是一个被typedef了的 void* 类型名。当然像这种API函数都要引用头文件,后面再有用到API函数的时候我就不赘述引用头文件了。        

C语言·贪吃蛇游戏(上)_第13张图片

2.3.2 GetConsoleCursorInfo

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

                        BOOL WINAPI GetConsoleCursorInfo(
                                    HANDLE               hConsoleOutput,
                                    PCONSOLE_CURSOR_INFO lpConsoleCursorInfo
                        );

        官网资料:GetConsoleCursorInfo 函数 - Windows Console | Microsoft Learn 

        PCONSOLE_CURSOR_INFO 是指向 CONSOLE_CURSOR_INFO 结构的指针,该指针接收有关主机游标(光标)的信息。

2.3.3 CONSOLE_CURSOR_INFO

        这个结构体包含有关控制台光标的信息

                        typedef struct _CONSOLE_CURSOR_INFO {
                                DWORD dwSize;
                                BOOL bVisible;
                        } CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO;

        官网资料:CONSOLE_CURSOR_INFO 结构 - Windows Console | Microsoft Learn

        dwSize 是由光标填充的字符单元格的百分比。此值介于1-100之间。光标外观会发生变化,从0到100是光标从最下面一直上长到最上面,最后填充满整个字符单元格。

        bVisible 是游标的可见性,一个布尔类型变量。如果光标可见,此成员为true,不可见为false

C语言·贪吃蛇游戏(上)_第14张图片

        我们现在用一下GetConsoleCursorInfo函数,把控制台中的光标信息存放到cursor_info结构体中,观察光标信息cursor_info中的值,其中dwSize是25,对应着光标占25%的字符单元格,bVisible是1,对应着 true 可见的。后面那个圈出来的光标是我自己点出来的,像那样的光标就是dwSize=100的光标。

2.3.4 SetConsoleCursorInfo

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

                BOOL WINAPI SetConsoleCursorInfo(
                        HANDLE hConsoleOutput,
                        const CONSOLE_CURSOR_INFO *lpConsoleCursorInfo
                );

        官网资料:SetConsoleCursorInfo 函数 - Windows Console | Microsoft Learn 

         我们可以用访问并改变结构体cursor_info的成员,然后再用SetConsoleCursorInfo,把改变后的信息交给程序。像这里我就把光标的dwSize改成了50,现在它看起来比25的时候高了不少。

C语言·贪吃蛇游戏(上)_第15张图片

        但是我们的主要任务是要隐藏光标,所以我们要修改光标的可见性,当然,在使用布尔类型时要注意引用头文件

C语言·贪吃蛇游戏(上)_第16张图片

2.3.5 SetConsoleCursorPosition

        设置指定控制台屏幕缓冲区中的光标位置,我们将想要设置的光标信息放在COORD类型的pos中,调用SetConsoleCursorPosition函数将光标位置设置到指定的位置。

                BOOL WINAPI SetConsoleCursorPosition(
                        HANDLE hConsoleOutput,
                        COORD pos
                );

         官网资料:SetConsoleCursorPosition 函数 - Windows Console | Microsoft Learn

C语言·贪吃蛇游戏(上)_第17张图片

        一顿操作之后你就会发现,hello world并不是从左上角的(0,0)开始打印了,而是从我设置好的光标位置开始打印的了。

        当然为了后续方便使用,我们可以把这一坨封装到一个函数里头去。

2.3.6 GetAsyncKeyState

        获取按键情况,GetAsyncKeyState的函数原型如下:

                SHORT GetAsyncKeyState(
                        int vKey
                );

        官网资料:GetAsyncKeyState function (winuser.h) - Win32 apps | Microsoft LearnGetAsyncKeyState 函数 (winuser.h) - Win32 apps |Microsoft 学习GetAsyncKeyState function (winuser.h) - Win32 apps | Microsoft Learn

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

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

        所以我们只需要判断返回值最低位是否为1就能知道这个按键是否被按过。

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

        下面我们实现检测数字键的功能,就是说我按下哪个数字键,就打印出哪个数字:

C语言·贪吃蛇游戏(上)_第18张图片

        解释一下,首先,我定义的这个宏 KEY_PRESS(vk) 让得到的虚拟键的反馈值按位与 1 就能知道最后返回值的最后一位是不是1了,也就是说,能够检测到有没有按这键。然后写一个死循环,一直判断这些虚拟键有没有被按过,如果按过,就把它打印出来。

        既然我们能够判断数字键有没有被按过,那么我们就能判断上下左右键有没有被按过,我们想监测哪个键就把对应的码值写上去就好了,如此说来,蛇的移动问题就解决了一半了

3. 贪吃蛇游戏设计与分析

3.1 地图

        我们最终的贪吃蛇游戏大概是这个样子的,那我们的地图该如何布置呢?

        欢迎界面

C语言·贪吃蛇游戏(上)_第19张图片

        操作介绍界面

C语言·贪吃蛇游戏(上)_第20张图片

        游戏界面

C语言·贪吃蛇游戏(上)_第21张图片

        在游戏地图上,我们打印墙体使用宽字符:,打印蛇身使用宽字符:,打印食物使用宽字符:

        普通的字符是占一个字节的,但是宽字符占两个字节,而且这些宽字符在视觉效果上也是一个普通字符的二倍

        这里简单讲一下C语言的国际化特性相关的知识,过去C语言并不适合非英语国家使用,C语言最初是美国人发明的,他们的语言中就26个字母,所以可能要使用到的字符非常少,但是其他用语言国家就不一定够用了。所以后来为了使C语言国际化,C语言的标准中不断加入了国际化的支持。比如宽字符的类型 wchar_t 和宽字符的输入和输出函数,加入了 头文件,其中提供了允许程序员针对特定地区调整程序行为的函数。

3.1.1 本地化

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

        1. 数字量的格式

        2. 货币量的格式

        3. 字符集

        4. 日期和时间的表示形式

3.1.2 类项

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

        LC_COLLATE:影响字符串比较函数 strcoll()strxfrm()

        LC_CTYPE:影响字符处理函数的行为

        LC_MONETARY:影响货币的格式

        LC_NUMERIC:影响 printf() 的数字格式

        LC_TIME:影响时间格式 strftime()wcsftime()

        LC_ALL:针对所有类项修改,将以上所有类别设置为给定的语言环境

        每个类项的详细说明:setlocale,_wsetlocale | Microsoft Learn

3.1.3 setlocale函数

        setlocale 函数用于修改当前的地区,可以针对一个类项修改,也可以LC_ALL修改所有

        char* setlocale (int category, const char* locale);

         官网资料:setlocale - C++ Reference (cplusplus.com)

        setlocale 的第一个参数可以是前面讲到的任何一个类项,区别就是影响哪个类项,或者是全都影响

        setlocale 的第二个参数仅定义了两种可能取值:"C" (正常模式) 和 "" (本地模式)。本地模式就是一个空字符串就行了,然后你的Windows是哪国版本就给你上那个地区的模式

        当没有专门调用setlocale来控制模式的话,默认是正常模式启动

        setlocale的返回值是一个字符串指针,表示已经设置好的格式。如果调用失败,则返回空指针NULL

        setlocale() 可以用来查询当前地区,这时第二个参数设置为NULL就可以了

C语言·贪吃蛇游戏(上)_第22张图片

        setlocale() 在我们贪吃蛇项目中的用处就是把程序本地化,然后来让我们使用宽字符

3.1.4 宽字符的打印

        宽字符的字面量必须加上前缀 L ,否则C语言会把字面量当作普通字符处理。前缀 L 在单引号前面,表示宽字符,宽字符的打印使用 wprintf() ,打印格式前面也要加上 L ,对应宽字符的占位符是 %lc ,宽字符串占位符是 %ls 。汉字也是宽字符

C语言·贪吃蛇游戏(上)_第23张图片

        现在我们就很明显看出来宽字符的宽了,它真的占了两个字符的位置。

        那么在控制台的坐标系中一个普通的字符是占一列位置的,那么一个宽字符事实上要占两列位置,但是它们所占的行是一样的,都只占一行

3.1.5 地图坐标

        我们假设要实现一个27行58列的棋盘,再围绕它画出墙,如图:

                C语言·贪吃蛇游戏(上)_第24张图片

        棋盘大小可以根据自己喜好设定,列数最好是行数的两倍,这样差不多能是一个正方形,然后列数最好设计成双数的,因为棋盘的墙还有里头的蛇和食物都是宽字符

3.1.6 蛇身和食物

        初始化状态,假设蛇身长度是5,蛇身的每个节点是 ● ,蛇头出现在一个固定的坐标处,比如(24,5) 处开始出现蛇,连续5个节点。

        注意:蛇的每一个节点的x坐标必须是2的倍数,否则蛇撞墙的判定会很迷

        关于食物,就是在墙体内随机生成一个坐标(列也同样必须是2的倍数),坐标不能和蛇身重合,然后打印★

4. 未完待续······

        到此,我们贪吃蛇游戏的前置知识就学完了,下节我们将着手写出这个游戏

你可能感兴趣的:(C语言学习之旅,游戏,c语言)