> 作者简介:დ旧言~,目前大二,现在学习Java,c,c++,Python等
> 座右铭:松树千年终是朽,槿花一日自为荣。> 目标:实现贪吃蛇
> 毒鸡汤:时间并不可真的帮我们去解决哪些问题,它只不过是会把原来怎么也想不通的问题,变得不再重要了。
> 望小伙伴们点赞👍收藏✨加关注哟💕💕
🌟前言
作为零零后的我们想必贪吃蛇都玩过吧,博主记得当时还在小霸王里面玩的这款小游戏,十分怀念,记忆深刻,每次都是偷偷躲在被窝里面玩,为了防止别抓,基本上是躲在厕所里面玩,上个厕所上个十年,丑事不必再提。作为一个程序员捏,当然要简单的实现它啦,也是为曾经的自己画上一个圆满的句号吧。那咱们闲话少谈,直接手撕贪吃蛇。
⭐游戏背景
我们采用的是VS2019编译环境,所以家人们准备好编译环境,当然咱们上手不能直接上代码,可能一开始上代码就是从入门到放弃,我懂大家的捏。咱们先介绍游戏,到时候反手就说,这个博主教我们怎么玩游戏,取关,必须取关,家人们不先知道怎么玩你怎么知道游戏的逻辑,给这些人拖出去斩了。
不知道大家还记得在C语言中我们实现了两款游戏,一个是三子棋,另一个是扫雷,这些游戏和贪吃蛇是久负盛名的游戏,它也和俄罗斯⽅块,扫雷等游戏位列经典游戏的⾏列。
博主后面这些经典小游戏都会一一实现,刺激
⭐游戏演示
看的是不是很神奇,一点也不神奇,不信你继续向下看。
贪吃蛇
⭐实现目标
使⽤C语⾔在Windows环境的控制台中模拟实现经典⼩游戏贪吃蛇
实现基本的功能:
• 贪吃蛇地图绘制
• 蛇吃⻝物的功能 (上、下、左、右⽅向键控制蛇的动作)
• 蛇撞墙死亡
• 蛇撞⾃⾝死亡
• 计算得分
• 蛇⾝加速、减速
• 暂停游戏
⭐技术要点
⭐win32 API介绍
使用Win32 API,应用程序可以充分挖掘Windows的32位操作系统的潜力。 Microsoft的所有32位平台都支持统一的API,包括函数、结构、消息、宏及接口。使用 Win32 API不但可以开发出在各种平台上都能成功运行的应用程序,而且也可以充分利用每个平台特有的功能和属性。
在具体编程时,程序实现方式的差异依赖于相应平台的底层功能的不同。最显著的差异是某些函数只能在更强大的平台上实现其功能。例如,安全函数只能在Windows NT操作系统下使用。另外一些主要差别就是系统限制,比如值的范围约束,或函数可管理的项目个数等等。
当然啦,咱们不用学习这里面全部知识,不然又要被黑子喷咯。
🌙Win32 API
有一些函数,并不用于交互,比如管理当前系统正在运行的进程、硬件系统状态的监视等等……这些函数只有一套,但是可以被所有的Windows程序调用(只要这个程序的权限足够高),简而言之,API是为程序所共享的。
为了达到所有程序能共享一套API的目的,Windows采用了“动态链接库”的办法。之所以叫“动态链接库”,是因为这样的函数库的调用方式是“随用随取”而不是像静态链接库那样“用不用都要带上”。
🌙控制台程序
像我们运行一个代码,就有一个黑框框,没错它就是控制台程序,Win32 API我们是可以控制它滴,终于有人可以管得住它了,不然它还是一个野孩子。
我们可以使⽤cmd命令来设置控制台窗⼝的⻓宽:设置控制台窗⼝的⼤⼩,30⾏,100列
1 mode con cols= 100 lines= 30
1 title 贪吃蛇
🌙控制台屏幕上的坐标COORD
typedef struct _COORD {
SHORT X;
SHORT Y;
} COORD, * PCOORD;
COORD pos = { 10 , 15 };
🌙GetStdHandle
HANDLE GetStdHandle(DWORD nStdHandle);HANDLE hOutput = NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值)
hOutput = GetStdHandle(STD _OUTPUT_HANDLE);
🌙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); // 获取控制台光标信息
🌙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); // 设置控制台光标状态
🌙SetConsoleCursorPosition
BOOL WINAPI SetConsoleCursorPosition (HANDLE hConsoleOutput,COORD pos);
COORD pos = { 10 , 5 };HANDLE hOutput = NULL ;// 获取标准输出的句柄 ( ⽤来标识不同设备的数值 )hOutput = GetStdHandle (STD_OUTPUT_HANDLE);// 设置标准输出上光标的位置为 posSetConsoleCursorPosition (hOutput,pos);
// 设置光标的坐标void SetPos ( short x, short y){COORD pos = { x, y };HANDLE hOutput = NULL ;// 获取标准输出的句柄 ( ⽤来标识不同设备的数值 )hOutput = GetStdHandle (STD_OUTPUT_HANDLE);// 设置标准输出上光标的位置为 posSetConsoleCursorPosition (hOutput, pos);}
🌙GetAsyncKeyState
SHORT GetAsyncKeyState ( int vKey );
# define KEY_PRESS(VK) ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )
⭐贪吃蛇游戏设计与分析
这里我们得了解游戏的逻辑和框架,别慌,一步一步慢慢来!!!
🌙地图设计
咱们看看地图是如何设计的:
既然要设计地图,必然需要在VS2019下控制台的坐标问题。
💫setlocale函数
char* setlocale (int category, const char* locale);
- setlocale 函数用于修改当前地区,可以针对⼀个类项修改,也可以针对所有类项。
- setlocale 的第一个参数可以是前面说明的类项中的一个,那么每次只会影响一个类项,如果第一个参数是LC_ALL,就会影响所有的类项。
C标准给第⼆个参数仅定义了2种可能取值:"C"和" "。在任意程序执⾏开始,都会隐藏式执⾏调⽤:
setlocale(LC_ALL, "C");
setlocale (LC_ALL, " " ); // 切换到本地环境
💫地图坐标
🌙蛇身和食物
🌙游戏流程设计
⭐核心逻辑实现分析(重点)
咱们从三个方面来讲解贪吃蛇的核心逻辑分析:test.c主函数,snake.h包含头文件,snake.c主函数的实现,跟我们玩链表差不多,都是老套路了那贪吃蛇的实现正式上路咯,启程。
🌙snake.h头文件的实现
像头文件这个东西必然是要包含一些头文件的,当然这只是它的一部分作用,像我们定义的链表,为了在各个源文件都能使用,我们就在头文件实现链表。如果函数很多,就把函数头包函数在头文件中,方便调用,那咱们看看在snake.h中有啥?
//包含头文件
#include <locale.h>
#include<stdio.h>
#include<assert.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
//键位的移动
#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;
int y;
struct SnakeNode* next;//下个节点
}SnakeNode, * pSnakeNode;
//贪吃蛇组成
typedef struct Snake
{
pSnakeNode _pSnake;//指向贪吃蛇头结点的指针
pSnakeNode _pFood;//指向食物结点的指针
int _Score;//贪吃蛇累计的总分
int _FoodWeight;//一个食物的分数
int _SleepTime;//每走一步休息的时间,时间越短,速度越快,时间越长,速度越慢
enum DIRECTION _Dir;//描述蛇的方向
enum GAME_STATUS _Status;//游戏的状态:正常、退出、撞墙、吃到自己
}Snake, * pSnake;
//游戏开始 - 完成游戏的初始化动作
void GameStart(pSnake ps);
//定位坐标
void SetPos(short x, short y);
//游戏开始的欢迎界面
void WelComeToGame();
//打印地图
void CreateMap();
//初始化贪吃蛇
void InitSnake(pSnake ps);
//创建食物
void CreateFood(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);
🌙snake.c源文件的实现
在这个文件中咱们实现贪吃蛇的函数:实现时当然需要包含snake.h的文件啦,需要用头文件里面的链表等.....
🌠游戏开始 - 初始化游戏
//1. 游戏开始 - 初始化游戏
void GameStart(pSnake ps)
{
//设置控制台的大小
system("mode con cols=100 lines=30");
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);
}
💫设置光标的坐标
光标的移动需要多次实现,为了我们方便使用移动光标位置,所以我们直接实现这个函数。不会有人跟我说光标是啥吧,看我打不死你,看到没:
设置光标位置在win32 API已经介绍咯。
//设置光标的坐标
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, 14);
printf("欢迎来到贪吃蛇小游戏");
SetPos(40, 25);
system("pause");//pause是暂停
system("cls");
SetPos(20, 14);
printf("使用 ↑ . ↓ . ← . → . 分别控制蛇的移动, F3是加速,F4是减速");
SetPos(40, 25);
system("pause");
system("cls");
}
💫创建地图/初始化贪食蛇
大家眼睛擦亮点,计算坐标时不要计算错了,可以根据自己设计自己喜欢的图:
切记一定要把蛇初始状态需要是偶数倍:初始化状态,假设蛇的长度是5,蛇身的每个节点是●,在固定的一个坐标处,比如(24, 5)处开始出现蛇,连续5个节点。注意:蛇的每个节点的x坐标必须是2个倍数,否则可能会出现蛇的⼀个节点有一半儿出现在墙体中,另外⼀般在墙外的现象,坐标不好对齐。
//创建地图
void CreateMap()
{
//上
SetPos(0, 0);
int i = 0;
for (i = 0; i <= 56; i += 2)
{
wprintf(L"%lc", WALL);
}
//下
SetPos(0, 26);
for (i = 0; i <= 56; i += 2)
{
wprintf(L"%lc", WALL);
}
//左
for (i = 1; i <= 25; i++)
{
SetPos(0, i);
wprintf(L"%lc", WALL);
}
//右
for (i = 1; i <= 25; i++)
{
SetPos(56, i);
wprintf(L"%lc", WALL);
}
}
//初始化贪食蛇
void InitSnake(pSnake ps)
{
//初始化蛇身 0 0 0 0 0
pSnakeNode cur = NULL;
//循环5个节点
for (int i = 0; i < 5; i++)
{
//开辟空间
cur = (pSnakeNode)malloc(sizeof(SnakeNode));
//判断是否开辟成功
if (cur == NULL)
{
perror("InitSnake:malloc");
exit(-1);
}
//初始化蛇的位置在(24,5)
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"%lc", BODY);
//指向下一个节点
cur = cur->next;
}
//对贪吃蛇的每个组成都初始化
ps->_Status = OK;//游戏的状态
ps->_Score = 0;//贪吃蛇累计的总分
ps->_pFood = NULL;//指向食物结点的指针
ps->_SleepTime = 200;//每走一步休息的时间
ps->_FoodWeight = 10;//一个食物的分数
//(我们默认蛇开始向右走)
ps->_Dir = RIGHT;//描述蛇的方向
}
💫创建食物
关于食物,就是在墙体内随机生成⼀个坐标(x坐标必须是2的倍数),坐标不能和蛇的身体重合,然后打印★。
在创建食物时我们需要注意一些事项:小本本拿好咯
- 食物可能与创建的蛇相同:就必须从新创建食物。(这里采用again)
- 食物需要开辟空间的:方便和蛇链接嘛,毕竟是单链表
- 食物的一些初始化
//创建食物
void CreateFood(pSnake ps)
{
//初始化
int x = 0;
int y = 0;
again:
do
{
x = rand() % 53 + 2;
y = rand() % 25 + 1;
} while (x % 2 != 0);//x坐标必须是2的倍数
//坐标不能和蛇的身体冲突
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;
ps->_pFood = pFood;
//打印食物
SetPos(x, y);
wprintf(L"%lc", FOOD);
}
🌠游戏运行 - 游戏的正常运行过程
//2. 游戏运行 - 游戏的正常运行过程
void GameRun(pSnake ps)
{
//打印帮助信息
PrintHelpInfo();
//循环方向,直到方向正确
do
{
//打印得分
SetPos(64, 10);
printf("得分:%05d", ps->_Score);
SetPos(64, 11);
printf("每个食物的分数:%2d", 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_ESCAPE))
{
ps->_Status = END_NORMAL;
break;
}
else if (KEY_PRESS(VK_SPACE))
{
//暂停蛇移动
Pause();
}
else if (KEY_PRESS(VK_F3))//加速
{
if (ps->_SleepTime >= 80)
{
ps->_SleepTime -= 30;
ps->_FoodWeight += 2;
}
}
else if (KEY_PRESS(VK_F4))//减速
{
if (ps->_SleepTime < 320)
{
ps->_SleepTime += 30;
ps->_FoodWeight -= 2;
}
}
//蛇动的时间
Sleep(ps->_SleepTime);
//蛇的移动
SnakeMove(ps);
} while (ps->_Status == OK);
}
💫打印帮助信息
这个函数根据自己的需要而写,这个就是看大家的创意咯
//打印帮助信息
void PrintHelpInfo()
{
SetPos(64, 15);
printf("1.不能撞墙,不能咬到自己");
SetPos(64, 16);
printf("2.使用 ↑.↓.←.→ 分别控制蛇的移动");
SetPos(64, 17);
printf("3.F3加速,F4减速");
SetPos(64, 18);
printf("4.ESC-退出, 空格-暂停游戏");
SetPos(64, 20);
printf("旧言专用版权");
}
💫判断蛇头到达的坐标处是否是食物
这个函数其实很挫,判断一下x和y坐标就行。
//判断蛇头到达的坐标处是否是食物
int NextIsFood(pSnake ps, pSnakeNode pnext)
{
if (ps->_pFood->x == pnext->x && ps->_pFood->y == pnext->y)
{
return 1;
}
else
{
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;
}
//释放食物空间
free(ps->_pFood);
//加分数
ps->_Score += ps->_FoodWeight;
//创建食物
CreateFood(ps);//新创建食物
}
💫不吃食物
运转到下一个坐标,开始覆盖,用头插,把前面的蛇尾置为空。
//不吃食物
void NoFood(pSnake ps, pSnakeNode pnext)
{
//头插
pnext->next = ps->_pSnake;
ps->_pSnake = pnext;
//打印蛇身
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);
cur->next = NULL;
}
💫蛇是否撞墙
这个函数还是比较简单的,判断墙体的坐标是否和蛇移动到的坐标相同
//蛇是否自杀
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()");
exit(-1);
}
pNext->next = NULL;
//蛇移动方向
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 Pause()
{
while (1)
{
Sleep(100);
if (KEY_PRESS(VK_SPACE))
{
break;
}
}
}
🌠游戏结束 - 游戏善后(释放资源)
等于释放内存,这个表示我们熟的来:
//3. 游戏结束 - 游戏善后(释放资源)
void GameEnd(pSnake ps)
{
//判断游戏状态
SetPos(20, 12);
switch (ps->_Status)
{
case END_NORMAL:
printf("您主动退出游戏\n");
break;
case KILL_BY_SELF:
printf("自杀了,游戏结束\n");
break;
case KILL_BY_WALL:
printf("撞墙了,游戏结束\n");
break;
}
//释放蛇身的结点
pSnakeNode cur = ps->_pSnake;
while (cur)
{
pSnakeNode del = cur;
cur = cur->next;
free(del);
}
ps->_pSnake = NULL;
}
🌙test.c源文件的实现
直接上代码好叭
//包含头文件
#include"snake.h"
void test()
{
int ch = 0;
do
{
Snake snake = { 0 };//创建了贪吃蛇
//1. 游戏开始 - 初始化游戏
GameStart(&snake);
//2. 游戏运行 - 游戏的正常运行过程
GameRun(&snake);
//3. 游戏结束 - 游戏善后(释放资源)
GameEnd(&snake);
SetPos(20, 18);
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;
}
🌞总代码
💧snake.c
#define _CRT_SECURE_NO_WARNINGS 1
//包含头文件
#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, 14);
printf("欢迎来到贪吃蛇小游戏");
SetPos(40, 25);
system("pause");//pause是暂停
system("cls");
SetPos(20, 14);
printf("使用 ↑ . ↓ . ← . → . 分别控制蛇的移动, F3是加速,F4是减速");
SetPos(40, 25);
system("pause");
system("cls");
}
//创建地图
void CreateMap()
{
//上
SetPos(0, 0);
int i = 0;
for (i = 0; i <= 56; i += 2)
{
wprintf(L"%lc", WALL);
}
//下
SetPos(0, 26);
for (i = 0; i <= 56; i += 2)
{
wprintf(L"%lc", WALL);
}
//左
for (i = 1; i <= 25; i++)
{
SetPos(0, i);
wprintf(L"%lc", WALL);
}
//右
for (i = 1; i <= 25; i++)
{
SetPos(56, i);
wprintf(L"%lc", WALL);
}
}
//初始化贪食蛇
void InitSnake(pSnake ps)
{
//初始化蛇身 0 0 0 0 0
pSnakeNode cur = NULL;
//循环5个节点
for (int i = 0; i < 5; i++)
{
//开辟空间
cur = (pSnakeNode)malloc(sizeof(SnakeNode));
//判断是否开辟成功
if (cur == NULL)
{
perror("InitSnake:malloc");
exit(-1);
}
//初始化蛇的位置在(24,5)
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"%lc", BODY);
//指向下一个节点
cur = cur->next;
}
//对贪吃蛇的每个组成都初始化
ps->_Status = OK;//游戏的状态
ps->_Score = 0;//贪吃蛇累计的总分
ps->_pFood = NULL;//指向食物结点的指针
ps->_SleepTime = 200;//每走一步休息的时间
ps->_FoodWeight = 10;//一个食物的分数
//(我们默认蛇开始向右走)
ps->_Dir = RIGHT;//描述蛇的方向
}
//创建食物
void CreateFood(pSnake ps)
{
//初始化
int x = 0;
int y = 0;
again:
do
{
x = rand() % 53 + 2;
y = rand() % 25 + 1;
} while (x % 2 != 0);//x坐标必须是2的倍数
//坐标不能和蛇的身体冲突
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;
ps->_pFood = pFood;
//打印食物
SetPos(x, y);
wprintf(L"%lc", FOOD);
}
//1. 游戏开始 - 初始化游戏
void GameStart(pSnake ps)
{
//设置控制台的大小
system("mode con cols=100 lines=30");
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 PrintHelpInfo()
{
SetPos(64, 15);
printf("1.不能撞墙,不能咬到自己");
SetPos(64, 16);
printf("2.使用 ↑.↓.←.→ 分别控制蛇的移动");
SetPos(64, 17);
printf("3.F3加速,F4减速");
SetPos(64, 18);
printf("4.ESC-退出, 空格-暂停游戏");
SetPos(64, 20);
printf("旧言专用版权");
//getchar();
}
//判断蛇头到达的坐标处是否是食物
int NextIsFood(pSnake ps, pSnakeNode pnext)
{
if (ps->_pFood->x == pnext->x && ps->_pFood->y == pnext->y)
{
return 1;
}
else
{
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;
}
//释放食物空间
free(ps->_pFood);
//加分数
ps->_Score += ps->_FoodWeight;
//创建食物
CreateFood(ps);//新创建食物
}
//不吃食物
void NoFood(pSnake ps, pSnakeNode pnext)
{
//头插
pnext->next = ps->_pSnake;
ps->_pSnake = pnext;
//打印蛇身
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);
cur->next = NULL;
}
//蛇是否撞墙
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;
}
//蛇是否自杀
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()");
exit(-1);
}
pNext->next = NULL;
//蛇移动方向
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 Pause()
{
while (1)
{
Sleep(100);
if (KEY_PRESS(VK_SPACE))
{
break;
}
}
}
//2. 游戏运行 - 游戏的正常运行过程
void GameRun(pSnake ps)
{
//打印帮助信息
PrintHelpInfo();
//循环方向,直到方向正确
do
{
//打印得分
SetPos(64, 10);
printf("得分:%05d", ps->_Score);
SetPos(64, 11);
printf("每个食物的分数:%2d", 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_ESCAPE))
{
ps->_Status = END_NORMAL;
break;
}
else if (KEY_PRESS(VK_SPACE))
{
//暂停蛇移动
Pause();
}
else if (KEY_PRESS(VK_F3))//加速
{
if (ps->_SleepTime >= 80)
{
ps->_SleepTime -= 30;
ps->_FoodWeight += 2;
}
}
else if (KEY_PRESS(VK_F4))//减速
{
if (ps->_SleepTime < 320)
{
ps->_SleepTime += 30;
ps->_FoodWeight -= 2;
}
}
//蛇动的时间
Sleep(ps->_SleepTime);
//蛇的移动
SnakeMove(ps);
} while (ps->_Status == OK);
}
//3. 游戏结束 - 游戏善后(释放资源)
void GameEnd(pSnake ps)
{
//判断游戏状态
SetPos(20, 12);
switch (ps->_Status)
{
case END_NORMAL:
printf("您主动退出游戏\n");
break;
case KILL_BY_SELF:
printf("自杀了,游戏结束\n");
break;
case KILL_BY_WALL:
printf("撞墙了,游戏结束\n");
break;
}
//释放蛇身的结点
pSnakeNode cur = ps->_pSnake;
while (cur)
{
pSnakeNode del = cur;
cur = cur->next;
free(del);
}
ps->_pSnake = NULL;
}
💧snake.h
#pragma once
//包含头文件
#include <locale.h>
#include<stdio.h>
#include<assert.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
//键位的移动
#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;
int y;
struct SnakeNode* next;//下个节点
}SnakeNode, * pSnakeNode;
//贪吃蛇组成
typedef struct Snake
{
pSnakeNode _pSnake;//指向贪吃蛇头结点的指针
pSnakeNode _pFood;//指向食物结点的指针
int _Score;//贪吃蛇累计的总分
int _FoodWeight;//一个食物的分数
int _SleepTime;//每走一步休息的时间,时间越短,速度越快,时间越长,速度越慢
enum DIRECTION _Dir;//描述蛇的方向
enum GAME_STATUS _Status;//游戏的状态:正常、退出、撞墙、吃到自己
}Snake, * pSnake;
//游戏开始 - 完成游戏的初始化动作
void GameStart(pSnake ps);
//定位坐标
void SetPos(short x, short y);
//游戏开始的欢迎界面
void WelComeToGame();
//打印地图
void CreateMap();
//初始化贪吃蛇
void InitSnake(pSnake ps);
//创建食物
void CreateFood(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);
💧test.c
#define _CRT_SECURE_NO_WARNINGS 1
//包含头文件
#include"snake.h"
void test()
{
int ch = 0;
do
{
Snake snake = { 0 };//创建了贪吃蛇
//1. 游戏开始 - 初始化游戏
GameStart(&snake);
//2. 游戏运行 - 游戏的正常运行过程
GameRun(&snake);
//3. 游戏结束 - 游戏善后(释放资源)
GameEnd(&snake);
SetPos(20, 18);
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;
}
🌟结束语
今天内容就到这里啦,时间过得很快,大家沉下心来好好学习,会有一定的收获的,大家多多坚持,嘻嘻,成功路上注定孤独,因为坚持的人不多。那请大家举起自己的小说手给博主一键三连,有你们的支持是我最大的动力💞💞💞,回见。