使用控制台实现贪吃蛇需要的技能加点:
控制台设置(包含于stdlib.h):
定义命令行窗口高/宽:
system("mode con cols=100 lines=30");
system() 函数是一个C标准库函数,它允许程序执行操作系统命令。
mode 是命令的关键字,代表“模式”,用于改变系统设备的配置。
con 是“console”的缩写,指代命令行界面或控制台窗口。
cols=100 设置了窗口的列数(宽度)为100个字符。
lines=30 设置了窗口的行数(高度)为30行。
长宽(二倍关系);
合起来为改变控制台(命令行)窗口的配置:高30 宽100.
设置标题:
system("title 贪吃蛇");
在程序运行结束前设定标题为贪吃蛇;
在程序结束后标题恢复为默认状态。
控制台窗口坐标:
坐标图(x轴俩个单位长度等于y轴一个单位)
使用windows.h头文件可以使用该结构体,该结构体有俩个成员:
typedef struct _COORD {SHORT X;SHORT Y;} COORD, *PCOORD;
通过X,Y可以确定一个控制台上的一个位置,后面会用它来调整光标位置。
光标
获取/使用句柄(控制台句柄):
获取句柄
句柄可以理解为一个中介,通过中介可以操纵使用中介手里的资源。例如你需要找个兼职,但是不知道怎么做才能兼职,于是你找了一个地区的兼职中介,你将需求告诉了中介,最后中介帮你搞定了找兼职的事,这个中介只管他所在地区的资源。
在这里你需要隐藏掉控制台的光标,毕竟我们不需要在贪吃蛇显示光标。
//获取当前控制台界面的句柄
HANDLE houtput = NULL;
//定义结构体用来接收存放句柄;
houtput = GetStdHandle(STD_OUTPUT_HANDLE);
GetStdHandle()是Windows API 提供的函数,参数为标准设备(标准输入、标准输出、标准错误),返回值为对应句柄
STD_OUTPUT_HANDLE 表示标准输出设备(当前控制台)。
使用句柄
我们可以通过这个句柄对当前的控制台进行一些操作,这里我们先来一个获取光标信息的操作:
//首先定义一个光标信息的结构体
CONSOLE_CURSOR_INFO cursor_info = {0};
该结构体是Windows API中用于描述控制台光标属性的数据结构,它包含以下两个成员:
typedef struct {DWORD dwSize;BOOL bVisible;
} CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO;
关键词解释:
dwSize:这是一个DWORD类型的变量,表示光标的大小。这里的“大小”并不是指光标的物理尺寸,而是指光标所覆盖的字符单元的百分比。例如,如果设置为50,则光标将占据半个字符单元的高度。此值必须在1到100之间;
DWORD类型是Windows API中定义的一种数据类型,全称为Double Word,表示一个32位的无符号整数。
bVisible:这是一个BOOL类型的变量,指示光标是否可见。如果bVisible为TRUE,则光标可见;如果为FALSE,则光标不可见。
下面演示一下获取句柄->通过该句柄得到光标数据->打印出光标数据
//获得标准输出设备的句柄HANDLE houtput = NULL;houtput = GetStdHandle(STD_OUTPUT_HANDLE);//定义一个光标信息的结构体CONSOLE_CURSOR_INFO cursor_info = {0};//获取和houtput句柄相关的控制台上的光标信息,存放在cursor_info中GetConsoleCursorInfo(houtput, &cursor_info);//打印当前控制台的光标大小,光标隐藏情况printf("%u, %d", cursor_info.dwSize, cursor_info.bVisible);
除了得到句柄,我们也可以定义一个光标属性的结构体,设定好里面的参数,然后将他交给句柄来改变光标属性:
//定义一个光标信息的结构体CONSOLE_CURSOR_INFO cursor_info = {0};//设定光标显示:否cursor_info.bVisible = false;//像前面一样,获得当前标准输出设备的句柄HANDLE houtput = NULL;houtput = GetStdHandle(STD_OUTPUT_HANDLE);//设置控制台上的光标信息(通过刚刚获得的句柄,SetConsoleCursorInfo通过句柄可以知道更改的是哪个窗口SetConsoleCursorInfo(houtput, &cursor_info);//暂停程序以便观察system("pause");观察发现光标不见了(不显示)
改变光标位置(实现在控制台不同位置打印文本,而不是默认的从左到右、从上到下):
获得当前标准输出设备的句柄HANDLE houtput = NULL;houtput = GetStdHandle(STD_OUTPUT_HANDLE);定义并初始化一个窗口坐标结构体,参数为横纵坐标:COORD pos = { 10, 20 };使用光标位置调整函数(参数为句柄和窗口坐标结构体)SetConsoleCursorPosition(houtput, pos);测试光标位置printf("10,20位置的光标在这里");
检测按键:
在游戏中,我们需要通过上下左右控制蛇的方向,所以我们还需要一个检测按键的方法:
以下举例为检测数字5是否被按过:
short ret = GetAsyncKeyState(0x35);
if ((ret & 1) == 1)//这里使用了位与运算符&来检查ret的最低位(第0位)是否为1printf("5被按过\n");elseprintf("没有被按过\n");
这里用到了GetAsyncKeyState函数:这是Windows API中的一个函数,用于检查指定虚拟键的当前状态。它接收一个虚拟键码作为参数,返回一个短整型(short)值,该值反映了按键的"状态"包括"是否被按下"。虚拟键码0x35对应于键盘上的数字键"5"。
这段代码调用了GetAsyncKeyState函数,传入数字键"5"的虚拟键码,获取其状态并存储在变量ret中
由于我们此次项目只检测按键是否被按过这一状态,不在乎按键的其他状态(如是否为持续按住),所以我们只会用到ret的最后一位数字,该数字为1表示按过,为0表示没按过。
我们可以用做一个宏用来检测某键是否被按过
#define KEY_PRESS(vk) ((GetAsyncKeyState(vk)&1)?1:0)
参数为虚拟键码vk。
虚拟键码表可以自行网上搜索
本地化:
有一些特殊的字符需要俩个字节的编码来表示,而使用这些字符需要设置一个本地化:由于世界不同地区的文字差异,时间格式差异,符号差异,货币单位差异...,不同地区有不同的双字节编码方式(同样的编码可能表示不同的字符)。
以下为查询当前程序的本地化设置。
#include <locale.h>//我们要用的setlocale函数在这个头文件 char* ret = setlocale(LC_ALL, NULL);printf("%s\n", ret);//打印出当前的本地化设置名称。
调用setlocale函数,参数LC_ALL是一个宏,代表所有类别,用它的意思是对所有的地区性的东西动手。第二个参数为NULL时,NULL表示不修改地区性的东西。setlocale会返回修改后的本地化设置名称(返回的设置名称被存储在ret指向的字符数组中,随后通过printf打印出来。)
以下为改变当前程序的本地化设置
ret = setlocale(LC_ALL, "");printf("%s\n", ret);//再次打印出本地化设置名称。
调用setlocale函数,这次第二个参数是空字符串""。当使用空字符串作为参数时,setlocale会尝试根据环境变量(如LC_ALL, LANG, LC_CTYPE等)来设置本地化信息,通常会选择用户或系统的默认本地化设置。
再次打印出本地化设置名称,这次是在尝试应用用户默认设置后得到的结果。如果用户的默认设置与之前的查询结果不同,这里应该能看到变化。
我们先尝试一下本地化后的汉字打印,以及本地的特殊符号打印:
//设置本地化setlocale(LC_ALL, "");//定义一个字符变量 ,由于该变量占俩个字节,所以叫做宽字符,宽字符的变量类型为wchar_t,赋值的变量前面加上L来说明该字符为宽字符,防止程序将其误会为普通的char类型字符wchar_t a = L'本';wchar_t b = L'地';wprintf(L"%lc\n", wc1);wprintf(L"%lc\n", wc2);wprintf(L"%lc\n", L'●');wprintf(L"%lc\n", L'★');
打印可以看到宽字符的宽度是普通字符的俩倍。
单链表和结构体知识:
蛇的身体我们通过单链表来维护。
贪吃蛇游戏实现:
适配本地环境,设定随机数"起点"
//设置适配本地环境setlocale(LC_ALL, "");srand((unsigned int)time(NULL));
我们生成食物需要用到随机数。
初始化游戏:
1. 打印开始界面(欢迎界面,下一步后记得清屏)
通过控制控制台坐标来调整光标位置,再打印文字来实现欢迎界面打印
system("cls")//下一步进入操作介绍前记得清屏
2. 功能介绍
介绍游戏的操作方式,如何控制移动方向,设置速度,实现方式和上一步同理
3. 绘制地图
打印一个墙体,墙用方格子表示,方格子是宽字符,需设置本地化
4. 创建蛇
本质是创建一个链表,然后打印出来
5. 创建食物
在随机的坐标生成食物,本质为创建一个不和蛇相交的结点并打印
注意食物的坐标横坐标应该为偶数
6. 设置游戏的相关信息(开局蛇的方向,分数,食物分数,蛇速,蛇状态为存活)
创建一个结构体来维护这些信息
运行游戏:
1.显示打印当前分数和食物的分值
2.用if else检测刚刚的按键情况并执行相应任务,改变方向、退出、加速、减速、暂停
3.结算蛇走一步
根据结算决定是否进行下一个循环:
啥事没有->继续:蛇身链表头增,尾删实现蛇移动一格
撞自己->结束 :蛇头遍历蛇身体存在相同坐标,此时代表撞了自己,设置蛇状态为撞自己并结束游戏。
撞墙->结束:蛇头遍历墙体坐标存在相同坐标,此时代表撞了墙,设置蛇状态为撞墙并结束游戏。
吃了食物->继续:蛇头坐标与食物坐标相同,蛇身链表不尾删,只头增,同时创建一个新食物,同初始化游戏第五条。
4.程序休息一点点时间(模拟蛇的速度)
Sleep(time);
结束游戏:
1.打印结束原因(你撞墙了,你撞自己了...)
2.释放蛇链表内存
3.询问是否再来一盘