接上文实现了大部分扫雷的初始化算法,下面要实现的是查找数组中相邻的0值域,以在点击时进行展开0值区。
这里需要初始化一个emptyMap二维数组以记录算法所查找到的零值域,同时可以记录哪些区域被展开过。
//连通判定,展开空白区
//不寻找斜角联通
_linkEmpty: function _linkEmpty(numX, numY) {
//四向寻值
try {
//阻止扩展数组操作,剪枝
/*if(numX != 17 && numY == 17) {
this._checkEmpty(numX + 1, numY);
this._checkEmpty(numX + 1, numY - 1);
}
if(numY != 17 && numX == 17) {
this._checkEmpty(numX, numY + 1);
this._checkEmpty(numX - 1, numY + 1);
}
if(numY != 17 && numX != 17) {
this._checkEmpty(numX, numY + 1);
this._checkEmpty(numX - 1, numY + 1);
this._checkEmpty(numX + 1, numY);
this._checkEmpty(numX + 1, numY - 1);
this._checkEmpty(numX + 1, numY + 1);
}
this._checkEmpty(numX - 1, numY);
this._checkEmpty(numX - 1, numY - 1);
this._checkEmpty(numX, numY - 1);
if(numX != 17 && numY == 17 && numY != 0) {
this._checkEmpty(numX + 1, numY);
this._checkEmpty(numX - 1, numY);
this._checkEmpty(numX, numY - 1);
} else if(numX == 17 && numY != 17 && numY != 0) {
this._checkEmpty(numX - 1, numY);
this._checkEmpty(numX, numY - 1);
this._checkEmpty(numX, numY + 1);
} else if(numX != 17 && numY != 17 && numX != 0 && numY != 0) {
this._checkEmpty(numX - 1, numY);
this._checkEmpty(numX + 1, numY);
this._checkEmpty(numX, numY - 1);
this._checkEmpty(numX, numY + 1);
} else if(numX != 17 && numX != 0 && numY == 0) {
this._checkEmpty(numX - 1, numY);
this._checkEmpty(numX + 1, numY);
this._checkEmpty(numX, numY + 1);
}*/
if(numX < 17) {
this._checkEmpty(numX + 1, numY);
}
if(numY < 17) {
this._checkEmpty(numX, numY + 1);
}
if(numX > 0) {
this._checkEmpty(numX - 1, numY);
}
if(numY > 0) {
this._checkEmpty(numX, numY - 1);
}
} catch(e) {
//TODO handle the exception
}
},
_checkEmpty: function _checkEmpty(numX, numY) {
//var $elem = this.$elem;
if(emptyMap[numX][numY] == 0 && (map[numX][numY] == -2 || map[numX][numY] == 0)) {
emptyMap[numX][numY] = 1;
var $blockPosition = numX * 18 + numY;
//console.log($blockPosition);
//console.log($($('.Block')[$blockPosition]));
var $elemBlock = $($('.Block')[$blockPosition]);
setTimeout(function() {
$elemBlock.removeClass();
$elemBlock.addClass("Block eBlock");
}, $blockPosition * 0.5);
/*
$('.Block')[$blockPosition].removeClass();
$('.Block')[$blockPosition].addClass("Block eBlock");*/
this._linkEmpty(numX, numY);
return true;
}
return false;
},
这里缩减了边界鉴定的代码,但是这样做会导致算法的时间复杂度变高(必然会鉴定四个边界值,而列举情况的话最坏情况也只是鉴定4次,但是代码可读性会很低),同样,寻找连通域的方式也是递归实现,若在四个方向上找到0值或-2值且emptyMap中标记该位置的地方没有被寻找到(为0)则将该位置在emptyMap中设为1,且将位置作为参数继续进行递归调用,反之返回。
同时在checkEmpty函数中一并进行了展开区域的操作,代表着每寻找到一个满足情况的0或-2都会被直接展开,这里设置了一个setTimeout以使得这一过程变得可见(而不是直接加载出来)。同时这里是通过改变class值以给予被通过索引值定位的方块不同的事件与样式(不使用event.target是因为传递参数相当麻烦,由于构架较为复杂,请参考我的github源码),是基本的dom树操作。
在第一次点击时必定触发空白域鉴定,其后点击0值时也会触发。
根据扫雷的规则,这种情况不会触发胜利鉴定或者失败鉴定
//点击雷域区
_clickMinesArray: function _clickMinesArray(e, x, y) {
if(e == null && (x == null || y == null)) return;
if(e != null) {
var $index = $('.Block').index($(e.target));
var numX = Math.floor($index / 18);
var numY = $index % 18;
var $elemBlock = $(e.target);
} else {
var $index = x * 18 + y;
var numX = x;
var numY = y;
var $elemBlock = $($('.Block')[$index]);
}
var $textspan = $("");
$elemBlock.removeClass();
$elemBlock.addClass("Block enBlock");
$textspan.text(map[numX][numY]);
//console.log($elemBlock);
if($elemBlock[0].children.length == 0) {
if(map[numX][numY] == 1) $textspan.css("color", "#2E6DA4");
else if(map[numX][numY] == 2) $textspan.css("color", "#5CB85C");
else if(map[numX][numY] == 3) $textspan.css("color", "#C9302C");
else if(map[numX][numY] == 4) $textspan.css("color", "#D58512");
else $textspan.css("color", "#EEA236");
$elemBlock.append($textspan);
};
},
这里传递了一个e(event,来自于click事件)用作确定被点击方块位置(或者x,y值直接确定方块位置),其后将map数组中的值直接给该方块显示。
这里会直接触发游戏失败结束
//点击雷区
_clickMines: function _ClickMines(e, index) {
var $num = this.$num;
if(e == null && index == null) return;
if(e != null) {
var $elemBlock = $(e.target);
} else {
var $elemBlock = $($('.Block')[index]);
}
$elemBlock.removeClass();
$elemBlock.addClass("Block qBlock");
for(var i = 0; i < 4; i++) {
(function(num) {
setTimeout(function() {
$elemBlock.removeClass();
$elemBlock.addClass("Block qBlock-active");
}, num * 200);
setTimeout(function() {
$elemBlock.removeClass();
$elemBlock.addClass("Block qBlock");
}, num * 300);
})(i);
};
for(var i = 0; i < $num; i++) {
for(var j = 0; j < $num; j++) {
if(map[i][j] == -1) {
this._boomMines(i, j);
}
}
}
$('.fBlock').removeClass().addClass("misBlock Block");
$('.Block').off("click");
$('.Block').off("mouseup");
$('.Block').off("mousedown");
$('.sBlock').off("mouseup");
clearTimeout(this.$clock);
/*var $index = $('.Block').index($(e.target));
var numX = Math.floor($index / 18);
var numY = $index % 18;*/
},
_boomMines: function _boomMines(x, y) {
var $index = x * 18 + y;
var $elemBlock = $($('.Block')[$index]);
if($elemBlock.hasClass("fBlock")) {
$elemBlock.removeClass();
$elemBlock.addClass("Block rBlock");
return;
}
$elemBlock.removeClass();
$elemBlock.addClass("Block qBlock");
(function(index) {
setTimeout(function() {
$elemBlock.removeClass();
$elemBlock.addClass("Block qBlock-active");
}, 10 * index);
})($index);
},
同样的,是修改了方块的class值以达到修改样式和设置事件的方式,这里的爆炸图标其实就是给class设置了一个icon,同时通过设置不同的setTimeout值以达到爆炸的动画,同时,当触发游戏结束鉴定后,会解绑所有的事件。
(失败动画过程截图)
这里会触发胜利条件的判断
//右键插旗
_setFlag: function _setFlag(e) {
var $index = $('.Block').index($(e.target));
var numX = Math.floor($index / 18);
var numY = $index % 18;
if($(e.target).hasClass("fBlock")) {
$(e.target).removeClass("fBlock");
$(e.target).addClass('sBlock');
flagMap[numX][numY] = 0;
this.$overNum++;
$('.Mines-body').text(this.$overNum);
} else {
$(e.target).removeClass("sBlock");
$(e.target).addClass("fBlock");
flagMap[numX][numY] = 1;
this.$overNum--;
$('.Mines-body').text(this.$overNum);
if(this.$overNum == 0 && this._checkVictor()) {
alert("You win!");
}
}
var _this = this;
$('.fBlock').off("mouseup").on("mouseup", function(e) {
if(e.which === 3) {
if($(e.target).hasClass("sBlock")) {
$(e.target).removeClass().addClass("Block fBlock");
_this.$overNum--;
$('.Mines-body').text(_this.$overNum);
if(this.$overNum == 0 && this._checkVictor()) {
alert("You win!");
}
return;
}
if($(e.target).hasClass("fBlock")) {
$(e.target).removeClass().addClass("Block sBlock");
_this.$overNum++;
$('.Mines-body').text(_this.$overNum);
return;
}
}
});
},
_checkVictor: function _checkVictor() {
var $num = this.$num;
for(var i = 0; i < $num; i++) {
for(var j = 0; j < $num; j++) {
if(map[i][j] == -1) {
var $index = i * 18 + j;
if(!$($('.Block')[$index]).hasClass("fBlock")) {
return false;
}
}
}
}
$('.fBlock').removeClass().addClass("misBlock Block");
$('.Block').off("click");
$('.Block').off("mouseup");
$('.Block').off("mousedown");
$('.sBlock').off("mouseup");
clearTimeout(this.$clock);
return true;
},
胜利条件即为当旗子的数量(旗子方块的数量)等于雷的总数时,会鉴定数组中的雷的位置与旗的位置是否相同,若全部相同则触发胜利条件,否则不作任何操作。
实际上所有的点击方块,都是在原矩阵的基础上进行展示操作,通过class值来改变div块的样式,透过动画来展示UI
扫雷完整项目github地址 https://github.com/xxx407410849/MinesSweeper
若本文对您有帮助请给我的git项目加个星星哦