文章目录
游戏背景介绍
贪吃蛇是一个非常经典的小游戏,笔者曾今在古早的案件手机,mp4上面玩过这款游戏,今天就让我们使用C语言一起复刻这个简单的小游戏吧~,好玩简单-
实现目标
在这个游戏中,我们需要控制一条可以上下左右移动的小蛇,在指定的墙体内进行移动,吃到食物后,蛇的身体会变长,当蛇撞墙或者撞到自己的身体的时候,游戏结束,我们需要实现的功能有:
- 蛇的移动
- 食物的生成
- 蛇的身体的增长
- 游戏结束的判断
- 游戏结束后的处理
适合人群
这是我学习完C语言语法和顺序表,链表之后的一个小项目,也同样适合像我一样刚学完C语言的同学,通过这个项目,巩固一下C语言的基础知识,也可以学习一下C语言的一些高级知识,比如Windows API的使用,宽字符的打印等等
所需技术
- C语言基础
- 链表
- 简单的Windows API
- 动态内存分配
- 等
浅玩Window API
什么是API
API全称Application Programming Interface,翻译过来就是应用程序接口,是一组预先定义的函数,类,协议的集合,这些函数,类,协议可以被其他程序调用,用来实现一些功能,比如Windows API就是用来实现Windows系统的一些功能的
上面说的有些复杂,我们可以把API想象成一个工厂,我们只需要知道向工厂输入什么,工厂会输出什么,而不需要知道工厂内部是怎么实现的
比如你向一个面包工厂输入了一些小麦,工厂会给你输出一些面包,而工厂内部可能是用了一些机器,工人等等来实现的,但是你不需要知道这些,你只需要知道你给他小麦,他给你面包就行了
控制台程序
控制台程序其实就是我们平时编译完文件之后运行打开的那个黑框框,我们可以在这个黑框框里面输入一些命令,然后程序会给我们输出一些结果,这个黑框框就是一个控制台,我们可以通过控制台程序来和用户进行交互
窗口大小,名称设置
既然是贪吃蛇小游戏,那么我们需要一个固定的窗口和一个有趣的名字,我们可以怎么做呢?
可以使用cmd命令在控制台程序中设置窗口的大小,名称
mode con cols=100 lines=30
title 贪吃蛇
- mode con cols=100 lines=30 设置窗口的大小为100列,30行
- title 贪吃蛇 设置窗口的名称为贪吃蛇
但是,我们只希望贪吃蛇程序运行后自动设置窗口的大小和名称,而不是让用户手动输入这些命令,因此我们可以使用system函数来调用这些命令,让程序自动设置窗口的大小和名称
#include <stdlib.h>
int main()
{
system("mode con cols=100 lines=30");
system("title 贪吃蛇");
return 0;
}
Handle(句柄)
如果我们想要对控制台的一些属性进行设置,比如设置光标的位置,设置控制台的颜色等等,我们就需要使用句柄来进行操作,我们可以把句柄想象成是控制台大哥的身份标识,只有知道大哥的身份标识才能找到大哥并更改他的一些属性
获取句柄
我们可以使用GetStdHandle函数来获取控制台的句柄,这个函数的原型是
HANDLE GetStdHandle(DWORD nStdHandle);
我们可以使用一个HANDLE类型变量来接受返回值
坐标结构体
控制台上面的每一个字符都有一个坐标,
坐标是从左上角开始计算的,左上角的坐标是(0,0),向右是x轴正方向,向下是y轴正方向
我们可以使用一个结构体来表示这个坐标
COORD是Windows API中的一个结构体,定义如下
typedef struct _COORD {
SHORT X;
SHORT Y;
} COORD, *PCOORD;
设置光标位置
我们平时在使用printf打印字符的时候,每打印一个字符,光标都会自动向后移动一个位置,我们可以使用SetConsoleCursorPosition函数来手动设置光标的位置
BOOL SetConsoleCursorPosition(
HANDLE hConsoleOutput,
COORD dwCursorPosition
);
eg:
#include <windows.h>
int main()
{
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); // 获取控制台句柄
COORD pos = {
10, 10}; // 设置坐标
SetConsoleCursorPosition(hOut, pos); // 设置光标位置
printf("hello world");
return 0;
}
光标属性
我们平时运行控制台程序的时候,会发现光标是一个闪烁的小方块,那我们有没有什么办法可以让小方块变大变小或是直接隐藏呢?
答案肯定是有哒
我们先来介绍一下光标的两个属性
- bVisible : 是否可见
- dwSize : 光标的大小 当这个值是100的时候,光标是一个小方块█,这个值也就是显示这个方块的百分比,比如50就是显示一半的方块
获取光标属性
我们可以使用GetConsoleCursorInfo函数来获取光标的属性
BOOL GetConsoleCursorInfo(
HANDLE hConsoleOutput,
PCONSOLE_CURSOR_INFO lpConsoleCursorInfo
);
eg:
#include <windows.h>
int main()
{
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); // 获取控制台句柄
CONSOLE_CURSOR_INFO cursorInfo;
GetConsoleCursorInfo(hOut, &cursorInfo;);
printf("bVisible:%d, dwSize:%d\n", cursorInfo.bVisible, cursorInfo.dwSize);
return 0;
}
大家可以自行运行试一试
设置光标属性
在贪吃蛇游戏中,我们肯定希望没有光标的出现,因此我们可以使用SetConsoleCursorInfo函数来设置光标的属性
BOOL SetConsoleCursorInfo(
HANDLE hConsoleOutput,
const CONSOLE_CURSOR_INFO *lpConsoleCursorInfo
);
eg:
#include <windows.h>
int main()
{
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); // 获取控制台句柄
CONSOLE_CURSOR_INFO cursorInfo;
cursorInfo.bVisible = 0; // 设置光标不可见 也可以写flase
cursorInfo.dwSize = 100; // 设置光标大小
SetConsoleCursorInfo(hOut, &cursorInfo);
return 0;
}
按键信息获取
我们需要使用↑↓←→来控制蛇的移动,因此我们可以使用GetAsyncKeyState函数来获取按键信息
SHORT GetAsyncKeyState(
int vKey
);
在返回的值中,如果最高位是1,表示这个键正在被按下,如果最低位是1,表示这个键被按过
我们可以使用一个预定义的宏来判断这个键是否被按下
#define KEY_PRESS(VK) (GetAsyncKeyState(VK) & 0x01 ? 1 : 0)
这个大家肯定看的懂,如果不懂的话可以去学习一下预编译和位运算
贪吃蛇游戏设计
我们可以把贪吃蛇游戏分为三个部分:
- 游戏前的初始化,包括窗口的设置,光标的设置,欢迎界面打印,地图的初始化,蛇的初始化,食物的初始化等等
- 游戏中的循环,包括蛇的移动,食物的生成,蛇的身体的增长,游戏结束的判断等等
- 游戏结束后的处理,包括释放资源,打印游戏结束的信息等等
补充:当程序需要实现按任意键继续的时候,我们使用system(“pause”)函数即可
游戏前的初始化
设置窗口的大小和名称
void CmdInit(void)
{
// 设置控制台窗口大小 100列 30行
system("mode con cols=100 lines=30");
// 设置控制台名称
system("title snake");
}
本地化设置
正常在C语言中,我们使用的是ASCII字符集,他只使用了一个字节来表示一个字符,而在不同的国家和地区,字符集是不一样的,因此我们可以使用setlocale函数来设置字符集
char *setlocale(int category, const char *locale);
在C标准库中,我们可以更改的地区设置有以下这些:
- LC_ALL:所有的地区设置
- LC_COLLATE:字符串比较
- LC_CTYPE:字符分类和转换
- LC_MONETARY:货币格式
- LC_NUMERIC:非货币数字格式
- LC_TIME:时间格式
我么可以使用setlocale函数来设置地区,比如把地区设置为当前地区
#include <locale.h>
int main()
{
setlocale(LC_ALL, "");
return 0;
}
宽字符
Waht is 宽字符
宽字符是指一个字符占用两个字节的字符,中文以及一些特殊字符都是宽字符,
而且宽字符在控制台中是占用两个x坐标的
graph LR
A[宽字符] --> B[占用两个坐标位置] --> D[普通字符:█]
B --> E[宽字符:██]
A --> C[占用两个字节]
宽字符的打印
在C语言中,我们可以使用wprintf函数来打印宽字符,在打印宽字符之前,我们需要进行本地化设置
int wprintf(const wchar_t *format, ...);
eg:
#include <stdio.h>
int main()
{
setlocale(LC_ALL, "");
wchar_t str[] = L"你好,世界\n";
wprintf(L"%s", str);
return 0;
}
光标的设置
- 定义一个变量来接受控制台的句柄
- 定义一个变量来接受光标的属性
- 改变光标的属性
- 通过SetConsoleCursorInfo函数,输入句柄和光标属性,设置光标的属性
欢迎界面打印
这里我们需要设置光标位置并打印所需信息,因此我们可以封装一个函数来快捷地设置光标位置
void SetPos(int x, int y)
{
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
COORD pos = {
x, y};
SetConsoleCursorPosition(hOut, pos);
}
然后我们就可以打印欢迎信息和游戏规则了
// 打印欢迎信息
void WelcomeToGame(void)
{
SetPos(45, 10);
wprintf(L"欢迎来到贪吃蛇游戏");
// 暂停
SetPos(45, 20);
system("pause");
}
// 游戏介绍
void GameIntroduction(void)
{
// 清屏
system("cls");
SetPos(45, 10);
wprintf(L"游戏介绍:");
SetPos(45, 12);
wprintf(L"1. 使用↑,↓,←,→控制蛇的移动");
SetPos(45, 14);
wprintf(L"2. 吃到食物蛇的长度加1");
SetPos(45, 16);
wprintf(L"2. F3加速, F4减速");
SetPos(45, 18);
wprintf(L"3. 空格暂停");
SetPos(45, 20);
wprintf(L"4. Esc退出游戏");
// 暂停
SetPos(45, 22);
system("pause");
}
地图绘制
我们这里使用□来表示墙体,然后运用宽字符的打印知识来绘制一个27 * 27的地图
// 地图绘制
// 上(0,0) - (56,0)
// 下(0,26) - (56,26)
// 左(0,0) - (0,26)
// 右(56,0) - (56,26)
// 注意打印的是宽字符 占两个x坐标 因此左右打印的时候要每打印一个x坐标加2
void MapDraw(void)
{
// 清屏
system("cls");
// 上墙
for (int i = 0; i < 57; i+=2)
{
SetPos(i, 0);
wprintf(L"%c", WALL);
}
// 下墙
for (int i = 0; i < 57; i+=2)
{
SetPos(i, 26);
wprintf(L"%c", WALL);
}
// 最上面和最下面已经打印过了
// 左墙
for (int i = 1