本次教程内容:

  • 键盘检测函数
  • 队列
  • 记录系统时间
  • 随机数
  • 改变控制台窗口大小

见一叶落,而知三秋至。

虽然还处于能够把控的状态,但现在的map.cpp代码结构已经越来越复杂,对象之间的知识和责任链也越来越不清晰。开发组小Q计划对代码再做一次重构。

但还没等理出头绪,小Pa却提出了新的需求:用现在的游戏机制,能实现贪吃蛇么?开发组的负责人小Q思考了一下表示:做一定的扩展之后能够实现。

小Pa于是高兴地回去准备地图了。

贪吃蛇还有地图么?不过先不管地图的事,看一看为了实现贪吃蛇的功能,现在游戏机制还需做哪些调整。
首先,必须引入一个新的键盘检测函数,getch()函数能够直接响应任意键,但毕竟是阻塞函数。意思是当程序运行到这里后,就会暂停,等待用户的输入。
对于现阶段的迷宫游戏来说,这样的功能已经足够了。但贪吃蛇不行,必须在用户不按键盘时,仍然保持移动。这就必须引入kbhit()函数,这个函数的作用是检测键盘,如果发现用户有按下键盘,再调用getch()获得所按的键,否则可以运行其他代码,比如蛇的移动。加上kbhit()后,由于处理RPG和贪吃蛇的键盘控制逻辑有明显的不同,所以将具体处理逻辑拆分成两个函数。增加一个地图的模式属性(mode)对地图做一个区分,究竟是RPG地图,还是贪吃蛇地图。新的键盘监听函数,源代码如下。

    void listen(){int a;while(1){if (kbhit()){a= getch();if (currentMap.mode==1) {handleKeyRPG(a);} else {handleKeySnake(a);}} else {if (currentMap.mode==1) {// 处理NPC的移动 } else {// 处理蛇的自动移动 autoSnake();}}}}

其实RGP游戏中,我们看到会自己走动的NPC,也必须依靠kbhit()函数才能实现。在这里预留了将来处理NPC自由行动的位置。具体handleKeySnake和autoSnake两个函数具体做了什么,让我们先看一个关键问题:现在的英雄怎样显示和隐藏。

以前的英雄只有一个字符,所以它的当前位置只需x,y两个变量来记录。现在变成一条蛇(或者在RPG游戏中被称作“火车”模式),蛇身上有很多位置,每个都必须记录。在不断增加记录头部位置的同时,还需删除尾部的记录。这种操作逻辑,比较自然地让我们相对“队列”这样一种数据结构。

所谓队列结构,与真实的排队是相似。它的特点用一句话概括就是先进先出。蛇的长度就是队的长度,蛇头就是队列尾,新的数据不断增加到队列尾;而蛇尾恰恰是队列头,就的数据从这里删除,并同时从屏幕上消除。

标准的队列有三个函数来实现这一功能:

  • push()      从队列尾推进一个新的数据
  • front()     看看队列头的数据(坐标)
  • pop()       从队列头弹出一个旧的数据

与队列操作结合后,贪吃蛇控制模式,操作流程与相关代码逻辑所在位置如下:

  • 1、英雄默认有一个当前移动方向,每200毫秒,自动向这个方向移动(autoSnake)
  • 2、与移动方向垂直的方向键,改变这个移动方向,键盘控制后,重新计时(setSnake)
  • 3、与移动方向相同或相反的方向键,无作用(setSnake)
  • 4、移动时,先看是否撞墙(trySnake)(即RPG模式下的不可移动),如果撞墙触发撞墙事件(testEvent)。
  • 5、进行糖果检测(testEvent),如果遇到糖果,英雄长度加1,本糖果消失,同时产生新的糖果(newSugar)。
  • 6、尾部隐藏(hideTail):获得队列头的位置,隐藏它,队列头部数据弹出。
  • 7、头部位置推入队列尾(hero.snakeTo)。
  • 8、显示英雄(用原来RPG逻辑就可以)(showHero)。

由于代码比较分散,我们只看其中重点的几段代码:

    void autoSnake(){DWORD t1 = GetTickCount();if (t1- pTime>= 200){trySnake(hero.dirx, hero.diry);pTime= t1;}} 

这里值得注意的是GetTickCount函数,获取当前的毫秒数。用当前毫秒数与上次毫秒数之差实现每200毫秒移动一次。

移动控制的主逻辑代码在trySnake

    void trySnake(int dx, int dy){int tox, toy;tox= hero.x+ dx;toy= hero.y+ dy;// 看是否撞墙if (currentMap.mapInfo[toy][tox]== ' ') {// 不撞墙,检测糖果testMove(tox, toy);// 无论是否吃到糖果,移动都继续hero.dirx= dx;hero.diry= dy;hideTail();hero.snakeTo(tox, toy);showHero();} else {// 撞墙,触发事件 testEvent("hitwall_"+ currentMap.name);} }

生成新糖果的代码:

    void newSugar(string aEaten){if (aEaten!=""){eventList.erase(aEaten);}// 移动糖果int x1= rand()%(currentMap.w/2- 2)* 2+ 2;int y1= rand()%(currentMap.h- 3)+ 1;Event evt1;evt1={"move", "Snake,"+int2str(x1)+","+int2str(y1), "sugar", ""};//cout << evt1.getTriggerText() << endl;eventList.insert(make_pair(evt1.getTriggerText(), evt1));showSugar(x1, y1);                  } 

这里值得注意的是怎样产生一个随机的位置。
函数rand()可以产生一个0~RAND_MAX的随机数,我们知道RAND_MAX是一个很大的数字就好了。
我们现在希望这个数字出现在一个范围内,具体来说,横坐标在2到地图宽度范围内,纵坐标在1到地图高度内。
我们使用求余数(%)的机制来实现区间随机数的获取。
一般地说,如果想获取从a到b之间的随机数(包含a与b,a<b),可以这样写

rand()%(b-a+1)+ a

其余代码比较简单,这里就不一一贴上来了。读者到文章底部的下载地址下载本课对应代码即可。

很快小Pa拿来了她设计的新地图,不但加了贪吃蛇地图,还加了一个两个游戏的总入口。


小Pa的兴趣很高,看她现在的状态,还打算设计更多的游戏模式。
但开发组小Q可是越来越郁闷了,为了实现贪吃蛇的功能,原本已经不甚清晰的代码结构,现在更是雪上加霜。

但小Q还是努力又增加了最后一个功能。由于现在的地图大小差别很大,以前固定大小的控制台窗口有时会导致显示上的混乱。于是在载入地图的函数中,增加了根据当前地图的大小,来设置控制台窗口大小的功能。

void setConsoleSize(int w, int h){string str1;str1="mode con cols="+ int2str(w* 2)+" lines="+ int2str(h);system(str1.c_str());
}

这个函数本当是放在tools.cpp之中,但小Q为了省事,随手把它放在了map.cpp中。从一件小事,可以看到开发人员的心态一件发生了改变。

课程小结:

现在的贪吃蛇游戏逻辑,还存在有几个很明显的问题。

  • 1、新出糖果如果和蛇身重叠,会被蛇尾删除,很难找到。
  • 2、蛇自己咬自己不会导致失败
  • 3、多次进入贪吃蛇地图后,以前的糖果会成为隐藏糖果,但仍然有效

这些问题都能解决,但会导致现有系统架构更加混乱。
如果这样走下去,不知道是问题先解决掉,还是开发人员先逃离。

本教程每节课的源代码,统一下载地址
链接:https://pan.baidu.com/s/1q4aoYesre1PHaCoV8gkhDQ 
提取码:8den 

第二章教程16:贪吃蛇相关推荐

  1. 第二次作业(贪吃蛇)

    学号:2017*****7194 姓名:龙永健 我的码云贪吃蛇项目仓库:https://gitee.com/longyongjian/sanshus_secret_base 2) 完成时间:2小时 3 ...

  2. Qt小游戏教程之贪吃蛇(带源码)

    #1.内容介绍 Qt小游戏开发系列将为大家带来几个用Qt设计可发的几款简单小游戏(贪吃蛇.俄罗斯方块.黑白棋.扫雷).今天将为大家带来的是贪吃蛇游戏的设计思路详解以及代码实现,本博客适用于有一定Qt基 ...

  3. 第二章教程12:地图管理器

    本次教程内容: 动态数组 映射 地图管理器 如果我们想从0到1地建立一个稍复杂一点的软件,持续的代码重构就是必不可少的. 在本教程之前,我们一直在用静态数组存储我们的地图信息.但正如我们从昨天的代码中 ...

  4. 第二章教程15:事件系统初入

    本次教程内容: 模块化的编程思维 事件概念的详细分析 字符串处理函数 通用的算法结构 能从架构上解决的问题,才是真正的解决了.凑合上的"解决"方案,充其量只是延缓了系统的崩溃,从而 ...

  5. 第二章教程14:管理器夺权

    本次教程内容: 播放背景音乐 键盘监听功能转移 结构体 融入事件机制 数字与字符串互相转换 不同的需求如果能用统一的操作方法来实现,无论是客户还是程序员都会受益. 再回顾一下上节课中小Pa提出的5个需 ...

  6. 贪吃蛇简易版(C++)

    导航 下一篇:贪吃蛇升级版(C++) 目录 一. 贪吃蛇简易版的实现 1. 贪吃蛇如何存储? 2. 贪吃蛇如何移动? 3. 食物如何生成? 4. 如何判断游戏结束? 二. 贪吃蛇简易版的优化 1. 添 ...

  7. c语言程序的英式棋盘,使用棋盘法的贪吃蛇代码

    使用棋盘法的贪吃蛇代码 在我此前发了一篇对其他人的贪吃蛇C代码的分析和注释,在那个代码中的算法主要是用一个线性表存储蛇的所有身体节点的位置.然后随着游戏进行,需要把相应的蛇身节点依次平移一次(把数组里 ...

  8. c语言贪吃蛇大作业报告,C语言贪吃蛇实验报告

    C语言贪吃蛇实验报告 C 语言程序设计实训报告姓 名 专 业 班 级 指导教师 二 011 年 7 月 14 日I I目录1 实训目的和要求 11.1 实训目的和任务 11.2 实训要求 12 实训任 ...

  9. 指数爆炸c语言编程,【草稿】贪吃蛇,黑白棋,五子棋,扫雷的思路。大家来看看行不行...

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 [新手进阶教程]贪吃蛇,黑白棋,五子棋,扫雷的思路. 以上我只做过贪吃蛇,看过<C语言入门经典>的黑白棋(具体玩法上网搜搜)的代码,不过,个人 ...

最新文章

  1. 关于WeX5的初步学习
  2. disp语句怎么格式 matlab_讲座回顾:Matlab使用教程
  3. 软件测试理论入门(一)
  4. 花开的声音 - 张靓颖
  5. 晨哥真有料丨你太容易被得到了!
  6. layui鼠标放上图片局部放大_老照片修复教程之—裁剪图片大小
  7. Android源码中添加 修改应用
  8. 阅读《构建之法》第6 第7章
  9. Form 表单提交参数
  10. 如何直接操作SVN将分支代码合并到主干
  11. 计算机组成原理——总线标准
  12. 戴尔计算机更新程序,戴尔电脑怎么关闭自动更新系统
  13. 【生活中的逻辑谬误】稻草人谬误和无力反驳不算证明
  14. 酷开u盘c02hb量产工具
  15. 阿丹的1234投资策略
  16. iOS获取高德地图实现持续定位功能
  17. 加入灯光模型出现白点
  18. 微信小程序:购物车总结(商品左右联动)
  19. Oracle使用dblink同步数据
  20. 物联网毕设选题 - 单片机智能远程宠物喂养系统(物联网 esp8266 stm32)

热门文章

  1. RestfulCRUD 规范
  2. 微信小程序---小程序中引入的echarts在滑动屏幕时抖动以及不跟随scroll滑动问题
  3. github.io绑定域名
  4. Stata:缺失值的填充和补漏
  5. H5获取用户所在城市 网页获取用户城市名称
  6. 要达到什么水平才能找到一份软件自动化测试的工作?
  7. 18.查询好友动态和推荐动态
  8. 山东春考计算机专业本科学校排名,山东春考大学本科排名及名单
  9. 关于CH340T 的一点记录
  10. YUV采样与格式总结