前端疯狂的轮子之网页扫雷游戏

大二的时候听说某大神可以徒手写扫雷,当时就一顿羡慕加崇拜,后来放假无聊就想起这件事来,准备实现一下儿时的梦想—徒手写扫雷。

 由于用原生js代码过于麻烦,就偷了个懒用了angularJS,主要它的数据绑定比较方便,让我把注意力都放在了扫雷的算法上。介绍一下核心过程,全部代码后面可以下载。

1.布局

页面布局我选用的一个table,雷区用table中的按钮来表示,直接用disabled切换状态,简单方便。

<div ng-app="myApp" ng-controller="myCtrl" class="main-container">
    <table border="none">
      <thead>
        <h4>time: {{vo.time}}s</h4>
      </thead>
      <tr ng-repeat="blockLine in vo.blocks">
        <td ng-repeat="block in blockLine track by $index">
          <!-- <button ng-click="vc.touch(block, $parent.$index, $index)"
            ng-disabled="block.mark!='0'">{{block.meta}}</button> -->
          <button class="dis-btn" ng-show="block.mark!='0'" ng-click="vc.touch(block, $parent.$index, $index)"
            ng-disabled="block.mark!='0'"
            ng-right-click="vc.markMine(block, $parent.$index, $index)">{{block.meta=='*'?'*':''}}{{block.mark=='1'&&block.meta!='0'?block.meta:' '}}</button>
          <button ng-show="block.mark=='0'" ng-click="vc.touch(block, $parent.$index, $index)"
            ng-disabled="block.mark!='0'"
            ng-right-click="vc.markMine(block, $parent.$index, $index)">{{block.meta=='*'?'*':' '}}{{block.mark=='1'&&block.meta!='0'?block.meta:' '}}</button>
        </td>
      </tr>
    </table>
  </div>

2.初始化10x10的二维数组作为雷区

这个没得说的,搞一个二维数组当雷区。

// 初始化数组10x10雷区
    $scope.vo.blocks = new Array();
    for (let i = 0; i < 10; i++) {
      $scope.vo.blocks[i] = new Array();
    }

3.初始化地雷

这个方法主要是10x10的数组中随机选十个位置放入地雷,每个雷区对象有两个属性,meta和mark,meta用来记录雷或数字,*代表该位置是雷,其他的则是数字,0则代表周围无雷。而mark主要用来标记该雷区是否已经被点开,1代表被点开,0代表未被点开。

// 初始化地雷
    function initmines() {
      var mineX = [];
      var mineY = [];
      for (let i = 0; i < 10; i++) {
        mineX[i] = randomNum(0, 9);
      }
      // 生成地雷, 保证随机地雷生成不重复
      while (mineY.length < 10) {
        let j = mineY.length + 1;
        let tempY = randomNum(0, 9);
        for (let k = 0; k < j; k++) {
          let cord = mineX[k] + '-' + mineY[k];

          if (cord == mineX[j] + '-' + tempY) {
            console.log('test-log-cord', cord);
            break;
          }
        }
        mineY.push(tempY);
      }
      // 填充地雷
      for (let i = 0; i < 10; i++) {
        for (let j = 0; j < 10; j++) {
          $scope.vo.blocks[i][j] = {
            meta: 0,
            mark: '0'
          };
        }
      }
      for (let k = 0; k < 10; k++) {
        $scope.vo.blocks[mineX[k]][mineY[k]] = {
          meta: 'm',
          mark: '0'
        };
      }
    }

3.初始化雷周围数字标记

为了方便直接把雷区单位周围的八个雷区的位置x,y放入两个数组roundBliockXs和roundBliockYs,遍历起来比较容易。遍历每个雷区,数该雷区周围八个雷区有几个地雷,将数字标记到当前雷区。注意边缘处理,这里我为了方便直接捕获的异常。碰到数组越界直接continue。

// 初始化雷周围的数字
    function initFlag() {
      for (let i = 0; i < 10; i++) {
        for (let j = 0; j < 10; j++) {
          if ($scope.vo.blocks[i][j].meta == 'm') continue;
          var mineCount = 0;
          let roundBliockXs = [i - 1, i, i - 1, i + 1, i, i + 1, i - 1, i + 1];
          let roundBliockYs = [j - 1, j - 1, j, j + 1, j + 1, j, j + 1, j - 1];
          for (let k = 0; k < 10; k++) {
            try {
              if ($scope.vo.blocks[roundBliockXs[k]][roundBliockYs[k]].meta == 'm') {
                mineCount++;
              }
            } catch (error) {
              continue;
            }
          }
          $scope.vo.blocks[i][j].meta = mineCount;
        }
      }
    }

至此整个雷区布局完毕,接下来就是点击各种事件了,点开每个格子 

4.点击空白区域

点击没有数字的区域要连锁展开相连的空白区域,并且保证边缘数字也展开,这里我想了半天没想出来怎么一次性展开,所以索性先展开空白区域,再遍历一遍雷区展开边缘数字。

// 点击自动展开无数字区域,递归算法
    function autoExpansion(x, y) {
      if ($scope.vo.blocks[x][y].mark != '1' && $scope.vo.blocks[x][y].meta == '0') {
        $scope.vo.blocks[x][y].mark = '1';
        if (x - 1 < 0 && y - 1 < 0) { //左上角特殊处理
          autoExpansion(x + 1, y); // 右遍历 x+1, y
          autoExpansion(x, y + 1); // 下遍历 x, y+1
        } else if (x + 1 > 9 && y - 1 < 0) { //右上角特殊处理
          autoExpansion(x - 1, y); // 左遍历 x-1, y
          autoExpansion(x, y + 1); // 下遍历 x, y+1
        } else if (x - 1 < 0 && y + 1 > 9) { //左下角特殊处理
          autoExpansion(x + 1, y); // 右遍历 x+1, y
          autoExpansion(x, y - 1); // 上遍历 x,y-1
        } else if (x + 1 > 9 && y + 1 > 9) { //右下角特殊处理
          autoExpansion(x - 1, y); // 左遍历 x-1, y
          autoExpansion(x, y - 1); // 上遍历 x,y-1
        } else if (x - 1 < 0) { // 左列特殊处理
          autoExpansion(x, y - 1); // 上遍历 x,y-1
          autoExpansion(x, y + 1); // 下遍历 x, y+1
          autoExpansion(x + 1, y); // 右遍历 x+1, y
        } else if (y - 1 < 0) { // 上列特殊处理
          autoExpansion(x - 1, y); // 左遍历 x-1, y
          autoExpansion(x, y + 1); // 下遍历 x, y+1
          autoExpansion(x + 1, y); // 右遍历 x+1, y
        } else if (x + 1 > 9) { // 右列特殊处理
          autoExpansion(x, y - 1); // 上遍历 x,y-1
          autoExpansion(x - 1, y); // 左遍历 x-1, y
          autoExpansion(x, y + 1); // 下遍历 x, y+1
        } else if (y + 1 > 9) { // 下列特殊处理
          autoExpansion(x, y - 1); // 上遍历 x,y-1
          autoExpansion(x - 1, y); // 左遍历 x-1, y
          autoExpansion(x + 1, y); // 右遍历 x+1, y
        } else {
          autoExpansion(x, y - 1); // 上遍历 x,y-1
          autoExpansion(x - 1, y); // 左遍历 x-1, y
          autoExpansion(x, y + 1); // 下遍历 x, y+1
          autoExpansion(x + 1, y); // 右遍历 x+1, y
        }
      }
    }
    // 展开边缘数字
    function expansionMarginal() {
      for (let i = 0; i < 10; i++) {
        for (let j = 0; j < 10; j++) {
          if ($scope.vo.blocks[i][j].meta == '0' && $scope.vo.blocks[i][j].mark == '1') {
            let roundBliockXs = [i - 1, i, i - 1, i + 1, i, i + 1, i - 1, i + 1];
            let roundBliockYs = [j - 1, j - 1, j, j + 1, j + 1, j, j + 1, j - 1];
            for (let k = 0; k < 10; k++) {
              try {
                $scope.vo.blocks[roundBliockXs[k]][roundBliockYs[k]].mark = '1';
              } catch (error) {
                continue;
              }
            }
          }
        }
      }
    }

连锁展开最好的方法就是递归算法了,但是我这里为了省事边缘处理的不好,有一些边缘雷区重复遍历了好几遍。

5.简单的右击标记

在angularJS中要使用右键,需要注入事件,代码如下

app.directive('ngRightClick', function ($parse) {
    return function (scope, element, attrs) {
      var fn = $parse(attrs.ngRightClick);
      element.bind('contextmenu', function (event) {
        event.preventDefault();
        fn(scope, {
          $event: event
        });
      })
    }
  });

右击雷区标记为m

 $scope.vc.markMine = function (block, x, y) {
      if ($scope.vo.blocks[x][y].meta != '*') {
        flagMeta = $scope.vo.blocks[x][y].meta;
        $scope.vo.blocks[x][y].meta = '*'
      } else {
        $scope.vo.blocks[x][y].meta = flagMeta;
      }
      $scope.$apply();
    }

开始游戏结束游戏和游戏计时等就不阐述了,至此,简易扫雷就写完了。

下载地址:https://download.csdn.net/download/oocom/11200814

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值