????????游戏背景:贪吃蛇是久负盛名的游戏,它也和俄罗斯?块,扫雷等游戏位列经典游戏的?列。
????????使?C语?在Windows环境的控制台中模拟实现经典?游戏贪吃蛇。
? ? ? ? 实现的基本功能:
? ? ? ? 1、贪吃蛇地图绘制。
? ? ? ? 2、蛇吃?物的功能 (上、下、左、右?向键控制蛇的动作)。
? ? ? ? 3、?蛇撞墙死亡。
? ? ? ? 4、蛇撞??死亡。
? ? ? ? 5、?计算得分。
? ? ? ? 6、?蛇?加速、减速。
? ? ? ? 7、?暂停游戏、退出游戏。
????????Windows 这个多作业系统除了协调应?程序的执?、分配内存、管理资源之外, 它同时也是?个很? 的服务中?,调?这个服务中?的各种服务(每?种服务就是?个函数),可以帮应?程式达到开启 视窗、描绘图形、使?周边设备等?的,由于这些函数服务的对象是应?程序(Application), 所以便 称之为 Application Programming Interface,简称 API 函数。WIN32 API也就是Microsoft Windows 32位平台的应?程序编程接?。
? ? ? ? 下面我们用vs2022演示,需要包含头文件<windows.h>。
? ? ? ? 以下函数都是在window.h中的,我们只需要用即可。?
????????平常我们运?起来的?框程序其实就是控制台程序。我们可以设计窗口的大小和title
? ? ? ? system在<stdlib.h>中
system("mode con cols=100 lines=30");
system("title 贪吃蛇");
????????COORD 是Windows API中定义的?种结构,表??个字符在控制台屏幕上的坐标。
typedef struct _COORD {
SHORT X;
SHORT Y;
} COORD, *PCOORD;
? ? ? ? 我们就可以控制一个字符在控制台屏幕上出现的位置。
COORD pos = { 10, 15 };
????????GetStdHandle是?个Windows API函数。它?于从?个特定的标准设备(标准输?、标准输出或标 准错误)中取得?个句柄(?来标识不同设备的数值),使?这个句柄可以操作设备。
HANDLE GetStdHandle(DWORD nStdHandle);
????????这里我们使用标准输出STD_OUTPUT_HANDLE。
//获取标准输出的句柄(?来标识不同设备的数值)
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
????????检索(查看)有关指定控制台屏幕缓冲区的光标??和可?性的信息。
BOOL WINAPI GetConsoleCursorInfo(
HANDLE hConsoleOutput,
PCONSOLE_CURSOR_INFO lpConsoleCursorInfo
);
? ? ? ? 使用:
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(handle, &CursorInfo);//获取控制台光标信息
????????这个结构体,包含有关控制台游标的信息。
typedef struct _CONSOLE_CURSOR_INFO {
DWORD dwSize;
BOOL bVisible;
} CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO;
????????dwSize,由光标填充的字符单元格的百分?。 此值介于1到100之间。 光标外观会变化,范围从完 全填充单元格到单元底部的?平线条。
????????bVisible,游标的可?性。 如果光标可?,则此成员为 TRUE。(我们会把它设为false,来让光标不在屏幕上出现)。
CursorInfo.bVisible = false; //隐藏控制台光标
????????设置指定控制台屏幕缓冲区的光标的??和可?性。
BOOL WINAPI SetConsoleCursorInfo(
HANDLE hConsoleOutput,
const CONSOLE_CURSOR_INFO *lpConsoleCursorInfo
);
? ? ? ? 使用:
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
//隐藏光标操作
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(handle, &CursorInfo);//获取控制台光标信息
CursorInfo.bVisible = false; //隐藏控制台光标
SetConsoleCursorInfo(handle, &CursorInfo);//设置控制台光标状态
????????设置指定控制台屏幕缓冲区中的光标位置,我们将想要设置的坐标信息放在COORD类型的pos中,调 ?SetConsoleCursorPosition函数将光标位置设置到指定的位置。
BOOL WINAPI SetConsoleCursorPosition(
HANDLE hConsoleOutput,
COORD pos
);
? ? ? ? 使用:
COORD pos = { 10, 5};
//获取标准输出的句柄(?来标识不同设备的数值)
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
//设置标准输出上光标的位置为pos
SetConsoleCursorPosition(handle, pos);
????????封装?个设置光标位置的函数。
//设置光标的坐标
void SetPos(short x, short y)
{
COORD pos = { x, y };
HANDLE handle = NULL;
//获取标准输出的句柄(?来标识不同设备的数值)
handle = GetStdHandle(STD_OUTPUT_HANDLE);
//设置标准输出上光标的位置为pos
SetConsoleCursorPosition(handle, pos);
}
? ? ? ? 想要对光标进行其他操作,也可以加入里面,这里只是举例。?
????????获取按键情况,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 )
????????这?不得不讲?下控制台窗?的?些知识,如果想在控制台的窗?中指定位置输出信息,我们得知道 该位置的坐标,所以?先介绍?下控制台窗?的坐标知识。 控制台窗?的坐标如下所?,横向的是X轴,从左向右依次增?,纵向是Y轴,从上到下依次增?。
?????????在游戏地图上,我们打印墙体使?宽字符:□,打印蛇使?宽字符●,打印?物使?宽字符★ 普通的字符是占?个字节的,这类宽字符是占?2个字节。为什么我们要使用宽字符呢?我们在控制台可以看见,x轴的2长度才和y轴的1长度相当,所以我们为了让界面好看工整,我们使用宽字符。
????????为了使C语?适应国家化,C语?的标准中不断加?了国际化的?持。?如:加?和宽字符的类型 wchar_t 和宽字符的输?和输出函数,加?<locale.h>头?件,其中提供了允许程序员针对特定地区(通常是国家或者说某种特定语?的地理区域)调整程序?为的函数。
????????<locale.h>提供的函数?于控制C标准库中对于不同的地区会产?不?样?为的部分。 在标准可以中,依赖地区的部分有以下?项:
????????数字量的格式 , 货币量的格式 ,?字符集 ,??期和时间的表?形式
????????通过修改地区,程序可以改变它的?为来适应世界的不同区域。但地区的改变可能会影响库的许多部 分,其中?部分可能是我们不希望修改的。所以C语??持针对不同的类项进?修改,下?的?个宏, 指定?个类项:
????????LC_COLLATE:影响字符串比较函数strcoll()和strxfrm()
????????LC_CTYPE:影响字符处理函数的行为
????????LC_MONETARY:影响货币格式
????????LC_NUMERIC:影响printf()的数字格式
????????LC_TIME:影响时间格式strftime()和wcsftime()
????????LC_ALL - 针对所有类项修改。将以上所有类别设置为给定的语言环境。
char* setlocale (int category, const char* locale);
????????setlocale 函数?于修改当前地区,可以针对?个类项修改,也可以针对所有类项。
????????setlocale 的第?个参数可以是前?说明的类项中的?个,那么每次只会影响?个类项,如果第?个参 数是LC_ALL,就会影响所有的类项。
????????C标准给第?个参数仅定义了2种可能取值:"C"(正常模式)和" "(本地模式)。
????????在任意程序执?开始,都会隐藏式执?调?:
setlocale(LC_ALL, "C");
????????当地区设置为"C"时,库函数按正常?式执?,?数点是?个点。
????????当程序运?起来后想改变地区,就只能显?调?setlocale函数。?" "作为第2个参数,调?setlocale 函数就可以切换到本地模式,这种模式下程序会适应本地环境。?如:切换到我们的本地模式后就?持宽字符(汉字)的输出等。
setlocale(LC_ALL, " ");//切换到本地环境
????????那如果想在屏幕上打印宽字符,怎么打印呢?
? ? ? ? 宽字符的字面量必须加上前缀“L”,否则C语言会把字面量当作窄字符类项处理。前缀“L”在单引号前,表示宽字符,对应wprintf()的占位符为%lc;“L“在双引号前面,表示宽字符串,对应wprintf()的占位符为%ls。
#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;
}
? ? ? ??我们假设实现?个棋盘27?,58列的棋盘(?和列可以根据??的情况修改),再围绕地图画出墙。最终行= 25,列 = 54 = 宽字符27列。(x轴的长度一定得是2的倍数,否则由于宽字符的原因,后面可能会出现蛇身的某个结点或者食物一半在内,一半在墙上的问题。)
????????初始化状态,假设蛇的?度是5,蛇?的每个节点是●,在固定的?个坐标处,?如(24, 5)处开始出现 蛇,连续5个节点。注意:蛇的每个节点的x坐标必须是2个倍数(这里指蛇的每个结点的左边一定得对应x上2的倍数),否则可能会出现蛇的?个节点有?半?出现在墙体中,另外?般在墙外的现象,坐标不好对?。
????????关于?物,就是在墙体内随机?成?个坐标(x坐标必须是2的倍数),坐标不能和蛇的?体重合,然后打印★。
????????在游戏运?的过程中,蛇每次吃?个?物,蛇的?体就会变??节,如果我们使?链表存储蛇的信息,那么蛇的每?节其实就是链表的每个节点。每个节点只要记录好蛇?节点在地图上的坐标就?, 所以舍蛇节点结构如下:
typedef struct SnakeNode
{
int x;//(x,y)坐标
int y;
struct SnakeNode* next;//蛇身结点的下一个结点
}SnakeNode, *pSnakeNode;
//*pSnakeNode 等价于 typedef struct SnakeNode* pSnakeNode
// 则pSnakeNode = SnakeNode*
//即也定义结构体指针
????????要管理整条贪吃蛇,我们再封装?个Snake的结构来维护整条贪吃蛇:
//方向
enum DIRECTION
{
UP = 1,
DOWN,
LEFT,
RIGHT
};
//蛇的状态
enum GAME_STATUS
{
OK,//正常运行
END_NORMAL,//ESC退出
KILL_BY_WALL,//撞墙
KILL_BY_SELF//撞到自身
};
//整个游戏过程都在维护这条蛇,即多个结点构成的链表
typedef struct Snake
{
pSnakeNode _pSnakeHead;//指向蛇的头结点的指针
pSnakeNode _pFood;//指向食物的指针
//食物本质上也就是蛇身的结点,只不过打印方式不同
int _CurrentScore;//记录当前分数
int _FoodScore;//记录每个食物多少分,默认十分
int _SleepTime;//每走一步的休眠时间,即蛇走的速度
//休眠越少,速度越快;反之越慢。
enum DIRECTION _Dir;//描述蛇的方向
enum GAME_STATUS _Status;//游戏状态:正常,退出,撞墙,撞到自己
}Snake, *pSnake;
????????
#define _CRT_SECURE_NO_WARNINGS -1
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<windows.h>
#include<locale.h>
#include<time.h>
#define WALL "□"
#define BODY "●"
#define FOOD "★"
#define BEGIN_X 24
#define BEGIN_Y 5
#define KEY_PRESS(VK) ((GetAsyncKeyState(VK) & 0x1) ? 1 : 0)
//方向
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;//(x,y)坐标
int y;
struct SnakeNode* next;//蛇身结点的下一个结点
}SnakeNode, *pSnakeNode;
//*pSnakeNode 等价于 typedef struct SnakeNode* pSnakeNode
// 则pSnakeNode = SnakeNode*
//即也定义结构体指针
//整个游戏过程都在维护这条蛇,即多个结点构成的链表
typedef struct Snake
{
pSnakeNode _pSnakeHead;//指向蛇的头结点的指针
pSnakeNode _pFood;//指向食物的指针
//食物本质上也就是蛇身的结点,只不过打印方式不同
int _CurrentScore;//记录当前分数
int _FoodScore;//记录每个食物多少分,默认十分
int _SleepTime;//每走一步的休眠时间,即蛇走的速度
//休眠越少,速度越快;反之越慢。
enum DIRECTION _Dir;//描述蛇的方向
enum GAME_STATUS _Status;//游戏状态:正常,退出,撞墙,撞到自己
}Snake, *pSnake;
//----游戏开始---- - 完成游戏初始化
void GameStart(pSnake psnake);
//游戏欢迎界面
void WelComeToGame();
//定位光标
void SetPos(short x, short y);
//打印地图
void CreatMap();
//初始化蛇
void InitSnake(pSnake psnake);
//创建食物
void CreatFood(pSnake psnake);
//----游戏运行---- - 游戏的正常运行过程
void GameRun(pSnake psnake);
//打印帮助信息
void PrintHelpInfo();
//游戏暂停和恢复
void Pause();
//蛇动起来
void SnakeMove(pSnake psnake);
//判断移动之后是否是食物
bool NextIsFood(pSnake psnake, pSnakeNode pnext);
//吃掉食物
void EatFood(pSnake psnake,pSnakeNode pnext);
//不吃食物
void EatFood(pSnake psnake, pSnakeNode pnext);
//撞墙
void KillByWall(pSnake psnake);
//撞到自己
void KillBySelf(pSnake psnake);
//-----游戏结束---- - 善后工作(释放资源)
void GameEnd(pSnake psnake);
#include"snake.h"
//----游戏开始---- - 完成游戏初始化
//定位光标
void SetPos(short x, short y)
{
COORD pos = { x,y };
HANDLE handle_out = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleCursorPosition(handle_out, pos);//设置光标位置
}
//打印欢迎界面
void WelComeToGame()
{
//定位光标
//来到中间,打印”欢迎....“
SetPos(40,15);
printf("欢迎来到贪吃蛇小游戏\n");
SetPos(40, 25);
system("pause");//暂停命令
system("cls");//清理屏幕信息
SetPos(25, 15);
printf("使用 ↑ ↓ ← → 分别控制蛇的移动,F3是加速,F4是减速");
SetPos(40, 25);
system("pause");
system("cls");
}
//打印地图
void CreatMap()
{
//打印 col = 58,row = 27的棋盘
//宽字符横向占两个字节
//上:[0,0]到[56,0],下:[0,26]到[56,26]
//左:[0,1]到[0,25],右:[56,1]到[56,25]
//上
SetPos(0, 0);
for (int i = 0; i <= 56; i += 2)
{
printf(WALL);
}
//下
SetPos(0, 26);
for (int i = 0; i <= 56; i += 2)
{
printf(WALL);
}
//左
for (int i = 1; i <= 25; i++)
{
SetPos(0, i);
printf(WALL);
}
//右
for (int i = 1; i <= 25; i++)
{
SetPos(56, i);
printf(WALL);
}
SetPos(60, 25);
//system("pause");
}
//初始化蛇
void InitSnake(pSnake psnake)
{
pSnakeNode cur = NULL;
//默认开始蛇有五个结点
for (int i = 0; i < 5; i++)
{
pSnakeNode cur = (pSnakeNode)malloc(sizeof(SnakeNode));
if (cur == NULL)
{
perror("InitSnake malloc fail\n");
exit(-1);
}
cur->x = BEGIN_X+2*i, cur->y = BEGIN_Y;//横着连续放置每个结点
cur->next = NULL;
//来一个单链表头插法,将“蛇头”指到最右边那个
if (psnake->_pSnakeHead == NULL)
{
psnake->_pSnakeHead = cur;
}
else
{
cur->next = psnake->_pSnakeHead;
psnake->_pSnakeHead = cur;
}
}
//打印蛇身
cur = psnake->_pSnakeHead;
while (cur != NULL)
{
SetPos(cur->x, cur->y);
printf(BODY);
cur = cur->next;
}
psnake->_Status = OK;
psnake->_CurrentScore = 0;
psnake->_FoodScore = 10;
psnake->_pFood = NULL;
psnake->_SleepTime = 200;//200ms
psnake->_Dir = RIGHT;//因为上面的蛇初始化,所以这里蛇初始向右。
SetPos(60, 25);
//system("pause");
}
//创建食物
void CreatFood(pSnake psnake)
{
//坐标应该是随机生成,但是随机是有约束的
//首先不能出墙,其次x坐标必须是2的倍数,最后食物不能和蛇的身体冲突
int x = 0;
int y = 0;
//墙中间范围是:x[2,54],y[1,25]
again:
do
{
x = rand() % 53 + 2;
y = rand() % 25 + 1;
} while (x % 2 != 0);//x的坐标必须是2的倍数
//坐标不能和蛇身冲突
pSnakeNode cur = psnake->_pSnakeHead;
while (cur != NULL)
{
if (cur->x == x && cur->y == y)
{
goto again;//冲突就重新执行
}
cur = cur->next;
}
pSnakeNode pFoodNode = (pSnakeNode)malloc(sizeof(SnakeNode));
if (pFoodNode == NULL)
{
perror("CreatFood malloc fail\n");
exit(-1);
}
pFoodNode->x = x, pFoodNode->y = y;
pFoodNode->next = NULL;
psnake->_pFood = pFoodNode;
SetPos(x, y);
printf(FOOD);
SetPos(60, 25);
//system("pause");
}
void GameStart(pSnake psnake)
{
//控制台窗口的设置
system("mode con cols=100 lines=30");
system("title 贪吃蛇");
//隐藏光标
HANDLE handle_out = GetStdHandle(STD_OUTPUT_HANDLE);//获得句柄
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(handle_out, &CursorInfo);//获取光标信息
CursorInfo.bVisible = false;//隐藏光标
SetConsoleCursorInfo(handle_out, &CursorInfo);//设置光标状态
//打印欢迎界面
WelComeToGame();
//创建地图
CreatMap();
//初始化蛇
InitSnake(psnake);
//创建食物
CreatFood(psnake);
}
//----游戏运行---- - 游戏的正常运行过程
//打印帮助信息
void PrintHelpInfo()
{
SetPos(66, 15);
printf("1、不能撞墙,不能咬到自己");
SetPos(66, 16);
printf("2、使用↑.↓.←.→ 分别控制蛇移动");
SetPos(66, 17);
printf("3、F3加速,F4减速");
SetPos(66, 18);
printf("4、ESC-退出,空格-暂停/继续游戏");
SetPos(66, 19);
printf("ZY@版权");
SetPos(60, 25);
//system("pause");
}
//游戏暂停和恢复
void Pause()
{
while (1)
{
Sleep(100);
if (KEY_PRESS(VK_SPACE))
{
break;
}
}
}
//判断移动之后是否是食物
bool NextIsFood(pSnake psnake, pSnakeNode pnext)
{
if (psnake->_pFood->x == pnext->x && psnake->_pFood->y == pnext->y)
return true;
return false;
}
//吃掉食物
void EatFood(pSnake psnake, pSnakeNode pnext)
{
//头插
pnext->next = psnake->_pSnakeHead;
psnake->_pSnakeHead = pnext;
//打印蛇
pSnakeNode cur = psnake->_pSnakeHead;
while (cur != NULL)
{
SetPos(cur->x, cur->y);
printf(BODY);
cur = cur->next;
}
//释放食物结点
free(psnake->_pFood);
//加成绩
psnake->_CurrentScore += psnake->_FoodScore;
//新创建食物
CreatFood(psnake);
}
//不吃食物
void NotEatFood(pSnake psnake, pSnakeNode pnext)
{
//头插
pnext->next = psnake->_pSnakeHead;
psnake->_pSnakeHead = pnext;
//打印蛇身,不打印最后一个结点,并找到最后一个结点
pSnakeNode cur = psnake->_pSnakeHead;
while (cur->next->next != NULL)
{
SetPos(cur->x, cur->y);
printf(BODY);
cur = cur->next;
}
//将原本最后一个结点处的身体打印为空格
SetPos(cur->next->x, cur->next->y);
printf(" ");
//释放最后一个结点
free(cur->next);
cur->next = NULL;
}
//撞墙
void KillByWall(pSnake psnake)
{
//撞墙
if (psnake->_pSnakeHead->x == 0 || psnake->_pSnakeHead->x == 56
|| psnake->_pSnakeHead->y == 0 || psnake->_pSnakeHead->y == 26)
psnake->_Status = KILL_BY_WALL;
}
//撞到自己
void KillBySelf(pSnake psnake)
{
//蛇头与蛇身的坐标重合就是撞到了
pSnakeNode cur = psnake->_pSnakeHead->next;
while (cur != NULL)
{
if (psnake->_pSnakeHead->x == cur->x && psnake->_pSnakeHead->y == cur->y)
{
psnake->_Status = KILL_BY_SELF;
break;
}
cur = cur->next;
}
}
//蛇动起来
void SnakeMove(pSnake psnake)
{
//创建移动的下一个位置结点
pSnakeNode pnext = (pSnakeNode)malloc(sizeof(SnakeNode));
{
if (pnext == NULL)
{
perror("pnext malloc fail\n");
exit(-1);
}
}
pnext->next = NULL;
switch (psnake->_Dir)
{
case UP:
pnext->x = psnake->_pSnakeHead->x;
pnext->y = psnake->_pSnakeHead->y - 1;
break;
case DOWN:
pnext->x = psnake->_pSnakeHead->x;
pnext->y = psnake->_pSnakeHead->y + 1;
break;
case LEFT:
pnext->x = psnake->_pSnakeHead->x - 2;
pnext->y = psnake->_pSnakeHead->y;
break;
case RIGHT:
pnext->x = psnake->_pSnakeHead->x + 2;
pnext->y = psnake->_pSnakeHead->y;
break;
}
//判断移动之后,是否是食物,或者撞墙,或者撞到自己
//食物
if (NextIsFood(psnake, pnext))
{
//吃掉食物
EatFood(psnake,pnext);
}
else
{
//不吃
NotEatFood(psnake,pnext);
}
//撞墙
KillByWall(psnake);
//撞到自己
KillBySelf(psnake);
}
void GameRun(pSnake psnake)
{
//右侧打印帮助信息
PrintHelpInfo();
//打印当前已经获得分数和每个食物的分数
//并检测按键
do
{
SetPos(66, 10);
printf("得分:%05d", psnake->_CurrentScore);
SetPos(66, 11);
printf("每个食物分数:%2d", psnake->_FoodScore);
//检测按键
//按上时,并且蛇前一秒并不是往下走,就可以向上走;其他也一样
if (KEY_PRESS(VK_UP) && psnake->_Dir != DOWN)
{
psnake->_Dir = UP;
}
else if (KEY_PRESS(VK_DOWN) && psnake->_Dir != UP)
{
psnake->_Dir = DOWN;
}
else if (KEY_PRESS(VK_LEFT) && psnake->_Dir != RIGHT)
{
psnake->_Dir = LEFT;
}
else if (KEY_PRESS(VK_RIGHT) && psnake->_Dir != LEFT)
{
psnake->_Dir = RIGHT;
}
else if (KEY_PRESS(VK_ESCAPE))//ESC
{
psnake->_Status = END_NORMAL;
break;
}
else if (KEY_PRESS(VK_SPACE))//空格
{
Pause();
}
else if (KEY_PRESS(VK_F3))//F3,加速
{
if (psnake->_SleepTime>=80)//最快50,食物最高分20
{
psnake->_SleepTime -= 30;//每次缩短20ms
psnake->_FoodScore += 2;
}
}
else if (KEY_PRESS(VK_F4))//F4,减速
{
if (psnake->_SleepTime < 320)//最慢320,食物最低分2
{
psnake->_SleepTime += 30;
psnake->_FoodScore -= 2;
}
}
//蛇休眠(暂停)
Sleep(psnake->_SleepTime);
//蛇动起来
SnakeMove(psnake);
} while (psnake->_Status == OK);
}
//-----游戏结束---- - 善后工作(释放资源)
void GameEnd(pSnake psnake)
{
SetPos(20, 12);
switch (psnake->_Status)
{
case END_NORMAL:
printf("您主动退出游戏,Game Over");
break;
case KILL_BY_SELF:
printf("游戏自杀,Game Over");
break;
case KILL_BY_WALL:
printf("对不起,您撞墙了,Game Over");
break;
}
SetPos(0, 27);
//释放蛇身的结点
pSnakeNode cur = psnake->_pSnakeHead;
while (cur != NULL)
{
pSnakeNode tmp = cur->next;
free(cur);
cur = tmp;
}
free(cur);
psnake->_pSnakeHead = NULL;
}
#include"snake.h"
void Test()
{
int ch = 0;
do
{
//创建贪吃蛇
Snake snake = { 0 };
//1、游戏开始 - 初始化游戏
GameStart(&snake);
//2、游戏运行 - 游戏的正常运行过程
GameRun(&snake);
//3、游戏结束 - 善后工作(释放资源)
GameEnd(&snake);
SetPos(20, 14);
printf("再来一次吗?[ Y / N ]:");
ch = getchar();
getchar();//清理掉\n
} while (ch == 'Y' || ch == 'y');
SetPos(0, 27);
}
int main()
{
setlocale(LC_ALL, "");//本地化
srand((unsigned int)time(NULL));
Test();
return 0;
}
? ? ? ? 贪吃蛇游戏作为经典的小游戏,实现难度并不是很大。不过这个作为我们今后设计项目来训练思维也是非常不错的。