~~~接上篇,上篇介绍了游戏实现过程中第一个比较繁琐的地方,现在展现在玩家面前的是一个有血有肉的棋盘,从某种意义上说玩家已经可以开始游戏了,但是不够人性化,玩家只能一个一个节点的点开,然后判断,然后标记等等,程序暂不能人性化的辅助玩家将游戏进行下去,趣味性不够强,接下来就来完善这些辅助功能。

二、空节点点击触发其周围所有空节点展开

此过程主要是辅助玩家快速标记雷点,增强游戏趣味性,即当游戏进行中,玩家点击棋盘中某个未展开的节点M时,如果该节点是空节点(对应的数值为0),则展开该节点的同时,需要遍历其周围8个方向上的节点,如果某个方向上的节点也是空节点,则再以该点为中心重复上述过程,直到遍历完所有相邻节点M的空节点为止。这个过程值得注意的是:

1. 游戏开始后(玩家第一次点击),如果第一个节点是非雷点,则程序开始监听空节点点击事件。 

2. 节点M是空节点,即其周围没有雷点存在,用户点击后该点周围8个方向上的节点均需要展开。

3. 遍历节点M周围节点时,如果遇到新的空节点,则立即以该节点为中心重复遍历过程,此处我们采用递归处理,默认从北方节点顺时针方向依次遍历。

4. 递归遍历时,节点M与其周围节点互为方位节点,为了避免陷入重复循环,提高效率,则过滤掉已经遍历过的节点,这里定义一个栈,压入已经遍历过的节点,每次进入递归时判断节点是否已经在栈中,如果在,则忽略。

5. 遍历时,如果节点非空(一定不是雷点),则只需展开即可,接着判断并遍历下一个方位节点。

6. 一次递归完成后,程序会自动清除或重置当前次递归遍历产生的临时数据和变量,开始监听下一次空节点的点击事件。

功能效果及遍历路径见下例:

如上图,节点M点击后,程序会按照黄色箭头(只画出部分)的顺序依次遍历并展开节点,直到由M节点触发的所有相邻的空节点遍历完毕,则递归完成。

这个过程需要在棋盘控制类(BombObjectList)中实现:

首先,需要定义两个对外的类的属性,标记为BombObjectList.fire_XBombObjectList.fire_X,这两个属性默认初始化值为-1,一旦玩家触发了空节点,则分别赋值为空节点在棋盘中的x、y坐标。

其次,在类中定义一个定时器,随时监听上面两个属性值的变化,一旦值均不为-1,表示玩家触发了空节点的点击事件,程序需要响应这个事件,我们定义一个响应函数,标记为CheckAroundBomb

最后,遍历该空节点,必要时进行递归,递归过程见上述描述,递归函数标记为checkBomb

类的对外属性定义如下:

BombObjectList.fire_X = -1;         //触发空节点弹开时节点的x坐标
BombObjectList.fire_Y = -1; //触发空节点弹开时节点的y坐标

定时器定义如下(黑色阴影部分为空节点点击事件监听过程),50毫秒监听一次:

 //开启对空节点的监听
var ListenFire = null;
me.ListenKong = function() {
if (ListenFire === null || ListenFire === undefined) {
ListenFire = setInterval(function() {
if (BombObjectList.fireFlag === 2) {
//游戏结束,关闭定时器
clearInterval(ListenFire);
ListenFire = null;
}
else if (BombObjectList.fireFlag === 1) {
//游戏进行中,检测空节点的点击事件
if (BombObjectList.fire_X !== -1 && BombObjectList.fire_Y !== -1) {
//根据x、y坐标找到该节点在节点集合中的对象
var tempObjEx = me.CheckObjItem(BombObjectList.fire_X, BombObjectList.fire_Y).obj;
if (tempObjEx !== null) {
me.CheckAroundBomb(tempObjEx);
}
}
//游戏进行中,监测双击事件
if (BombObjectList.DC_X !== -1 && BombObjectList.DC_Y !== -1) {
//根据x、y坐标找到该节点在节点集合中的对象
var tempObjEx = me.CheckObjItem(BombObjectList.DC_X, BombObjectList.DC_Y).obj;
if (tempObjEx !== null) {
if (BombImgObject.MouseType === 3) {
//双击按下事件
me.CheckAroundBombDC_Down(tempObjEx);
}
else {
//双击弹起事件
me.CheckAroundBombDC_Up(tempObjEx);
}
}
}
}
}, 50);

事件响应函数CheckAroundBomb和递归函数定义如下:

 //递归函数
function checkBomb(obj) {
//对当前空区进行八方位踩点,递归查询相连的所有空区,并全部打开
for (var i = 0; i < me.enmbVal.length; i++) {
var _Obj = eval("obj." + me.enmbVal[i]);
//判断该方位是否存在,存在则展开节点
if (_Obj != null) {
var _X = _Obj.X;
var _Y = _Obj.Y;
var tempObjEx = me.CheckObjItem(_X, _Y).obj;
if (tempObjEx.DisplayNum === 0) {
//如果为空,递归查询空节点,并展开其8个方位的所有节点
tempObjEx.ImgObj.ShowNumImg();
//将当前节点入栈,以免后续再次将其作为空节点进入递归流程
var isIn = (function() {
for (var s = 0; s < stackObj.length; s++) {
if (stackObj[s].Equals(tempObjEx)) {
return true;
}
}
return false;
} ());
if (!isIn) {
stackObj.push(tempObjEx);
//进入递归流程
checkBomb(tempObjEx);
}
}
else {
tempObjEx.ImgObj.ShowNumImg();
}
}
}
}
//存储遍历过的节点
var stackObj = [];
me.CheckAroundBomb = function(that) {
if (!(that instanceof BombObject) || that.constructor !== BombObject || that == null) {
throw new Error("the obj is not allowed.");
return;
}
else {
stackObj.push(that);
//进入递归调度,查找并展开8个方向上的节点
checkBomb(that);
//重置触自动发展现时监听的节点坐标,准备监听下一个
BombObjectList.fire_X = -1;
BombObjectList.fire_Y = -1;
//清空遍历过的堆栈对象列表
stackObj.length = 0;
}
};

三、鼠标左键和右键一起按下时的事件响应

这个过程是这三个里面最繁琐的一个,不过它的实现可以大大增强游戏的趣味性,在扫雷游戏过程中,这个功能是用的最频繁的,对脚本程序的检测和判断及效率要求较高,总体来说,主要基于节点数值和其周围雷点个数一直的原则进行判断,分析得出存在以下几种情况:

1. 玩家双击的对象是未展开的节点,此时系统并不需要过多的判断,因为玩家还没有标记出该点周围的雷点,程序只需闪烁提示该点周围的节点即可,鼠标事件后复原到按下前的状态。

2. 玩家双击的对象是已展开的节点,此时有两种情况,比如改节点的数值为N,而玩家在其周围标记的雷点个数为M:

  如果N!=M,则提示用户进行修改,闪烁提示同上一条;

  如果M=N,接下来要判断该点周围的未标记节点的类型,记未标记雷点的个数为C,若C>0,则用户标记错误,鼠标按下后游戏结束,提示用户标记错误;若C=0,则用户标记正确,展开所有周围未标记节点,当这些节点中有空节点时,则需要递归展现所有相邻的空节点,过程同上述处理过程。

上述几种情况的示例见下图:

对于这一过程的实现,我们分两步进行:

第一步:响应鼠标左右键一起按下事件,函数标记为CheckAroundBombDC_Down,此过程记录点击节点周围的游戏上下文,如该点周围已经标记的雷点个数markRoundNum,未标记节点中是否有雷点标记位hasBomb。

第二步:响应鼠标左右键一起提起事件,函数标记为CheckAroundBombDC_Up,如果该点未展开,则复原图片对象,什么也不做;如果该点已展开,若其周围有雷点,则复原图片,什么也不做,否则展开其周围的非雷点,必要时进行递归展现。

此外,棋盘控制类中需要定义个定时器,随时监听左右键一起按下事件,并将事件触发对象节点的x、y坐标记录到棋盘类的两个对外属性BombObjectList.DC_XBombObjectList.DC_Y,就像监听空节点点击事件一样。

属性定义如下:

 BombObjectList.DC_X = -1;           //左右键一起按下时节点的x坐标
BombObjectList.DC_Y = -1; //左右键一起按下时节点的y坐标

定时器定义如下(黑色阴影部分为空节点点击事件监听过程),50毫秒监听一次:

 //开启对空节点的监听
var ListenFire = null;
me.ListenKong = function() {
if (ListenFire === null || ListenFire === undefined) {
ListenFire = setInterval(function() {
if (BombObjectList.fireFlag === 2) {
//游戏结束,关闭定时器
clearInterval(ListenFire);
ListenFire = null;
}
else if (BombObjectList.fireFlag === 1) {
//游戏进行中,检测空节点的点击事件
if (BombObjectList.fire_X !== -1 && BombObjectList.fire_Y !== -1) {
//根据x、y坐标找到该节点在节点集合中的对象
var tempObjEx = me.CheckObjItem(BombObjectList.fire_X, BombObjectList.fire_Y).obj;
if (tempObjEx !== null) {
me.CheckAroundBomb(tempObjEx);
}
}
//游戏进行中,监测双击事件
if (BombObjectList.DC_X !== -1 && BombObjectList.DC_Y !== -1) {
//根据x、y坐标找到该节点在节点集合中的对象
var tempObjEx = me.CheckObjItem(BombObjectList.DC_X, BombObjectList.DC_Y).obj;
if (tempObjEx !== null) {
if (BombImgObject.MouseType === 3) {
//双击按下事件
me.CheckAroundBombDC_Down(tempObjEx);
}
else {
//双击弹起事件
me.CheckAroundBombDC_Up(tempObjEx);
}
}
}
}
}, 50);

鼠标左右键一起按下和弹起事件定义如下:

 var tempAroundObj = []; //存储双击时遍历的8方位节点对象
var hasBomb = false; //标记上一变量存储的对象是否包含雷点
//双击按下时事件处理程序
me.CheckAroundBombDC_Down = function(obj) {
if (!(obj instanceof BombObject) || obj.constructor !== BombObject || obj == null) {
throw new Error("the obj is not allowed.");
return;
}
else {
var markRoundNum = 0;
for (var i = 0; i < me.enmbVal.length; i++) {
var _Obj = eval("obj." + me.enmbVal[i]);
//判断该方位是否存在,存在则展开节点
if (_Obj != null) {
var _X = _Obj.X;
var _Y = _Obj.Y;
var tempObjEx = me.CheckObjItem(_X, _Y).obj;
var tempImgPic = tempObjEx.ImgObj;
if (!tempImgPic.ImgObj.flag) {
//该节点尚未展开,此时需更改该图片
tempImgPic.ShowNumImg(1);
tempAroundObj.push(tempObjEx);
//如果是雷点,且未标记
if (tempObjEx.IsBomb && tempImgPic.ImgObj.src.indexOf("flag") < 0) hasBomb = true;
//统计核心点旁边被标记的雷点个数
if (tempObjEx.IsBomb && tempImgPic.ImgObj.src.indexOf("flag") >= 0) markRoundNum++;
}
}
}
//判断自己
if (!obj.ImgObj.ImgObj.flag) {
//该节点尚未展开,此时需更改该图片
obj.ImgObj.ShowNumImg(1);
tempAroundObj.push(obj);
}
else {
//如果核心点是展开的,则需要比较核心点的数值和其周围已经标记处的雷点个数
if ((markRoundNum === parseInt(obj.DisplayNum)) && hasBomb) {
//标记错误,游戏结束
hasBomb = false;
}
else {
//允许用户修改,什么也不做
}
}
}
}
//双击弹起时事件处理程序,检测当前点周围的节点,必要时进行递归
me.CheckAroundBombDC_Up = function(obj) {
if (!(obj instanceof BombObject) || obj.constructor !== BombObject || obj == null) {
throw new Error("the obj is not allowed.");
return;
}
else {
//如果核心节点是未展开的或者遍历列表中包含雷点,则复原遍历列表中对象对应的图片对象
if (!obj.ImgObj.ImgObj.flag || hasBomb) {
for (var i = 0; i < tempAroundObj.length; i++) {
//复原节点图片
tempAroundObj[i].ImgObj.ShowNumImg(2);
}
}
else {
//如果核心点是展开的,没有雷点,则自动展开,空节点需要递归
for (var j = 0; j < tempAroundObj.length; j++) {
//展开节点
tempAroundObj[j].ImgObj.ShowNumImg();
//如果节点为空,则需要递归遍历展开
if (tempAroundObj[j].DisplayNum === 0) {
checkBomb(tempAroundObj[j]);
}
}
}
//复原监听变量
BombObjectList.DC_X = -1;
BombObjectList.DC_Y = -1;
//删除遍历的临时节点对象列表
tempAroundObj.length = 0;
hasBomb = false;
}
}

至此,游戏的三个难点基本解决,看到这里,还有两个东西没有介绍,一个是雷点类(即节点信息类)BombObject,另一个是节点对应的图片对象类BombImgObject,下篇将揭开其神秘的面纱。

接下篇~~~~

web版扫雷小游戏(三)的更多相关文章

  1. web版扫雷小游戏(一)

    作为一名程序猿,平时的爱好也不多,说起游戏,我不太喜欢大型的网游,因为太耗时间,偶尔玩玩经典的单机小游戏,比如windows下自带的游戏扫雷(秀一下,高级下最高纪录110s). 现阶段正在致力于web ...

  2. web版扫雷小游戏(四)

    ~~~接上篇,游戏的主体框架完成了,接下来我们对游戏中存在的两个主要实体进行分析,一个是雷点类BombObject(节点对象),一个是节点对象对应的图片对象BombImgObject,根据第一篇的介绍 ...

  3. web版扫雷小游戏(二)

    接上篇~~第一次写这种技术博客,发现把自己做的东西介绍出来还是一件脑力活,不是那么轻松啊,好吧,想到哪写到哪,流水记录之,待完成之后再根据大家的意见进行修改吧. 游戏实现 根据对扫雷游戏的体验和分析, ...

  4. jQuery实践-网页版2048小游戏

    ▓▓▓▓▓▓ 大致介绍 看了一个实现网页版2048小游戏的视频,觉得能做出自己以前喜欢玩的小游戏很有意思便自己动手试了试,真正的验证了这句话-不要以为你以为的就是你以为的,看视频时觉得看懂了,会写了, ...

  5. 用Kotlin破解Android版微信小游戏-跳一跳

    前言 微信又更新了,从更新日志上来看,似乎只是一次不痛不痒的小更新.不过,很快就有人发现,原来微信这次搞了个大动作——在小程序里加入了小游戏.今天也是朋友圈被刷爆的缘故. 看到网上 有人弄了一个破解版 ...

  6. Angular4 扫雷小游戏

    扫雷小游戏,可以升级过关,难度随关卡增加.但是有很明显的bug,以后有时间会继续优化! HTML: <div class="mainContent"> <div ...

  7. 扫雷小游戏PyQt5开发【附源代码】

    也没啥可介绍哒,扫雷大家都玩过. 雷的分布算法也很简单,就是在雷地图(map:二维数组)中,随机放雷,然后这个雷的8个方位(上下左右.四个对角)的数字(非雷的标记.加一后不为雷的标记)都加一. 如何判 ...

  8. C++扫雷小游戏(基于CMD命令行)

    这个小游戏是笔者在大一C语言课程设计的时候写的,基于命令行,为了显得漂亮一些,特别加上了彩色特效~~~ 注意:Win10系统须将命令行调为旧版命令行,否则有可能会显示乱码! 代码示例: #includ ...

  9. JavaScript版拼图小游戏

    慕课网上准备开个新的jQuery教程,花了3天空闲时间写了一个Javascript版的拼图小游戏,作为新教程配套的分析案例 拼图游戏网上有不少的实现案例了,但是此源码是我自己的实现,所以不做太多的比较 ...

随机推荐

  1. Android——Cocosd2d-x手机游戏开发学习思路

    手机APP应用如雨后春笋般冒了出来,而在众多的APP应用中,游戏占据了半壁江山.它丰富着人们的业余生活,增进了人们之间的沟通交流.也有许多开发的朋友对游戏开发情有独钟,他们不止是享受着有很多的人们去下 ...

  2. JavaFX 3D部分介绍(3) Lights

    声明:   本博客文章原创类别的均为个人原创,版权所有.转载请注明出处: http://blog.csdn.net/ml3947,另外本人的个人博客:http://www.wjfxgame.com. ...

  3. 微信开发 提示 Redirect_uri参数错误解决方法

    出现这个问题有多种原因: 1.没有配置网页授权 我们可以根据微信的开发者文档http://mp.weixin.qq.com/wiki/17/c0f37d5704f0b64713d5d2c37b468d ...

  4. json中文编码问题

    在和微信接口交互的过程中需要传参json而里面有中文的存在,所以json_encode($data)编码后会形成 {"group":{"name":" ...

  5. opencv学习笔记-图像对比度、亮度调节

    在数学中我们学过线性理论,在图像亮度和对比度调节中同样适用,看下面这个公式: 在图像像素中其中: 参数f(x)表示源图像像素. 参数g(x) 表示输出图像像素. 参数a(需要满足a>0)被称为增 ...

  6. 查看数据库中有哪些活动的事务,对应的会话id,执行的语句

    select dbt.database_id, DB_NAME(dbt.database_id) '数据库名', dbt.transaction_id, at.name, at.transaction ...

  7. c的基础 1. 无符号数和补码

    计算机中储存和处理的信息是以二进制信号表示的.单个的位不是是很实用,而将这些位 组合在一起,加上某种解释,即给不同的可能位模式赋予含义,我们就行表示怎样有限集合的元素,即实现各种数据结构.计算机中使用 ...

  8. UVA 11551 - Experienced Endeavour(矩阵高速幂)

    UVA 11551 - Experienced Endeavour 题目链接 题意:给定一列数,每一个数相应一个变换.变换为原先数列一些位置相加起来的和,问r次变换后的序列是多少 思路:矩阵高速幂,要 ...

  9. [D3] 6. Color Scale

    <!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8&quo ...

  10. Android M(6.0) 权限爬坑之旅

    坑一:用Android5.0编译的apk,在Android6.0上运行完全没有问题. 在Android6.0以上才需要在运行时请求权限,在旧Android版本上保留原有逻辑,安装时授予权限. 用旧版本 ...