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

    xiaoxiao2022-12-09  72

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

     由于用原生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

     

    最新回复(0)