• web版扫雷小游戏(二) Fish


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

    游戏实现

    根据对扫雷游戏的体验和分析,游戏的实现过程有三个比较繁琐的地方:

    1. 棋盘布局及初始化,这个过程主要完成游戏棋盘的生成,布局雷点的位置并记录存储,根据雷点的位置及雷点与数值之间的对应关系,遍历雷点8个方向上的8个节点,查找这个8个节点各自周围的雷点数之和S,最终记录并存储节点的数值(S),完成棋盘上各非雷点的数值计算后,根据节点和图片对象的关系呈现用户界面给玩家。

    2. 玩家鼠标左键点击棋盘上某个节点之后,程序根据节点在棋盘上的坐标,分析该节点的属性,如果该节点为空(即其周围没有雷点,数值为0),则遍历节点8个方向上的节点(这8个节点必然是非雷点)并展开,如果这8个节点中有空节点,则重复上述过程,递归查找并遍历所有。

    3.  棋盘上某节点(数值为N)被标记展开,且在其周围已经标记了M个雷点,当玩家用鼠标左键和右键一起点击时,需要分三种情况考虑,当M=N时,如果玩家雷点标记正确,则遍历该点8个方向上的节点并展开(此时8个方向上已没有雷点),如果玩家雷点标记错误,则游戏结束。当M>N或者M时,表示玩家多或少标记了雷点,此时需要提醒玩家修改,闪烁该点在8个方向上没有展开的节点。

    为了更清晰的表达和理解,棋盘、雷点底层数据结构、雷点表层图片对象在页面上的关系如下图:

     

    游戏过程中出现的节点描述及示例如下图:

     

    下面根据上述三个难点的分析,逐个进行编程解决:

     一、棋盘的布局及初始化

    该过程包括棋盘的生成、雷点的布置、非雷点数值计算、棋盘展现四个过程,主要流程见下图:

     

    我们通过定义棋盘类(BombObjectList)来实现,类的初步定义(主要实现了该类基本属性的定义)具体如下: 

     1 //雷点集合棋盘展现的主控操作类
     2 function BombObjectList(PlaceId, XCount, YCount, BombCount) {
     3     //保存当前对象,以防函数嵌套时指代不清
     4     var me = this;
     5 
     6     me.ContentId = PlaceId;     //展现棋盘容器的元素id
     7     me.ContentObj = null;       //展现棋盘容器的元素对象
     8     me.ObjList = [];            //棋盘节点集合对象列表
     9     me.xNum = XCount;           //棋盘x轴长度
    10     me.yNum = YCount;           //棋盘y轴长度
    11     me.bombNum = BombCount;     //雷点的个数
    12     me.PlateNum = [this.xNum];  //棋盘节点集合数组二维数组
    13     me.BombList = [];           //雷点集合列表
    14     me.enmbVal = ["North", "NorthEast", "East", "EastSouth", "South", "SouthWest", "West", "WestNorth"];    //8方位踩点的枚举值
    15 }

     为了实现上述四个过程,这里为该类定义了一个初始化函数,标记为Initial

    me.Initial = function() {

    第一步,生成棋盘,根据玩家定义的宽度(XCount)和高度(YCount)初始化棋盘,棋盘中的节点对象(BombObject)均采用默认值,节点数值初始化为0:

           //初始化模板数据,默认数据
            for (var i = 0; i < me.xNum; i++) {
                me.PlateNum[i] = [];
                for (var j = 0; j < me.yNum; j++) {
                    me.ObjList.push(new BombObject(i, j, 0, XCount, YCount));
                    me.PlateNum[i][j] = 0;
                }
            }

    第二步,布置雷点,在棋盘节点范围内,根据玩家定义的雷点个数(BombCount)采用随机数动态生成坐标值对并记录(重复的要重新生成),然后修改棋盘中对应该坐标的节点数值:

            //布雷
            for (var s = 0; s < this.bombNum; ) {
                var x = parseInt(Math.random() * me.xNum);
                var y = parseInt(Math.random() * me.yNum);
                //判断是否已经标记为雷,是则再次获取,不是则进行标记
                if (me.PlateNum[x][y] !== -1) {
                    //获取该点在节点集合中的对象,并修改
                    var CheckResult = me.CheckObjItem(x, y).obj;
                    CheckResult.IsBomb = true;
                    CheckResult.DisplayNum = -1;
                    CheckResult.ImgObj.SetImgNum(-1);
                    //更新点数棋盘
                    me.PlateNum[x][y] = -1;
                    //更新雷点数组
                    me.BombList.push(CheckResult);
                    //取下一个雷点
                    s++;
                }
            }

     第三步,计算非雷点数值,遍历上一步产生的雷点,计算雷点周围8个位置上节点(是雷点的除外)的数值,然后修改棋盘中对应该非雷点的节点数值,方位遍历顺序见上图:

        //计算数值
            for (var r in me.BombList) {
                var tempObj = me.BombList[r];
                //对当前雷点进行八方位踩点,计算其周围的节点数值,计算过就不再计算,且不算雷点
                for (var i = 0; i < me.enmbVal.length; i++) {
                    var _Obj = eval("tempObj." + me.enmbVal[i]);
                    //如果该方位的节点是否存在
                    if (_Obj != null) {
                        var _X = _Obj.X;
                        var _Y = _Obj.Y;
                        //获取该点在节点集合中的对象
                        var tempObjEx = me.CheckObjItem(_X, _Y).obj;
                        //如果该方位点的数值为0(非0表示计算过或者是雷点),表示需要计算其周围8个点雷的总数
                        if (tempObjEx.DisplayNum === 0) {
                            var num = me.CountAroundNum(tempObjEx);
                            tempObjEx.DisplayNum = num;
                            tempObjEx.ImgObj.SetImgNum(num);
                            //更新点数棋盘
                            me.PlateNum[_X][_Y] = num;
                        }
                    }
                }
            }

    至此棋盘的初始化完成,返回this对象,方便链式调用:

        //返回当前this对象,以实现链式调用
            return me;
        }

     第四步,棋盘的展现,遍历棋盘中所有节点对象,并将节点包裹一层图片对象,然后输出到页面的文档中,这里通过定义一个展现函数实现,标记为Display:

    me.Display = function() {
            //输出
            var oFragment = document.createDocumentFragment();
            for (var h = 0; h < me.ObjList.length; h++) {
                var strObj = me.ObjList[h].ImgObj.ImgObj;
                oFragment.appendChild(strObj);
                if ((h + 1) % me.yNum === 0) {
                    oFragment.appendChild(document.createElement("br"));
                }
            }
            //清空掉已有的元素
            for (var i = me.ContentObj.childNodes.length - 1; i >= 0; i--) {
                var tempNode = me.ContentObj.childNodes[i];
                tempNode.parentNode.removeChild(tempNode);
            }
            //赋值新生成的元素
            me.ContentObj.appendChild(oFragment);
        }

     以上四步完成了游戏棋盘部分的生成和展现,其中用到两个辅助函数CheckObjItem和CountAroundNum,前者是根据x、y坐标获取棋盘中对应的节点对象,后者是计算雷点某方位上节点的数值,其代码如下:

     1 //根据x、y坐标获取节点集合中节点
     2     me.CheckObjItem = function(x, y) {
     3         //验证x、y坐标
     4         if (x >= 0 && x < me.xNum && y >= 0 && y < me.yNum) {
     5             try {
     6                 //查找该点
     7                 var tempObj = me.ObjList[x * me.yNum + y];
     8                 if (tempObj.EqualsEx(x, y)) {
     9                     if (tempObj.DisplayNum === -1) {
    10                         //如果该点是雷点
    11                         return { obj: tempObj, IsIn: true };
    12                     }
    13                     else {
    14                         return { obj: tempObj, IsIn: false };
    15                     }
    16                 }
    17             }
    18             catch (e) {
    19                 //找不到该点
    20                 return { obj: null, IsIn: false };
    21             }
    22         }
    23         else {
    24             throw new Error("the input is validate.");
    25             return { obj: null, IsIn: false };
    26         }
    27     };
    28     //计算非雷点的数值并存储
    29     me.CountAroundNum = function(obj) {
    30         if (!(obj instanceof BombObject) || obj.constructor !== BombObject || obj == null) {
    31             throw new Error("the obj is not allowed.");
    32             return 0;
    33         }
    34         else {
    35             var result = 0;
    36 
    37             //对当前非雷区进行八方位踩点,计算周围雷点个数
    38             for (var i = 0; i < me.enmbVal.length; i++) {
    39                 var _Obj = eval("obj." + me.enmbVal[i]);
    40                 //判断该方位是否存在
    41                 if (_Obj != null) {
    42                     var _X = _Obj.X;
    43                     var _Y = _Obj.Y;
    44                     //判断是不是雷
    45                     if (me.PlateNum[_X][_Y] === -1) {
    46                         //如果是雷,就加1
    47                         result++;
    48                     }
    49                 }
    50             }
    51             return result;
    52         }
    53     };
    View Code

    接下篇~~~ 

  • 相关阅读:
    BZOJ1477 青蛙的约会
    Code Style
    线段树合并
    动态开点
    主席树
    启发式合并
    树的重心
    树的直径
    扩展欧几里得
    裴蜀定理
  • 原文地址:https://www.cnblogs.com/freshfish/p/3386782.html
Copyright © 2020-2023  润新知