Web版扫雷开发小记(3)

前篇:

web版扫雷开发小记(1)
web版扫雷开发小记(2)
web版扫雷开发小记(3)
web版扫雷开发小记(4)

完整代码:我的Github

在线试玩:点击

上一节完成了数字的显示,这次主要完成一个功能,即当当前方块周围没有雷(roundMines为0)时,自动挖开周边的雷。

思路分析

首先先弄明白扫雷中的这一游戏规则。

当用户点击到一个方块,而它的周围没有雷时,就会挖开它周围的方块,而当它的周边共计8个方块中,有一个方块也是周围没有雷的话,又会继续挖开这个方块周围的方块。如下图:


Web版扫雷开发小记(3)_第1张图片
空的方块

很容易想到,这是一个递归的思想,设计一个cleanArea()函数,遍历某方块周围的八个方块,当其中一个方块的roundMines属性为0时,则递归调用cleanArea()函数,直到该区域方块都被挖开为止。

(这里的实现搜罗了下其他人的想法,还可以用BFS实现,毕竟递归太低效了。不过我算法不咋样,还是先用着递归吧,有时间了来研究研究BFS)

函数实现

cleanArea()函数接收两个参数,当前点击方块的列columns和行rows

通过两层for 循环嵌套,遍历当前方块周边的8个方块,将周边方块的行列数push到定义的一个数组中,然后通过读取该数组的值,对方块进行clearRect()

添加isClicked属性

然而按照这个思路实现起来有诸多问题,最主要的一个问题就是循环会遍历到该方块本身。

例如,当前方块行为12,列为15,它的roundMines为0,那么函数就会遍历周边的方块,当我遍历到比如行11,列15的方块时,发现它的roundMines为0,那么递归函数调用,以行11,列15为当前方块,开始遍历,其他方块暂且不论,当检查到行12,列15的方块时(是的两个方块互为周边块)。发现也是0,好,又是一个递归调用。于是两个递归互相调用,永无止境……

这是我最开始写的:

function cleanArea(columns, rows, mineObject) {
    for (var k = -1; k < 2; k++) {
        for (var l = -1; l < 2; l++) {
            if (columns + l > -1 && rows + k > -1 && columns + l < 30 && rows + k < 16 ) {
                mineObject.cleanArr.push({
                    columns: columns + l,
                    rows: rows + k
                });
                if (mineObject.mines[columns + l][rows + k].roundMines === 0) {
                    mineObject.cleanArea(columns + l, rows + k, mineObject);
                }
            }
        }
    }
}

这个问题解决起来也简单,添加一个条件判断语句就好。例如这里两个范围-1到1的变量l和k,代表行和列的变化,那么当两个变量都为0时,就代表是当前块。

但是当我添加一个(l!==0)&&(k!==0)的条件时,程序每次在其中一个变量为0时就会判断为true。百思不得其解,自认这个判断写得也没有问题,搜索了一下大家也都是这么写得。没办法,只有搁置。

同时也感谢这个bug,让我思考到了更为便捷且语义更加明确的方法:

在之前定义的Mineblock类中,再添加一个isClicked属性,初始值为false。对于每个方块,当被点击到时isClicked更改为true。对于上述方法也同样如此,当我在将它push到定义的数组中时,同时更改isClicked属性。

那么在我判断中,我只需要加入isClicked===false的判断,就可以规避掉所有或被点击或被挖开的方块。

代码如下:

function cleanArea(columns, rows, mineObject) {
    for (var k = -1; k < 2; k++) {
        for (var l = -1; l < 2; l++) {
            if (columns + l > -1 && rows + k > -1 && columns + l < 30 && rows + k < 16 && mineObject.mines[columns + l][rows + k].isClicked === false) {
                mineObject.cleanArr.push({
                    columns: columns + l,
                    rows: rows + k
                });
                mineObject.mines[columns + l][rows + k].isClicked = true;
                if (mineObject.mines[columns + l][rows + k].roundMines === 0) {
                    mineObject.cleanArea(columns + l, rows + k, mineObject);
                }
            }
        }
    }
}

效果可以看上面的截图

添加游戏成功条件

游戏失败的条件很简单,点击到一个雷方块时就结束。游戏成功稍微麻烦一点,概念上很简单,所有不是雷的方块都被点开时游戏就成功了。但是怎么做呢?

最开始的想法很蠢,每次点击或者挖开一个方块时,都调用一个函数。这个函数的作用是什么呢?遍历当前所有isMined===false的对象,查看它的isClicked属性是否等于true。若所有方块都已经被挖开,那么游戏成功。

听起来思路其实很清晰,但这个方法不用说,太不节约了。

最后决定给MineSweeping对象添加一个属性count,作用很简单,每点开或者挖开一个方块时,count数都+1,当count数达到480-99(高级扫雷)时,即游戏成功。

你可能感兴趣的:(Web版扫雷开发小记(3))