目录
3.2.4 NextIsFood 判断走的下一个节点是否是食物
1、实现环境、思路
使用C语言在Windows环境的控制台中模拟实现经典小游戏贪吃蛇实现基本的功能:
• 贪吃蛇地图绘制
• 蛇吃食物的功能 (上、下、左、右方向键控制蛇的动作)
• 蛇撞墙死亡
• 蛇撞自身死亡
• 计算得分
• 蛇身加速、减速
• 暂停游戏
2、win32 API 函数
2.1 控制台程序
在Windows中win+R输入cmd即可唤起控制台程序,使用cmd命令可以设置控制台窗口长宽。
mode con cols=120 lines=40
title Snake
2.2 GetStdHandle
GetStdHandle是一个Windows API函数。它用于从一个特定的标准设备(标准输入、标准输出或标准错误)中取得一个句柄(用来标识不同设备的数值,使用这个句柄可以操作设备。
HANDLE GetStdHandle(DWORD nStdHandle);
就单对这个贪吃蛇的项目来说,我们需要在windows的控制台上执行的,也就是我们需要找到这个控制台,取得这个控制台的句柄,建立联系。
实例:
HANDLE hOutput = NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
2.3 GetConsoleCursorInfo
检索有关指定控制台屏幕缓冲区的光标大小和可见性的信息
BOOL WINAPI GetConsoleCursorInfo(
HANDLE hConsoleOutput, //句柄
PCONSOLE_CURSOR_INFO lpConsoleCursorInfo //一个结构体接收有关控制台游标的信息
);
实例
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;
• dwSize,由光标填充的字符单元格的百分比。 此值介于1到100之间。 光标外观会变化,范围从完全填充单元格到单元底部的水平线条。
• bVisible,游标的可见性。 如果光标可见,则此成员为 TRUE。
例如我们要隐藏闪烁的光标:
CursorInfo.bVisible 1 = 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 控制台屏幕上坐标COORD
typedef struct _COORD {
SHORT X;
SHORT Y;
} COORD, *PCOORD;
COORD pos={ 10 , 15 };
2.7 SetConsoleCursorPosition
设置指定控制台屏幕缓冲区中的光标位置,我们将想要设置的坐标信息放在COORD类型的pos中,调用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.8 GetAsyncKeyState
获取按键情况,GetAsyncKeyState的函数原型如下:
SHORT GetAsyncKeyState(
int vKey
);
GetAsyncKeyState 的返回值是short类型,在上一次调用 GetAsyncKeyState 函数后,如果
返回的16位的short数据中,最高位是1,说明按键的状态是按下,如果最高是0,说明按键的状态是抬起;如果最低位被置为1则说明,该按键被按过,否则为0。
如果我们要判断一个键是否被按过,可以检测GetAsyncKeyState返回值的最低值是否为1.
define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )
3、贪吃蛇游戏设计与分析
在游戏运行的过程中,蛇每次吃一个食物,蛇的身体就会变长一节,如果我们使用链表存储蛇的信
息,那么蛇的每一节其实就是链表的每个节点。每个节点只要记录好蛇身节点在地图上的坐标就行,所以蛇节点结构如下:
typedef struct SnakeNode
{
int x;
int y;
struct SnakeNode* next; //链接每一个节点组成的一条蛇
}SnakeNode, * pSnakeNode;
要管理整条贪吃蛇,我们再封装一个Snake的结构来维护整条贪吃蛇:
typedef struct Snake {
pSnakeNode _psnake;//贪吃蛇的第一个节点,头结点的指针
pSnakeNode _pFood; //指向食物节点的指针
int _Score;//贪吃蛇累计的总分
int _FootWeight;//一个食物的分数
int _SleepTime;//每走一步休息的时间,时间越短,速度越快,时间越长。速度越慢
enum DIRECTION _Dir;//描述蛇的方向
enum GAME_STATUS _Status;//游戏的状态:正常、退出、撞墙、吃到自己
}Snake, * pSnake;
以下是枚举蛇方向和存活状态:
enum DIRECTION
{
UP = 1,
DOWN,
LEFT,
RIGHT
};
//贪吃蛇的存活的状态
enum GAME_STATUS {
OK,//正常运行
END_NORMAL,//按esc退出
KILL_BY_WALL,//撞墙
KILL_BY_SELF//自杀
};
我们设计这个贪吃蛇分三个步骤,初始化贪吃蛇(包括地图的创建以及贪吃蛇本身的结构)、运行游戏的过程、结束游戏(释放资源),接下来就以这些步骤开始:
3.1 GameStart 初始化游戏
游戏的初始化
//游戏初始化
void GameStart(pSnake ps) {
setlocale(LC_ALL, "");
system("mode con cols=120 lines=40"); //设置了x=120,y=40控制台窗口
system("title 贪吃蛇");
//1_隐藏光标
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); //先获得控制台的句柄
//一个控制台光标的信息的结构体 CONSOLE_CURSOR_INFO
CONSOLE_CURSOR_INFO CursorInfo;
//通过句柄获取控制台的光标信息,并储存在 CursorInfo 内
GetConsoleCursorInfo(handle, &CursorInfo);
//通过修改 CursorInfo 内的光标信息 bVisible 隐藏掉,不显示
CursorInfo.bVisible = false; //需要头文件 <stdbool.h>
//修改成功后需要执行修改
SetConsoleCursorInfo(handle, &CursorInfo);
//2_打印欢迎界面
welComeToGame();
//3_创建地图
CreateMap();
//4_初始化贪吃蛇
InitSnake(ps);
//5_创建食物
CreatFood(ps);
}
3.1.1 welComeToGame 打印欢迎界面
void welComeToGame() {
SetPos(51, 18); //定位控制台光标的位置
printf("欢迎来到贪吃蛇游戏");
SetPos(53, 36);
//Windows 中暂停的指令,按任意键结束暂停
system("pause");
//清除之前屏幕上打印的信息
system("cls");
SetPos(51, 18);
printf("游戏玩法规则如下:");
SetPos(35, 20);
printf("←↑→↓来操控贪吃蛇移动,Esc退出游戏,Space暂停游戏");
SetPos(53, 22);
printf("F3加速,F4减速");
SetPos(53, 36);
system("pause");
system("cls");
}
3.1.2 CreateMap 地图
void CreateMap() {
//上
SetPos(0, 0);
for (int i = 0; i <= 78; i += 2) {
wprintf(L"%lc", WALL);
}
//左
for (int i = 1; i <= 34; i++) {
SetPos(0, i);
wprintf(L"%lc", WALL);
}
//右
for (int i = 1; i <= 34; i++) {
SetPos(78, i);
wprintf(L"%lc", WALL);
}
//下
SetPos(0, 34);
for (int i = 0; i <= 78; i += 2) {
wprintf(L"%lc", WALL);
}
}
3.1.3 InitSnake 初始化贪吃蛇
void InitSnake(pSnake ps) {
pSnakeNode cur = NULL;
for (int i = 0; i < 5; i++) {
cur = (pSnakeNode)malloc(sizeof(SnakeNode));
if (cur == NULL) {
perror("InitSnake() error!");
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;
}
}
// 遍历这个链表
while (cur) {
SetPos(cur->x, cur->y);
wprintf(L"%lc", BODY);
cur = cur->next;
}
ps->_Status = OK;
ps->_Score = 50;
ps->_pFood = NULL;
ps->_SleepTime = 200;
ps->_FootWeight = 10;
ps->_Dir = RIGHT;
}
3.3.4 CreatFood 创建食物
void CreatFood(pSnake ps) {
//1、坐标应该是随机生成的,但是这个随机是由约束的
//2、食物坐标必须在方框内
//3、食物的x坐标必须是2的倍数
int x = 0;
int y = 0;
again:
do {
x = rand() % 76 + 2;
y = rand() % 33 + 1;
} while (x % 2 != 0);
//4、食物的坐标不能跟蛇身重叠,冲突 ,一一进行比较
pSnakeNode cur = ps->_psnake;
while (cur) {
//比较
if (cur->x == x && cur->y == y) {
//出现重叠的就 goto 前面重新来过
goto again;
}
cur = cur->next;
}
pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode));
if (pFood == NULL) {
perror("CreateFood(); malloc error!");
return;
}
pFood->x = x;
pFood->y = y;
ps->_pFood = pFood;
SetPos(x, y);
wprintf(L"%lc", FOOD);
}
3.2 GameRun 运行游戏
游戏运行期间,右侧打印帮助信息,提示玩家根据游戏状态检查游戏是否继续,如果是状态是OK,游戏继续,否则游戏结束。如果游戏继续,就是检测按键情况,确定蛇下一步的方向,或者是否加速减速,是否暂停或者退出游戏。确定了蛇的方向和速度,蛇就可以移动了。
void GameRun(pSnake ps) {
PrintHelpInfo(); //打印游戏规则介绍和分数
do {
SetPos(85, 10);
printf("得分:%05d", ps->_Score);
SetPos(85, 11);
printf("食物分数:%2d", ps->_FootWeight);
//检测按键情况
//本来方向就是向上就不能往相反方向了
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_ESCAPE)) {
ps->_Status = END_NORMAL;
break;
}
else if (KEY_PRESS(VK_SPACE)) {
//封装一个暂停函数
Pause();
}
//加速
else if (KEY_PRESS(VK_F3)) {
if (ps->_SleepTime >= 100) {
ps->_SleepTime -= 30;
ps->_FootWeight += 2;
}
}
//减速
else if (KEY_PRESS(VK_F4)) {
if (ps->_SleepTime <= 320) {
ps->_SleepTime += 30;
ps->_FootWeight -= 2;
}
}
//设置完后要应用休眠的时间
Sleep(ps->_SleepTime);
//让蛇移动起来
SnakeMove(ps);
} while (ps->_Status == OK);
}
3.2.1 PrintHelpInfo 打印规则和分数
void PrintHelpInfo() {
SetPos(85, 12);
printf("1、不能撞墙,不能吃到自己");
SetPos(85, 14);
printf("2、←↑→↓来操控贪吃蛇移动");
SetPos(85, 16);
printf("3、F3加速,F4减速");
SetPos(85, 18);
printf("4、Esc退出游戏,Space暂停游戏");
}
3.2.2 Pause 暂停功能
void Pause() {
//让系统一直休眠,直到再次按下空格键
while (1) {
Sleep(200);
if (KEY_PRESS(VK_SPACE)) {
break;
}
}
}
3.2.3 SnakeMove 蛇的移动
先创建下一个节点,根据移动方向和蛇头的坐标,蛇移动到下一个位置的坐标。确定了下一个位置后,看下一个位置是否是食物(NextIsFood),是食物就做吃食物处理(EatFood),如果不是食物则做前进一步的处理(NoFood)。蛇身移动后,判断此次移动是否会造成撞墙(KillByWall)或者撞上自己蛇身(KillBySelf),从而影响游戏的状态。
void SnakeMove(pSnake ps) {
//为下一个移动的点创建一个节点
pSnakeNode pNext = (pSnakeNode)malloc(sizeof(SnakeNode));
if (pNext == NULL) {
perror("SnakeMove() malloc() error!");
return;
}
switch (ps->_Dir) {
case UP:
{
pNext->x = ps->_psnake->x;
pNext->y = ps->_psnake->y - 1;
}
break;
case DOWN: {
pNext->x = ps->_psnake->x;
pNext->y = ps->_psnake->y + 1;
}
break;
case LEFT: {
pNext->x = ps->_psnake->x - 2;
pNext->y = ps->_psnake->y;
}
break;
case RIGHT: {
pNext->x = ps->_psnake->x + 2;
pNext->y = ps->_psnake->y;
}
break;
}
//判断是否是食物
if (NextIsFood(ps, pNext)) {
//吃掉食物
EatFood(ps, pNext);
}
else {
//不吃
NoFood(ps, pNext);
}
判断是否撞墙
KillByWall(ps);
判断是否自杀
KillBySelf(ps);
}
3.2.4 NextIsFood 判断走的下一个节点是否是食物
int NextIsFood(pSnake ps, pSnakeNode pNext) {
if (ps->_pFood->x == pNext->x && ps->_pFood->y == pNext->y) {
return 1;
}
return 0;
}
3.2.5 EatFood 是食物就吃掉
void EatFood(pSnake ps, pSnakeNode pNext) {
pNext->next = ps->_psnake;
ps->_psnake = pNext;
pSnakeNode cur = ps->_psnake;
while (cur) {
SetPos(cur->x, cur->y);
wprintf(L"%lc", BODY);
cur = cur->next;
}
//给总分加上分数
ps->_Score += ps->_FootWeight;
free(ps->_pFood);
//还得重新创建新的食物
CreatFood(ps);
}
3.2.6 NoFood 不是食物也吃,然后释放尾节点
void NoFood(pSnake ps, pSnakeNode pNext) {
//头插入节点
pNext->next = ps->_psnake;
ps->_psnake = pNext;
//释放尾节点
pSnakeNode cur = ps->_psnake;
while (cur->next->next != NULL) {
SetPos(cur->x, cur->y);
wprintf(L"%lc", BODY);
cur = cur->next;
}
SetPos(cur->next->x, cur->next->y);
printf(" ");
free(cur->next);
cur->next = NULL;
}
3.2.7 KillByWall 撞墙
void KillByWall(pSnake ps) {
if (ps->_psnake->x == 0 || ps->_psnake->y == 35 || ps->_psnake->x == 80 || ps->_psnake->y == 0) {
ps->_Status = KILL_BY_WALL;
}
}
3.2.8 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;
}
cur = cur->next;
}
}
3.3 GameEnd 结束游戏
void GameEnd(pSnake ps) {
SetPos(39, 16);
switch (ps->_Status) {
case END_NORMAL:
printf("退出游戏成功!(っ °Д °;)っ下次还要再来一把");
break;
case KILL_BY_WALL:
printf("撞墙啦!(╯°□°)╯︵ ┻━┻");
break;
case KILL_BY_SELF:
printf("自杀啦!你怎么咬到自己了啊!( ´・・)ノ(._.`)");
break;
}
SetPos(39, 18);
pSnakeNode cur = ps->_psnake;
while (cur) {
pSnakeNode del = cur;
cur = cur->next;
free(del);
}
}
4、 完整代码实现,分3个文件,复制粘贴即可运行
4.1 Snake.h
#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<locale.h>
#include<stdbool.h>
#include<windows.h>
#include<time.h>
//检测按键定义的宏
#define KEY_PRESS(VK) (GetAsyncKeyState(VK)& 0x1 ? 1:0)
#define WALL L'□'
#define BODY L'●'
#define FOOD L'★'
#define POS_X 24
#define POS_Y 5
//方向
enum DIRECTION
{
UP = 1,
DOWN,
LEFT,
RIGHT
};
//贪吃蛇的存活的状态
enum GAME_STATUS {
OK,//正常运行
END_NORMAL,//按esc退出
KILL_BY_WALL,//撞墙
KILL_BY_SELF//自杀
};
//贪吃蛇本身的节点
typedef struct SnakeNode {
int x;
int y;
struct SnakeNode* next;
}SnakeNode, * pSnakeNode; //相当于 typedef struct SnakeNode* pSnakeNode;
//描述这条贪吃蛇结构
typedef struct Snake {
pSnakeNode _psnake;//贪吃蛇的第一个节点,头结点的指针
pSnakeNode _pFood; //指向食物节点的指针
int _Score;//贪吃蛇累计的总分
int _FootWeight;//一个食物的分数
int _SleepTime;//每走一步休息的时间,时间越短,速度越快,时间越长。速度越慢
enum DIRECTION _Dir;//描述蛇的方向
enum GAME_STATUS _Status;//游戏的状态:正常、退出、撞墙、吃到自己
}Snake, * pSnake;
//定位控制台光标
void SetPos(int x, int y);
//游戏初始化
void GameStart(pSnake ps);
//2_打印欢迎界面
void welComeToGame();
//3_创建地图
void CreateMap();
//4_初始化贪吃蛇
void InitSnake(pSnake ps);
//————————————————————————————————————————————————————————
//运行过程
void GameRun(pSnake ps);
//打印基本信息
void PrintHelpInfo();
//暂停
void Pause();
//蛇移动
void SnakeMove(pSnake ps);
//判断是否是食物
int NextIsFood(pSnake ps, pSnakeNode pNext);
//吃掉食物
void EatFood(pSnake ps, pSnakeNode pNext);
//不吃食物
void NoFood(pSnake ps, pSnakeNode pNext);
//判断是否撞墙
void KillByWall(pSnake ps);
//判断是否自杀
void KillBySelf(pSnake ps);
//————————————————————————————————————————————————————————————
//游戏结束
void GameEnd(pSnake ps);
4.2 Snake.c
#include"Snake.h"
void SetPos(int x, int y) {
//获取控制台句柄
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
//COORD是控制台存储一个坐标的结构体,
COORD pos = { x,y };
//设置光标的所在位置 SetConsoleCursorPosition
SetConsoleCursorPosition(handle, pos);
}
//打印欢迎界面
void welComeToGame() {
SetPos(51, 18);
printf("欢迎来到贪吃蛇游戏");
SetPos(53, 36);
//Windows 中暂停的指令,按任意键结束暂停
system("pause");
//清除之前屏幕上打印的信息
system("cls");
SetPos(51, 18);
printf("游戏玩法规则如下:");
SetPos(35, 20);
printf("←↑→↓来操控贪吃蛇移动,Esc退出游戏,Space暂停游戏");
SetPos(53, 22);
printf("F3加速,F4减速");
SetPos(53, 36);
system("pause");
system("cls");
}
//游戏地图
void CreateMap() {
//上
SetPos(0, 0);
for (int i = 0; i <= 78; i += 2) {
wprintf(L"%lc", WALL);
}
//左
for (int i = 1; i <= 34; i++) {
SetPos(0, i);
wprintf(L"%lc", WALL);
}
//右
for (int i = 1; i <= 34; i++) {
SetPos(78, i);
wprintf(L"%lc", WALL);
}
//下
SetPos(0, 34);
for (int i = 0; i <= 78; i += 2) {
wprintf(L"%lc", WALL);
}
}
//初始化贪吃蛇
void InitSnake(pSnake ps) {
pSnakeNode cur = NULL;
for (int i = 0; i < 5; i++) {
cur = (pSnakeNode)malloc(sizeof(SnakeNode));
if (cur == NULL) {
perror("InitSnake() error!");
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;
}
}
// 遍历这个链表
while (cur) {
SetPos(cur->x, cur->y);
wprintf(L"%lc", BODY);
cur = cur->next;
}
ps->_Status = OK;
ps->_Score = 50;
ps->_pFood = NULL;
ps->_SleepTime = 200;
ps->_FootWeight = 10;
ps->_Dir = RIGHT;
}
//创建食物
void CreatFood(pSnake ps) {
//1、坐标应该是随机生成的,但是这个随机是由约束的
//2、食物坐标必须在方框内
//3、食物的x坐标必须是2的倍数
int x = 0;
int y = 0;
again:
do {
x = rand() % 76 + 2;
y = rand() % 33 + 1;
} while (x % 2 != 0);
//4、食物的坐标不能跟蛇身重叠,冲突 ,一一进行比较
pSnakeNode cur = ps->_psnake;
while (cur) {
//比较
if (cur->x == x && cur->y == y) {
//出现重叠的就 goto 前面重新来过
goto again;
}
cur = cur->next;
}
pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode));
if (pFood == NULL) {
perror("CreateFood(); malloc error!");
return;
}
pFood->x = x;
pFood->y = y;
ps->_pFood = pFood;
SetPos(x, y);
wprintf(L"%lc", FOOD);
}
//游戏初始化
void GameStart(pSnake ps) {
setlocale(LC_ALL, "");
system("mode con cols=120 lines=40");
system("title 贪吃蛇");
//1_隐藏光标
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); //先获得控制台的句柄
//一个控制台光标的信息的结构体 CONSOLE_CURSOR_INFO
CONSOLE_CURSOR_INFO CursorInfo;
//通过句柄获取控制台的光标信息,并储存在 CursorInfo 内
GetConsoleCursorInfo(handle, &CursorInfo);
//通过修改 CursorInfo 内的光标信息 bVisible 隐藏掉,不显示
CursorInfo.bVisible = false; //需要头文件 <stdbool.h>
//修改成功后需要执行修改
SetConsoleCursorInfo(handle, &CursorInfo);
//2_打印欢迎界面
welComeToGame();
//3_创建地图
CreateMap();
//4_初始化贪吃蛇
InitSnake(ps);
//5_创建食物
CreatFood(ps);
}
//—————————以下是运行过程——————————————————————————————————————————————————
//打印基本信息
void PrintHelpInfo() {
SetPos(85, 12);
printf("1、不能撞墙,不能吃到自己");
SetPos(85, 14);
printf("2、←↑→↓来操控贪吃蛇移动");
SetPos(85, 16);
printf("3、F3加速,F4减速");
SetPos(85, 18);
printf("4、Esc退出游戏,Space暂停游戏");
}
//暂停
void Pause() {
while (1) {
Sleep(200);
if (KEY_PRESS(VK_SPACE)) {
break;
}
}
}
//判断是否是食物
int NextIsFood(pSnake ps, pSnakeNode pNext) {
if (ps->_pFood->x == pNext->x && ps->_pFood->y == pNext->y) {
return 1;
}
return 0;
}
//吃掉食物
void EatFood(pSnake ps, pSnakeNode pNext) {
pNext->next = ps->_psnake;
ps->_psnake = pNext;
pSnakeNode cur = ps->_psnake;
while (cur) {
SetPos(cur->x, cur->y);
wprintf(L"%lc", BODY);
cur = cur->next;
}
//给总分加上分数
ps->_Score += ps->_FootWeight;
free(ps->_pFood);
//还得重新创建新的食物
CreatFood(ps);
}
//不吃食物
void NoFood(pSnake ps, pSnakeNode pNext) {
//头插入节点
pNext->next = ps->_psnake;
ps->_psnake = pNext;
//释放尾节点
pSnakeNode cur = ps->_psnake;
while (cur->next->next != NULL) {
SetPos(cur->x, cur->y);
wprintf(L"%lc", 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 == 0 || ps->_psnake->y == 35 || ps->_psnake->x == 80 || ps->_psnake->y == 0) {
ps->_Status = KILL_BY_WALL;
}
}
//判断是否自杀
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;
}
cur = cur->next;
}
}
//蛇移动
void SnakeMove(pSnake ps) {
//为下一个移动的点创建一个节点
pSnakeNode pNext = (pSnakeNode)malloc(sizeof(SnakeNode));
if (pNext == NULL) {
perror("SnakeMove() malloc() error!");
return;
}
switch (ps->_Dir) {
case UP:
{
pNext->x = ps->_psnake->x;
pNext->y = ps->_psnake->y - 1;
}
break;
case DOWN: {
pNext->x = ps->_psnake->x;
pNext->y = ps->_psnake->y + 1;
}
break;
case LEFT: {
pNext->x = ps->_psnake->x - 2;
pNext->y = ps->_psnake->y;
}
break;
case RIGHT: {
pNext->x = ps->_psnake->x + 2;
pNext->y = ps->_psnake->y;
}
break;
}
//判断是否是食物
if (NextIsFood(ps, pNext)) {
//吃掉食物
EatFood(ps, pNext);
}
else {
//不吃
NoFood(ps, pNext);
}
判断是否撞墙
KillByWall(ps);
判断是否自杀
KillBySelf(ps);
}
void GameRun(pSnake ps) {
PrintHelpInfo();
do {
SetPos(85, 10);
printf("得分:%05d", ps->_Score);
SetPos(85, 11);
printf("食物分数:%2d", ps->_FootWeight);
//检测按键情况
//本来方向就是向上就不能往相反方向了
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_ESCAPE)) {
ps->_Status = END_NORMAL;
break;
}
else if (KEY_PRESS(VK_SPACE)) {
//封装一个暂停函数
Pause();
}
//加速
else if (KEY_PRESS(VK_F3)) {
if (ps->_SleepTime >= 100) {
ps->_SleepTime -= 30;
ps->_FootWeight += 2;
}
}
//减速
else if (KEY_PRESS(VK_F4)) {
if (ps->_SleepTime <= 320) {
ps->_SleepTime += 30;
ps->_FootWeight -= 2;
}
}
//设置完后要应用休眠的时间
Sleep(ps->_SleepTime);
//让蛇移动起来
SnakeMove(ps);
} while (ps->_Status == OK);
}
//————————游戏善后———————————————————————————————————————————————————
//游戏结束
void GameEnd(pSnake ps) {
SetPos(39, 16);
switch (ps->_Status) {
case END_NORMAL:
printf("退出游戏成功!(っ °Д °;)っ下次还要再来一把");
break;
case KILL_BY_WALL:
printf("撞墙啦!(╯°□°)╯︵ ┻━┻");
break;
case KILL_BY_SELF:
printf("自杀啦!你怎么咬到自己了啊!( ´・・)ノ(._.`)");
break;
}
SetPos(39, 18);
pSnakeNode cur = ps->_psnake;
while (cur) {
pSnakeNode del = cur;
cur = cur->next;
free(del);
}
}
4.3 test.c
#include"Snake.h"
void test() {
int ch = 0;
do {
Snake snake = { 0 };//创建贪吃蛇
//初始化状态界面
//1、游戏开始 - 初始化游戏
GameStart(&snake);
//2、游戏运行 - 游戏的正常运行过程
GameRun(&snake);
//3、游戏结束 - 游戏善后(释放资源)
GameEnd(&snake);
printf("是否再来一把(Y/N)");
ch = getchar();
getchar();//清理
} while (ch == 'Y' || ch == 'y');
}
int main()
{
setlocale(LC_ALL, ""); //使用本地的ASCII环境
srand((unsigned int)time(NULL)); //随机数的设置
test();
return 0;
}