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.rhkb.cn/news/247816.html

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

相关文章

22.云原生之GitLab CICD实战及解析【干货】

云原生专栏大纲 文章目录 准备工作gitlab-ci.yml流水线mven打包项目制作并推送镜像kaniko方式docker方式 部署到k8s验证执行情况 GitLab Runner k8s执行器工作流程注册配置kubernetes runnerkubernetes runner配置通过修改 Pod 规范为每个构建作业创建一个 PVC自定义卷装载持久…

腾讯云Linux(OpenCloudOS)安装tomcat9(9.0.85)

腾讯云Linux(OpenCloudOS)安装tomcat9 下载并上传 tomcat官网 https://tomcat.apache.org/download-90.cgi 下载完成后上传至自己想要放置的目录下 解压文件 输入tar -xzvf apache-tomcat-9.0.85.tar.gz解压文件&#xff0c;建议将解压后的文件重新命名为tomcat,方便后期进…

如何编辑图片上的文字?分享5种可以编辑的工具!

在数字时代&#xff0c;图片已经成为信息传递的重要载体。有时候&#xff0c;我们需要在图片上添加文字&#xff0c;以增加信息的清晰度或创意性。那么&#xff0c;如何编辑图片上的文字呢&#xff1f;本文将为你揭秘编辑图片文字的必备工具&#xff0c;让你轻松实现创意表达。…

python小项目:口令保管箱

代码&#xff1a; #! python3 # python 编程-----口令保管箱passwords{emails: F7minlBDDuvMJuxESSKHFhTxFtjVB6,blog:VmALvQyKAxiVH5G8v01if1MLZF3sdt,luggage:12345,} import sys,pyperclip if len(sys.argv)<2:print(usage:python python3文件[accout]-copy accout pass…

【Linux网络编程】网络编程套接字(1)

【Linux网络编程】网络编程套接字(1) 目录 【Linux网络编程】网络编程套接字(1)源IP地址和目的IP地址端口号端口号和进程ID的关系 网络通信TCP协议UDP协议网络字节序socket编程接口简单的UDP网络程序 作者&#xff1a;爱写代码的刚子 时间&#xff1a;2024.1.29 前言&#xff1…

《HTML 简易速速上手小册》第9章:HTML5 新特性(2024 最新版)

文章目录 9.1 HTML5 新增标签和属性9.1.1 基础知识9.1.2 案例 1&#xff1a;创建一个结构化的博客页面9.1.3 案例 2&#xff1a;使用新的表单元素创建事件注册表单9.1.4 案例 3&#xff1a;创建一个具有高级搜索功能的搜索表单 9.2 HTML5 表单增强9.2.1 基础知识9.2.2 案例 1&a…

【Algorithms 4】算法(第4版)学习笔记 01 - 1.5 案例研究:union-find算法

文章目录 前言参考目录学习笔记1&#xff1a;动态连通性2&#xff1a;UF 实现 1&#xff1a;快速查找 quick-find2.1&#xff1a;demo 演示 12.2&#xff1a;demo 演示 22.3&#xff1a;quick-find 代码实现3&#xff1a;UF 实现 2&#xff1a;快速合并 quick-union3.1&#xf…

第 6 章:Linux中使用时钟、计时器和信号

在本章中&#xff0c;我们将开始探索Linux环境中可用的各种计时器。随后&#xff0c;我们将深入了解时钟的重要性&#xff0c;并探讨UNIX时间的概念。接下来&#xff0c;我们将揭示在Linux中使用POSIX准确测量时间间隔的方法。之后&#xff0c;我们将进入std::chrono的领域&…

第二篇:数据结构与算法-顺序表

顺序表 动态星空制作 #include <iostream> #include <graphics.h> #include <Windows.h> using namespace std;#define MAX_START 100 //星星数 #define MAX_MARGIN 80 //随机地 #define WIN_WIDTH 640 //窗口宽 #define WIN_HEIGHT 480 //窗口高 #define…

.ui文件相关

目录 ui类生成过程&#xff1a; 提问&#xff1a; 等以后自己熟练了用代码写这些样式内容&#xff0c;尽量用代码写&#xff0c;原因很简单&#xff1a; 用代码写的可以直接修改代码&#xff0c;但是在设计界面修改的东西&#xff0c;电脑没有QC这玩意&#xff0c;还真不好改…

计算机网络-数据交换方式(电路交换 报文交换 分组交换及其两种方式 )

文章目录 为什么要数据交换&#xff1f;总览电路交换电路交换的各个阶段建立连接数据传输释放连接 电路交换的特点电路交换的优缺点 报文交换报文交换流程报文交换的优缺点 分组交换分组交换流程分组交换的优缺点 数据交换方式的选择分组交换的两种方式数据报方式数据报方式的特…

深入浅出 diffusion(4):pytorch 实现简单 diffusion

1. 训练和采样流程 2. 无条件实现 import torch, time, os import numpy as np import torch.nn as nn import torch.optim as optim from torchvision.datasets import MNIST from torchvision import transforms from torch.utils.data import DataLoader from torchvision.…

基于Redis的高可用分布式锁——RedLock

目录 RedLock简介 RedLock工作流程 获取锁 释放锁 RedLock简介 Redis作者提出来的高可用分布式锁由多个完全独立的Redis节点组成&#xff0c;注意是完全独立&#xff0c;而不是主从关系或者集群关系&#xff0c;并且一般是要求分开机器部署的利用分布式高可以系统中大多数存…

基于ncurse的floppy_bird小游戏

1. 需求分析 将运动分解为鸟的垂直运动和杆的左右运动。 2. 概要设计 2.1 鸟运动部分 2.2 杆的运动 3. 代码实现 #include <stdio.h> #include <ncurses.h>#include <stdlib.h> #include <time.h>int vx 0; int vy 1;int bird_r; int bird_c;int…

2023年算法CDO-CNN-BiLSTM-ATTENTION回归预测(matlab)

2023年算法CDO-CNN-BiLSTM-ATTENTION回归预测&#xff08;matlab&#xff09; CDO-CNN-BiLSTM-Attention切诺贝利灾难优化器优化卷积-长短期记忆神经网络结合注意力机制的数据回归预测 Matlab语言。 切诺贝利灾难优化器Chernobyl Disaster Optimizer (CDO)是H. Shehadeh于202…

力扣题集(第一弹)

一日练,一日功;一日不练十日空。 学编程离不开刷题&#xff0c;接下来让我们来看几个力扣上的题目。 1. 242. 有效的字母异位词 题目描述 给定两个字符串 s 和 t &#xff0c;编写一个函数来判断 t 是否是 s 的字母异位词。 注意&#xff1a;若 s 和 t 中每个字符出现的次数…

详解OpenHarmony各部分文件在XR806上的编译顺序

大家好&#xff0c;今天我们来谈一谈编程时一个很有趣的话题——编译顺序。我知道&#xff0c;一提到编译可能大家会感到有点儿头疼&#xff0c;但请放心&#xff0c;我不会让大家头疼的。我们要明白&#xff0c;在开始写代码之前&#xff0c;了解整个程序的编译路径是十分有必…

[晓理紫]每日论文分享(有中文摘要,源码或项目地址)--大模型、扩散模型、视觉语言导航

专属领域论文订阅 VX 关注{晓理紫}&#xff0c;每日更新论文&#xff0c;如感兴趣&#xff0c;请转发给有需要的同学&#xff0c;谢谢支持 如果你感觉对你有所帮助&#xff0c;请关注我&#xff0c;每日准时为你推送最新论文。 为了答谢各位网友的支持&#xff0c;从今日起免费…

【国产MCU】-认识CH32V307及开发环境搭建

认识CH32V307及开发环境搭建 文章目录 认识CH32V307及开发环境搭建1、CH32V307介绍2、开发环境搭建3、程序固件下载1、CH32V307介绍 CH32V307是沁恒推出的一款基于32位RISC-V设计的互联型微控制器,配备了硬件堆栈区、快速中断入口,在标准RISC-V基础上大大提高了中断响应速度…

Unity3d实现简单的战斗

使用u3d实现一个简单的战斗demo&#xff0c;记下学到的知识点&#xff0c;以备后查。 1.判断鼠标是否点中制定物体 if (Input.GetMouseButton(0)) {Ray ray Camera.main.ScreenPointToRay(Input.mousePosition);if (Physics.Raycast(ray, out RaycastHit hit)){//坐标转换Ve…