接上一篇文章介绍完需要使用到的WIN32API的相关知识,本篇文章让我们来开始使用他们来创建我们的贪吃蛇欢迎界面以及游戏所需要的地图。
准备工作:
为了后面我们构建贪吃蛇游戏所需要的各项函数便于观察,同时便于我们的函数声明,在这里我们先创建三个文件,分别为1.Snake.h,2.Snake.c,3.test.c。它们分别用来:
1.用于函数的声明,数值的定义,自定义类型的定义以及头文件的引用。
2.函数功能的实现。
3.对Snake.c中的函数进行引用最终实现贪吃蛇游戏。
同时我们这里分装三个用于整合的函数GameStart(),GameRun(),GameEnd(),分别为游戏的初始化,游戏运行,游戏的善后工作部分,本篇我们来进行初始化工作
一,欢迎与介绍界面的打印
如上图所示,这便是我们要打印的第一部分欢迎界面,下面是实现步骤:
1.1界面大小的设置及本地环境的切换
首先我们要对界面大小进行设置,这里我们设置长度为100宽度为30(Windows控制台主机的长宽比约为2 :1)。
system("mode con cols=100 lines=30");
紧接着我们要对C进行本地化处理,这里我们要介绍一个新函数setlocale,定义如下:
char* setlocale (int category, const char* locale);
setlocale 函数用于修改当前地区,可以针对⼀个类项修改,也可以针对所有类项。 setlocale 的第⼀个参数可以是前面说明的类项中的一个,那么每次只会影响⼀个类项,如果第⼀个参 数是LC_ALL,就会影响所有的类项。 C标准给第⼆个参数仅定义了2种可能取值:"C"(正常模式)和 ""(本地模式)。
setlocale 的返回值是⼀个字符串指针,表示已经设置好的格式。如果调⽤失败,则返回空指针 NULL 。 setlocale() 可以⽤来查询当前地区,这时第⼆个参数设为 NULL 就可以了。
setlocale(LC_ALL, "");//切换到本地环境
切换完本地环境后,我们需要进行宽字符的打印,这时候我们就需要用到wprintf函数进行打印宽字符:
wprintf(L"%lc\n", L'P');
宽字符打印必须加上前缀 L ,否则C语言会把字面量当作窄字符类型处理。前缀 L 在单引号前 面,表示宽字符,宽字符的打印使⽤ wprintf ,对应 wprintf() 的占位符为 %lc ;在双引号前面,表示宽字符串,对应 wprintf() 的占位符为 %ls 。
1.2界面光标的隐藏及光标位置调整打印文字
首先让我们用上节学的知识来隐藏光标,实现代码如下(注意这里false需要用到头文件stdbool.h):
HANDLE houtput = NULL;
houtput = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO CursorInfo = { 25,false };
SetConsoleCursorInfo(houtput, &CursorInfo);
接着让我们把输出光标移到控制台的中间位置(这里我们直接创建一个函数以便于我们后面可以随时的移动光标):
void Set_Pos(short a,short b)
{
HANDLE houtput = NULL;
houtput = GetStdHandle(STD_OUTPUT_HANDLE);
COORD pos = { a,b };
SetConsoleCursorPosition(houtput, pos);
}
1.3打印欢迎界面
void Welcom_Menu()
{
Set_Pos(35, 10);
wprintf(L"欢迎来到贪吃蛇小游戏");
Set_Pos(35, 20);
system("pause");
system("cls");
}
1.4打印介绍界面
void Welcom_Menu()
{
Set_Pos(35, 10);
wprintf(L"欢迎来到贪吃蛇小游戏");
Set_Pos(35, 20);
system("pause");
system("cls");
Set_Pos(35, 10);
wprintf(L"按 ↑ ↓ ← →键操控贪吃蛇移动\n");
Set_Pos(35, 11);
wprintf(L"按小键盘‘1’或‘2’键加速或减速\n");
Set_Pos(35, 12);
wprintf(L"Tips:加速吃食物有额外分数加成");
Set_Pos(35, 20);
system("pause");
system("cls");
}
二,地图的创建
本篇文章设置的棋盘27⾏,58列(行和列可以根据自己的情况修改),然后我们先定义墙体所需要用到的特殊字符‘□’:
#define WALL L'□'
接下来我们来打印地图,结果如下:
实现代码如下:
void GameMap()
{
Set_Pos(0, 0);
int i = 0;
for (; i < 58; i += 2)
{
wprintf(L"%lc", WALL);
}
Set_Pos(0, 26);
for (i = 0; i < 58; i += 2)
{
wprintf(L"%lc", WALL);
}
for (i = 1; i < 26; i++)
{
Set_Pos(0, i);
wprintf(L"%lc", WALL);
}
for (i = 1; i < 26; i++)
{
Set_Pos(56, i);
wprintf(L"%lc", WALL);
}
}
三,贪吃蛇蛇身的初始化
3.1蛇身信息的信息维护
在之后的代码实现过程中,如果我们的蛇身信息没有包装在一块,而是零散的分开处理,这样无疑会增大我们的工作量,所以我们自定义一个蛇的结构体,我的自定义结构如下:
typedef struct SnakeNode
{
int x;
int y;
struct SnakeNode* next;
}SnakeNode, * pSnakeNode;
typedef struct Snake
{
pSnakeNode _pSnake;//维护整条蛇的指针
pSnakeNode _pFood;//维护⻝物的指针
enum DIRECTION _Dir;//蛇头的⽅向,默认是向右
enum GAME_STATUS _Status;//游戏状态
int _Socre;//游戏当前获得分数
int _foodWeight;//默认每个⻝物10分
int _SleepTime;//每⾛⼀步休眠时间
}Snake, * pSnake;
当然,这里只是最基础的需要维护的蛇身信息的整理,如果读者想要实现更多功能,可以以此为基础去增添结构体的成员。但我们仔细看图,发现状态和方向我们是可以列举出来的,所以我们这里使用枚举常量来整合它们:
enum DIRECTION
{
UP = 1,
DOWN,
LEFT,
RIGHT
};
enum GAME_STATUS
{
OK,//正常运⾏
KILL_BY_WALL,//撞墙
KILL_BY_SELF,//咬到⾃⼰
END_NOMAL//正常结束
};
我们这个游戏中目前只有一条蛇,所以我们可以直接在最开始将维护蛇身的指针定义出来:
Snake snake = { 0 };
GameStart(&snake);
3.2蛇身信息的初始化
由于控制台的长宽比近似为2 :1,所以我们使用宽字符打印蛇身,这里我们使用头插法以链表的形式初始化一个长度为5的蛇身,同时设置蛇的初始移动方向为右,一个食物的得分为10,总成绩初始化为0,起始状态设置为正常运行状态’OK‘:
void SnakeInit(pSnake ps)
{
pSnakeNode cur = NULL;
int i = 0;
for (i = 0; i < 5; i++)
{
cur = (pSnakeNode)mallloc(sizeof(SnakeNode));
if (cur == NULL)
{
perror("SnakeInit:malloc;cur()");
return;
}
cur->next = NULL;
cur->x = POS_X + 2 * i;
cur->y = POS_Y;
if (ps->_pSnake == NULL)
{
ps->_pSnake = cur;
}
else
{
cur->next = ps->_pSnake;
ps->_pSnake = cur;
}
}
cur = ps->_pSnake;
while (cur)
{
Set_Pos(cur->x, cur->y);
wprintf(L"%lc", BODY);
cur = cur->next;
}
ps->_Dir = RIGHT;
ps->_foodWeight = 10;
ps->_SleepTime = 300;
ps->_Socre = 0;
ps->_Status = OK;
}
3.3食物位置的初始化
我们都知道,在贪吃蛇的游戏过程中,蛇每吃掉一个食物自身长度就会增加一格,所以既然蛇身的维护我们使用链表,那食物信息的维护我们也使用malloc来开辟空间储存信息。
除此之外,食物的信息也要在随机位置生成,不能生成在墙体外或墙体上,不能与蛇身位置重叠,同时需要确保x坐标为2的倍数(因为我们用的是宽字符打印的蛇身),以下为我的实现代码(记得在GameStart函数的最前面设置srand使他的随机值随着时间的变化而变化):
int x = 0;
int y = 0;
again:
do
{
x = rand()%53 + 2;
y = rand()%24 + 1;
} while (x % 2 != 0);
pSnakeNode cur = ps->_pSnake;
while (cur)
{
if (cur->x == x && cur->y == y)
goto again;
cur = cur->next;
}
pSnakeNode cur2 = (pSnakeNode)malloc(sizeof(SnakeNode));
if (cur2 == NULL)
{
perror("malloc():FoofInit:cur2:");
return;
}
cur2->next = NULL;
cur2->x = x;
cur2->y = y;
Set_Pos(x, y);
wprintf(L"%lc", FOOD);
ps->_pFood = cur2;
cur2 = NULL;
OK,到这里我们的初始化工作就完成了,接下来我们要让这条蛇动起来,我们下篇文章见。