贪吃蛇—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.傲游.搜狗.世界之窗.效果图如下: 在线预览 源码下 ...
随机推荐
- javascript面向对象继承和原型
一.理解什么是对象:任何东西都可以是对象,对象就是一组无序属性的集合 对象具有属性和方法1.1 属性的类型属性内部又定义了两种属性:数据属性和访问器属性 (1)数据属性:有4个描述的行为 Config ...
- js基础笔录
1.页面中获取对象 document.getElementById("demo") 2.在页面加载时向 HTML 的 <body> 写文本 document.write ...
- memcached for .net on windows
memcached for windowshttp://www.cnblogs.com/dudu/archive/2009/07/19/1526407.htmlhttp://www.cnblogs.c ...
- 2017.12.4 JavaWeb中EL表达式的运用
<%@ page contentType="text/html; charset=gb2312"%> <html> <head> <tit ...
- Luogu [P1958] 上学路线_NOI导刊2009普及(6)
上学路线_NOI导刊2009普及(6) 题目详见:上学路线_NOI导刊2009普及(6) 这是一道基础的DFS(深搜)题,堪称模板,是新手练习搜索与回溯的好题选. 大致思路:从(1,1)开始搜索,每次 ...
- 搭建mock服务器(微信小程序)
搭建mock服务器(微信小程序) 如何在微信小程序使用mock.js实在是个问题,为了完全模拟访问路由和数据,选择在搭建本地mock服务器是一个不错的选择. 以下示例了一个mock服务器的搭建过程以及 ...
- SummerVocation_Learning--java的String类运用
题目: 编写一个程序,输出一个字符串中的大写字母数,小写字母数,及其它字母数. 思路1: 可以先遍历整个字符串,在判断每个字符的类型. public class TestString { public ...
- rsyn远程自动同步
rsync是远程自动同步工具,同时也能实现本地文件的复制,能够实现cp ,scp的功能,但是在远程同步上rsync要scp高效,因为scp能实现增量传输,每次都得全量传输,如果传输大文件时会很消耗网络 ...
- stark组件前戏(3)之django路由分发的本质include
django路由分发的三种方式 方式一: from django.urls import re_path, include urlpatterns = [ re_path(r'^web/', incl ...
- while循环中continue和break的区别
除了满足while条件外,还有两种方法可以终止循环,它们分别是break和continue.它们唯一的区别是break跳出整个循环,直接执行下面的代码了;而continue是终止当次循环,不执行下面的 ...