c语言实战之贪吃蛇

文章目录

  • 前言
    • 效果展示
    • 游戏用到的图片
    • 游戏思路一览
    • 游戏前准备
      • 一、贪吃蛇、食物、障碍物节点坐标的结构体
      • 二、枚举游戏状态、和贪吃蛇的方向
      • 三、维护运行的结构体
    • 游戏开始前的初始化
      • 一、学习图形库相关知识
      • 二、设置背景
      • 三、欢迎界面
      • 四、初始化贪吃蛇
      • 五、生成障碍物
      • 六、生成食物
      • 七、游戏前的初始化
    • 游戏运行过程
      • 一、打印分数和显示当前食物分数
      • 二、检测按键输入
      • 三、的移动过程
      • 四、游戏运行
    • 游戏结束


前言

1、使用工具:vs2022、EasyX图形库。
2、面向对象:非常适用于刚学完c语言和学过单链表的小伙伴哦。
3、作用:能够提高学习编程的兴趣、复习学过的c语言和单链表、提高编程的能力和逻辑能力。

效果展示

贪吃蛇

游戏用到的图片

背景:
在这里插入图片描述

蛇节点:
在这里插入图片描述
食物:
在这里插入图片描述
障碍物:
在这里插入图片描述
图片来自网络。
当然也可以改成其他图片哦。

游戏思路一览

![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/5d8189372b25414f9c2d66dc00299869.png

游戏前准备

一、贪吃蛇、食物、障碍物节点坐标的结构体

1、因为要包含不同类型所以我们用结构体。
2、我们用单链表来将贪吃蛇连在一起,不同位置的食物、障碍物也是通过单链表连在一起,这样方便我们找到它们的位置。
3、结构体成员:记录它们的坐标:(x,y),和记录下一个位置的前驱结构体指针:next。
4、参考代码:

//蛇的节点和食物
typedef struct SnakesNode
{//坐标int x;int y;//链接struct SnakesNode* next;
}SnakesNode, *pSnakesNode;

二、枚举游戏状态、和贪吃蛇的方向

1、游戏状态:正常运行、正常结束、撞墙、咬到自己、撞到障碍物。
2、游戏状态的作用:通过游戏状态判断蛇下一步应该怎么走。
3、参考代码:

//游戏状态
enum GAME_STATUS
{OK,//正常运⾏KILL_BY_WALL,//撞墙KILL_BY_SELF,//咬到⾃⼰KILL_BY_BARR,//撞到障碍物了END_NOMAL//正常结束
};

4、贪吃蛇的方向:上下左右。
5、方向的作用:判断贪吃蛇下一步的方向。
6、参考代码:

//⽅向
enum DIRECTION
{UP = 1,//上DOWN,//下LEFT,//左RIGHT//右
};

三、维护运行的结构体

结构体成员:
维护整条贪吃蛇的指针、维护⻝物的指针、维护障碍物的指针、贪吃蛇头的方向默认是向右、游戏状态、当前获得分数、当前食物的分数、每走⼀步休眠时间。

//维护整个运行
typedef struct Snakes
{pSnakesNode _pSnake;//维护整条贪吃蛇的指针pSnakesNode _pFood;//维护⻝物的指针pSnakesNode _pBarrier;//维护障碍物的指针enum DIRECTION _Dir;//蛇头的⽅向默认是向右enum GAME_STATUS _Status;//游戏状态int _Socre;//当前获得分数int _foodWeight;//默认每个⻝物10分int _SleepTime;//每⾛⼀步休眠时间
}Snakes,*pSnakes;

游戏开始前的初始化

一、学习图形库相关知识

在初始化之前我们先来了解一下图形库中的函数,如果没有的话可以先下载EasyX,这个直接在浏览器上搜索下载就行了,这个图形库会自己适配vs2022,并且里面有很多关于图形库的函数,感兴趣的可以去了解一下哦
界面:
在这里插入图片描述
关于坐标:
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/30f0f931b84d432b8a1c1369e13edd8f.png

我们现在来了解我们制作贪吃蛇用到的一些函数
使用下面函数都要包含头文件:#include<graphics.h>
1、loadimage函数
作用:获取图片到 IMAGE(图像对象的类型) 指针里。

// 从图片文件获取图像(bmp/gif/jpg/png/tif/emf/wmf/ico)
void loadimage(IMAGE* pDstImg,			// 保存图像的 IMAGE 对象指针LPCTSTR pImgFile,		// 图片文件名int nWidth = 0,			// 图片的拉伸宽度int nHeight = 0,		// 图片的拉伸高度bool bResize = false	// 是否调整 IMAGE 的大小以适应图片
);

参数说明:
在这里插入图片描述
这个函数报错问题:
更改以下设置就可以了:
在这里插入图片描述

2、putimage函数
作用:将图片加载到设备上。

// 绘制图像
void putimage(int dstX,				// 绘制位置的 x 坐标int dstY,				// 绘制位置的 y 坐标IMAGE *pSrcImg,			// 要绘制的 IMAGE 对象指针DWORD dwRop = SRCCOPY	// 三元光栅操作码
);

3、outtextxy函数
作用:在指定坐标输出字符串。

void outtextxy(//坐标int x,int y,LPCTSTR str //字符串
);

4、initgraph函数
作用:初始化窗口大小。

HWND initgraph(int width,int height,int flag = NULL
);

二、设置背景

1、我们背景窗口设置为1000,640,游戏的范围为800,640。
2、多出来的范围用于显示分数。

//绘制窗口大小
initgraph(1000, 640);
//图像对象
IMAGE bg_img;
//设置背景
loadimage(&bg_img, "blackground.bmp", 800, 640, true);
//加载到设备
putimage(0, 0, &bg_img);

效果:
在这里插入图片描述

三、欢迎界面

1、我们先在中间打印,然后再重新加载一个背景将前面的覆盖,再打印按键的提示,最后再覆盖一次,为后面的游戏准备。

//欢迎界⾯
void WelcomeToGame(IMAGE *bg_img)
{//在中间打印outtextxy(340, 300, "欢迎进入游戏...");//在末尾提示outtextxy(0, 620, "请按任意键开始游戏...");//任意键继续的一个函数system("pause");//重新加载背景putimage(0, 0, bg_img);//游戏按键提示outtextxy(190, 300, "⽤ ↑ . ↓ . ← . → 分别控制蛇的移动, F3为加速,F4为减速");outtextxy(0, 620, "请按任意键开始游戏...");system("pause");//再重新加载背景putimage(0, 0, bg_img);
}

效果:
在这里插入图片描述

四、初始化贪吃蛇

1、通过链表来将贪吃蛇连起来,默认为5个节点。
2、贪吃蛇的初始位置:(0,0),贪吃蛇一个节点的大小,我们设置为20*20
用于个宏定义来设置,方便以后更改:

//贪吃蛇的初始位置
#define POS_X 0
#define POS_Y 0
//节点大小
#define SIZE 20

3、链接过程:先创建一个头节点然后再用头插法将其他的节点头插进去,最后将头节点赋给维护贪吃蛇的指针,其他节点的位置就是与前一个节点的x隔着一个SIZE的大小(因为默认向右)。
4、再创建过程顺便将图片加载出来。
5、代码参考:

//加载贪吃蛇节点的函数
void Node(pSnakesNode p)
{//函数对象IMAGE node;//获取图片信息loadimage(&node, "屏幕截图 2024-01-28 140514.png", SIZE, SIZE, true);//加载到设备上putimage(p->x, p->y, &node);
}
//初始化贪吃蛇身
void InitSnake(pSnakes ps)
{//头节点pSnakesNode tmp = (pSnakesNode)malloc(sizeof(SnakesNode));//判断是否成功assert(tmp);//初始化第一个节点tmp->next = NULL;tmp->x = POS_X;tmp->y = POS_Y;//将这个节点加载到设备上Node(tmp);//有头插法将其他4个节点加入for (int i = 1; i <=4; i++){pSnakesNode tmp1 = (pSnakesNode)malloc(sizeof(SnakesNode));assert(tmp1);		tmp1->x = POS_X+i*SIZE;tmp1->y = POS_Y;//新的接后的一个tmp1->next = tmp;tmp = tmp1;tmp1 = NULL;Node(tmp);}//将结构体中维护蛇节点的指针赋值ps->_pSnake = tmp;tmp = NULL;//其他属性初始化ps->_Dir = RIGHT;ps->_Socre = 0;ps->_Status = OK;ps->_SleepTime = 150;ps->_foodWeight = 10;ps->_pBarrier = NULL;
}

效果:
在这里插入图片描述

五、生成障碍物

1、开始设置10个障碍物。
2、利用链表将所有障碍物连起来(通过头插入),这样方便找到它们的位置。
3、生成的位置:不能在贪吃蛇的蛇身、要在游戏的地图范围内,并且要是SIZE的倍数(保证与贪吃蛇的位置在同一个水平上)。
4、参考代码:

//加载障碍物图片
void Barr(pSnakesNode p)
{IMAGE barrier;loadimage(&barrier, "屏幕截图 2024-01-28 165936.png", SIZE, SIZE, true);putimage(p->x ,p->y, &barrier);
}
//生成障碍物
void barrier(pSnakes ps)
{//障碍物的坐标int x = 0;int y = 0;pSnakesNode p;//生成障碍物的坐标for (int i = 0; i < 10; i++){	again://跳回do {//1、要在地图内x = rand() % 780 + 1;y = rand() % 620 + 1;//2、x和y要在SIZE的倍数上} while (x % SIZE != 0 || y % SIZE != 0);//3、不能出现在贪吃蛇的身上pSnakesNode tmp = ps->_pSnake;while (tmp != NULL){if (tmp->x == x && tmp->y == y){//如果碰到是在贪吃蛇的身上的话就跳转到开始的位置goto again;}tmp = tmp->next;}p = (pSnakesNode)malloc(sizeof(SnakesNode));assert(p);p->next = NULL;p->x = x;p->y = y;//加载障碍物图片Barr(p);//如果ps->_pBarrier == NULL则将p赋给ps->_pBarrier,其他的用头插插入if (ps->_pBarrier == NULL){ps->_pBarrier = p;}else{p->next = ps->_pBarrier;ps->_pBarrier = p;p = NULL;}	}
}

六、生成食物

1、开始设置10个食物。
2、利用链表将所有食物连起来(通过头插入),这样方便找到它们的位置。
3、生成的位置:不能在贪吃蛇的蛇身、要在游戏的地图范围内,并且要是SIZE的倍数(保证与贪吃蛇的位置在同一个水平上),不要在障碍物上。
4、参考代码:

//加载食物图片到设备上
void Foob(pSnakesNode p)
{IMAGE food;loadimage(&food, "屏幕截图 2024-01-28 151002.png", SIZE, SIZE, true);putimage(p->x, p->y, &food);
}
//创建单个食物
pSnakesNode Create(pSnakes ps)
{
//食物坐标pSnakesNode p;int x = 0;int y = 0;
again:
in:do {//1、要在地图内x = rand() % 780 + 1;y = rand() % 620 + 1;//2、是SIZE的倍数} while (x % SIZE != 0 || y % SIZE != 0);//3、不能出现在贪吃蛇的身上pSnakesNode tmp = ps->_pSnake;while (tmp != NULL){if (tmp->x == x && tmp->y == y){goto again;}tmp = tmp->next;}//4、不能出现在障碍物身上pSnakesNode tmp1 = ps->_pBarrier;while (tmp1 != NULL){if (tmp1->x == x && tmp1->y == y){goto in;}tmp1 = tmp1->next;}p = (pSnakesNode)malloc(sizeof(SnakesNode));assert(p);p->next = NULL;p->x = x;p->y = y;//加载图片Foob(p);//返回这个食物节点return p;
}
//生成10个食物
void CreateFood(pSnakes ps)
{pSnakesNode p;//通过循环进行头插for (int i = 0; i < 10; i++){pSnakesNode p = Create(ps);if (ps->_pFood == NULL){ps->_pFood= p;}else{p->next = ps->_pFood;ps->_pFood = p;p = NULL;}}
}

5、生成障碍物和食物效果
在这里插入图片描述

七、游戏前的初始化

void GameStart(pSnakes ps)
{//绘制窗口大小initgraph(1000, 640);//图像对象IMAGE bg_img;//设置背景loadimage(&bg_img, "blackground.bmp", 800, 640, true);//加载到设备putimage(0, 0, &bg_img);//欢迎界面WelcomeToGame(&bg_img);// 初始化InitSnake(ps);//生成障碍物barrier(ps);//创建⻝物CreateFood(ps);
}

游戏运行过程

一、打印分数和显示当前食物分数

我们之前提到过在设置窗口的时候设置了比游戏地图大,因此我们就在多的位置上显示获得的分数和当前食物的得分。

//记录的分
char str[10] = { 0 };
//利用该函数将当前得分输入到str上
sprintf(str, "%d", ps->_Socre);
//记录当前食物的分数
char str1[5] = { 0 };
sprintf(str1, "%d", ps->_foodWeight);
//在规定的位置上输出
outtextxy(810, 20, "当前得分:");
outtextxy(940, 20, str);
outtextxy(810, 80, "当前食物得分:");
outtextxy(940, 80, str1);

效果:
在这里插入图片描述

二、检测按键输入

1、利用GetAsyncKeyState函数检测按键
头文件:#include<Windows.h>
函数原型:

SHORT GetAsyncKeyState([in] int vKey
);

将键盘上每个键的虚拟键值传递给函数,函数通过返回值来分辨按键的状态。
虚拟键值参考
GetAsyncKeyState 的返回值是short类型,在上⼀次调用 GetAsyncKeyState 函数后,如果返回的16位的short数据中,最高位是1,说明按键的状态是按下,如果最⾼是0,说明按键的状态是抬起;如果最低位被置为1则说明,该按键被按过,否则为0。
如果我们要判断⼀个键是否被按过,可以检测GetAsyncKeyState返回值的最低值是否为1。
因此我们可以定义一个宏来判断按键是否被按过

//按键
#define KEY_PRESS(VK) ((GetAsyncKeyState(VK)&0x1) ? 1 : 0)

这样我们就可以通过按键控制贪吃蛇的状态了。
2、按键:
⽤ ↑ . ↓ . ← . → 分别控制蛇的移动, F3为加速,F4为减速,空格为暂停,ESC为正常退出
暂停:
利用死循环来控制暂停(通过Sleep函数来延迟),当再次接受到空格键时直接打破。

//暂停
void pause()
{while (1){   //延迟函数Sleep(300);if (KEY_PRESS(VK_SPACE)){break;}}
}

加速减速:
通过减少延迟的时间就可以加速,减少就反之,当然加速每个食物的分数就会增加,减速每个食物的分数就会减少。

通过选择语句来判断

//判断按键
//上,走上就不能走下了,防止重叠
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();
}
//ESC
else if (KEY_PRESS(VK_ESCAPE))
{      //改变游戏状态ps->_Status = END_NOMAL;break;
}
//F3
else if (KEY_PRESS(VK_F3))
{//控制延迟的时间if (ps->_SleepTime >= 50){ps->_SleepTime -= 20;//分数增加ps->_foodWeight += 2;}
}
//F4
else if (KEY_PRESS(VK_F4))
{//控制延迟的时间if (ps->_SleepTime < 350){ps->_SleepTime += 20;//分数减少ps->_foodWeight -= 2;//不能太慢if (ps->_SleepTime == 350){ps->_foodWeight = 1;}}
}

三、的移动过程

第一步获得下一步的坐标:

//计算下一个坐标的位置
// //下一个节点
pSnakesNode tmp = (pSnakesNode)malloc(sizeof(SnakesNode));
assert(tmp);
tmp->next=NULL;
//上
if (ps->_Dir == UP)
{tmp->y = ps->_pSnake->y - SIZE;tmp->x = ps->_pSnake->x;
}
//下
else if (ps->_Dir == DOWN)
{tmp->y = ps->_pSnake->y + SIZE;tmp->x = ps->_pSnake->x;
}
//左
else if (ps->_Dir == LEFT)
{tmp->y = ps->_pSnake->y;tmp->x = ps->_pSnake->x-SIZE;
}
//右
else if (ps->_Dir == RIGHT)
{tmp->y = ps->_pSnake->y;tmp->x = ps->_pSnake->x+SIZE;
}

第二步判断是不是食物:
通过下一步的坐标和食物的坐标对比,如果符合的话就证明是食物

//判断是不是食物
bool judgment(pSnakesNode psn, pSnakes ps)
{pSnakesNode tmp = ps->_pFood;//遍历食物的所有坐标while (tmp != NULL){if (tmp->x == psn->x && tmp->y == psn->y){return true;}tmp = tmp->next;}return false;
}

第三步下一步是食物:
1、将下一步的坐标和贪吃蛇链接,并重新加载这个贪吃蛇节点图片。
2、重新生成一个新食物并重新加载食物节点图片。
3、利用前后指针的方法找到该食物的坐标和该食物的上一个坐标。
3、如果该食物是头节点的话,保存下一个节点同时释放该食物节点,用新食物做食物的头节点并链接保存的节点。
4、如果不是的话就保存该食物节点的下一个节点,用新食物的next连这个节点,再用保存的该食物的上一个节点的next连新食物,最后释放该食物节点。
5、进行加分。

//下⼀个节点是⻝物
void EatFood(pSnakesNode psn, pSnakes ps)
{//直接将下一个坐标和当前的贪吃蛇链接,并加载图片psn->next = ps->_pSnake;ps->_pSnake = psn;Node(psn);//找食物节点//前pSnakesNode p = ps->_pFood;//后pSnakesNode p1 =NULL;while (p != NULL){if (p->x == psn->x && p->y == psn->y){break;}p1 = p;p = p->next;}//生成一个食物pSnakesNode  p2 = Create(ps);//头if (p1 == NULL){p2->next = p->next;free(p);ps->_pFood = p2;}else{p1->next = p2;p2->next = p->next;free(p);}//加分ps->_Socre += ps->_foodWeight;
}

第四步下一步不是食物:
1、链接。
2、找到倒数第二个节点,在这个过程顺便加载新的贪吃蛇节点图片。
3、通过倒数第二个节点,将倒数第一个节点位置加载为背景颜色然后释放最后一个节点。

//下一个节点不是食物
void NoFood(pSnakesNode psn, pSnakes ps)
{//链接psn->next = ps->_pSnake;ps->_pSnake = psn;pSnakesNode tmp = ps->_pSnake;//找倒数第二个节点while (tmp->next->next!= NULL){Node(tmp);tmp = tmp->next;}IMAGE b;//设置背景loadimage(&b, "blackground.bmp", SIZE, SIZE, true);putimage(tmp->next->x, tmp->next->y, &b);//释放最后一个free(tmp->next);tmp->next = NULL;
}

第五步判断是否撞墙:
通过判断蛇头的位置的坐标是否超出地图的范围,超过了就改变蛇的状态。

//撞墙检测
int KillByWall(pSnakes ps)
{
//判断蛇头的位置是否超出地图的范围if (ps->_pSnake->x<0 || ps->_pSnake->x>780 || ps->_pSnake->y<0|| ps->_pSnake->y>620){//改变贪吃蛇的状态为撞墙ps->_Status = KILL_BY_WALL;return 1;}else{return 0;}
}

第六步判断是否撞到障碍物
通过遍历障碍物的坐标与蛇头坐标对比。

//撞到障碍物
int Hittheobstacles(pSnakes ps)
{pSnakesNode tmp = ps->_pSnake;pSnakesNode tmp1 = ps->_pBarrier;//遍历while (tmp1 != NULL){if (tmp1->x == tmp->x && tmp1->y == tmp->y){//改变游戏运行状态为撞到障碍物ps->_Status = KILL_BY_BARR;return 1;}tmp1 = tmp1->next;}return 0;
}

第七步判断是否咬到自己了
通过从贪吃蛇的第二个节点开始遍历与蛇头坐标对比。

/撞自⾝检测
int KillBySelf(pSnakes ps)
{pSnakesNode tmp = ps->_pSnake->next;while (tmp){//判断if (tmp->x == ps->_pSnake->x && tmp->y == ps->_pSnake->y){//改变游戏运行状态ps->_Status= KILL_BY_SELF;return 1;}tmp = tmp->next;}return 0;
}

最后总的贪吃蛇移动的代码:

//贪吃蛇的移动
void SnakeMove(pSnakes ps)
{//计算下一个坐标的位置// //下一个节点pSnakesNode tmp = (pSnakesNode)malloc(sizeof(SnakesNode));assert(tmp);tmp->next=NULL;//上if (ps->_Dir == UP){tmp->y = ps->_pSnake->y - SIZE;tmp->x = ps->_pSnake->x;}//下else if (ps->_Dir == DOWN){tmp->y = ps->_pSnake->y + SIZE;tmp->x = ps->_pSnake->x;}//左else if (ps->_Dir == LEFT){tmp->y = ps->_pSnake->y;tmp->x = ps->_pSnake->x-SIZE;}//右else if (ps->_Dir == RIGHT){tmp->y = ps->_pSnake->y;tmp->x = ps->_pSnake->x+SIZE;}//判断是否是食物//是食物if (judgment(tmp,ps)){	 EatFood(tmp, ps);}//不是食物else{NoFood(tmp, ps);}//是否撞墙KillByWall(ps);//是否咬到自己KillBySelf(ps);//是否撞到障碍物Hittheobstacles(ps);
}

四、游戏运行

1、通过do while循环,至少有一次的按键机会。
2、while()来判断游戏状态,如果不是OK就代表结束游戏了。
3、rhythm是全局变量,用于控制游戏节奏,每走一次就加1,默认为0,当每走到一定步数时,速度就会加快,同时每个食物的分数增加,还有当走到一定步数时障碍物会重新刷新。

//游戏运⾏
void GameRun(pSnakes ps)
{do {//记录的分char str[10] = { 0 };//利用该函数将当前得分输入到str上sprintf(str, "%d", ps->_Socre);//记录当前食物的分数char str1[5] = { 0 };sprintf(str1, "%d", ps->_foodWeight);//在规定的位置上输出outtextxy(810, 20, "当前得分:");outtextxy(940, 20, str);outtextxy(810, 80, "当前食物得分:");outtextxy(940, 80, str1);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 >= 50){ps->_SleepTime -= 20;ps->_foodWeight += 2;}}else if (KEY_PRESS(VK_F4)){if (ps->_SleepTime < 350){ps->_SleepTime += 20;ps->_foodWeight -= 2;if (ps->_SleepTime == 350){ps->_foodWeight = 1;}}}//暂停Sleep(ps->_SleepTime);//移动蛇SnakeMove(ps);//控制节奏rhythm++;if (rhythm % 150 == 0&&rhythm<=600&&ps->_SleepTime>=45){ps->_SleepTime -= 15;ps->_foodWeight += 2;}//重新刷新障碍物if (rhythm%200==0){IMAGE b;//设置背景loadimage(&b, "blackground.bmp", SIZE, SIZE, true);//释放之前的障碍物pSnakesNode tmp = ps->_pBarrier;while (tmp != NULL){putimage(tmp->x, tmp->y, &b);pSnakesNode tmp1 = tmp->next;free(tmp);tmp = tmp1;}ps->_pBarrier = NULL;barrier(ps);}} while (ps->_Status==OK);//判断游戏状态}

游戏结束

1、游戏结束后,通过判断游戏状态,来告诉玩家是什么原因无的。
2、释放蛇、食物、障碍物,最后结束程序。

/游戏结束
void GameEnd(pSnakes ps)
{
//判断是什么原因结束的if (ps->_Status == END_NOMAL){outtextxy(340, 300, "正常结束游戏");}else if (ps->_Status == KILL_BY_SELF){outtextxy(340, 300, "咬到自己了");}else if (ps->_Status == KILL_BY_WALL){outtextxy(340, 300, "撞墙了");}else if (ps->_Status == KILL_BY_BARR){outtextxy(340, 300, "撞到障碍物了");}//释放蛇pSnakesNode tmp = ps->_pSnake;while (tmp != NULL){pSnakesNode tmp1 = tmp->next;free(tmp);tmp = tmp1;}//释放障碍物tmp = ps->_pBarrier;while (tmp != NULL){pSnakesNode tmp1 = tmp->next;free(tmp);tmp = tmp1;}//最后释放食物tmp = ps->_pFood;while (tmp != NULL){pSnakesNode tmp1 = tmp->next;free(tmp);tmp = tmp1;}//按任意键system("pause");//结束程序exit(1);
}

总参考代码:
test.cpp

#include"Snakes.h"
void test()
{srand((unsigned int)time(NULL));Snakes snake = {0};GameStart(&snake);GameRun(&snake);GameEnd(&snake);
}
int main()
{test();return 0;
}

Snakes.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<Windows.h>
#include<locale.h>
#include<time.h>
#include<stdbool.h>
#include<assert.h>
#include<graphics.h>	
#include <conio.h>
#include<string>//按键
#define KEY_PRESS(VK) ((GetAsyncKeyState(VK)&0x1) ? 1 : 0)//蛇的初始位置
#define POS_X 0
#define POS_Y 0
#define SIZE 20
//⽅向
enum DIRECTION
{UP = 1,DOWN,LEFT,RIGHT
};//游戏状态
enum GAME_STATUS
{OK,//正常运⾏KILL_BY_WALL,//撞墙KILL_BY_SELF,//咬到⾃⼰KILL_BY_BARR,//撞到障碍物了END_NOMAL//正常结束
};//蛇的节点和食物
typedef struct SnakesNode
{//坐标int x;int y;//链接struct SnakesNode* next;
}SnakesNode, *pSnakesNode;//维护整个运行
typedef struct Snakes
{pSnakesNode _pSnake;//维护整条蛇的指针pSnakesNode _pFood;//维护⻝物的指针pSnakesNode _pBarrier;//维护障碍物的指针enum DIRECTION _Dir;//蛇头的⽅向默认是向右enum GAME_STATUS _Status;//游戏状态int _Socre;//当前获得分数int _foodWeight;//默认每个⻝物10分int _SleepTime;//每⾛⼀步休眠时间
}Snakes,*pSnakes;//游戏开始前的初始化
void GameStart(pSnakes ps);//欢迎界⾯
//void WelcomeToGame();
//创建地图
void CreateMap();
//设置光标信息
void SetPos(short x, short y);
//初始化蛇
void InitSnake(pSnakes ps);
//生成障碍物void barrier(pSnakes ps);
//创建⻝物
void CreateFood(pSnakes ps);
// 暂停响应
void pause();
//下⼀个节点是⻝物
void EatFood(pSnakesNode psn, pSnakes ps);
//下一个节点不是食物
void NoFood(pSnakesNode psn, pSnakes ps);
//蛇的移动
void SnakeMove(pSnakes ps);
//撞墙检测
int KillByWall(pSnakes ps);
//撞⾃⾝检测
int KillBySelf(pSnakes ps);
//游戏运⾏
void GameRun(pSnakes ps);
//游戏结束
void GameEnd(pSnakes ps);

Snakes.cpp

#include"Snakes.h"//控制游戏节奏
int rhythm = 0;
//设置光标的坐标
void SetPos(short x, short y)
{COORD pos = { x, y };HANDLE hOutput = NULL;//获取标准输出的句柄(⽤来标识不同设备的数值)hOutput = GetStdHandle(STD_OUTPUT_HANDLE);//设置标准输出上光标的位置为posSetConsoleCursorPosition(hOutput, pos);
}//欢迎界⾯
void WelcomeToGame(IMAGE *bg_img)
{//在中间打印outtextxy(340, 300, "欢迎进入游戏...");//在末尾提示outtextxy(0, 620, "请按任意键开始游戏...");//任意键继续函数system("pause");//重新加载背景putimage(0, 0, bg_img);//游戏按键提示outtextxy(190, 300, "⽤ ↑ . ↓ . ← . → 分别控制蛇的移动, F3为加速,F4为减速");outtextxy(0, 620, "请按任意键开始游戏...");system("pause");//再重新加载背景putimage(0, 0, bg_img);}void Node(pSnakesNode p)
{IMAGE node;loadimage(&node, "屏幕截图 2024-01-28 140514.png", SIZE, SIZE, true);putimage(p->x, p->y, &node);
}
void Barr(pSnakesNode p)
{IMAGE barrier;loadimage(&barrier, "屏幕截图 2024-01-28 165936.png", SIZE, SIZE, true);putimage(p->x ,p->y, &barrier);
}
void Foob(pSnakesNode p)
{IMAGE food;loadimage(&food, "屏幕截图 2024-01-28 151002.png", SIZE, SIZE, true);putimage(p->x, p->y, &food);
}//创建单个食物
pSnakesNode Create(pSnakes ps)
{pSnakesNode p;int x = 0;int y = 0;
again:
in:do {//1、要在地图内x = rand() % 780 + 1;y = rand() % 620 + 1;//2、x需要在奇数上} while (x % SIZE != 0 || y % SIZE != 0);//3、不能出现在蛇的身上pSnakesNode tmp = ps->_pSnake;while (tmp != NULL){if (tmp->x == x && tmp->y == y){goto again;}tmp = tmp->next;}//4、不能出现在障碍物身上pSnakesNode tmp1 = ps->_pBarrier;while (tmp1 != NULL){if (tmp1->x == x && tmp1->y == y){goto in;}tmp1 = tmp1->next;}p = (pSnakesNode)malloc(sizeof(SnakesNode));assert(p);p->next = NULL;p->x = x;p->y = y;Foob(p);return p;}//创建⻝物 
void CreateFood(pSnakes ps)
{pSnakesNode p;int x = 0;int y = 0;for (int i = 0; i < 10; i++){pSnakesNode p = Create(ps);if (ps->_pFood == NULL){ps->_pFood= p;}else{p->next = ps->_pFood;ps->_pFood = p;p = NULL;}}
}
//初始化蛇身
void InitSnake(pSnakes ps)
{//头节点pSnakesNode tmp = (pSnakesNode)malloc(sizeof(SnakesNode));//判断是否成功assert(tmp);//初始化第一个节点tmp->next = NULL;tmp->x = POS_X;tmp->y = POS_Y;//将这个节点加载到设备上Node(tmp);//有头插法将其他4个节点加入for (int i = 1; i <=4; i++){pSnakesNode tmp1 = (pSnakesNode)malloc(sizeof(SnakesNode));assert(tmp1);		tmp1->x = POS_X+i*SIZE;tmp1->y = POS_Y;//新的接后的一个tmp1->next = tmp;tmp = tmp1;tmp1 = NULL;Node(tmp);}//将结构体中维护蛇节点的指针赋值ps->_pSnake = tmp;tmp = NULL;//其他属性初始化ps->_Dir = RIGHT;ps->_Socre = 0;ps->_Status = OK;ps->_SleepTime = 150;ps->_foodWeight = 10;ps->_pBarrier = NULL;
}
//生成障碍物
void barrier(pSnakes ps)
{//障碍物的坐标int x = 0;int y = 0;pSnakesNode p;//生成障碍物的坐标for (int i = 0; i < 10; i++){	again://跳回do {//1、要在地图内x = rand() % 780 + 1;y = rand() % 620 + 1;//2、x和y要在SIZE的倍数上} while (x % SIZE != 0 || y % SIZE != 0);//3、不能出现在蛇的身上pSnakesNode tmp = ps->_pSnake;while (tmp != NULL){if (tmp->x == x && tmp->y == y){//如果碰到是在蛇的身上的话就跳转到开始的位置goto again;}tmp = tmp->next;}p = (pSnakesNode)malloc(sizeof(SnakesNode));assert(p);p->next = NULL;p->x = x;p->y = y;//加载障碍物图片Barr(p);//如果ps->_pBarrier == NULL则将p赋给ps->_pBarrier,其他的用头插插入if (ps->_pBarrier == NULL){ps->_pBarrier = p;}else{p->next = ps->_pBarrier;ps->_pBarrier = p;p = NULL;}	}
}void GameStart(pSnakes ps)
{//绘制窗口大小initgraph(1000, 640);//图像对象IMAGE bg_img;//设置背景loadimage(&bg_img, "blackground.bmp", 800, 640, true);//加载到设备putimage(0, 0, &bg_img);//欢迎界面WelcomeToGame(&bg_img);//创造地图//CreateMap();//蛇身初始化InitSnake(ps);//生成障碍物barrier(ps);//创建⻝物CreateFood(ps);
}
//打印右侧帮助信息
void PrintHelpInfo()
{//打印提⽰信息SetPos(64, 15);printf("不能穿墙,不能咬到⾃⼰\n");SetPos(64, 16);printf("⽤↑.↓.←.→分别控制蛇的移动.");SetPos(64, 17);printf("F1 为加速,F2 为减速\n");SetPos(64, 18);printf("ESC :退出游戏.space:暂停游戏.");
}
//暂停
void pause()
{while (1){Sleep(300);if (KEY_PRESS(VK_SPACE)){break;}}
}
//判断是不是食物
bool judgment(pSnakesNode psn, pSnakes ps)
{pSnakesNode tmp = ps->_pFood;while (tmp != NULL){if (tmp->x == psn->x && tmp->y == psn->y){return true;}tmp = tmp->next;}return false;
}
//下⼀个节点是⻝物
void EatFood(pSnakesNode psn, pSnakes ps)
{//直接将下一个坐标和当前的贪吃蛇链接,并加载图片psn->next = ps->_pSnake;ps->_pSnake = psn;Node(psn);//找食物节点//前pSnakesNode p = ps->_pFood;//后pSnakesNode p1 =NULL;while (p != NULL){if (p->x == psn->x && p->y == psn->y){break;}p1 = p;p = p->next;}//生成一个食物pSnakesNode  p2 = Create(ps);//头if (p1 == NULL){p2->next = p->next;free(p);ps->_pFood = p2;}else{p1->next = p2;p2->next = p->next;free(p);}//加分ps->_Socre += ps->_foodWeight;
}//下一个节点不是食物
void NoFood(pSnakesNode psn, pSnakes ps)
{psn->next = ps->_pSnake;ps->_pSnake = psn;pSnakesNode tmp = ps->_pSnake;//找倒数第二个节点while (tmp->next->next!= NULL){Node(tmp);tmp = tmp->next;}IMAGE b;//设置背景loadimage(&b, "blackground.bmp", SIZE, SIZE, true);putimage(tmp->next->x, tmp->next->y, &b);//释放最后一个free(tmp->next);tmp->next = NULL;
}
//撞墙检测
int KillByWall(pSnakes ps)
{if (ps->_pSnake->x<0 || ps->_pSnake->x>780 || ps->_pSnake->y<0|| ps->_pSnake->y>620){ps->_Status = KILL_BY_WALL;return 1;}else{return 0;}
}
//撞⾃⾝检测
int KillBySelf(pSnakes ps)
{pSnakesNode tmp = ps->_pSnake->next;while (tmp){if (tmp->x == ps->_pSnake->x && tmp->y == ps->_pSnake->y){ps->_Status= KILL_BY_SELF;return 1;}tmp = tmp->next;}return 0;
}
//撞到障碍物
int Hittheobstacles(pSnakes ps)
{pSnakesNode tmp = ps->_pSnake;pSnakesNode tmp1 = ps->_pBarrier;while (tmp1 != NULL){if (tmp1->x == tmp->x && tmp1->y == tmp->y){ps->_Status = KILL_BY_BARR;return 1;}tmp1 = tmp1->next;}return 0;
}//蛇的移动
void SnakeMove(pSnakes ps)
{//计算下一个坐标的位置// //下一个节点pSnakesNode tmp = (pSnakesNode)malloc(sizeof(SnakesNode));assert(tmp);tmp->next=NULL;//上if (ps->_Dir == UP){tmp->y = ps->_pSnake->y - SIZE;tmp->x = ps->_pSnake->x;}//下else if (ps->_Dir == DOWN){tmp->y = ps->_pSnake->y + SIZE;tmp->x = ps->_pSnake->x;}//左else if (ps->_Dir == LEFT){tmp->y = ps->_pSnake->y;tmp->x = ps->_pSnake->x-SIZE;}//右else if (ps->_Dir == RIGHT){tmp->y = ps->_pSnake->y;tmp->x = ps->_pSnake->x+SIZE;}//判断是否是食物//是食物if (judgment(tmp,ps)){	 EatFood(tmp, ps);}//不是食物else{NoFood(tmp, ps);}KillByWall(ps);KillBySelf(ps);Hittheobstacles(ps);
}//游戏运⾏
void GameRun(pSnakes ps)
{do {//记录的分char str[10] = { 0 };//利用该函数将当前得分输入到str上sprintf(str, "%d", ps->_Socre);//记录当前食物的分数char str1[5] = { 0 };sprintf(str1, "%d", ps->_foodWeight);//在规定的位置上输出outtextxy(810, 20, "当前得分:");outtextxy(940, 20, str);outtextxy(810, 80, "当前食物得分:");outtextxy(940, 80, str1);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 >= 50){ps->_SleepTime -= 20;ps->_foodWeight += 2;}}else if (KEY_PRESS(VK_F4)){if (ps->_SleepTime < 350){ps->_SleepTime += 20;ps->_foodWeight -= 2;if (ps->_SleepTime == 350){ps->_foodWeight = 1;}}}//暂停Sleep(ps->_SleepTime);//移动蛇SnakeMove(ps);rhythm++;if (rhythm % 150 == 0&&rhythm<=600&&ps->_SleepTime>=45){ps->_SleepTime -= 15;ps->_foodWeight += 2;}if (rhythm%200==0){IMAGE b;//设置背景loadimage(&b, "blackground.bmp", SIZE, SIZE, true);//释放pSnakesNode tmp = ps->_pBarrier;while (tmp != NULL){putimage(tmp->x, tmp->y, &b);pSnakesNode tmp1 = tmp->next;free(tmp);tmp = tmp1;}ps->_pBarrier = NULL;barrier(ps);}} while (ps->_Status==OK);}
//游戏结束
void GameEnd(pSnakes ps)
{if (ps->_Status == END_NOMAL){outtextxy(340, 300, "正常结束游戏");}else if (ps->_Status == KILL_BY_SELF){outtextxy(340, 300, "咬到自己了");}else if (ps->_Status == KILL_BY_WALL){outtextxy(340, 300, "撞墙了");}else if (ps->_Status == KILL_BY_BARR){outtextxy(340, 300, "撞到障碍物了");}//释放蛇pSnakesNode tmp = ps->_pSnake;while (tmp != NULL){pSnakesNode tmp1 = tmp->next;free(tmp);tmp = tmp1;}//释放障碍物tmp = ps->_pBarrier;while (tmp != NULL){pSnakesNode tmp1 = tmp->next;free(tmp);tmp = tmp1;}//最后释放食物tmp = ps->_pFood;while (tmp != NULL){pSnakesNode tmp1 = tmp->next;free(tmp);tmp = tmp1;}system("pause");exit(1);
}

以上就是我的分享了,如果有什么错误,欢迎在评论区留言。
最后,祝大家天天开心

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/438252.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【JaveWeb教程】(35)SpringBootWeb案例之《智能学习辅助系统》登录功能的详细实现步骤与代码示例(8)

目录 案例-登录和认证1. 登录功能1.1 需求1.2 接口文档1.3 思路分析1.4 功能开发1.5 测试 案例-登录和认证 在前面的课程中&#xff0c;我们已经实现了部门管理、员工管理的基本功能&#xff0c;但是大家会发现&#xff0c;我们并没有登录&#xff0c;就直接访问到了Tlias智能…

CMake 完整入门教程(一)

1 前言 每一次学习新东西都是很有乐趣的&#xff0c;虽然刚开始会花费时间用来学习&#xff0c;但是实践证明&#xff0c;虽然学习新东西可能会花费一些时间&#xff0c;但是它们带来的好处会远远超过这些花费的时间。学习新东西是值得的&#xff0c;也是很有乐趣的。 网络上…

如何搭建Nextcloud云存储网盘并实现无公网ip访问本地文件【内网穿透】

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

如何实现无公网ip远程SSH连接家中本地的树莓派

文章目录 如何通过 SSH 连接到树莓派步骤1. 在 Raspberry Pi 上启用 SSH步骤2. 查找树莓派的 IP 地址步骤3. SSH 到你的树莓派步骤 4. 在任何地点访问家中的树莓派4.1 安装 Cpolar4.2 cpolar进行token认证4.3 配置cpolar服务开机自启动4.4 查看映射到公网的隧道地址4.5 ssh公网…

免费分享一套微信小程序外卖跑腿点餐(订餐)系统(uni-app+SpringBoot后端+Vue管理端技术实现) ,帅呆了~~

大家好&#xff0c;我是java1234_小锋老师&#xff0c;看到一个不错的微信小程序外卖跑腿点餐(订餐)系统(uni-appSpringBoot后端Vue管理端技术实现) &#xff0c;分享下哈。 项目视频演示 【免费】微信小程序外卖跑腿点餐(订餐)系统(uni-appSpringBoot后端Vue管理端技术实现)…

全能相似度计算与语义匹配搜索工具包,多维度实现多种算法,涵盖文本、图像等领域。支持文图搜索,满足您在不同场景下的搜索需求

全能相似度计算与语义匹配搜索工具包,多维度实现多种算法,涵盖文本、图像等领域。支持文图搜索,满足您在不同场景下的搜索需求。 Similarities:精准相似度计算与语义匹配搜索工具包,多维度实现多种算法,覆盖文本、图像等领域,支持文搜、图搜文、图搜图匹配搜索 Similar…

jrt运维命令改造

以前发布网站都是定死网站放置路径的&#xff0c;现在JRT想面向更广范围推广&#xff0c;所以就不能明确确定网站放置目录&#xff0c;为此需要改造一下jrt命令和sh来满足目录不确定情况和多个程序用不同管理命令的要求。 以前是写死的&#xff0c;现在改为调程序运行目录的sh…

wsl-ubuntu 安装 nginx

wsl-ubuntu 安装 nginx 1. 安装 nginx2. 确认 nginx 启动状态3. 重启 nginx4. 停止 nginx 1. 安装 nginx sudo apt install nginx2. 确认 nginx 启动状态 systemctl status nginx3. 重启 nginx systemctl restart nginx4. 停止 nginx systemctl stop nginx完成&#xff01;…

网络相关知识

关于作者&#xff1a;CSDN内容合伙人、技术专家&#xff0c; 从零开始做日活千万级APP。 专注于分享各领域原创系列文章 &#xff0c;擅长java后端、移动开发、商业变现、人工智能等&#xff0c;希望大家多多支持。 目录 一、导读二、概览三、相关工具3.1 network profiler/ In…

Spring cloud智慧工地信息平台管理系统源码

目录 报警统计 实时报警列表 工程进度 劳务信息 隐患信息 施工安全管理 人员证书管理 专项安全方案 安全方案审批 隐患排查管理 安全检查统计 危险源Top10 整改超时预警 检查问题数量统计 安全隐患趋势 安全日志管理 视频监控查看 视频回放 AI危险源识别 AI应用总览 AI设备 机械…

mysql注入联合查询

环境搭建 下载复现漏洞的包 下载小皮面板 将下载好的文件解压在小皮面板的phpstudy_pro\WWW路径下 将这个文件phpstudy_pro\WWW\sqli-labs-php7-master\sql-connections\db-creds.inc 中的密码更改为小皮面板中的密码 选择php版本 在小皮中启动nginx和数据库 使用环回地址访…

23种设计模式-结构型模式

1.代理模式 在软件开发中,由于一些原因,客户端不想或不能直接访问一个对象,此时可以通过一个称为"代理"的第三者来实现间接访问.该方案对应的设计模式被称为代理模式. 代理模式(Proxy Design Pattern ) 原始定义是&#xff1a;让你能够提供对象的替代品或其占位符。…