目录
前言
之前我们用C语言实现了一个扫雷小游戏:扫雷游戏,今天我们通过C语⾔函数、枚举、结构体、动态内存管理、预处理指令、链表、Win32 API等知识,来实现一个贪吃蛇小游戏。
一、贪吃蛇游戏
1.1 游戏背景
1.2 游戏功能
1.3 技术要点
实现贪吃蛇小游戏,我们需要掌握C语⾔函数、枚举、结构体、动态内存管理、预处理指令、链表、Win32 API等知识。
二、Win32 API
上面提到的中有个Win32 API我们不太熟悉,其实
2.1 控制台程序
mode con cols=100 lines=30
参考:mode命令
也可以通过tiele命令设置控制台窗口的名字:
title 贪吃蛇
参考:title命令
这些能在控制台窗口执行的命令,也可以调用C语⾔函数system来执行。例如:
#include<stdio.h>
int main{
//设置控制台窗⼝的⻓宽:设置控制台窗⼝的⼤⼩,30⾏,100列
system("mode con cols=100 lines=30");
//设置cmd窗⼝名称
system("title 贪吃蛇");
return 0;
}
2.2 控制台屏幕上的坐标COORD
//结构体类型声明
typedef struct _COORD {
SHORT X;
SHORT Y;
} COORD, *PCOORD;
给坐标进行赋值:
COORD pos = { 10, 15 };
2.3 GetStdHandle
HANDLE GetStdHandle(DWORD nStdHandle);
HANDLE hOutput = NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
2.4 GetConsoleCursorInfo
函数原型:
BOOL WINAPI GetConsoleCursorInfo(
HANDLE hConsoleOutput,
PCONSOLE_CURSOR_INFO lpConsoleCursorInfo
);
//PCONSOLE_CURSOR_INFO 是指向CONSOLE_CURSOR_INFO结构的指针,该结构接收有关主机游标
HANDLE hOutput = NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息
2.4 CONSOLE_CURSOR_INFO
这个结构体类型,包含有关控制台光标的信息
typedef struct _CONSOLE_CURSOR_INFO {
DWORD dwSize;//光标大小
BOOL bVisible;//光标可见性
} CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO;
CursorInfo.bVisible = false; //隐藏控制台光标
2.5 SetConsoleCursorInfo
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);//设置控制台光标状态
2.6 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);
}
2.7 GetAsyncKeyState
我们要获取按键情况,可以通过GetAsyncKeyState函数,函数原型如下:
SHORT GetAsyncKeyState(int vKey);
我们可以定义成一个宏:
#define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )
如果返回为1则代表被按过,反之返回0。
三、贪吃蛇游戏设计与分析
3.1 地图
3.1.1 宽字符
3.1.2 <locale.h>本地化
3.1.3 类项
3.1.4 setlocale函数
函数原型:
char* setlocale (int category, const char* locale);
setlocale(LC_ALL, "C");
setlocale(LC_ALL, " ");//切换到本地环境
3.1.5 宽字符的打印
#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;
}
3.1.6 地图坐标
3.2 蛇身和食物
3.3 数据结构设计
//蛇身节点
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//正常结束
};
3.4 游戏流程
游戏流程如下:
四、核心逻辑实现分析
4.1 游戏主逻辑
#include"Snake.h"
void test() {
srand((unsigned int)time(NULL));
int ch = 0;
//循环支持多次游玩
do {
Snake snake = { 0 };
GameStart(&snake);//游戏开始前的准备
GameRun(&snake); //游戏运行
GameEnd(&snake); //游戏结束
SetPos(24, 13);
printf("是否再来一把(y/n):");
ch = getchar();
getchar();//清理\n
} while (ch == 'Y' || ch == 'y');
}
int main() {
setlocale(LC_ALL, "");//修改当前地区为本地模式,为了⽀持中⽂宽字符的打印
test();
SetPos(24, 28);
return 0;
}
4.2 游戏开始(GameStart)
这个模块完成游戏的初始化任务:
//游戏初始化
void GameStart(pSnake ps) {
//设置控制台窗⼝的⼤⼩,30⾏,100列
//mode 为DOS命令
system("mode con cols=100 lines=30");
//设置cmd窗⼝名称
system("title 贪吃蛇");
//隐藏光标
//获取标准输出的句柄(⽤来标识不同设备的数值)
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
//影藏光标操作
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息
CursorInfo.bVisible = false; //隐藏控制台光标
SetConsoleCursorInfo(hOutput, &CursorInfo);//设置控制台光标状态
//欢迎界面
WelcomeToGame();
//游戏地图
CreateMap();
//初始化贪吃蛇
InitSnake(ps);
//创造第一个食物
CreateFood(ps);
}
4.2.1 打印欢迎界面
void WelcomeToGame() {
SetPos(40, 13);
printf("欢迎来到贪吃蛇小游戏");
SetPos(55, 18);
system("pause");
system("cls");
SetPos(25, 13);
printf("用↑.↓.←.→ 分别控制蛇的移动,F3为加速,F4为减速\n");
SetPos(25, 14);
printf("加速将得到更高的分数");
SetPos(55, 18);
system("pause");
system("cls");
}
通过SetPos定位光标的位置,来打印两个欢迎信息。
4.2.2 游戏地图
创建地图就是通过宽字符打印出墙体,关键是计算好坐标才能在想要的位置打印墙体。
我们先来定义个宏表示墙体:
#define WALL L'□'
//游戏地图
void CreateMap() {
int i = 0;
//上
for (i = 0; i < 58; i += 2)
{
wprintf(L"%c", WALL);
}
//下
SetPos(0, 26);
for (i = 0; i < 58; i += 2)
{
wprintf(L"%c", WALL);
}
//左
for (i = 1; i < 26; i++)
{
SetPos(0, i);
wprintf(L"%c", WALL);
}
//右
for (i = 1; i < 26; i++)
{
SetPos(56, i);
wprintf(L"%c", WALL);
}
}
打印效果
4.2.3 初始化蛇
蛇头的初始坐标:
//初始蛇的位置
#define POS_X 24
#define POS_Y 5
打印蛇的宽字符:
#define BODY L'●'
初始化蛇的函数:InitSnake
//蛇身
void InitSnake(pSnake ps) {
pSnakeNode cur = NULL;
//创建蛇身节点
int i = 0;
//头插法
for (i = 0; i < 5; i++) {
cur = (pSnakeNode)malloc(sizeof(SnakeNode));
if (cur == NULL)
{
perror("InitSnake()::malloc()");
return;
}
cur->x = POS_X + i * 2;
cur->y = POS_Y;
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"%c", BODY);
cur = cur->next;
}
//初始化蛇的其他信息
ps->_SleepTime = 200;
ps->_Socre = 0;
ps->_Status = OK;
ps->_Dir = RIGHT;
ps->_foodWeight = 10;
}
打印效果:
4.2.4 创建第一个食物
#define FOOD L'★'
//创建食物
void CreateFood(pSnake ps) {
int x=0;
int y=0;
//食物位置必须与蛇头位置对齐,必须是二的倍数
again:
do {
x = rand() % 53 + 2;
y = rand() % 25 + 1;
} while (x % 2 != 0);
//食物不能出现在蛇身上
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"%c", FOOD);
ps->_pFood = pFood;
}
4.3 游戏运行(GameRun)
我们定义一个宏来检测按键情况:
//检查按键是否按
#define KEY_PRESS(VK) ((GetAsyncKeyState(VK)&0x1) ? 1 : 0)
//游戏运行
void GameRun(pSnake ps) {
//打印帮助信息
PrintfHelp();
do {
//打印分数情况
SetPos(62, 10);
printf("得分:%d", ps->_Socre);
SetPos(75, 10);
printf("每个食物分数:%02d", ps->_foodWeight);
//判断蛇头的方向
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_SPACE)) {
//暂停
pause();
}
else if (KEY_PRESS(VK_ESCAPE)) {
//退出游戏
ps->_Status = END_NOMAL;
break;
}
else if (KEY_PRESS(VK_F3)) {
//加速,只能加速五次
if (ps->_SleepTime >= 80) {
ps->_SleepTime -= 30;
ps->_foodWeight += 2;
}
}
else if (KEY_PRESS(VK_F4)) {
//减速,只能减速到食物得分为2
if (ps->_foodWeight > 2) {
ps->_SleepTime += 30;
ps->_foodWeight -= 2;
}
}
//蛇的移动
SnakeMove(ps);
Sleep(ps->_SleepTime);
} while (ps->_Status == OK);
}
4.3.1 帮助信息
右侧打印帮助信息,提示玩家:
//帮助信息
void PrintfHelp() {
SetPos(68, 14);
printf("1.不能撞墙,不能咬到自己\n");
SetPos(68, 15);
printf("2.用↑.↓.←.→ 分别控制蛇的移动\n");
SetPos(68, 16);
printf("3.F3为加速,F4为减速\n");
SetPos(68, 17);
printf("ESC:退出游戏 空格: 暂停");
SetPos(68, 19);
printf("版权@sparks5210\n");
}
效果如下:
4.3.2 蛇身移动
//蛇的移动
void SnakeMove(pSnake ps) {
//创建指向下一个节点
pSnakeNode pNextNode = (pSnakeNode)malloc(sizeof(SnakeNode));
if (pNextNode == NULL)
{
perror("SnakeMove()::malloc()");
return;
}
//通过指向判断下一个节点的位置
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;
}
//如果下一个节点是食物
if (NextIsFood(pNextNode, ps)) {
//吃食物
EatFood(pNextNode, ps);
}
else {
NoFood(pNextNode, ps);
}
//撞到墙游戏结束
KillByWall(ps);
//撞到自己游戏结束
KillBySelf(ps);
}
4.3.2.1 判断是否是食物
我们需要判断一下下一个位置是否是食物,如果是返回真,不是返回零。
判断食物的函数是:NextIsFood
//判断是否是食物
int NextIsFood(pSnakeNode pNextNode, pSnake ps) {
return (pNextNode->x == ps->_pFood->x)&& (pNextNode->y == ps->_pFood->y);
}
4.3.2.2 吃食物
如果下一个位置是食物,我们就要进行吃食物的操作,把食物的节点变成新的头,蛇身长度变长。
进行吃操作的函数是:EatFood
//吃食物
void EatFood(pSnakeNode psn, pSnake ps) {
//头插法
psn->next = ps->_pSnake;
ps->_pSnake = psn;
//打印蛇身
pSnakeNode cur = ps->_pSnake;
while (cur) {
SetPos(cur->x, cur->y);
wprintf(L"%c", BODY);
cur = cur->next;
}
ps->_Socre += ps->_foodWeight;
//释放食物空间
free(ps->_pFood);
//创造新的食物
CreateFood(ps);
}
4.3.2.3 正常走
//下一个位置不是食物
void NoFood(pSnakeNode psn, pSnake ps) {
//让下一个位置变成新的头
psn->next = ps->_pSnake;
ps->_pSnake = psn;
//释放最后位置
pSnakeNode cur = ps->_pSnake;
while (cur->next->next) {
SetPos(cur->x, cur->y);
wprintf(L"%c", BODY);
cur = cur->next;
}
//尾节点打印空格
SetPos(cur->next->x, cur->next->y);
printf(" ");
free(cur->next);
cur->next = NULL;
}
4.3.2.4 KillByWall
//撞到墙游戏结束
void KillByWall(pSnake ps) {
if (ps->_pSnake->x == 56 ||
ps->_pSnake->x == 0 ||
ps->_pSnake->y == 0 ||
ps->_pSnake->y == 26)
{
ps->_Status = KILL_BY_WALL;
return ;
}
return 0;
}
如果撞到墙,更改游戏状态为KILL_BY_WALL游戏结束。
4.3.2.5 KillBySelf
//撞到自己
void KillBySelf(pSnake ps) {
pSnakeNode cur = ps->_pSnake->next;
while (cur) {
if (cur->x == ps->_pSnake->x && cur->y == ps->_pSnake->y) {
ps->_Status = KILL_BY_SELF;
return 1;
}
cur = cur->next;
}
return 0;
}
如果撞到自己,更改游戏状态为 KILL_BY_SELF游戏结束。
4.4 游戏结束(GameEnd)
void GameEnd(pSnake ps) {
SetPos(24, 12);
switch (ps->_Status)
{
case KILL_BY_WALL:
printf("撞到墙了,游戏结束\n");
break;
case KILL_BY_SELF:
printf("撞到自己了,游戏结束\n");
break;
case END_NOMAL:
printf("游戏正常退出\n");
break;
}
pSnakeNode cur = ps->_pSnake;
//释放贪吃蛇
while (cur)
{
pSnakeNode del = cur;
cur = cur->next;
free(del);
}
//释放食物
free(ps->_pFood);
}
五、完整代码
下面是游戏的完整代码
test.c
#include"Snake.h"
void test() {
srand((unsigned int)time(NULL));
int ch = 0;
//循环支持多次游玩
do {
Snake snake = { 0 };
GameStart(&snake);//游戏开始前的准备
GameRun(&snake); //游戏运行
GameEnd(&snake); //游戏结束
SetPos(24, 13);
printf("是否再来一把(y/n):");
ch = getchar();
getchar();//清理\n
} while (ch == 'Y' || ch == 'y');
}
int main() {
setlocale(LC_ALL, "");//修改当前地区为本地模式,为了⽀持中⽂宽字符的打印
test();
SetPos(24, 28);
return 0;
}
Snake.h
#include<stdlib.h>
#include<stdbool.h>
#include<Windows.h>
#include<locale.h>
#include<time.h>
#define WALL L'□'
#define BODY L'●'
#define FOOD L'★'
//初始蛇的位置
#define POS_X 24
#define POS_Y 5
//检查按键是否按
#define KEY_PRESS(VK) ((GetAsyncKeyState(VK)&0x1) ? 1 : 0)
//蛇的方向
enum DIRECTION {
UP = 1,
DOWN,
LEFT,
RIGHT
};
//游戏状态
enum GAME_STATUS {
OK,//正常运⾏
KILL_BY_WALL,//撞墙
KILL_BY_SELF,//咬到⾃⼰
END_NOMAL//正常结束
};
//蛇身节点
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;
//游戏初始化
void GameStart(pSnake ps);
//欢迎界面
void WelcomeToGame();
//设置光标的位置
void SetPos(short x, short y);
//游戏地图
void CreateMap();
//初始化贪吃蛇
void InitSnake(pSnake ps);
//食物位置
void CreateFood(pSnake ps);
//打印帮助信息
void PrintfHelp();
//暂停游戏
void pause();
//蛇的移动
void SnakeMove(pSnake ps);
//判断是否是食物
int NextIsFood(pSnakeNode pNextNode, pSnake ps);
//吃食物
void EatFood(pSnakeNode psn, pSnake ps);
//正常走,不是食物
void NoFood(pSnakeNode psn, pSnake ps);
//撞到墙
void KillByWall(pSnake ps);
//撞到自己
void KillBySelf(pSnake ps);
//游戏运行
void GameRun(pSnake ps);
//游戏结束
void GameEnd(pSnake ps);
Snake.c
#include"Snake.h"
//设置光标的位置
void SetPos(short x, short y)
{
COORD pos = { x, y };
HANDLE hOutput = NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
//设置标准输出上光标的位置为pos
SetConsoleCursorPosition(hOutput, pos);
}
//欢迎界面
void WelcomeToGame() {
SetPos(40, 13);
printf("欢迎来到贪吃蛇小游戏");
SetPos(55, 18);
system("pause");
system("cls");
SetPos(25, 13);
printf("用↑.↓.←.→ 分别控制蛇的移动,F3为加速,F4为减速\n");
SetPos(25, 14);
printf("加速将得到更高的分数");
SetPos(55, 18);
system("pause");
system("cls");
}
//游戏地图
void CreateMap() {
int i = 0;
//上
for (i = 0; i < 58; i += 2)
{
wprintf(L"%c", WALL);
}
//下
SetPos(0, 26);
for (i = 0; i < 58; i += 2)
{
wprintf(L"%c", WALL);
}
//左
for (i = 1; i < 26; i++)
{
SetPos(0, i);
wprintf(L"%c", WALL);
}
//右
for (i = 1; i < 26; i++)
{
SetPos(56, i);
wprintf(L"%c", WALL);
}
}
//蛇身
void InitSnake(pSnake ps) {
pSnakeNode cur = NULL;
//创建蛇身节点
int i = 0;
//头插法
for (i = 0; i < 5; i++) {
cur = (pSnakeNode)malloc(sizeof(SnakeNode));
if (cur == NULL)
{
perror("InitSnake()::malloc()");
return;
}
cur->x = POS_X + i * 2;
cur->y = POS_Y;
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"%c", BODY);
cur = cur->next;
}
//初始化蛇的其他信息
ps->_SleepTime = 200;
ps->_Socre = 0;
ps->_Status = OK;
ps->_Dir = RIGHT;
ps->_foodWeight = 10;
}
//食物位置
void CreateFood(pSnake ps) {
int x=0;
int y=0;
//食物位置必须与蛇头位置对齐,必须是二的倍数
again:
do {
x = rand() % 53 + 2;
y = rand() % 25 + 1;
} while (x % 2 != 0);
//食物不能出现在蛇身上
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"%c", FOOD);
ps->_pFood = pFood;
}
//游戏初始化
void GameStart(pSnake ps) {
//设置控制台窗⼝的⼤⼩,30⾏,100列
//mode 为DOS命令
system("mode con cols=100 lines=30");
//设置cmd窗⼝名称
system("title 贪吃蛇");
//隐藏光标
//获取标准输出的句柄(⽤来标识不同设备的数值)
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
//影藏光标操作
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息
CursorInfo.bVisible = false; //隐藏控制台光标
SetConsoleCursorInfo(hOutput, &CursorInfo);//设置控制台光标状态
//欢迎界面
WelcomeToGame();
//游戏地图
CreateMap();
//初始化贪吃蛇
InitSnake(ps);
//创造第一个食物
CreateFood(ps);
}
//帮助信息
void PrintfHelp() {
SetPos(68, 14);
printf("1.不能撞墙,不能咬到自己\n");
SetPos(68, 15);
printf("2.用↑.↓.←.→ 分别控制蛇的移动\n");
SetPos(68, 16);
printf("3.F3为加速,F4为减速\n");
SetPos(68, 17);
printf("ESC:退出游戏 空格: 暂停");
SetPos(68, 19);
printf("版权@sparks5210\n");
}
//暂停游戏
void pause() {
while (1) {
Sleep(200);
if (KEY_PRESS(VK_SPACE))
break;
}
}
//判断是否是食物
int NextIsFood(pSnakeNode pNextNode, pSnake ps) {
return (pNextNode->x == ps->_pFood->x)&& (pNextNode->y == ps->_pFood->y);
}
//吃食物
void EatFood(pSnakeNode psn, pSnake ps) {
//头插法
psn->next = ps->_pSnake;
ps->_pSnake = psn;
//打印蛇身
pSnakeNode cur = ps->_pSnake;
while (cur) {
SetPos(cur->x, cur->y);
wprintf(L"%c", BODY);
cur = cur->next;
}
ps->_Socre += ps->_foodWeight;
//释放食物空间
free(ps->_pFood);
//创造新的食物
CreateFood(ps);
}
//下一个位置不是食物
void NoFood(pSnakeNode psn, pSnake ps) {
//让下一个位置变成新的头
psn->next = ps->_pSnake;
ps->_pSnake = psn;
//释放最后位置
pSnakeNode cur = ps->_pSnake;
while (cur->next->next) {
SetPos(cur->x, cur->y);
wprintf(L"%c", BODY);
cur = cur->next;
}
SetPos(cur->next->x, cur->next->y);
printf(" ");
free(cur->next);
cur->next = NULL;
}
//撞到墙游戏结束
void KillByWall(pSnake ps) {
if (ps->_pSnake->x == 56 ||
ps->_pSnake->x == 0 ||
ps->_pSnake->y == 0 ||
ps->_pSnake->y == 26)
{
ps->_Status = KILL_BY_WALL;
return ;
}
return 0;
}
//撞到自己
void KillBySelf(pSnake ps) {
pSnakeNode cur = ps->_pSnake->next;
while (cur) {
if (cur->x == ps->_pSnake->x && cur->y == ps->_pSnake->y) {
ps->_Status = KILL_BY_SELF;
return 1;
}
cur = cur->next;
}
return 0;
}
//蛇的移动
void SnakeMove(pSnake ps) {
//创建指向下一个节点
pSnakeNode pNextNode = (pSnakeNode)malloc(sizeof(SnakeNode));
if (pNextNode == NULL)
{
perror("SnakeMove()::malloc()");
return;
}
//通过指向判断下一个节点的位置
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;
}
//如果下一个节点是食物
if (NextIsFood(pNextNode, ps)) {
//吃食物
EatFood(pNextNode, ps);
}
else {
NoFood(pNextNode, ps);
}
//撞到墙游戏结束
KillByWall(ps);
//撞到自己游戏结束
KillBySelf(ps);
}
//游戏运行
void GameRun(pSnake ps) {
//打印帮助信息
PrintfHelp();
do {
SetPos(62, 10);
printf("得分:%d", ps->_Socre);
SetPos(75, 10);
printf("每个食物分数:%02d", ps->_foodWeight);
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_SPACE)) {
//暂停
pause();
}
else if (KEY_PRESS(VK_ESCAPE)) {
//退出游戏
ps->_Status = END_NOMAL;
break;
}
else if (KEY_PRESS(VK_F3)) {
//加速,只能加速五次
if (ps->_SleepTime >= 80) {
ps->_SleepTime -= 30;
ps->_foodWeight += 2;
}
}
else if (KEY_PRESS(VK_F4)) {
//减速,只能减速到食物得分为2
if (ps->_foodWeight > 2) {
ps->_SleepTime += 30;
ps->_foodWeight -= 2;
}
}
//蛇的移动
SnakeMove(ps);
Sleep(ps->_SleepTime);
} while (ps->_Status == OK);
}
//游戏结束
void GameEnd(pSnake ps) {
SetPos(24, 12);
switch (ps->_Status)
{
case KILL_BY_WALL:
printf("撞到墙了,游戏结束\n");
break;
case KILL_BY_SELF:
printf("撞到自己了,游戏结束\n");
break;
case END_NOMAL:
printf("游戏正常退出\n");
break;
}
pSnakeNode cur = ps->_pSnake;
//释放贪吃蛇
while (cur)
{
pSnakeNode del = cur;
cur = cur->next;
free(del);
}
//释放食物
free(ps->_pFood);
}
总结
上述文章,我们通过C语言学习到的知识实现了一个贪吃蛇的小游戏,希望对你有所帮助。