前言
Hello, 今天我们来一起完成一个实战项目:贪吃蛇。
相信大家都不会对这个游戏感到陌生,贪吃蛇游戏是久负盛名的游戏,他和俄罗斯方块,扫雷游戏等游戏位列世界经典游戏之列。这次我们旨在通过实战项目贪吃蛇的实现,从设计到代码的实现来提升我们的编程能力和逻辑运算能力!
游戏效果展示:
贪吃蛇游戏效果演示
1.项目目标:
- 使用C语言在 Windows环境下的控制台模拟实现经典小游戏贪吃蛇
- 实现的基本功能:
- 贪吃蛇地图的绘制
- 蛇吃食物的功能(上、下、左、右方向的控制键来控制蛇的动作)
- 蛇撞到墙后立即死亡
- 蛇撞到自身立即死亡
- 计算得分
- 蛇身发的加速和减速
- 暂停游戏
2.技术要点
C语言函数、枚举、动态内存管理、预处理指令、初步解接触数据结构中的链表。
3.Windows系统工具函数
3.1Win32API介绍
本次实现贪吃蛇会使用一些Win32API的知识,我们想要顺利的完成任务就要去认真掌握其中的一些函数。
Windows这个多作业系统除了协调应用程序的执行、分配管理、资源管理之外,他还是一个好大吃的服务中心,调用这个服务中心的服务(每一种服务就是一种函数)。可以帮助我们达到开启视窗、描绘图像、使用周边设备等目的,由于这些函数的服务对象是一些应用程序(Application),所以百年称之为Applicatino Programming Interface,简称API函数。WIN32API也是Microsoft Windows32w位平台的应用程序编程接口。
3.2控制台程序
平常我们运行起来的黑框程序其实就是控制台程序。
我们可以使用cmd命令来设置控制台窗口的长和宽:设置控制台窗口的大小,30行,100列
mode con cols=100 lines=30
也可以通过命令设置窗口的名字:
title 贪吃蛇
这些能在控制台窗口执行的命令,也可以调用C语言函数system来执行。例如:
#include<stdio.h>
int main()
{
//设置控制台窗⼝的⻓宽:设置控制台窗⼝的⼤⼩,30⾏,100列
system("mode con cols=100 lines=30");
//设置cmd窗⼝名称
system("title 贪吃蛇");
getchar();
return 0;
}
3.3控制平台上的坐标COORD
COORD是WindowsAPI上定义的一个结构体·,表示一个字符在控制台屏幕上的坐标
typedef struct _COORD {
SHORT X;
SHORT Y;
} COORD, *PCOORD;
給坐标赋值:
COORD pos = { 10, 15 };
3.4GetStdHandle
GetStdHandle是一个WindowsAPI函数。它用于从一个特定的标准设备(标准输入、标准输出或者是标准错误)中取得一个句柄(用来标识不同设备的数值),使用这个句柄可以操作设备。
HANDLE GetStdHandle(DWORD nStdHandle);
实例:
HANDLE hOutput = NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
3.5GetConsoleCursorInfo
检索有关指定的控制台的屏幕缓冲区堆的光标大小和课件行的信息
BOOL WINAPI GetConsoleCursorInfo(
HANDLE hConsoleOutput,
PCONSOLE_CURSOR_INFO lpConsoleCursorInfo
)
实例:
HANDLE hOutput = NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息
3.5.1 CONSOLE_CURSOR_INFO
这个结构体,包含控制台光标信息
typedef struct _CONSOLE_CURSOR_INFO {
DWORD dwSize;
BOOL bVisible;
} CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO;
- deSize,有光标填充的字符单元格的百分比。此值介于1到100之间。光标外观会随着变化,范围从完全填充单元格到单元底部的水平线条。
- bVisible,游标的可见性。如果光标可见则此成员为TRUE。
CursorInfo.bVisible = false; //隐藏控制台光标
3.6SetConsoleCursorInfo
设置指定的控制台屏幕缓冲区光标的大小和可见性。
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);//设置控制台光标状态
3.7 SetConsoleCursorPosition
设置制定控制台屏幕缓冲区中光标位置,我们将想要的设置的光标坐标信息放在COOR来行的pos中,调用SetConsoleCursorPosition的函数将光标的位置设定到指定的位置。
BOOL WINAPI SetConsoleCursorPosition(
HANDLE hConsoleOutput,
COORD pos
)
实例:
COORD pos = { 10, 5};
HANDLE hOutput = NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
//设置标准输出上光标的位置为pos
SetConsoleCursorPosition(hOutput, pos);
SetPos:封装一个光标位置的函数
//设置光标的坐标
void SetPos(short x, short y)
{
COORD pos = { x, y };
HANDLE hOutput = NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
//设置标准输出上光标的位置为pos
SetConsoleCursorPosition(hOutput, pos);
}
3.8GetAsyncKeyState
获取按键情况,这个函数的原型如下:
SHORT GetAsyncKeyState(
int vKey
);
将键盘上的每个按键的虚拟键值传递给函数,函数通过返回值来分辨案件的状态。
GetAsyncKeyState的返回值是short类型的,在上一次调用GetAsyncKeyState函数后,如果返回的是16位的short数据,最高位是1,说明按键的状态是按下,如果最高位是0,则按键的状态是抬起;如果最低位被置为1则说明,该按键被按过,否则为0。
如果我们要判断一个案件是否被按过,可以检测GetAsyncKeyState返回值的最低位是否为1.
#define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )
4.贪吃蛇游戏的设计与分析
4.1地图
我们最终想要的贪吃蛇大纲可能就是这个样子,按我们要如何进行的图的布置呢?
这里我们不得不在讲一下控制窗台的知识了,如果想要在控制台的窗口指定位置输出信息,我们得知道该位置的坐标,所以首先介绍一下控制窗口的坐标知识。
控制窗口的坐标只是如下所示,横向的是x轴,从左向右依次增长,纵向是Y轴,从上到下一次增长。
在游戏地图上,我们打印墙体使用宽字符:□ ;打印蛇身我们使用:●;打印食物‘使用宽字符:★’,普通的字符只占一个字节,这类宽字符是占用2个字节。
这里简单的讲一下C语言国际化特性相关的知识,过去C语言并不适合非英语的国家(地区)使用。
C语言最初假定假定字符都是自己的。但是这些假定并不是在世界所有的地方都适用。
后来C鱼啊一年适应到了国际化,C语言标准中不断加入国际化的支持,比如:加入和宽字符的类型wchar_t和宽字符的输入和输出函数,加入<locale.h>头文件,其中提供了允许程序员针对特定地区(通常是国家或者说特定语言的地理地区)调整程序行为的函数。
4.1.1<locale.h>本地化
<locale.h>提供的函数用于控制C语言标准库中对于不同地区会产生的不一样行为的部分·。
在标准可以中,依赖地区的部分有以下几项:
- 数字量的格式
- 货币量的格式
- 字符集
- 日期和时间的表示形式
4.1.2类项
通过修改地区,程序可以改变它的行为来适应世界的不同区域。单区域的改变可能影响库的许多部分其中一部分可能使我们不希望改变的。所以C语言针对不同的类项进行修改,下面的一个宏,指定一个类项:
• LC_COLLATE
• LC_CTYPE
• LC_MONETARY
setlocale(LC_ALL, "C");
• LC_NUMERIC
• LC_TIME
• LC_ALL - 针对所有类项修改
4.1.3 setlocale函数
char* setlocale (int category, const char* locale);
setlocale函数用于修改当前的地区,可以针对一个类想项修改,也可以针对所有的类项修改。
setlocale的·第一个参数可以是前面说明的类项中的一个,那么每次只会影响一个类项,如果第一个·参数是LC_ALL,就会影响所有的类项。
C标准给定的第二个参数仅定义了两种可能得取值:“c” 和“ ”。
在任意程序运行是,都会隐藏式执行调用:
setlocale(LC_ALL, "C");
当地区设置为"c"时,库函数按正常的方式执行,小数点是一个点。
的那个吃呢工序运行起来之后想要改变地区,就只能显示调用setlocale函数。用“”作为第二个参数,调用setlocale函数就可以切回到本地模式,这中模式下程序 会适应本地环境。比如:切换到我们本地模式后就支持宽字符(汉字的输出)等。
setlocale(LC_ALL, " ");//切换到本地环境
4.1.4宽字符的打印
那如果想在屏幕上打印宽字符,怎么打印呢?
#include <stdio.h>
#include<locale.h>
int main() {
setlocale(LC_ALL, ""
wchar_t ch1 = L'●';
wchar_t ch2 = L'⽐';
wchar_t ch3 = L'特';
wchar_t ch4 = L'★';
printf("%c%c\n", 'a', 'b');
wprintf(L"%c\n", ch1);
wprintf(L"%c\n", ch2);
wprintf(L"%c\n", ch3);
wprintf(L"%c\n", ch4);
return 0;
}
从输出来看,我们发现一个普通的字符占一个字符的位置但是打印一个汉字字符,占用2个字符的位置,那么我们如果在贪吃蛇中使用宽字符,就得憨憨处理地图上的坐标计算。
1个坐标1个正常字符
2个坐标可以存放一个宽字符
4.1.5地图坐标
我们假设实现一个棋盘27行,58列的棋盘(行和列可以根据自己的爱好设计修改),再围绕地图画出墙如下:
4.2蛇身和食物
初始化状态,假设蛇的长度为5,蛇身上的·节点是●,在固定的一个坐标处,比如(24,5)处开始出现蛇,连续5个节点。注意:蛇的每个节点的x坐标必须是2的倍数,否则可能出现蛇的一个节点有一半出现在墙体中。另外一半在墙体外的现象,坐标不好对齐。
关于食物,就是在墙体内随机生成一个坐标(X坐标必须是2的倍数),坐标不能和蛇的身体重合,让后打印★