EasyX图形库__C语言__贪吃蛇核心代码
前言:(第一次博客啊,格式不太会)
简介:
1.虽然用的是EasyX的图形库,但其实EGE应该也可以,函数功能好多都是相通的;
2.去掉注释,代码全长不到150行,不要担心太啰嗦;
3.这里只给了核心代码——没有加蛇撞墙会死的设定,这个很简单,想搞自己可以搞上;
4.写这个之前,我先跑去吸收了两个算是……一点算法思想吧,一个是多物体同时移动里面的移动方向控制(多物体同时移动代码链接),另一个是常见的“方块版”贪吃蛇怎么实现蛇移动;
5.用的VS2019编译器,方便装EasyX图形库,不想用VS的可以拿DevCpp装EGE的改写;
6.适合对象:会单向链表就够了
先上效果:
机制:按着A、D键可以左右拐弯,A左D右
这是直接修改代码初始长度得到的
也可以代码调加粗点
开始讲解(完整代码在最后面):
第一次搞,所以我准备一步一步地上代码,让大家看清过程;
第一步:“初始化”
1.我们肯定要画图,不管其他怎么搞,先把画布给定下来,所以用宏定义一下画布宽度和高度:
2.然后我们肯定要画蛇,那就一个个地画圆就OK了,连起来就是“蛇”了;
那么对于每一个圆都应该得到它的坐标,蛇的长度会变长,还会移动,所以我们用结构体+动态内存分配来搞它;蛇的粗细也要控制,这个用宏定义圆半径就OK;
这里坐标我用的double类型,虽然画图函数好像都是int类型;
3.搞完了全局main函数外的初始化,下来搞main函数里的;
首先,蛇初始的长度、蛇头初始坐标(有了蛇头身子就挨着朝一个方向搞就OK了)、然后蛇的速度(主要是速度方向);
先贴个图再解释:
dir就是方向direction,vx、vy就是水平和竖直方向速度,正余弦函数取dir做参数分别得到x、y速度,可以保证只用改变dir就能实现转向;
vx、vy的意义,想想物理的运动学吧:分解就OK了,蛇初始长度length=10;蛇头初始坐标x、y;
第二步:开始画蛇
1.先把初始化的蛇画出来,这个时候要构建链表了,我们有蛇头,所以身体部分的坐标可以沿着速度方向搞出来;
vx、vy的作用就是根据dir来控制移动方向,这里我们可以懒省事,拿来初始画蛇,不用深究,总之就是先随便画length个连在一起的圆;
颜色我调出来的蓝色,自己选;
2.开始动态画蛇,这里要用到连续绘图(动态绘图)的三个XXXXBatchDraw()函数,不然屏幕会闪,不了解的去百度或者直接下载EasyX的help手册;
这里是画蛇的关键了,先上代码
死循环不解释,移动的自己看注释,到这里我们已经实现蛇的转向和自由移动了;
实在看不懂的也别着急,先去看看“方块版”的贪吃蛇怎么实现蛇移动的就懂了,方向控制不明白的,去看看前面给的EGE的那个链接;
第三步:食物和增长
前面的难的都搞定了,后面很容易了;
代码条不好截图,我直接先上全部代码了:
#include <stdio.h>
#include <math.h>
#include <time.h>
#include <ctype.h>
#include <graphics.h>
#include <windows.h>
#include <conio.h>
#include "resource.h"//你们的没资源放不了,注释掉运行;
#include <mmsystem.h>
#pragma comment(lib,"winmm.lib");
#define Width 1680
#define Height 980
//墙不想搞了,想搞自己加
/*#define wall_right
#define wall_left
#define wall_up
#define wall_down */
#define radius 10.0
#define PI 3.14159265358979
#define delay 100
typedef struct Snack Snack;
typedef struct Food Food;
struct Snack
{
double x, y;
Snack* pnext;
};
struct Food
{
int x, y;
};
Food foods[10];
int foodnum = 10, times = 1;
void Make_Food();
int main(void)
{
//复制的代码没资源运行不过,把下面这一行注释掉就OK;
PlaySoundA((LPCSTR)IDR_WAVE1, GetModuleHandle(NULL), SND_RESOURCE | SND_ASYNC | SND_LOOP);
for (int i = 0; i < 10; i++)
{//这里是我的函数设定了,-1表示这个结构体没有食物,初始的时候没食物全-1
foods[i].x = -1;
foods[i].y = -1;
}
int i;
char keya, keyd;
// Standard:初始化标准
double x = 200, y = 200, length = 10, dir = PI / 2;
double vx = sinf(dir), vy = cosf(dir), distance, temp1, temp2;
// 链表构建与初始化
Snack* phead = (Snack*)calloc(sizeof(Snack), 1);
Snack* pcurrent = NULL;
phead->x = x, phead->y = y;
phead->pnext = NULL;
pcurrent = phead;
for (i = 0; i < length; i++)
{
pcurrent->pnext = (Snack*)calloc(sizeof(Snack), 1);
pcurrent = pcurrent->pnext;
pcurrent->pnext = NULL;
pcurrent->x = x - 1.5 * i * vx * radius, pcurrent->y = y - 1.5 * i * vy * radius;
}
initgraph(Width, Height, EW_SHOWCONSOLE);
setcolor(RGB(255, 255, 0));
setfillcolor(RGB(100, 150, 200));
Make_Food();
for (pcurrent = phead; pcurrent != NULL; pcurrent = pcurrent->pnext)
{
fillcircle(pcurrent->x, pcurrent->y, radius);
}
BeginBatchDraw();
for (;;)
{
keya = GetKeyState('A');
keyd = GetKeyState('D');
if (keya < 0)
{
dir += 1.2f;//这里是转向度的大小,我定的1.2
vx = sinf(dir), vy = cosf(dir);
}
else if (keyd < 0)
{
dir -= 1.2f;//这里是转向度的大小,我定的1.2
vx = sinf(dir), vy = cosf(dir);
}
pcurrent = (Snack*)calloc(sizeof(Snack), 1);
pcurrent->x = phead->x;
pcurrent->y = phead->y;
pcurrent->pnext = phead;
phead = pcurrent;
pcurrent->x += 1.5 * vx * radius;
pcurrent->y += 1.5 * vy * radius;
pcurrent = phead;
setfillcolor(RGB(0, 255, 0));//蛇头单独挑出来加颜色,“高亮显示”
fillcircle(pcurrent->x, pcurrent->y, radius);
pcurrent = pcurrent->pnext;
setfillcolor(RGB(100, 150, 200));
for (; pcurrent->pnext->pnext != NULL; pcurrent = pcurrent->pnext)
{
fillcircle(pcurrent->x, pcurrent->y, radius);
}
for (i = 0; i < 10; i++)
{
distance = pow(phead->x - foods[i].x, 2) + pow(phead->y - foods[i].y, 2);
if (distance < radius * radius)
{
foods[i].x = -1;
foods[i].y = -1;
foodnum--;
Make_Food();
foodnum++;
for (pcurrent = phead; pcurrent->pnext->pnext != NULL;)
pcurrent = pcurrent->pnext;
temp1 = pcurrent->x, temp2 = pcurrent->y;
pcurrent = pcurrent->pnext;
pcurrent->pnext = (Snack*)calloc(sizeof(Snack), 1);
pcurrent->pnext->x = 2 * temp1 - pcurrent->x;
//数学:这里是中点坐标公式,中点就是原来的最后一个节点坐标,
//倒数第二个点就是一个点,推出要添加的新节点的坐标
pcurrent->pnext->y = 2 * temp2 - pcurrent->y;//
pcurrent->pnext->pnext = NULL;
pcurrent = pcurrent->pnext;
fillcircle(pcurrent->x, pcurrent->y, radius);
}
}
free(pcurrent->pnext);
pcurrent->pnext = NULL;
Make_Food();
FlushBatchDraw();
Sleep(delay);
cleardevice();
}
EndBatchDraw();
_getch();
}
void Make_Food()//产生食物函数
{
int i;
srand(time(NULL));
if (foodnum < 10 || times == 1)
for (i = 0; i < 10; i++)
{
if (foods[i].x == -1)
{
foods[i].x = rand() % (Width - 200);//限定范围
foods[i].y = rand() % (Height - 200);//限定范围
setfillcolor(RGB(rand(), rand(), rand()));
fillcircle(foods[i].x, foods[i].y, 5);
setfillcolor(RGB(100, 150, 200));
}
times++;
}
else
for (i = 0; i < 10; i++)
{
setfillcolor(RGB(rand(), rand(), rand()));
fillcircle(foods[i].x, foods[i].y, 5);
setfillcolor(RGB(100, 150, 200));
}
}
好了,下来开始慢慢说,食物的出现要随机出现,所以srand()+rand()+time() .(懂得都懂)
食物定义与产生
只有一个食物肯定太水,所以干脆搞个食物坐标结构体,这里我定义死了十个食物,全局变量foodnum,想改自己修改吧;
rand()%取余是保证食物的生成位置限定,不然生成到框框上,让人怎么吃???
关于食物会生成到蛇身上的,不要担心,这个是清屏重绘,一瞬间的事,和“方块版”的机制不一样;
食物这里我想快点结束了,所以有点草率,有的地方不精美,自己看着办吧;
蛇吃食物的判定与增长
蛇吃到食物就是用两点距离公式,看看蛇头的圆有没有包含食物的圆的圆心,包含了就吃掉,吃掉的食物坐标都改成-1,方便再生成食物的判定;
蛇的增长,和前面动态画蛇一样,就把链表再添一个圆,就OK了,*我的那个添加的代码放的位置 严格来说好像是顺序错误的,但是清屏重绘,一瞬间的事,就不改了;
蛇增长时新节点位置的确定: 这里要添加一个新的节点,我们用数学的中点坐标公式即可,原先最后一个节点的坐标当中点,链表取到倒数第二个点,拿temp1、temp2装进去,推出新节点的坐标位置;
不足与缺陷:
蛇的视觉效果上,速度和蛇的大小(粗细)相挂钩,蛇的行进单位是百分比个蛇身圆,所以速度快慢其实是不可完全控制的,只能选定合适的蛇身大小来相对控制。
这个我想了好久也没有很合适的办法,只有过一个稍麻烦的想法(但是要推倒重来了,和前面画蛇思路不同):
把Snack结构体再加上成员vx,vy,dir,把每个蛇身的运动都记录下来,分别控制每个蛇身的运动;
再定义一个结构体Corners(成员应包括x,y,dir的变化量),用Corners数组记录下每个拐弯处的坐标,后续的蛇身圆行进到Corners数组坐标时就改变dir,若未遇到Corners,就接着原先各自的速度行进;
这样不简洁,而且起初用的浮点数坐标,这样判定感觉怪怪的……
(有大佬能改进这个的,希望能给我指点一下,感激不尽!)
结语 :
1.跑代码的时候把那两个说的注释掉,不然报错;
2.嫌蛇转向度不够或过多的可以改里面的数值,也可以添加函数专门控制;
3.我最后没有写释放内存的代码,因为死循环,所以偷个懒;
4.已经提过这里只是关键代码,所以没有搞界面美化
5.大家有想法的,评论区提一下;我的代码肯定能有很多拉胯的地方,欢迎指出;