Win7扫雷的H5完整复刻实现(二) / js扫雷算法处理空白连通域与点击不同方块的实现

空白连通域鉴定/点击空白方块

接上文实现了大部分扫雷的初始化算法,下面要实现的是查找数组中相邻的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 = $("<span></span>");
		$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项目加个星星哦

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值