上节我们用方向控制函数写了个小画图程序,它虽然简单好玩,但我们不应该止步于此。革命尚未成功,同志还需努力。

开始撸代码之前,我们先理清一下思路。和前面画图程序不同,贪吃蛇可以有很多节,可以用一个足够大的结构体数组来储存它。 还需要一个食物坐标。定义如下:

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图形库(下):从画图程序到贪吃蛇【自带穿墙术】的更多相关文章

  1. 贪吃蛇—C—基于easyx图形库(上):基本控制函数实现 画图程序

    自从学了c语言,就一直想做个游戏,今天将之付之行动,第一次写的特别烂,各种bug:就不贴了.今天网上看了好几个贪吃蛇,重新写了一次,做出来的效果还可以. p.s.  easyx图形库是为了方便图形学教 ...

  2. 基于EasyX库的贪吃蛇游戏——C语言实现

    接触编程有段时间了,一直想学习怎么去写个游戏来练练手.在看了B站上的教学终于可以自己试试怎么实现贪吃蛇这个游戏了.好了,废话不多说,我们来看看如何用EasyX库来实现贪吃蛇. 一.准备 工具vc++6 ...

  3. easyx图形库做贪吃蛇游戏

    编程总是对着一个黑窗口,可以说是非常乏味了,于是喵喵就翻出来了以前用easyx图形库做图形界面的贪吃蛇游戏. 不过大家只是当做提高编程的乐趣来学习吧,想进一步做的话可以学习QT,还有其他的框架. 这是 ...

  4. 基于jQuery向下弹出遮罩图片相册

    今天给大家分享一款基于jQuery向下弹出遮罩图片相册.单击相册图片时,一个遮罩层从上到下动画出现.然后弹出显示图片.这款插件适用浏览器:IE8.360.FireFox.Chrome.Safari.O ...

  5. 一款基于jquery的下拉点击改变背景图片

    今天给大家介绍一款基于jquery的下拉点击改变背景图片.单击右上角的图片,下拉显示可选择的背景图片,单击图片变为背景图.效果图下: 在线预览   源码下载 实现的代码. html代码: <a ...

  6. 基于Windows环境下cmd/编译器无法输入中文,显示中文乱码解决方案

    基于Windows环境下cmd/编译器无法输入中文,显示中文乱码解决方案 两个月前做C++课设的时候,电脑编译器编译结果出现了中文乱码,寻求了百度和大神们,都没有解决这个问题,百度上一堆解释是对编译器 ...

  7. 基于jsmpeg库下使用ffmpeg创建视频流连接websocket中继器传输视频并播放

    这个功能的基本工作是这样的: 1.使用node运行jsmpeg库下的websocket-relay.js文件,这个文件的作用是创建一个websocket视频传输中继器 2.运行ffmpeg,将输出发送 ...

  8. 【申嵌视频】基于VMWare虚拟机下安装ubuntu操作系统的详细步骤

    [申嵌视频]基于VMWare虚拟机下安装ubuntu操作系统 适合搭建mini2440, Tiny6410, smart210,Tiny4412, NanoPC-T2, NanoPC-T3, Nano ...

  9. 基于jQuery select下拉框美化插件

    分享一款基于jQuery select下拉框美化插件.该插件适用浏览器:IE8.360.FireFox.Chrome.Safari.Opera.傲游.搜狗.世界之窗.效果图如下: 在线预览   源码下 ...

随机推荐

  1. YII2 定义页面提示

    控制器里面这样写: 单条消息: 键值是规定好的,不要去自定义哦! \Yii::$app->getSession()->setFlash('error', 'This is the mess ...

  2. 如何解决EXCEL中的科学计数法

    EXCEL虽然能够有效的处理数据,尤其是数字的计算.但是,在单元格中输入数字的时候,很多时候都会受到科学计算法的困扰. 当单元格中输入的数字,超过11位时,就会自动变成科学计数法.无论您怎么调整列的宽 ...

  3. 【洛谷1120】小木棍(一道有技巧的dfs)

    点此看题面 大致题意: 给你\(N\)根小木棍,请你把它们拼成若干根长度相同的木棍,问你最小可能长度. 枚举+\(dfs\) 显然的,木棍的长度肯定是\(\sum_{i=1}^n len[i]\)的一 ...

  4. 【转】VS2010发布、打包安装程序(超全超详细)

    1. 在vs2010 选择“新建项目”→“ 其他项目类型”→“ Visual Studio Installer→“安装项目”: 命名为:Setup1 . 这是在VS2010中将有三个文件夹, 1.“应 ...

  5. 增强的格式化字符串format函数

    http://blog.csdn.net/handsomekang/article/details/9183303 自python2.6开始,新增了一种格式化字符串的函数str.format(),可谓 ...

  6. css属性选择器=,~=,^=,$=,*=,|=

    http://www.w3school.com.cn/css/css_selector_attribute.asp =. property和value必须完全一致 : ~=.“约等于”?: ^=. 从 ...

  7. double类型的小数,四舍五入保留两位小数

    import java.math.BigDecimal; public class Kewai{ public static void main(String[] args) { double f = ...

  8. 零基础快速入门SpringBoot2.0教程 (二)

    一.SpringBoot2.x使用Dev-tool热部署 简介:介绍什么是热部署,使用springboot结合dev-tool工具,快速加载启动应用 官方地址:https://docs.spring. ...

  9. primeng 中 pickList组件的使用

    primeng 是为angular 开发的一个强大的组建库,有很多强大的功能,拿来即用.但要真正满足自己的业务需求,就是按自己的需求进行修改,比如默认的样式等等. 进入正题. pickList 组件的 ...

  10. utf8、ansii、unicode编码之间的转换

    #include "stdafx.h"#include "windows.h"#include <iostream>#include <str ...