目录
前言
一、贪吃蛇游戏
1.1 游戏背景
1.2 游戏功能
1.3 技术要点
二、Win32 API
2.1 控制台程序
2.2 控制台屏幕上的坐标COORD
2.3 GetStdHandle
2.4 GetConsoleCursorInfo
2.4 CONSOLE_CURSOR_INFO
2.5 SetConsoleCursorInfo
2.6 SetConsoleCursorPosition
2.7 GetAsyncKeyState
三、贪吃蛇游戏设计与分析
3.1 地图
3.1.1 宽字符
3.1.2 本地化
3.1.3 类项
3.1.4 setlocale函数
3.1.5 宽字符的打印
3.1.6 地图坐标
3.2 蛇身和食物
3.3 数据结构设计
3.4 游戏流程
四、核心逻辑实现分析
4.1 游戏主逻辑
4.2 游戏开始(GameStart)
4.2.1 打印欢迎界面
4.2.2 游戏地图
4.2.3 初始化蛇
4.2.4 创建第一个食物
4.3 游戏运行(GameRun)
4.3.1 帮助信息
4.3.2 蛇身移动
4.3.2.1 判断是否是食物
4.3.2.2 吃食物
4.3.2.3 正常走
4.3.2.4 KillByWall
4.3.2.5 KillBySelf
4.4 游戏结束(GameEnd)
五、完整代码
test.c
Snake.h
Snake.c
前言
之前我们用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。
参考:虚拟键码 (Winuser.h) - Win32 apps
三、贪吃蛇游戏设计与分析
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语言学习到的知识实现了一个贪吃蛇的小游戏,希望对你有所帮助。