JS数字拼图程序重置方法

JS数字拼图程序重置方法

本文源程序来自js实现九宫格拼图小游戏

在该文章最后提到此程序中, 用来进行重置的算法非常简单, 所以会有一半情况会出现无解的情况, 只能重置进行下一局. 本来我只是把代码copy下来玩了一下,并没有发现提示有bug, 而且连续七八次都没碰见无解的情况, (真是在想不到的地方欧了起来,但是在写程序找bug时并无球用😂😂😂), 不过最终还是碰见了无解的情况. 一番挣扎之后, 决定动动懒惰的小手修改一下重置方法. 下面是我修改的过程, 最后也贴上我拙劣的代码实现, 希望各位指正错误与不足之处

一、无解的情况

以下是无解情况的示例,在3×3数字拼图还原中,出现了最后两块7,8号位置相反的情况。

这样的话,无论如何是还原不了的,除非把最后两个扣下来。这种情况的出现,在重置拼图顺序时就已经确定下来了,也就是说我们要在重置的一开始就避免无解序列的出现。

二、为什么出现无解情况

因为我也不会什么算法,所以关于这个问题,也是通过搜索相关的分析来进行了解的。总结来说就是无解是因为重置之后的排列是一个奇排列,有解要求重置之后的排列是一个偶排列。下面看具体解释↓↓↓

以上图为例,它的排列就是一行一行把它写下来:658 713 420,其中0是空位置

这样一个排列是偶排列,还记得这是我大一线性代数中的内容,要不是这个程序我都忘了。偶排列逆序数为偶数的排列(关于逆序数可以进入链接查看定义),以上排列的逆序数为20 (计算这里的逆序数不用跟0比较,我应该没算错吧),为偶数,所以是有解的情况。

如果排列是568 713 420,那么就是无解的情况,因为交换上面有解情况的前两个数字后就变成一个奇排列。定理:经过一次对换,奇排列变成偶排列,偶排列变成奇排列。

三、如何实现生成的情况都是有解

有了上面的数学理论的支撑,要生成有解的情况,我要做的核心就是要生成一个排列,这个排列还是一个偶排列。下面是我具体的解决办法。

1.将八个数字随机打乱

这里我使用的就是简单的洗牌算法,原程序好像也是用类似的方法打乱的。

var arr = [0, 1, 2, 3, 4, 5, 6, 7, 8];  //未"洗牌"序列
for (var i = 1; i < 8; i++) {
    // 从 i 到最后随机选一个元素
    var rand = parseInt(Math.random() * (9 - i)) + i;
    //i和rand位交换
    arr[i] = arr.splice(rand, 1, arr[i])[0];
}

这里未洗牌序列第一个是0,但是没有用到这一位,因为原程序的数组都是这样,没有用第一个元素位置,为了后面不搞混位置就延续了这个方式。

这样打乱之后,arr[ ]中就得到了随机的排列。

2.判断是否可用及修正无解排列

因为只有偶排列才能有解,所以下面要计算刚刚得到排列的逆序数。最容易想到的实现方法:暴力循环,虽然时间复杂度O(n²),但因为我写的是3×3的数字拼图,只有八个数字,所以采用两层嵌套循环还是可以的,如果规模增大,考虑到效率的问题,应该要应用归并来求逆序数,但是我也就嘴上说说,徒手写不出来。

求出来逆序数之后,判断奇偶,根据前面的定理,如果为奇排列,则交换前两个位置的数,变成偶排列。这样就得到有解的情况。

//计算逆序数
var sum = 0;                    //逆序数
for (var i = 1; i < 8; ++i) {
   for (var j = i + 1; j < 9; ++j) {
        if (arr[i] > arr[j])
            sum += 1;
   }
}
//奇排列变换偶排列--交换arr[1],arr[2]
if (sum % 2 != 0)
    arr[1] = arr.splice(2, 1, arr[1])[0];

最后将偶排列的数字放在对应的位置即可。例如arr[0,3,2,8,6,1,7,5,4],就对应下图情况。

/*原代码重置方法*/
function random_d() {
      for (var i = 9; i > 1; --i) {
        var to = parseInt(Math.random() * (i - 1) + 1);//产生随机数,范围为1到i,不能超出范围,因为没这个id的DIV
        if (d[i] != 0) {
          document.getElementById("d" + d[i]).style.left = d_posXY[to][0] + "px";
          document.getElementById("d" + d[i]).style.top = d_posXY[to][1] + "px";
        }
        //把当前的DIV位置设置为随机产生的DIV的位置
        if (d[to] != 0) {
          document.getElementById("d" + d[to]).style.left = d_posXY[i][0] + "px";
          document.getElementById("d" + d[to]).style.top = d_posXY[i][1] + "px";
        }
        //把随机产生的DIV的位置设置为当前的DIV的位置
        var tem = d[to];
        d[to] = d[i];
        d[i] = tem;
        //然后把它们两个的DIV保存的编号对调一下
      }
    }


/*下面是我的重置方法*/
function random_d() {
    var arr = [0, 1, 2, 3, 4, 5, 6, 7, 8];  //未"洗牌"序列
    var sum = 0;                    //逆序数
    for (var i = 1; i < 8; i++) {
        // 从 i 到最后随机选一个元素
        var rand = parseInt(Math.random() * (9 - i)) + i;
        arr[i] = arr.splice(rand, 1, arr[i])[0];       //i和rand位交换
    }
    //计算逆序数--无脑循环
    for (var i = 1; i < 8; ++i) {
        for (var j = i + 1; j < 9; ++j) {
            if (arr[i] > arr[j])
                sum += 1;
        }
    }
    //奇排列变换偶排列--交换arr[1],arr[2]
    if (sum % 2 != 0)
        arr[1] = arr.splice(2, 1, arr[1])[0];
    //此时得到洗牌后序列
    document.getElementById("demo").innerHTML = arr;
    for (var loc = 1; loc < 9; ++loc) {
        d[loc] = arr[loc];

        document.getElementById("d" + arr[loc]).style.left = d_posXY[loc][0] + "px";
        document.getElementById("d" + arr[loc]).style.top = d_posXY[loc][1] + "px";
    }
    d[9] = 0;
}

由于是原作者的成果,我就不整个贴出代码,如果需要,可以在开头链接的网页中获取代码并进行两个重置方法的测试。本文只区区一孔之见,难免有疏漏错误之处,恳请留言指正。

四、写在最后

九宫格的数字拼图问题其实跟八数码问题有点像,我是通过八数码问题的有无解的判断来思考这个拼图游戏如何初始化成有解状态,我的初始化方法中,空格固定是在最后一格,如何随机空位是我没有实现的地方。本文是在原代码作者的基础之上进行修改创作的,感谢原作者将代码发出来供人学习,也让我学到很多JS的知识。如果大家有好的重置方法以及随即空位的方法思路可以留言分享,一起学习。

posted @ 2021-08-26 23:02  Myrechen  阅读(583)  评论(0)    收藏  举报