欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 汽车 > 维修 > 手撕——贪吃蛇小游戏(上)(前置)

手撕——贪吃蛇小游戏(上)(前置)

2025/4/30 20:43:39 来源:https://blog.csdn.net/2401_88433210/article/details/147474230  浏览:    关键词:手撕——贪吃蛇小游戏(上)(前置)

引言

        使用C语言在Windows环境的控制台中模拟实现经典小游戏贪吃蛇。这一章是需要具备的前置知识,技术要点里有不会的可以在我的主页里面搜索对应的知识点。

游戏画面:

贪吃蛇游戏展示

需要的技术要点:

        C语言函数、枚举、结构体、动态内存管理、预处理指令、链表、Win32API等。

Win32PI下面会详细介绍这次需要用到的函数。

一、Win32API介绍

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

1.控制台程序 

平常我们运行起来的黑框程序其实就是控制台程序 

我们可以使用cmd命令来设置控制台窗口的长宽:设置控制台窗口的大小,30行,100列、

mode con cols=100 lines=30

通过命令设置控制台窗⼝的名字:

title 贪吃蛇

这些能在控制台窗口执行的命令,可以调用C语言函数system来执行:

int main()
{//设置控制台窗口的长宽system("mode con cols=100 lines=30");//设置cmd窗口名称system("title 贪吃蛇");system("pause");//暂停程序return 0;
}

 2.控制台屏幕上的坐标COORD

COORD是WindowsAPI中定义的⼀个结构体,表示一个字符在控制台屏幕上的坐标

 typedef struct _COORD {SHORT X;SHORT Y;} COORD, *PCOORD;

赋值:

 COORD pos = { 10, 15 };

3.GetStdHandle

GetStdHandle 函数 - Windows Console | Microsoft Learn

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

语法 

HANDLE WINAPI GetStdHandle( _In_ DWORD nStdHandle );

参数:

        在贪吃蛇游戏中使用 STD_OUTPUT_HANDLE 来控制标准输出设备(即控制台窗口)

返回值

HANDLE是一个指针类型(void*指针)

获取控制台句柄:

//获取标准输出的句柄(通过这个句柄来对控制台进行操作)
HANDLE hOutput = NULL;
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);

4.GetConsoleCursorInfo

Console 翻译:控制台

cursor 翻译:光标

GetConsoleCursorInfo 函数 - Windows Console | Microsoft Learn

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

语法

BOOL WINAPI GetConsoleCursorInfo(_In_  HANDLE               hConsoleOutput,  //句柄_Out_ PCONSOLE_CURSOR_INFO lpConsoleCursorInfo //CONSOLE_CURSOR_INFO 结构的地址);

需要搭配 CONSOLE_CURSOR_INFO结构体来使用。

4.1CONSOLE_CURSOR_INFO

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

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

成员:

dwSize:由游标填充的字符单元的百分比。 该值介于 1 到 100 之间。 游标外观各不相同,范围从完全填充单元到显示为单元底部的横线。

bVisible:游标的可见性。 如果游标可见,则此成员为 TRUE。

	//获取标准输出的句柄(通过这个句柄来对控制台进行操作)hOutput = GetStdHandle(STD_OUTPUT_HANDLE);CONSOLE_CURSOR_INFO curInfo;  //存有光标信息的结构体//获得和hOutput句柄相关的控制台上的光标信息,存放在curInfo中GetConsoleCursorInfo(hOutput, &curInfo); printf("%d ", curInfo.dwSize);  //打印结构体里面的信息

         获得了当前光标信息的结构体后,可以对结构体里面的内容进行修改,但是还需要通过SetConsoleCursorInfo 函数 ,来设置指定控制台屏幕缓冲区的光标的大小和可见性。

5.SetConsoleCursorInfo 

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

语法(和GetConsoleCursorInfo类似):

BOOL WINAPI SetConsoleCursorInfo(_In_       HANDLE              hConsoleOutput,  //句柄_In_ const CONSOLE_CURSOR_INFO *lpConsoleCursorInfo  //光标信息的结构体地址
);

例如:隐藏光标

	//获取标准输出的句柄(通过这个句柄来对控制台进行操作)hOutput = GetStdHandle(STD_OUTPUT_HANDLE);CONSOLE_CURSOR_INFO curInfo;  //存有光标信息的结构体//获得和hOutput句柄相关的控制台上的光标信息,存放在curInfo中GetConsoleCursorInfo(hOutput, &curInfo); curInfo.bVisible = false;  //修改光标信息//设置获得和hOutput句柄相关的控制台上的光标信息SetConsoleCursorInfo(hOutput, &curInfo); 

6.SetConsoleCursorPosition

SetConsoleCursorPosition 函数 - Windows Console | Microsoft Learn

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

语法:

BOOL WINAPI SetConsoleCursorPosition(_In_ HANDLE hConsoleOutput,    //句柄_In_ COORD  dwCursorPosition   //COORD类型的变量
);

 例如:

	int main(){//获取标准输出的句柄(通过这个句柄来对控制台进行操作)HANDLE hOutput = NULL;hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//定位光标的位置COORD pos = { 10, 29 };SetConsoleCursorPosition(hOutput, pos);getchar();}

 封装一个设置光标位置的函数:

void set_pos(int x, int y)
{//获取标准输出的句柄(通过这个句柄来对控制台进行操作)HANDLE hOutput = NULL;hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//定位光标的位置COORD pos = { x, y };SetConsoleCursorPosition(hOutput, pos);
}

7.GetAsyncKeyState

获取按键情况

语法:

SHORT GetAsyncKeyState([in] int vKey
);

参数:  [in] int vKey 虚拟键代码(不是ASCII)

参考:Virtual-Key 代码 (Winuser.h) - Win32 apps | Microsoft Learn

这里给出了部分:

        作用:将键盘上每个键的虚拟键值传递给函数,函数通过返回值来分辨按键的状态。 GetAsyncKeyState 的返回值是short类型,在上⼀次调⽤ GetAsyncKeyState 函数后,如果返回的16位的short数据中,最高位是1,说明按键的状态是按下,如果最高是0,说明按键的状态是抬起;如果最低位被置为1则说明,该按键被按过,否则为0。

        在游戏中,我们只需要判断一个数是否被按过,可以检测GetAsyncKeyState返回值的最低值是否为1。(被按过:最低位为1 ,未被按过:最低位为0)

参考代码:

当我们按下哪个键,屏幕上就输出哪个键

//定义一个检测按键是否被按过的宏
#define KEY_PRESS(kv) ((GetAsyncKeyState(kv)&1) ? 1:0)
//最低位是1,就返回1,
//最低位是0,就返回0int main()
{while (1){if (KEY_PRESS(0x60)) //0{printf("0\n");}else if (KEY_PRESS(0x61)){printf("1\n");}else if (KEY_PRESS(0x62)){printf("2\n");}else if (KEY_PRESS(0x63)){printf("3\n");}else if (KEY_PRESS(0x64)){printf("4\n");}else if (KEY_PRESS(0x65)){printf("5\n");}else if (KEY_PRESS(0x66)){printf("6\n");}else if (KEY_PRESS(0x67)){printf("7\n");}else if (KEY_PRESS(0x68)){printf("8\n");}else if (KEY_PRESS(0x69)){printf("9\n");}}return 0;
}

二、C语言的国际化特性相关的知识

        在游戏地图上,我们打印墙体使用宽字符:□,打印蛇使用宽字符●,打印⻝物使用宽字符★ 普通的字符是占⼀个字节的,宽字符是占用2个字节。

        简单的讲⼀下C语言的国际化特性相关的知识,过去C语言并不适合非英语国家(地区)使用。 C语言最初假定字符都是自己的。但是这些假定并不是在世界的任何地方都适用。

C语言字符默认是采用ASCII编码的,ASCII字符集采用的是单字节编码,且只使用了单字节中的低 7 位,最高位是没有使用的,可表示为0xxxxxxxx;可以看到,ASCII字符集共包含128个字符,在英语国家中,128个字符是基本够用的,但是,在其他国家语言中,比如,在法语中,字母上方有注音符 号,它就无法用ASCII码表示。于是,⼀些欧洲国家就决定,利用字节中闲置的最高位编入新的符号。比如,法语中的é的编码为130(⼆进制10000010)。这样一来,这些欧洲国家使用的编码体系,可以表示最多256个符号。但是,这里又出现了新的问题。不同的国家有不同的字母,因此,哪怕它们都使用256个符号的编码方式,代表的字母却不⼀样。比如,130在法语编码中代表了é,在希伯来语编码中却代表了字母Gimel( ),在俄语编码中又会代表另⼀个符号。但是不管怎样,所有这些编码方式中,0--127表示的符号是⼀样的,不⼀样的只是128--255的这⼀段。 至于亚洲国家的文字,使用的符号就更多了,汉字就多达10万左右。⼀个字节只能表示256种符号, 肯定是不够的,就必须使用多个字节表达⼀个符号。比如,简体中文常见的编码方式是GB2312,使用两个字节表示一个汉字,所以理论上最多可以表示256x256=65536个符号。

        后来为了使C语言适应国际化,C语言的标准中不断加入了国际化的支持。比如:加入宽字符的类型wchar_t 和宽字符的输入和输出函数,加入<localel.h>头文件,其中提供了允许程序员针对特定地区(通常是国家或者说某种特定语言的地理区域)调整程序行为的函数。

<locale.h>本地化(为了打印宽字符)

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

• 数字量的格式

• 货币量的格式

• 字符集

• 日期和时间的表示形式

1类项: 

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

• LC_COLLATE

• LC_CTYPE

• LC_MONETARY

• LC_NUMERIC

• LC_TIME

• LC_ALL-针对所有类项修改

2setlocale 函数

setlocale - C++ Reference (cplusplus.com)

原型

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

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

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

C标准给第二个参数仅定义了2种可能取值: C 和 ""。

当第二个参数为C时,库函数按正常方式执行。

setlocale(LC_ALL,"C");  //按照正常方式执行

当第二个参数为""时,就进行本地化,切换到本地模式,这样就可以打印宽字符了

setlocale(LC_ALL,"");  //按照本地方式执行

setlocale函数返回值是一个char*(可以理解为字符串),即当前是什么模式。

第二个参数是NULL时,可以查看当前的模式;

int main()
{char* ret = setlocale(LC_ALL,NULL);//获取当前模式printf("%s\n", ret);ret = setlocale(LC_ALL, "");//设置为本地模式printf("%s\n", ret);return 0;
}

3打印宽字符

对宽字符的打印有自己的规则:

1.宽字符类型:wchar_t 表示宽字符类型

2.单引号('')对应的占位符是:%lc,双引号("")对应的占位符是:%ls;

3.在赋值时,单引号或者双引号前面需要加上 L,表示是宽字符类型。

4.打印宽字符用的函数是:wprintf();语法和printf()类似。但是:站位符双引号前""需要加L。

看代码: 

int main()
{//设置本地化setlocale(LC_ALL, "");char a = 'a';char b = 'b';printf("%c%c\n", a, b);wchar_t wc1 = L'★';wchar_t wc2 = L'□';wchar_t wc3 = L'●';wprintf(L"%lc\n", wc1);wprintf(L"%lc\n", wc2);wprintf(L"%lc\n", wc3);return 0;
}

        从输出的结果来看,我们发现⼀个普通字符占⼀个字符的位置 但是打印一个汉字字符,占用2个字符的位置,那么我们如果要在贪吃蛇中使用宽字符,就得处理好地图上坐标的计算。

三、前置总结

        OK啦,看到这里,前面的知识点一定要搞懂,特别是Win32API的句柄和各个函数之间的关系。下一章,正式开始贪吃蛇游戏的设计和代码的实现。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词