大二的时候听说某大神可以徒手写扫雷,当时就一顿羡慕加崇拜,后来放假无聊就想起这件事来,准备实现一下儿时的梦想—徒手写扫雷。
由于用原生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