目录
目录
3.4.2打印欢迎界面和功能介绍——WelcomeToGame
3.5.4.2判断下一个位置是否是食物——NextIsFood
3.5.4.4不是食物,吃掉食物,尾巴删除一节——NoFood
贪吃蛇,一种很贪吃的蛇
贪吃蛇是一款久负盛名的游戏,它也和俄罗斯方块,扫雷等游戏位列经典游戏的行列。
接下来,我们用500行代码实现这个游戏!
技术要点:
C语⾔函数、枚举、结构体、动态内存管理、预处理指令、链表、Win32API等。
1.游戏成品展示
_贪吃蛇
2.准备工作
2.1第一步:控制台设置
代码运行起来后,我们有一个调试控制台窗口:
写上一段代码:运行
2.2第二步:Win32API函数介绍
2.2.1Win32API简介
Windows这个多作业系统除了协调应⽤程序的执⾏、分配内存、管理资源之外,它同时也是⼀个很⼤的服务中⼼,调⽤这个服务中⼼的各种服务(每⼀种服务就是⼀个函数),可以帮应⽤程序达到开启视窗、描绘图形、使⽤周边设备等⽬的,由于这些函数服务的对象是应⽤程序(Application),所以便称之为Application Programming Interface,简称API函数。WIN32API也就是Microsoft Windows32位平台的应⽤程序编程接⼝。
在贪吃蛇项目中,我们需要使用到5个API个函数:
GetStdHandle、GetConsoleCursorInfo、SetConsoleCursorInfo、SetConsoleCursorPosition、GetAsyncKeyState
我们来一一领略
2.2.2GetStdHandle
GetStdHandle是⼀个Windows API函数。它⽤于从⼀个特定的标准设备(标准输⼊、标准输出或标准错误)中取得⼀个句柄(⽤来标识不同设备的数值),使⽤这个句柄可以操作设备。
在贪吃蛇项目中,我们隐藏光标、打印地图等一系列操作,都是在我们的输出设备——屏幕上进行。因此,我们需要得到对我们屏幕的操作权限,也就是句柄。
2.2.3GetConsoleCursorInfo
检索有关指定控制台屏幕缓冲区的光标⼤⼩和可⻅性的信息
BOOL WINAPI GetConsoleCursorInfo(
HANDLE hConsoleOutput,
PCONSOLE_CURSOR_INFO lpConsoleCursorInfo
);
PCONSOLE_CURSOR_INFO 是指向 CONSOLE_CURSOR_INFO 结构的指针,该结构接收有关主机游标
_CONSOLE_CURSOR_INFO是一个结构体:
typedef struct _CONSOLE_CURSOR_INFO {
DWORD dwSize;//由游标填充的字符单元的百分比
BOOL bVisible;//游标的可见性
} CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO;
2.2.4SetConsoleCursorInfo
相信各位发现了,GetConsoleCursorInfo和SetConsoleCursorInfo,一个是Get,一个是Set
我们获取了屏幕上光标的信息后,可以对它的dwSize和bVisible两个成员进行修改,因为我们
要隐藏光标,所以把成员bVisible设置为false即可隐藏光标。设置完成后,需要使用SetConsoleCursorInfo设置标准输出设备,才能产生效果
代码如下:
2.2.5SetConsoleCursorPosition
在游戏实现的地图打印和信息打印的环节,我们需要定位光标的位置,SetConsoleCursorPosition
应运而生!
SetConsoleCursorPosition:
设置指定控制台屏幕缓冲区中的光标位置,我们将想要设置的坐标信息放在COORD类型的pos中,调⽤SetConsoleCursorPosition函数将光标位置设置到指定的位置。
BOOL WINAPI SetConsoleCursorPosition(
HANDLE hConsoleOutput,
COORD pos
);
这里简单介绍一下COORD:
COORD是Windows API中定义的⼀个结构体,表⽰⼀个字符在控制台屏幕幕缓冲区上的坐标,坐标系(0,0)的原点位于缓冲区的顶部左侧单元格。
COORD类型的声明:
typedef struct _COORD {
SHORT X;
SHORT Y;
} COORD, *PCOORD;
于是,我们可以封装一个函数,定位我们光标的位置:
//定位光标位置
void SetPos(short x, short y)
{
//获得标准输出设备的句柄
HANDLE houtput = NULL;
houtput = GetStdHandle(STD_OUTPUT_HANDLE);//定位光标位置
COORD pos = { x,y };
SetConsoleCursorPosition(houtput, pos);
}
这是我们实现贪吃蛇游戏的一个利器。
2.2.6GetAsyncKeyState
获取按键情况,GetAsyncKeyState的函数原型如下:
SHORT GetAsyncKeyState(
int vKey
);
将键盘上每个键的虚拟键值传递给函数,函数通过返回值来分辨按键的状态。
GetAsyncKeyState 的返回值是short类型,在上⼀次调⽤ GetAsyncKeyState 函数后,如果
返回的16位的short数据中,最⾼位是1,说明按键的状态是按下,如果最⾼是0,说明按键的状态是抬起;如果最低位被置为1则说明,该按键被按过,否则为0。
如果我们要判断⼀个键是否被按过,可以检测GetAsyncKeyState返回值的最低值是否为1。
在我们实现的贪吃蛇游戏中,我们无须一直按着按键,只需判断方向键是否按过,以此来改变蛇的运动方向即可。因此,我们关注GetAsyncKeyState返回值的最低位的值是否为1。
参考:虚拟键码
到此为止,我们就把贪吃蛇项目设计的5个WIN32API介绍完毕!!!
我们离胜利进了一步!!!
2.3第三步:setlocale函数.宽字符打印
在游戏地图上,我们打印墙体使⽤宽字符:□,打印蛇使⽤宽字符●,打印⻝物使⽤宽字符★
普通的字符是占⼀个字节的,这类宽字符是占⽤2个字节。
而我们想打印宽字符,就用到setlocale函数切换到本地模式,才能达成目的。
setlocale函数声明:
char* setlocale (int category, const char* locale);
这里我们做对category(类项)做一下简单介绍:
通过修改地区,程序可以改变它的⾏为来适应世界的不同区域。但地区的改变可能会影响库的许多部分,其中⼀部分可能是我们不希望修改的。所以C语⾔⽀持针对不同的类项进⾏修改,下⾯的⼀个宏,指定⼀个类项:
• LC_COLLATE:影响字符串⽐较函数 strcoll() 和 strxfrm() 。
• LC_CTYPE:影响字符处理函数的⾏为。
• LC_MONETARY:影响货币格式。
• LC_NUMERIC:影响 printf() 的数字格式。
• LC_TIME:影响时间格式 strftime() 和 wcsftime() 。
• LC_ALL:针对所有类项修改,将以上所有类别设置为给定的语⾔环境。
setlocale函数⽤于修改当前地区,可以针对⼀个类项修改,也可以针对所有类项。
setlocale的第⼀个参数可以是前⾯说明的类项中的⼀个,那么每次只会影响⼀个类项,如果第⼀个参数是LC_ALL,就会影响所有的类项。
C标准给第⼆个参数仅定义了2种可能取值:"C"(正常模式)和" "(本地模式)。
在任意程序执⾏开始,都会隐藏式执⾏调⽤:
setlocale(LC_ALL, "C");
当地区设置为"C"时,库函数按正常⽅式执⾏,⼩数点是⼀个点。
当程序运⾏起来后想改变地区,就只能显⽰调⽤setlocale函数。⽤" "作为第2个参数,调⽤setlocale函数就可以切换到本地模式,这种模式下程序会适应本地环境。
⽐如:切换到我们的本地模式后就⽀持宽字符(汉字)的输出等。
setlocale(LC_ALL, " "); //切换到本地环境
那如果想在屏幕上打印宽字符,怎么打印呢?
宽字符的字⾯量必须加上前缀“L”,否则C语⾔会把字⾯量当作窄字符类型处理。前缀“L”在单引
号前⾯,表⽰宽字符,对应 wprintf() 的占位符为 %lc ;在双引号前⾯,表⽰宽字符串,对应
wprintf() 的占位符为 %ls 。
演示:
3.贪吃蛇游戏实现
3.1游戏大纲
我们创建三个文件:test.c、snake.c和snake.h
test.c:包含游戏的整体逻辑
snake.h:包含所有头文件,定义常量,类型声明,函数声明
snake.c:实现snake.h中声明的函数
主逻辑分为3个过程:
• 游戏开始(GameStart)完成游戏的初始化
• 游戏运⾏(GameRun)完成游戏运⾏逻辑的实现
• 游戏结束(GameEnd)完成游戏结束的说明,实现资源释放
3.2snake.h:符号定义.类型声明.头文件包含
我们先在snake.h的头文件中,包含头文件,定义符号,声明类型:
3.3test.c:构建游戏整体逻辑
我们把游戏整体逻辑封装到test()中:
有的功能我们直接在三个阶段的函数主体中实现,有的功能我们另外封装成函数去实现:
例如,GameStart函数中,第一个功能"隐藏光标,设置窗口",我们直接在GameStart函数主体中实现;而第二个功能“打印欢迎界面和功能介”,我们封装成了WelcomeToGame函数另外去实现。
接下来,我们在snake.h中声明函数,再到snake.c的文件中一一实现即可。
当然,在snake.c中,我们不要忘记包含我们"snake.h"的头文件。
3.4游戏开始(GameStart)
1.隐藏光标,设置窗口
2.打印欢迎界面和功能介绍——WelcomeToGame
3.绘制地图——CreateMap
4.创建蛇,初始化游戏信息——InitSnake
5.创建食物——CreateFood
以上,我们在GameStart的函数主体中另外封装了4个函数。
在snake.h中声明我们要实现的函数:
接下来我们到snake.c的文件中实现:
GameStart框架
3.4.1隐藏光标,设置窗口
3.4.2打印欢迎界面和功能介绍——WelcomeToGame
运行:
3.4.3绘制地图——CreateMap
3.4.4创建蛇,初始化游戏信息——InitSnake
3.4.5创建食物——CreateFood
1.x坐标必须为2的倍数:
2.
x坐标的范围:2~54
y坐标的范围:1~25
3.x和y的坐标不能和蛇身节点的坐标冲突:
rand函数的调用,需要在主函数main中调用srand,用来初始化生成随机数的种子:
GameStart完整代码
3.5游戏运行(GameRun)
1.右侧打印帮助信息——PrintHelpInfo
2.打印当前已获得总分和食物的分数
3.获取按键状态
4.根据按键状态移动蛇——SnakeMove
4.1根据蛇头的坐标和方向,计算下一个节点的坐标
4.2判断下一个位置是否是食物——NextIsFood
4.3是食物就吃掉——EatFood
4.4不是食物,吃掉食物,尾巴删除一节——NoFood
4.5判断是否撞墙——KillByWall
4.6判断是否撞上自己——KillBySelf
5.循环2~4,直到游戏状态不为OK
以上,我们在GameRun函数的主体中另外封装了2个函数,而在SnakeMove函数的主体中还另外封装了4个函数。
先在snake.h中声明我们要实现的函数:
接下来我们到snake.c的文件中实现:
GameRun框架
3.5.1右侧打印帮助信息——PrintHelpInfo
3.5.2打印当前已获得总分和食物的分数
3.5.3获取按键状态
要改变方向需要满足两个条件:1.按下按键;2.不能与当前的方向相反。
比如,蛇的方向是向右,你按下←不能转向,否则会自己撞上自己。
3.5.4根据按键状态移动蛇——SnakeMove
SnakeMove框架
3.5.4.1根据蛇头的坐标和方向, 计算下一个节点的坐标
3.5.4.2判断下一个位置是否是食物——NextIsFood
3.5.4.3是食物就吃掉——EatFood
注意把食物的分数加到总分上,而且要重新创建食物
3.5.4.4不是食物,吃掉食物,尾巴删除一节——NoFood
要注意把最后一个节点释放掉,同时把倒数第二个节点的next指针置为NULL
3.5.4.5判断是否撞墙——KillByWall
蛇头的坐标若为:
(0,0)
(0,26)
(56,0)
(56,26)
就是撞墙了,把游戏状态修改为KILL_BY_WALL
3.5.4.6判断是否撞上自己——KillBySelf
从第二个蛇身节点开始遍历,判断蛇头坐标与蛇身坐标是否相同
若相同,就是撞上了自己,游戏状态修改为KILL_BY_SELF
SnakeMove完整代码
3.5.5循环2~4,直到游戏状态不为OK
GameRun完整代码
3.6游戏结束(GameEnd)
1.打印游戏结束的原因
2.销毁蛇身节点
我们直接在GameEnd函数的主体中,实现以上两个功能。
先在snake.h中声明我们要实现的函数:
接下来我们到snake.c的文件中实现:
GameEnd框架
3.6.1打印游戏结束的原因
3.6.2.销毁蛇身节点
GameEnd完整代码
3.7循环游戏
来到test.c的文件中,完成我们的最后一步!
4.贪吃蛇完整参考代码
snake.h
#define _CRT_SECURE_NO_WARNINGS
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <time.h>
#include <stdbool.h>
//定义墙体.蛇身.食物的符号
#define WALL L'□'//墙体
#define BODY L'●'//蛇身
#define FOOD L'★'//食物
//定义蛇的坐标
#define POS_X 24
#define POS_Y 5
//类型的声明
//蛇身节点
typedef struct SnakeNode
{
int x;
int y;
struct SnakeNode* next;
}SnakeNode, *pSnakeNode;
//typedef SnakeNode* pSnakeNode;
enum DIRECTION
{
UP = 1,
DOWN,
LEFT,
RIGHT
};
enum GAME_STATUS
{
OK,
KILL_BY_WALL,//撞墙
KILL_BY_SELF,//撞到自己
END_NORMAL//正常退出
};
//贪吃蛇
typedef struct Snake
{
pSnakeNode pSnake;//指向蛇头的指针
pSnakeNode pFood;//指向食物的指针
enum DIRECTION dir;//蛇的方向
enum GAME_STATUS status;//游戏状态
int sleep_time;//休眠时间,时间越短,速度越快,时间越长,速度越慢
int score;//总分数
int foodWeight;//一个食物的分数
}Snake,*pSnake;
//定位光标位置
void SetPos(short x, short y);
//游戏的初始化
void GameStart(pSnake ps);
//1.隐藏光标,设置窗口
//2.打印欢迎界面和功能介绍——WelcomeToGame
void WelcomeToGame();
//3.绘制地图——CreateMap
void CreateMap();
//4.创建蛇,初始化游戏信息——InitSnake
void InitSnake(pSnake ps);
//5.创建食物——CreateFood
void CreateFood(pSnake ps);
//游戏运行
void GameRun(pSnake ps);
//1.右侧打印帮助信息——PrintHelpInfo
void PrintHelpInfo();
//2.打印当前已获得总分和食物的分数
//3.获取按键状态
//4.根据按键状态移动蛇——SnakeMove
void SnakeMove(pSnake ps);
// 4.1根据蛇头的坐标和方向,计算下一个节点的坐标
// 4.2判断下一个位置是否是食物——NextIsFood
int NextIsFood(pSnakeNode pn, pSnake ps);
// 4.3是食物就吃掉——EatFood
void EatFood(pSnakeNode pn, pSnake ps);
// 4.4不是食物,吃掉食物,尾巴删除一节——NoFood
void NoFood(pSnakeNode pn, pSnake ps);
// 4.5判断是否撞墙——KillByWall
void KillByWall(pSnake ps);
// 4.6判断是否撞上自己——KillBySelf
void KillBySelf(pSnake ps);
//5.循环2~4,直到游戏状态不为OK
//游戏结束——善后工作
void GameEnd(pSnake ps);
//1.打印游戏结束的原因
//2.销毁蛇身节点
snake.c
#define _CRT_SECURE_NO_WARNINGS
#include "snake.h"
void SetPos(short x, short y)
{
HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);//获取标准输出设备的句柄
COORD pos = { x,y };//定义光标位置
SetConsoleCursorPosition(houtput, pos);//设置光标位置
}
//2.打印欢迎界面和功能介绍——WelcomeToGame
void WelcomeToGame()
{
//打印欢迎界面
SetPos(40,15);//定位光标位置
printf("欢迎来到贪吃蛇小游戏");
SetPos(40, 25);//让"按任意键继续..."出现的位置美观
system("pause");
system("cls");
//打印功能介绍
SetPos(25, 12);
printf("用 ↑. ↓ . ← . → 来控制蛇的移动,按F3加速,F4减速\n");
SetPos(25, 13);
printf("加速能够得到更高的分数\n");
SetPos(40, 25);//让"按任意键继续..."出现的位置美观
system("pause");
system("cls");
}
//3.绘制地图——CreateMap
void CreateMap()
{
//绘制27行,58列的地图
int i = 0;
//上
SetPos(0, 0);
for (i = 0; i < 29; i++)//一个宽字符'□'占两列,58/2=29
{
wprintf(L"%lc", WALL);
}
//下
SetPos(0, 26);
for (i = 0; i < 29; i++)
{
wprintf(L"%lc", WALL);
}
//左
for (i = 1; i <= 25; i++)
{
SetPos(0, i);
wprintf(L"%lc", WALL);
}
//右
for (i = 0; i <= 25; i++)
{
SetPos(56, i);
wprintf(L"%lc", WALL);
}
}
//4.创建蛇,初始化游戏信息——InitSnake
void InitSnake(pSnake ps)
{
//创建5个蛇身节点
pSnakeNode cur = NULL;
int i = 0;
for (i = 0; i < 5; i++)
{
cur = (pSnakeNode)malloc(sizeof(SnakeNode));
if (cur == NULL)
{
perror("InitSnaek::malloc()");
return;
}
//节点创建成功,给蛇身节点的坐标赋值.next指针置空
cur->x = POS_X + 2 * i;//24 26 28 30 32
cur->y = POS_Y; //5 5 5 5 5
cur->next = NULL;
//头插法——把蛇身节点插入链表中
if (ps->pSnake == NULL)//链表是空链表
{
ps->pSnake = cur;
}
else//链表是非空链表
{
cur->next = ps->pSnake;
ps->pSnake = cur;
}
}
//遍历链表,打印蛇
cur = ps->pSnake;
while (cur)
{
SetPos(cur->x, cur->y);
wprintf(L"%lc", BODY);
cur = cur->next;
}
//初始化游戏信息
ps->dir = RIGHT;//方向默认向右
ps->status = OK;//游戏状态
ps->sleep_time = 200;//蛇的速度,时间越短,速度越快,时间越长,速度越慢
ps->score = 0;//总分数
ps->foodWeight = 10;//食物的分数
}
//5.创建食物——CreateFood
void CreateFood(pSnake ps)
{
//创建食物节点坐标
int x = 0;
int y = 0;
//x必须是2的倍数
//x:2~54
//y:1~25
again:
do
{
x = rand() % 53 + 2;
y = rand() % 25 + 1;
} while (x % 2 != 0);//若x不为2的倍数,重新生成坐标
//x和y不能和蛇身节点冲突
pSnakeNode cur = ps->pSnake;
//遍历蛇身节点,判断蛇身节点坐标与食物节点坐标是否冲突
while (cur)
{
if (cur->x == x && cur->y == y)
{
goto again;
}
cur = cur->next;
}
//创建食物节点
pSnakeNode pFood =(pSnakeNode)malloc(sizeof(SnakeNode));
if (pFood == NULL)
{
perror("CreateFood::malloc()");
return;
}
//食物节点创建成功
pFood->x = x;
pFood->y = y;
pFood->next = NULL;
//打印食物
SetPos(pFood->x, pFood->y);
wprintf(L"%lc", FOOD);
//存储到Snake中指向食物节点的指针中
ps->pFood = pFood;
}
//游戏运行
void GameStart(pSnake ps)
{
//1.隐藏光标,设置窗口
HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);//获取标准输出设备的句柄
CONSOLE_CURSOR_INFO cursor_info = { 0 };
GetConsoleCursorInfo(houtput, &cursor_info);//获取控制台光标信息
cursor_info.bVisible = false;//隐藏控制台光标
SetConsoleCursorInfo(houtput, &cursor_info);//设置控制台光标信息
system("mode con cols=100 lines=30");
system("title 贪吃蛇");
//2.打印欢迎界面和功能介绍——WelcomeToGame
WelcomeToGame();
//3.绘制地图——CreateMap
CreateMap();
//4.创建蛇,初始化游戏信息——InitSnake
InitSnake(ps);
//5.创建食物——CreateFood
CreateFood(ps);
}
//1.右侧打印帮助信息——PrintHelpInfo
void PrintHelpInfo()
{
SetPos(64, 15);
wprintf(L"%ls", L"不能穿墙,不能咬到自己");
SetPos(64, 16);
wprintf(L"%ls", L"用 ↑. ↓ . ← . → 来控制蛇的移动");
SetPos(64, 17);
wprintf(L"%ls", L"按F3加速,F4减速");
SetPos(64, 18);
wprintf(L"%ls", L"按ESC退出游戏,按空格暂停游戏");
SetPos(64, 20);
wprintf(L"%ls", L"Chocolate_旭日东升制作");
}
#define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) && 1) ? 1 : 0 )
void Pause()
{
while (1)
{
Sleep(200);
if (KEY_PRESS(VK_SPACE))//按空格继续游戏
{
break;
}
}
}
// 4.2判断下一个节点是否为食物——NextIsFood
int NextIsFood(pSnakeNode pn, pSnake ps)
{
return (pn->x == ps->pFood->x && pn->y == ps->pFood->y);
}
// 4.3是食物就吃掉——EatFood
void EatFood(pSnakeNode pn, pSnake ps)
{
//头插法
ps->pFood->next = ps->pSnake;
ps->pSnake = ps->pFood;
//打印蛇
pSnakeNode cur = ps->pSnake;
while (cur)
{
SetPos(cur->x, cur->y);
wprintf(L"%lc", BODY);
cur = cur->next;
}
ps->score += ps->foodWeight;
//重新创建食物
CreateFood(ps);
}
// 4.4不是食物,吃掉食物,尾巴删除一节——NoFood
void NoFood(pSnakeNode pn, pSnake ps)
{
//头插法
pn->next = ps->pSnake;
ps->pSnake = pn;
//打印蛇
pSnakeNode cur = ps->pSnake;
while (cur->next->next)//打印至倒数第二个节点
{
SetPos(cur->x, cur->y);
wprintf(L"%lc", BODY);
cur = cur->next;
}
//把最后一个节点打印成空格,然后释放该节点
SetPos(cur->next->x, cur->next->y);
printf(" ");
free(cur->next);
//把倒数第二个节点的next指针置为NULL
cur->next = NULL;
}
// 4.5判断是否撞墙——KillByWall
void KillByWall(pSnake ps)
{
if (ps->pSnake->x == 0 || ps->pSnake->x == 56 || ps->pSnake->y == 0 || ps->pSnake->y == 26)
{
ps->status = KILL_BY_WALL;
}
}
// 4.6判断是否撞上自己——KillBySelf
void KillBySelf(pSnake ps)
{
pSnakeNode cur = ps->pSnake->next;
while (cur)
{
if (ps->pSnake->x == cur->x && ps->pSnake->y == cur->y)
{
ps->status = KILL_BY_SELF;
break;
}
cur = cur->next;
}
}
//4.根据按键情况移动蛇
void SnakeMove(pSnake ps)
{
pSnakeNode pNextNode = (pSnakeNode)malloc(sizeof(SnakeNode));//创建一个节点,表示蛇即将到的下一个节点
if (pNextNode == NULL)
{
perror("SnakeMove::malloc()");
return;
}
// 4.1根据蛇头的坐标和方向, 计算下一个节点的坐标
switch (ps->dir)
{
case UP:
pNextNode->x = ps->pSnake->x;
pNextNode->y = ps->pSnake->y - 1;
break;
case DOWN:
pNextNode->x = ps->pSnake->x;
pNextNode->y = ps->pSnake->y + 1;
break;
case LEFT:
pNextNode->x = ps->pSnake->x - 2;
pNextNode->y = ps->pSnake->y;
break;
case RIGHT:
pNextNode->x = ps->pSnake->x + 2;
pNextNode->y = ps->pSnake->y;
break;
}
// 4.2判断下一个节点是否为食物——NextIsFood
if (NextIsFood(pNextNode, ps))
{
// 4.3是食物就吃掉——EatFood
EatFood(pNextNode, ps);
}
else
{
// 4.4不是食物,吃掉食物,尾巴删除一节——NoFood
NoFood(pNextNode, ps);
}
// 4.5判断是否撞墙——KillByWall
KillByWall(ps);
// 4.6判断是否撞上自己——KillBySelf
KillBySelf(ps);
}
//游戏运行
void GameRun(pSnake ps)
{
//1.打印帮助信息
PrintHelpInfo();
//5.循环2~4,直到游戏状态不为OK
do
{
//2.打印当前已获得分数和食物的分数
SetPos(64, 10);
printf("总分数:%d\n", ps->score);
SetPos(64, 11);
printf("当前食物的分数:%2d\n", ps->foodWeight);
//3.获取按键情况
if (KEY_PRESS(VK_UP) && ps->dir != DOWN)
{
ps->dir = UP;
}
else if (KEY_PRESS(VK_DOWN) && ps->dir != UP)
{
ps->dir = DOWN;
}
else if (KEY_PRESS(VK_LEFT) && ps->dir != RIGHT)
{
ps->dir = LEFT;
}
else if (KEY_PRESS(VK_RIGHT) && ps->dir != LEFT)
{
ps->dir = RIGHT;
}
else if (KEY_PRESS(VK_F3))
{
//加速
if (ps->sleep_time > 50)
{
ps->sleep_time -= 30;
ps->foodWeight += 2;//一个食物分数最高是20分
}
}
else if (KEY_PRESS(VK_F4))
{
//减速
if (ps->sleep_time < 320)
{
ps->sleep_time += 30;
ps->foodWeight -= 2;//一个食物分数最低是2分
}
}
else if (KEY_PRESS(VK_SPACE))
{
//暂停
Pause();
}
else if (KEY_PRESS(VK_ESCAPE))
{
//正常退出
ps->status = END_NORMAL;
}
//4.根据按键情况移动蛇
SnakeMove(ps);
Sleep(ps->sleep_time);//蛇每次移动要休眠的时间,时间短,速度快,时间长,速度慢
} while (ps->status == OK);
}
//游戏结束
void GameEnd(pSnake ps)
{
//1.告知游戏结束的原因
SetPos(24, 12);
switch (ps->status)
{
case END_NORMAL:
printf("您主动退出游戏\n");
break;
case KILL_BY_WALL:
printf("您撞墙了,游戏结束!\n");
break;
case KILL_BY_SELF:
printf("您撞上了自己,游戏结束!\n");
break;
}
//2.释放蛇身节点
pSnakeNode cur = ps->pSnake;
while (cur)
{
pSnakeNode next = cur->next;
free(cur);
cur = next;
}
}
test.c
#define _CRT_SECURE_NO_WARNINGS
#include "snake.h"
void test()
{
srand((unsigned int)time(NULL));
int ch = 0;
do
{
system("cls");//每次循环清理屏幕
//创建贪吃蛇——包含贪吃蛇所有游戏信息
Snake snake = { 0 };
//游戏的初始化——GameStart
//1.隐藏光标,设置窗口
//2.打印欢迎界面和功能介绍——WelcomeToGame
//3.绘制地图——CreateMap
//4.创建蛇,初始化游戏信息——InitSnake
//5.创建食物——CreateFood
GameStart(&snake);
//游戏运行——GameRun
//1.右侧打印帮助信息——PrintHelpInfo
//2.打印当前已获得总分和食物的分数
//3.获取按键状态
//4.根据按键状态移动蛇——SnakeMove
// 4.1根据蛇头的坐标和方向,计算下一个节点的坐标
// 4.2判断下一个位置是否是食物——NextIsFood
// 4.3是食物就吃掉——EatFood
// 4.4不是食物,吃掉食物,尾巴删除一节——NoFood
// 4.5判断是否撞墙——KillByWall
// 4.6判断是否撞上自己——KillBySelf
//5.循环2~4,直到游戏状态不为OK
GameRun(&snake);
//游戏结束——游戏善后工作
//1.打印游戏结束的原因
//2.销毁蛇身节点
GameEnd(&snake);
SetPos(24, 13);
printf("再来一局吗?(Y/N):");
ch = getchar();
getchar();//清理玩家回车——\n
} while (ch == 'Y' || ch == 'y');
SetPos(0, 27);
}
int main()
{
setlocale(LC_ALL, "");//切换到本地环境,以支持宽字符'□''●''★'的打印
test();
return 0;
}
✿✿✿完结撒花✿✿✿