• web版扫雷小游戏(三)


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

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

     此过程主要是辅助玩家快速标记雷点,增强游戏趣味性,即当游戏进行中,玩家点击棋盘中某个未展开的节点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毫秒监听一次:

     1 //开启对空节点的监听
     2     var ListenFire = null;
     3     me.ListenKong = function() {
     4         if (ListenFire === null || ListenFire === undefined) {
     5             ListenFire = setInterval(function() {
     6                 if (BombObjectList.fireFlag === 2) {
     7                     //游戏结束,关闭定时器
     8                     clearInterval(ListenFire);
     9                     ListenFire = null;
    10                 }
    11                 else if (BombObjectList.fireFlag === 1) {
    12                     //游戏进行中,检测空节点的点击事件
    13                     if (BombObjectList.fire_X !== -1 && BombObjectList.fire_Y !== -1) {
    14                         //根据x、y坐标找到该节点在节点集合中的对象
    15                         var tempObjEx = me.CheckObjItem(BombObjectList.fire_X, BombObjectList.fire_Y).obj;
    16                         if (tempObjEx !== null) {
    17                             me.CheckAroundBomb(tempObjEx);
    18                         }
    19                     }
    20                     //游戏进行中,监测双击事件
    21                     if (BombObjectList.DC_X !== -1 && BombObjectList.DC_Y !== -1) {
    22                         //根据x、y坐标找到该节点在节点集合中的对象
    23                         var tempObjEx = me.CheckObjItem(BombObjectList.DC_X, BombObjectList.DC_Y).obj;
    24                         if (tempObjEx !== null) {
    25                             if (BombImgObject.MouseType === 3) {
    26                                 //双击按下事件
    27                                 me.CheckAroundBombDC_Down(tempObjEx);
    28                             }
    29                             else {
    30                                 //双击弹起事件
    31                                 me.CheckAroundBombDC_Up(tempObjEx);
    32                             }
    33                         } 
    34                     }
    35                 }
    36             }, 50);

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

     1 //递归函数
     2     function checkBomb(obj) {
     3         //对当前空区进行八方位踩点,递归查询相连的所有空区,并全部打开
     4         for (var i = 0; i < me.enmbVal.length; i++) {
     5             var _Obj = eval("obj." + me.enmbVal[i]);
     6             //判断该方位是否存在,存在则展开节点
     7             if (_Obj != null) {
     8                 var _X = _Obj.X;
     9                 var _Y = _Obj.Y;
    10                 var tempObjEx = me.CheckObjItem(_X, _Y).obj;
    11                 if (tempObjEx.DisplayNum === 0) {
    12                     //如果为空,递归查询空节点,并展开其8个方位的所有节点
    13                     tempObjEx.ImgObj.ShowNumImg();
    14                     //将当前节点入栈,以免后续再次将其作为空节点进入递归流程
    15                     var isIn = (function() {
    16                         for (var s = 0; s < stackObj.length; s++) {
    17                             if (stackObj[s].Equals(tempObjEx)) {
    18                                 return true;
    19                             }
    20                         }
    21                         return false;
    22                     } ());
    23                     if (!isIn) {
    24                         stackObj.push(tempObjEx);
    25                         //进入递归流程
    26                         checkBomb(tempObjEx);
    27                     }
    28                 }
    29                 else {
    30                     tempObjEx.ImgObj.ShowNumImg();
    31                 }
    32             }
    33         }
    34     }
    35     //存储遍历过的节点
    36     var stackObj = [];
    37     me.CheckAroundBomb = function(that) {
    38         if (!(that instanceof BombObject) || that.constructor !== BombObject || that == null) {
    39             throw new Error("the obj is not allowed.");
    40             return;
    41         }
    42         else {
    43             stackObj.push(that);
    44             //进入递归调度,查找并展开8个方向上的节点
    45             checkBomb(that);
    46             //重置触自动发展现时监听的节点坐标,准备监听下一个
    47             BombObjectList.fire_X = -1;
    48             BombObjectList.fire_Y = -1;
    49             //清空遍历过的堆栈对象列表
    50             stackObj.length = 0;
    51         }
    52     };

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

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

    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,就像监听空节点点击事件一样。

    属性定义如下:

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

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

     1 //开启对空节点的监听
     2     var ListenFire = null;
     3     me.ListenKong = function() {
     4         if (ListenFire === null || ListenFire === undefined) {
     5             ListenFire = setInterval(function() {
     6                 if (BombObjectList.fireFlag === 2) {
     7                     //游戏结束,关闭定时器
     8                     clearInterval(ListenFire);
     9                     ListenFire = null;
    10                 }
    11                 else if (BombObjectList.fireFlag === 1) {
    12                     //游戏进行中,检测空节点的点击事件
    13                     if (BombObjectList.fire_X !== -1 && BombObjectList.fire_Y !== -1) {
    14                         //根据x、y坐标找到该节点在节点集合中的对象
    15                         var tempObjEx = me.CheckObjItem(BombObjectList.fire_X, BombObjectList.fire_Y).obj;
    16                         if (tempObjEx !== null) {
    17                             me.CheckAroundBomb(tempObjEx);
    18                         }
    19                     }
    20                     //游戏进行中,监测双击事件
    21                     if (BombObjectList.DC_X !== -1 && BombObjectList.DC_Y !== -1) {
    22                         //根据x、y坐标找到该节点在节点集合中的对象
    23                         var tempObjEx = me.CheckObjItem(BombObjectList.DC_X, BombObjectList.DC_Y).obj;
    24                         if (tempObjEx !== null) {
    25                             if (BombImgObject.MouseType === 3) {
    26                                 //双击按下事件
    27                                 me.CheckAroundBombDC_Down(tempObjEx);
    28                             }
    29                             else {
    30                                 //双击弹起事件
    31                                 me.CheckAroundBombDC_Up(tempObjEx);
    32                             }
    33                         } 
    34                     }
    35                 }
    36             }, 50);

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

     1 var tempAroundObj = []; //存储双击时遍历的8方位节点对象
     2     var hasBomb = false;    //标记上一变量存储的对象是否包含雷点
     3     //双击按下时事件处理程序
     4     me.CheckAroundBombDC_Down = function(obj) {
     5         if (!(obj instanceof BombObject) || obj.constructor !== BombObject || obj == null) {
     6             throw new Error("the obj is not allowed.");
     7             return;
     8         }
     9         else {
    10             var markRoundNum = 0;
    11             for (var i = 0; i < me.enmbVal.length; i++) {
    12                 var _Obj = eval("obj." + me.enmbVal[i]);
    13                 //判断该方位是否存在,存在则展开节点
    14                 if (_Obj != null) {
    15                     var _X = _Obj.X;
    16                     var _Y = _Obj.Y;
    17                     var tempObjEx = me.CheckObjItem(_X, _Y).obj;
    18                     var tempImgPic = tempObjEx.ImgObj;
    19                     if (!tempImgPic.ImgObj.flag) {
    20                         //该节点尚未展开,此时需更改该图片
    21                         tempImgPic.ShowNumImg(1);
    22                         tempAroundObj.push(tempObjEx);
    23                         //如果是雷点,且未标记
    24                         if (tempObjEx.IsBomb && tempImgPic.ImgObj.src.indexOf("flag") < 0) hasBomb = true;
    25                         //统计核心点旁边被标记的雷点个数
    26                         if (tempObjEx.IsBomb && tempImgPic.ImgObj.src.indexOf("flag") >= 0) markRoundNum++;
    27                     }
    28                 }
    29             }
    30             //判断自己
    31             if (!obj.ImgObj.ImgObj.flag) {
    32                 //该节点尚未展开,此时需更改该图片
    33                 obj.ImgObj.ShowNumImg(1);
    34                 tempAroundObj.push(obj);
    35             }
    36             else {
    37                 //如果核心点是展开的,则需要比较核心点的数值和其周围已经标记处的雷点个数
    38                 if ((markRoundNum === parseInt(obj.DisplayNum)) && hasBomb) {
    39                     //标记错误,游戏结束
    40                     hasBomb = false;
    41                 }
    42                 else { 
    43                     //允许用户修改,什么也不做
    44                 }
    45             }
    46         }
    47     }
    48     //双击弹起时事件处理程序,检测当前点周围的节点,必要时进行递归
    49     me.CheckAroundBombDC_Up = function(obj) {
    50         if (!(obj instanceof BombObject) || obj.constructor !== BombObject || obj == null) {
    51             throw new Error("the obj is not allowed.");
    52             return;
    53         }
    54         else {
    55             //如果核心节点是未展开的或者遍历列表中包含雷点,则复原遍历列表中对象对应的图片对象
    56             if (!obj.ImgObj.ImgObj.flag || hasBomb) {
    57                 for (var i = 0; i < tempAroundObj.length; i++) {
    58                     //复原节点图片
    59                     tempAroundObj[i].ImgObj.ShowNumImg(2);
    60                 }
    61             }
    62             else {
    63                 //如果核心点是展开的,没有雷点,则自动展开,空节点需要递归
    64                 for (var j = 0; j < tempAroundObj.length; j++) {
    65                     //展开节点
    66                     tempAroundObj[j].ImgObj.ShowNumImg();
    67                     //如果节点为空,则需要递归遍历展开
    68                     if (tempAroundObj[j].DisplayNum === 0) {
    69                         checkBomb(tempAroundObj[j]);
    70                     }
    71                 }
    72             }
    73             //复原监听变量
    74             BombObjectList.DC_X = -1;
    75             BombObjectList.DC_Y = -1;
    76             //删除遍历的临时节点对象列表
    77             tempAroundObj.length = 0;
    78             hasBomb = false;
    79         }
    80     }

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

    接下篇~~~~

  • 相关阅读:
    python第二十四课——set中的函数
    python第二十三课——dict中的函数
    python第二十二课——list函数
    10 编译PHP并与nginx整合
    09 nginx Rewrite(重写)详细解析
    07 nginx Location之正则匹配
    06 nginx Location详解之精准匹配
    JQ 修改样式
    05 nginx定时任务完成日志切割
    linux 时间与本地时间不对应解决办法
  • 原文地址:https://www.cnblogs.com/freshfish/p/3388329.html
Copyright © 2020-2023  润新知