贪吃蛇—C—基于easyx图形库(下):从画图程序到贪吃蛇【自带穿墙术】
上节我们用方向控制函数写了个小画图程序,它虽然简单好玩,但我们不应该止步于此。革命尚未成功,同志还需努力。
开始撸代码之前,我们先理清一下思路。和前面画图程序不同,贪吃蛇可以有很多节,可以用一个足够大的结构体数组来储存它。 还需要一个食物坐标。定义如下:
typedef struct Position //坐标结构
{
int x;
int y;
}Pos; Pos array; //移动方向向量
Pos snake[] = {}; //蛇的结构体数组,谁能够无聊到吃299999个食物~_~
long len=1; //蛇的长度
Pos egg; //食物坐标
之前的画图程序是四个方向都可以走,可蛇是不能倒着走的,所以方向控制函数要改成这样:
void command() //获取键盘命令
{
if (_kbhit()) //如果有键盘消息
switch (_getch()) /*这里不能用getchar()*/
{
case 'a':
if (array.x != || array.y != ) {//如果命令不是倒着走,就修正方向向量,否则不做改变,下同。
array.x = -;
array.y = ;
}
break;
case 'd':
if (array.x != - || array.y != ) {
array.x = ;
array.y = ;
}
break;
case 'w':
if (array.x != || array.y != ) {
array.x = ;
array.y = -;
}
break;
case 's':
if (array.x != || array.y != -) {
array.x = ;
array.y = ;
}
break;
}
}
蛇可能不止一节,所以移动函数需要做出改变。仔细一想就知道,走了一步之后,除了头结点外,每个节点的下一个坐标为它前一个结点之前的坐标,而头节点的坐标等于它本身坐标加上移动向量(这里是 方向向量*10)
还有个问题是蛇走过的痕迹需要擦除,每走一步,它留下的痕迹应该是走这一步之前蛇的最末一个结点的坐标,我们需要擦除掉它。
结果如下:
void move() //修改各节点坐标以达到移动的目的
{
setcolor(BLACK); //覆盖尾部走过的痕迹
rectangle(snake[len-].x - , snake[len-].y - , snake[len-].x + , snake[len-].y + ); for (int i = len-; i >; i--) //除了头结点外,每个节点的下一个坐标为它前一个结点当前的坐标
{
snake[i].x = snake[i - ].x;
snake[i].y = snake[i - ].y;
}
snake[].x += array.x*; //头节点的坐标等于它本身坐标加上移动向量(这里是 方向向量*10)
snake[].y += array.y*;
}
另外,我们的蛇是有穿墙术的~~~它的实现方法非常简单:
void break_wall()
{
if (snake[].x >= ) //如果越界,从另一边出来
snake[].x = ;
else if (snake[].x <= )
snake[].x = ;
else if (snake[].y >= )
snake[].y = ;
else if (snake[].y <= )
snake[].y = ;
}
接下来是食物相关函数,这个算是重点。
1. 食物生成
我们希望食物每次出现的位置都是随机的, 可以这样实现。
srand((unsigned)time(NULL));
egg.x = rand() % * + ; //头节点位置随机化
egg.y = rand() % * + ;
而且食物不能与蛇重合,最好也不要离蛇太近。综合起来就是这样:(srand在初始化中会被调用,所以这里略去了)
void creat_egg()
{
while (true)
{
int ok = ; //这是个标记,用于判断函数是否进入了某一分支
egg.x = rand() % * + ; //头节点位置随机化
egg.y = rand() % * + ;
for (int i = ; i < len; i++) //判断是否离蛇太近
{
if (snake[i].x == && snake[i].y == )
continue;
if (fabs(snake[i].x - egg.x) <= && fabs(snake[i].y - egg.y) <= )
ok = -; //如果,进入此分支,改变标记
break;
}
if (ok == ) //如果不重合了,跳出函数
return;
}
}
2. 吃到食物
如果吃到食物,那么需要消除被吃掉的食物,生成新食物,蛇也要增长一节。
我觉得这里最麻烦的就是蛇变长的实现:是在蛇头添加一节,还是在蛇尾?添加在蛇头(尾)的上下左右哪一边?
想来想去,只有在蛇头位置,我们可以根据当前方向向量,在移动方向上新添一节。这对应的代码如下:
//add snake node
len += ;
for (int i = len - ; i > ; i--) //所有数据后移一个单位,腾出snake[0]给新添的一节
{
snake[i].x = snake[i - ].x;
snake[i].y = snake[i - ].y;
}
snake[].x += array.x * ; //这就是新添的这一节的位置
snake[].y += array.y * ;
吃到食物的完整代码如下:
void eat_egg()
{
if (fabs(snake[].x - egg.x) <= && fabs(snake[].y - egg.y) <= ) //判断是否吃到食物,因为食物位置有点小偏差,只好使用范围判定~~
{
setcolor(BLACK); //hide old egg
circle(egg.x, egg.y, );
creat_egg(); //create new egg
//add snake node
len += ;
for (int i = len - ; i >; i--)
{
snake[i].x = snake[i - ].x;
snake[i].y = snake[i - ].y;
}
snake[].x += array.x * ; //每次移动10pix
snake[].y += array.y * ;
}
}
游戏结束判定
最后,我们还差一个死亡判定,因为自带穿墙术,所以实际的死亡判定只有一个,就是咬到自己,代码如下:
void eat_self()
{
if (len == ) //只有一节当然吃不到自己~~
return;
for (int i = ; i < len; i++)
if (fabs(snake[i].x - snake[].x) <= && fabs(snake[i].y - snake[].y) <= ) //如果咬到自己(为了不出bug,使用了范围判定)
{
outtextxy(, , "GAME OVER!"); //你的蛇死了~
Sleep(); //3s时间让你看看你的死相~~
closegraph();
exit(); //退出
}
}
当然,你也可以直接丢掉这个函数,然后开心地狂咬自己—_—||
最后:画图函数
画出食物和蛇,其实蛇没必要全部画出来,只要画蛇头就可以了,但这之中有些小问题,谁有兴趣可以自己玩玩,我是懒得动了~
void draw() //画出蛇和食物
{
setcolor(BLUE);
for (int i = ; i < len; i++)
{
rectangle(snake[i].x - , snake[i].y - , snake[i].x + , snake[i].y + );
}
setcolor(RED); //画蛋(怎么感觉怪怪的~)
circle(egg.x, egg.y, );
Sleep();
}
到这里,游戏大功告成~~ 什么?你说运行不起来?那是因为少了初始化函数,和游戏循环啦~~这几个都比较简单,就直接放下面了:
void init() //初始化
{
initgraph(, ); //初始化图形界面
srand((unsigned)time(NULL)); //初始化随机函数
snake[].x = rand() % * + ; //头节点位置随机化
snake[].y = rand() % * + ;
array.x = pow(-,rand()); //初始化方向向量,左或者右
array.y = ;
creat_egg();
} int main()
{
init();
while (true)
{
command(); //获取键盘消息
move(); //修改头节点坐标-蛇的移动
eat_egg();
draw(); //作图
eat_self();
} return ;
}
好了,这是真的大功告成了。给你们看看死亡方式之自尽:
完整代码如下:
#include<graphics.h>
#include<conio.h>
#include<time.h>
#include<math.h> typedef struct Position //坐标结构
{
int x;
int y;
}Pos; Pos snake[] = {};
Pos array;
Pos egg;
long len=; void creat_egg()
{
while (true)
{
int ok = ;
srand((unsigned)time(NULL)); //初始化随机函数
egg.x = rand() % * + ; //头节点位置随机化
egg.y = rand() % * + ;
for (int i = ; i < len; i++)
{
if (snake[i].x == && snake[i].y == )
continue;
if (fabs(snake[i].x - egg.x) <= && fabs(snake[i].y - egg.y) <= )
ok = -;
break;
}
if (ok == )
return;
}
} void init() //初始化
{
initgraph(, ); //初始化图形界面
srand((unsigned)time(NULL)); //初始化随机函数
snake[].x = rand() % * + ; //头节点位置随机化
snake[].y = rand() % * + ;
array.x = pow(-,rand()); //初始化方向向量
array.y = ;
creat_egg();
} void command() //获取键盘命令
{
if (_kbhit()) //如果有键盘消息
switch (_getch()/*这里不能用getchar()*/)
{
case 'a':
if (array.x != || array.y != ) {//如果不是反方向
array.x = -;
array.y = ;
}
break;
case 'd':
if (array.x != - || array.y != ) {
array.x = ;
array.y = ;
}
break;
case 'w':
if (array.x != || array.y != ) {
array.x = ;
array.y = -;
}
break;
case 's':
if (array.x != || array.y != -) {
array.x = ;
array.y = ;
}
break;
}
} void move() //修改各节点坐标以达到移动的目的
{
setcolor(BLACK); //覆盖尾部走过的痕迹
rectangle(snake[len-].x - , snake[len-].y - , snake[len-].x + , snake[len-].y + ); for (int i = len-; i >; i--)
{
snake[i].x = snake[i - ].x;
snake[i].y = snake[i - ].y;
}
snake[].x += array.x*; //每次移动10pix
snake[].y += array.y*; if (snake[].x >= ) //如果越界,从另一边出来
snake[].x = ;
else if (snake[].x <= )
snake[].x = ;
else if (snake[].y >= )
snake[].y = ;
else if (snake[].y <= )
snake[].y = ;
} void eat_egg()
{
if (fabs(snake[].x - egg.x)<= && fabs(snake[].y - egg.y)<=)
{
setcolor(BLACK); //shade old egg
circle(egg.x, egg.y, );
creat_egg();
//add snake node
len += ;
for (int i = len - ; i >; i--)
{
snake[i].x = snake[i - ].x;
snake[i].y = snake[i - ].y;
}
snake[].x += array.x * ; //每次移动10pix
snake[].y += array.y * ;
}
} void draw() //画出蛇和食物
{
setcolor(BLUE);
for (int i = ; i < len; i++)
{
rectangle(snake[i].x - , snake[i].y - , snake[i].x + , snake[i].y + );
}
setcolor(RED);
circle(egg.x, egg.y, );
Sleep();
} void eat_self()
{
if (len == )
return;
for (int i = ; i < len; i++)
if (fabs(snake[i].x - snake[].x) <= && fabs(snake[i].y - snake[].y) <= )
{
Sleep();
outtextxy(, , "GAME OVER!");
Sleep();
closegraph();
exit();
}
} int main()
{
init();
while (true)
{
command(); //获取键盘消息
move(); //修改头节点坐标-蛇的移动
eat_egg();
draw(); //作图
eat_self();
} return ;
}
snakey
可能还有若干bug留存,欢迎大家指正~~
甲铁城镇~
贪吃蛇—C—基于easyx图形库(下):从画图程序到贪吃蛇【自带穿墙术】的更多相关文章
- 贪吃蛇—C—基于easyx图形库(上):基本控制函数实现 画图程序
自从学了c语言,就一直想做个游戏,今天将之付之行动,第一次写的特别烂,各种bug:就不贴了.今天网上看了好几个贪吃蛇,重新写了一次,做出来的效果还可以. p.s. easyx图形库是为了方便图形学教 ...
- 基于EasyX库的贪吃蛇游戏——C语言实现
接触编程有段时间了,一直想学习怎么去写个游戏来练练手.在看了B站上的教学终于可以自己试试怎么实现贪吃蛇这个游戏了.好了,废话不多说,我们来看看如何用EasyX库来实现贪吃蛇. 一.准备 工具vc++6 ...
- easyx图形库做贪吃蛇游戏
编程总是对着一个黑窗口,可以说是非常乏味了,于是喵喵就翻出来了以前用easyx图形库做图形界面的贪吃蛇游戏. 不过大家只是当做提高编程的乐趣来学习吧,想进一步做的话可以学习QT,还有其他的框架. 这是 ...
- 基于jQuery向下弹出遮罩图片相册
今天给大家分享一款基于jQuery向下弹出遮罩图片相册.单击相册图片时,一个遮罩层从上到下动画出现.然后弹出显示图片.这款插件适用浏览器:IE8.360.FireFox.Chrome.Safari.O ...
- 一款基于jquery的下拉点击改变背景图片
今天给大家介绍一款基于jquery的下拉点击改变背景图片.单击右上角的图片,下拉显示可选择的背景图片,单击图片变为背景图.效果图下: 在线预览 源码下载 实现的代码. html代码: <a ...
- 基于Windows环境下cmd/编译器无法输入中文,显示中文乱码解决方案
基于Windows环境下cmd/编译器无法输入中文,显示中文乱码解决方案 两个月前做C++课设的时候,电脑编译器编译结果出现了中文乱码,寻求了百度和大神们,都没有解决这个问题,百度上一堆解释是对编译器 ...
- 基于jsmpeg库下使用ffmpeg创建视频流连接websocket中继器传输视频并播放
这个功能的基本工作是这样的: 1.使用node运行jsmpeg库下的websocket-relay.js文件,这个文件的作用是创建一个websocket视频传输中继器 2.运行ffmpeg,将输出发送 ...
- 【申嵌视频】基于VMWare虚拟机下安装ubuntu操作系统的详细步骤
[申嵌视频]基于VMWare虚拟机下安装ubuntu操作系统 适合搭建mini2440, Tiny6410, smart210,Tiny4412, NanoPC-T2, NanoPC-T3, Nano ...
- 基于jQuery select下拉框美化插件
分享一款基于jQuery select下拉框美化插件.该插件适用浏览器:IE8.360.FireFox.Chrome.Safari.Opera.傲游.搜狗.世界之窗.效果图如下: 在线预览 源码下 ...
随机推荐
- Windows底层开发前期学习准备工作
1.若对Windows底层开发没有兴趣,不建议继续深究, 若有些兴趣可以继续. 2. 先广泛打基础,比如C/ASM/C++/MFC,再学习Windows核心编程,对R3上的一些开发有所熟悉,再系统的学 ...
- hdu-3371 Connect the Cities---kruskal
题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=3371 题目大意: 给n个城市,m条路,k组已知路,求最小费用联通所有城市: 解题思路: kruska ...
- 【洛谷】CYJian的水题大赛 解题报告
点此进入比赛 \(T1\):八百标兵奔北坡 这应该是一道较水的送分题吧. 理论上来说,正解应该是DP.但是,.前缀和优化暴力就能过. 放上我比赛时打的暴力代码吧(\(hl666\)大佬说这种做法的均摊 ...
- 【洛谷3157】[CQOI2011] 动态逆序对(CDQ分治)
点此看题面 大致题意: 给你一个从\(1\)到\(n\)的排列,问你每次删去一个元素后剩余的逆序对个数. 关于\(80\)分的树套树 为了练树套树,我找到了这道题目. 但悲剧的是,我的 线段树套\(T ...
- squid隐藏squid的版本号
reply_header_access Via deny all reply_header_access Cache-Control deny all reply_header_access Serv ...
- SunmmerVocation_Learning--Java数组的创建
一维数组声明方式: type var[] 或 type[] var; 如int a[], int[] a; Java中声明数组不能指定其长度,如int a[5]是非法的. 一维数组对象的创建: Jav ...
- linux中管道(pipe)一谈
/*********************************************** 管道(pipe)是Linux上进程间通信的一种方式,其是半双工(数据流只能在一个方向上流动(还需要经过 ...
- 对比传统方式访问数据库和SpringData访问数据库
我们在写代码的时候应该一边写一边测试,这样的话可以尽快的找到错误,在代码写多了之后去找错误的话不容易给错误定位 传统方式访问数据库 1:创建一个Maven web项目 2:修改pom.xml为以下内容 ...
- 本地已经存在的项目如何跟github发生关联
切换到本地项目地址 git init 初始化项目.该步骤会创建一个 .git文件夹是附属于该仓库的工作树. git add . git commit -am 'initial commit' git ...
- SpringSecurity项目报错
启动时,提示: Unable to start ServletWebServerApplicationContext due to missing ServletWebServerFactory be ...