文章目录
一、游戏设计与分析
这个游戏代码我们分成三个文件来完成。
-
Snake.h —— 游戏的头文件
这个文件中包含游戏代码实现所需要的结构体定义以及函数声明。 -
Snake.c —— 游戏的源文件
这个文件中是实现游戏操作的代码。 -
test.c —— 游戏的测试文件
这个文件是用来检验代码能否正常运行。
1、地图
我们最终的贪吃蛇⼤纲要是这个样⼦,那我们的地图如何布置呢?
这⾥不得不讲⼀下控制台窗⼝的⼀些知识,如果想在控制台的窗⼝中指定位置输出信息,我们得知道该位置的坐标,所以⾸先介绍⼀下控制台窗⼝的坐标知识。
控制台窗⼝的坐标如下所⽰,横向的是X轴,从左向右依次增⻓,纵向是Y轴,从上到下依次增⻓。
在游戏地图上,我们打印墙体使⽤宽字符:□,打印蛇使⽤宽字符●,打印⻝物使⽤宽字符★普通的字符是占⼀个字节的,这类宽字符是占⽤2个字节。
(1)、 <locale.h>本地化
<locale.h>提供的函数⽤于控制C标准库中对于不同的地区会产⽣不⼀样⾏为的部分。
在标准中,依赖地区的部分有以下⼏项:
• 数字量的格式
• 货币量的格式
• 字符集
• ⽇期和时间的表⽰形式
类型
通过修改地区,程序可以改变它的⾏为来适应世界的不同区域。但地区的改变可能会影响库的许多部
分,其中⼀部分可能是我们不希望修改的。所以C语⾔⽀持针对不同的类项进⾏修改,下⾯的⼀个宏,
指定⼀个类项:
• LC_COLLATE:影响字符串⽐较函数strcoll() 和strxfrm() 。
• LC_CTYPE:影响字符处理函数的⾏为。
• LC_MONETARY:影响货币格式。
• LC_NUMERIC:影响printf() 的数字格式。
• LC_TIME:影响时间格式strftime() 和wcsftime() 。
• LC_ALL-针对所有类项修改,将以上所有类别设置为给定的语⾔环境。
(2)、 setlocale函数
1 char* setlocale (int category, const char* locale);
setlocale 函数⽤于修改当前地区,可以针对⼀个类项修改,也可以针对所有类项。
setlocale 的第⼀个参数可以是前⾯说明的类项中的⼀个,那么每次只会影响⼀个类项,如果第⼀个参数是LC_ALL,就会影响所有的类项。
C标准给第⼆个参数仅定义了2种可能取值:“C”(正常模式)和""(本地模式)。
1 setlocale(LC_ALL, "C");
当地区设置为"C"时,库函数按正常⽅式执⾏,⼩数点是⼀个点。
当程序运⾏起来后想改变地区,就只能显⽰调⽤setlocale函数。⽤""作为第2个参数,调⽤setlocale函数就可以切换到本地模式,这种模式下程序会适应本地环境。
⽐如:切换到我们的本地模式后就⽀持宽字符(汉字)的输出等。
1. setlocale(LC_ALL, " ");//切换到本地环境
(3)、宽字符的打印
那如果想在屏幕上打印宽字符,怎么打印呢?
宽字符的字⾯量必须加上前缀“L”,否则C语⾔会把字⾯量当作窄字符类型处理。前缀“L”在单引号前⾯,表⽰宽字符,对应wprintf() 的占位符为 %lc 。在双引号前⾯,表⽰宽字符串,对应wprintf() 的占位符为%ls 。
#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"%lc\n", ch1);
wprintf(L"%lc\n", ch2);
wprintf(L"%lc\n", ch3);
wprintf(L"%lc\n", ch4);
return 0;
}
两个普通字符的大小等于一个宽字符的大小
2、游戏分析
(1)、Snake.h
首先,在Snake.h中创建两个结构体:一个蛇的结构体;一个蛇的节点的结构体。
- 蛇的节点的结构体
整个蛇身可以看作是一个单链表,所有节点一次连接起来的。
蛇的一个节点 要包括该点在控制台坐标系上的X轴值和Y轴值,以及该节点的下一个节点
如下:
//蛇身节点类型
typedef struct SnakeNode
{
int x;
int y;
struct SnakeNode* next;
}SnakeNode, * PSnakeNode;
//PSnakeNode是指向该结构体的指针
//eg: PSnakeNode n 等同于 SnakeNode* n
- 蛇身的结构体
我们可以想象一下,将舍生看作是一个链表,游戏过程中要将蛇运动起来,要想完成贪吃蛇的运动,我们需要再蛇身中添加那些元素呢?
整个蛇身要包含 指向蛇头的指针、指向食物节点的指针、蛇的移动方向、游戏状态、应该食物的分数、总成绩、休息时间。
//贪吃蛇
typedef struct Snake
{
PSnakeNode pSnake; //指向蛇头的指针
PSnakeNode pFood; //指向食物节点的指针
enum DIRECTTON dir; //蛇移动的方向
enum GAME_STATUS status; //游戏状态
int food_weight; //一个食物的分数
int score; //总成绩
int sleep_time; //休息时间,时间越短,速度越快,时间越长,速度越慢
}Snake, * PSnake;
完成结构体后,我们还要继续分析结构体中的元素。
- 蛇的移动方向
//蛇移动的方向
enum DIRECTTON
{
up = 1,
down,
left,
right
};
- 游戏状态
//游戏状态
//正常,撞墙,撞自己,正常退出,
enum GAME_STATUS
{
ok,//正常
KILL_BY_WALL,//撞墙
KILL_BY_SELF,//撞自己
END_NORMAL//正常退出
};
(2)、test.c
在测试文件中,想要打印游戏地图,那要先适应本地化模式,所以我们在main函数中使用setlocale函数适配本地环境。
为了方便之后的代码测试,我们在main函数中调用一个自定义函数test01。
void test01()
{
//创建贪吃蛇
Snake snake = {
0 };
//一、初始化游戏
GameStart(&snake);
//二、运行游戏
GameRun(&snake);
//三、结束游戏——善后工作
GameEnd(&snake);
}
int main()
{
//先适配本地环境
setlocale(LC_ALL, "");
test01();
return 0;
}
(3)、Snake.c
接下来,我们简单的将代码的实现分为三部分:
-
初始化游戏GameStart();
1.打印环境界面和功能介绍 2.绘制地图 3.创建蛇 4.创建食物
-
运行游戏GameRun();
1、键盘上的按键情况 2、蛇每走一步的状态 3、蛇上下左右走 4、判断下一个节点是不是食物 5、判断蛇有没有撞墙或者撞到自己
-
结束游戏——善后工作GameEnd();
二、初始化游戏
1、打印环境界面和功能介绍
我们要先获取控制板上光标的信息,对光标进行相应的操作。
这里所要用到的函数我在上一篇文章中已经详细解释过了,所以我们这里直接使用。
详情请参考上一篇文章:link
//隐藏光标,将光标设置到指定位置
//设置面板
system("mode con cols=100 lines=30");
system("title 贪吃蛇");
//获取标准输出设备的句柄
HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);
//定义一个光标信息结构体
CONSOLE_CURSOR_INFO cursor_info = {
0 };
//获取光标信息
GetConsoleCursorInfo(houtput, &cursor_info);
//设置光标可见度
cursor_info.bVisible = false;
//设置光标信息
SetConsoleCursorInfo(houtput, &cursor_info);
我们要定位光标,在相应位置打印相应的文字。
这里可以创建一个函数SetPos,来定位光标
//获取光标位置
v