华容道再研究

这次我又给自己挖了一个更大的坑,已经确定华容道一共可分为六个领域,或者叫森林。分别为0到5 横式,比如0横式,表示布局为一个曹操,0个横将,5个竖将,4个兵; 2横式,表示布局为一个曹操,2个横将,3个竖将,4个兵;

六个森林,一定是没有任何关系的,独立的,因为对于滑块类游戏的规则,横将不可能变化为竖将,只有位置可以变化。

现在我想找出每个森林共有多少个布局?每个森林又有多少颗相互独立的树?

相互独立的树是什么?假设一横式的森林有树A 和 树B,那么树A的任何节点都不会出现在树B中,即树A 和 树B的交集一定是空集,树A中的任意布局通过滑块游戏的规则可以演化为树A所有的其他布局,但不可能演化为树B中的任意布局。

那么,首先就需要一个枚举类,能够一个不漏地,并且也一个不多地枚举出华容道的所有布局,这样一个枚举类是最难以编写的,几乎没有人能一次性写对,而花多长时间实现这样一个无bug的枚举类,可以反映出思维的敏锐度,类似于头脑的转速。我比较差,大概花了三天,先开始我以为只要一天时间就够了,结果错的离谱,因为后来我发现,我想得还不是很清楚,就已经开始编码了,结果编写都一半时,发现一个难题,”怎么样才能比较优雅地从当前布局推出下一个布局呢?”

写到一半又发现,靠,之前的处理办法是错误的,又要推倒重来,
嗯,这种情况当时没有考虑到…

一旦正确地枚举出了所有的布局,问题就简单了,首先问题1有了答案,已经有华容道自动求解的现成代码,只要稍作修改,就能根据一个布局,得到包含这个布局的一颗树。有了从一个布局得到一颗树的方法,问题2也解决了,还可以判断一个森林,其中有多少颗树是有曹出布局的。

怎么枚举?用字典序来枚举,就是定义一个枚举规则,然后给计算机任意一个有效布局(可以放置1个大王,5个横将或者竖将,4个兵),计算机都能推出其下一个或者上一个布局,根据这个枚举规则,每个布局都有了一个唯一地,一一对应地序列号。

我是这样定义枚举规则的,首先放置大王,然后放置横将,然后放置竖将,最后放置4个兵,然后给4列5行的棋盘就行编号,
0 1 2 3
4 5 6 7
8 9 10 11
12 13 14 15
16 17 18 19
对于 一横式,第一个布局是:

    // 5 5 4 4 | 0  1   2  3
    // 1 1 3 3 | 4  5   6  7
    // 3 3 1 1 | 8  9   10 11
    // 1 1 0 0 | 12 13  14 15
    // 0 0 0 0 | 16 17  18 19

最后一个布局是:

    // 0 0 0 0 | 0  1   2  3
    // 0 0 3 3 | 4  5   6  7
    // 3 3 1 1 | 8  9   10 11
    // 1 1 5 5 | 12 13  14 15
    // 4 4 1 1 | 16 17  18 19

为了代码更清晰简洁,先不考虑4个兵的放置,因为前面关于华容道自动求解的盘面编码类的代码,已经有了关于4个兵的组合序号表了,直接拿来用。

扫描二维码关注公众号,回复: 2158516 查看本文章

这个枚举规则,保证两个空格总是逐渐地 从高位移动到低位,
而相应地,大王,横将竖将总是逐渐地 从低位移动到高位。

package game.hrd.game.hrd.refactor;

import game.hrd.LocalConst;

/**
 * Created by huangcongjie on 2017/12/24.
 */
public class LayoutEnumerator {
    private int hNum = 0; //横将个数    只能在构造函数初始化
    private int sNum = 0; //竖将个数    只能在构造函数初始化
    public int mIndex = -1;  //大王的位置

    private int[] layout;
    private int[][] movableS, movableH;
    private int[] sCursors, hCursors;

    int[] WQ = LocalConst.WQ2;
    private int[] binCombineArr = new int[15];
    private int count = 0;

    public LayoutEnumerator(int hCount) {
        if (hCount < 0 || hCount > 5) {
            throw new RuntimeException("invalid horizontal piece number! the hCount should be [0, 5]");
        }
        setBinCombineArr();
        hNum = hCount;
        sNum = 5 - hCount;
        sCursors = new int[sNum];
        hCursors = new int[hNum];
        movableS = new int[sNum][];
        movableH = new int[hNum][];
        mIndex = 0;

        layout = new int[20];
        layout[mIndex] = layout[mIndex + 1] = 5;
        layout[mIndex + 4] = layout[mIndex + 5] = 1;
        initHorizontalArrs(layout, 0, 0);
        initVerticalArrs(layout, 0, 0);
    }

    public LayoutEnumerator next() {
        //小兵移不动了,开始移动竖将
        if (sNext()) {
            count++;
            //移动竖将成功
            return this;
        }
        //竖将移不动了,开始移动横将
        if (hNext()) {
            boolean moved = true;
            while ( !initVerticalArrs(layout, 0, 0) ) {
                //横将的摆放,让竖将没位置了
                for (int i = 0; i < layout.length; i++) {
                    if (layout[i] == 3) {
                        layout[i] = layout[i + 4] = 0;
                    }
                }
                moved = hNext();
                if (!moved) {
                    //横将移不动了
                    break;
                }
            }
            if (moved) {
                count++;
                //移动横将成功
                return this;
            }
        }
        //横将移不动了,开始移动大王
        mIndex++;
        if (LocalConst.COL[mIndex] == 3) {
            mIndex++;
        }
        if (mIndex > 14) {
            //大王都移不动了,所有布局都按字典序列枚举完毕
            return null;
        } else {
            count++;
            //移动大王成功,开始初始化 横将,竖将,空格和小兵
            layout = new int[20];
            layout[mIndex] = layout[mIndex + 1] = 5;
            layout[mIndex + 4] = layout[mIndex + 5] = 1;
            initHorizontalArrs(layout, 0, 0);
            initVerticalArrs(layout, 0, 0);
            return this;
        }
    }


    // 3 5 5 3 | 0  1   2  3
    // 1 1 1 1 | 4  5   6  7
    // 3 4 4 3 | 8  9   10 11
    // 1 0 0 1 | 12 13  14 15
    // 0 0 0 0 | 16 17  18 19
    //-------------------------
    // 0 3 4 7 8 11 12 13 14 15 >
    // 0 3 4 7 8 11 12
    // 3 4 7 8 11 12 13
    // 8 11 12 13 14
    // 11 12 13 14 15
    private boolean sNext() {
        int i = sNum - 1;
        boolean flag = false;
        for (; i >=0; i--) {
            int loc = movableS[i][sCursors[i]];
            layout[loc] = layout[loc + 4] = 0;
            int a = sCursors[i] + 1;
            for (int j = a; j < movableS[i].length; j++) {
                int loc2 = movableS[i][j];
                if (layout[loc2] == 0 && layout[loc2 + 4] == 0) {
                    sCursors[i] = j;
                    flag = true;
                    break;
                }
            }
            if (flag) {
                break;
            }
        }
        if (flag) {
            int loc = movableS[i][sCursors[i]];
            layout[loc] = 3;
            layout[loc + 4] = 1;
            //change movableS[i+] and set sCursors[i+] = 0;
            initVerticalArrs(layout, loc + 1, i + 1);
        }
        return flag;
    }

    private boolean hNext() {
        int i = hNum - 1;
        boolean flag = false;
        for (; i >=0; i--) {
            int loc = movableH[i][hCursors[i]];
            layout[loc] = layout[loc + 1] = 0;
            int a = hCursors[i] + 1;
            for (int j = a; j < movableH[i].length; j++) {
                int loc2 = movableH[i][j];
                if (layout[loc2] == 0 && layout[loc2 + 1] == 0) {
                    hCursors[i] = j;
                    flag = true;
                    break;
                }
            }
            if (flag) {
                break;
            }
        }
        if (flag) {
            int loc = movableH[i][hCursors[i]];
            layout[loc] = 4;
            layout[loc + 1] = 4;
            initHorizontalArrs(layout, loc + 2, i + 1);
        }
        return flag;
    }

    private boolean initVerticalArrs(int[] layout, int startLoc, int index) {
        if (index >= sNum) {
            return true;
        }
        sCursors[index] = 0;
        int[] movableLst = new int[12];
        int movableCount = 0;
        for (int i = startLoc; i < layout.length; i++) {
            if (LocalConst.ROW[i] < 4 &&
                    layout[i] == 0 && layout[i+4] == 0) {
                movableLst[movableCount] = i;
                movableCount++;
            }
        }
        if (movableCount == 0) {
            //横将的摆放不合法,因为使得有竖将无法摆放
            return false;
        }

        int sCount = sNum - index;
        int[] temp = HrdUtil.copy(layout);
        int[] rArr = new int[sCount];
        int num = 0;
        for (int i = 15; i >= 0; i--) {
            if (temp[i] == 0 && temp[i+4] == 0) {
                rArr[num] = i;
                num++;
                if (sCount == num) {
                    break;
                }
                temp[i] = 3;
            }
        }

        int[] locs = new int[12];
        int n = 0;
        for (int j = 0; j < movableCount; j++) {
            int max = rArr[sCount - 1];
            int loc = movableLst[j];
            if (loc <= max) {
                locs[n] = loc;
                n++;
            }
        }
        int[] locations = new int[n];
        for (int m = 0; m < n; m++) {
            locations[m] = locs[m];
        }
        movableS[index] = locations;
        int firstLoc = movableLst[0];
        layout[firstLoc] = 3;
        layout[firstLoc + 4] = 1;
        return initVerticalArrs(layout, firstLoc + 1, index + 1);
    }

    private void initHorizontalArrs(int[] layout, int startLoc, int index) {
        if (index >= hNum) {
            return;
        }
        hCursors[index] = 0;

        int[] movableLst = new int[11];
        int movableCount = 0;
        for (int i = startLoc; i < 20; i++) {
            if (LocalConst.COL[i] < 3 &&
                    layout[i] == 0 && layout[i+1] == 0) {
                movableLst[movableCount] = i;
                movableCount++;
            }
        }
        int hCount = hNum - index;
        int num = 0;
        int[] rArr = new int[hCount];
        for (int i = 18; i >= 0; i--) {
            if (LocalConst.COL[i] < 3 && layout[i] == 0 && layout[i+1] == 0) {
                rArr[num] = i;
                num++;
                if (hCount == num) {
                    break;
                }
                i--;
            }
        }
        int[] locs = new int[11];
        int n = 0;
        for (int j = 0; j < movableCount; j++) {
            int max = rArr[hCount - 1];
            int loc = movableLst[j];
            if (loc <= max) {
                locs[n] = loc;
                n++;
            }
        }
        int[] locations = new int[n];
        for (int m = 0; m < n; m++) {
            locations[m] = locs[m];
        }
        movableH[index] = locations;
        int firstLoc = movableLst[0];
        layout[firstLoc] = layout[firstLoc + 1] = 4;
        initHorizontalArrs(layout, firstLoc + 2, index + 1);
    }

    private void initVerticalArrs3(int[] layout, int startLoc, int sCount) {
        if (sCount < 1)
            return;
        for (int i = 0; i < sCount; i++) {
            sCursors[sNum - 1 - i] = 0;
        }
        int[] movableLst = new int[12];
        int movableCount = 0;
        for (int i = startLoc; i < layout.length; i++) {
            if (LocalConst.ROW[i] < 4 &&
                    layout[i] == 0 && layout[i+4] == 0) {
                movableLst[movableCount] = i;
                movableCount++;
            }
        }
        int[] temp = HrdUtil.copy(layout);
        int[] arr = new int[sCount];
        int num = 0;
        for (int i = startLoc; i < temp.length; i++) {
            if (LocalConst.ROW[i] < 4 &&
                    temp[i] == 0 && temp[i+4] == 0) {
                arr[num] = i;
                num++;
                if (sCount == num) {
                    break;
                }
                temp[i+4] = 1;
            }
        }
        temp = HrdUtil.copy(layout);
        int[] rArr = new int[sCount];
        num = 0;
        for (int i = 15; i >= 0; i--) {
            if (temp[i] == 0 && temp[i+4] == 0) {
                rArr[num] = i;
                num++;
                if (sCount == num) {
                    break;
                }
                temp[i] = 3;
            }
        }
        int offset = sNum - sCount;
        for (int i = 0; i < sCount; i++) {
            int[] locs = new int[12];
            int n = 0;
            for (int j = 0; j < movableCount; j++) {
                int max = 99;
                if (i < sCount - 1) {
                    max = rArr[sCount - 1 - i];
                }
                int min = -1;
                if (i > 0) {
                    min = arr[i];
                }
                int loc = movableLst[j];
                if (loc >= min && loc <= max) {
                    locs[n] = loc;
                    n++;
                }
            }
            int[] locations = new int[n];
            for (int m = 0; m < n; m++) {
                locations[m] = locs[m];
            }
            movableS[offset + i] = locations;
        }
        for (int i = 0; i < arr.length; i++) {
            int loc = arr[i];
            layout[loc] = 3;
            layout[loc + 4] = 1;
        }
    }
    // 4 4 4 4 | 0  1   2  3
    // 5 5 4 4 | 4  5   6  7
    // 1 1 0 0 | 8  9   10 11
    // 0 0 0 0 | 12 13  14 15
    // 0 0 0 0 | 16 17  18 19
    //--------------------------------
    // 0 1 2 6 10 12 13 14 16 17 18 >
    // 0 1 2 6 10 12 13 14
    // 2 6 10 12 13 14 16
    // 6 10 12 13 14 16 17 18

    public int[] getLayout() {
        return layout;
    }

    private void setBinCombineArr() {
        int m = 0;
        for (int i = 1; i < 63; i++) {
            int k = 0;
            for (int j = 0; j < WQ.length; j++) {
                if ( (i & WQ[j]) > 0) {
                    k++;
                }
            }
            if (k == 4) {
                binCombineArr[m] = i;
                m++;
            }
        }
    }

    public int[][] getBin15Layouts() {
        int[] locations = new int[6];
        int m = 0;
        for (int i = 0; i < layout.length; i++) {
            if (layout[i] == 0 || layout[i] == 2) {
                locations[m] = i;
                m++;
            }
        }
        int[][] ret = new int[15][];
        int k1 = -1, k2 = -1;
        for (int i = 0; i < binCombineArr.length; i++) {
            int[] bingLayout = HrdUtil.copy(layout);
            int combo = binCombineArr[i];
            for (int j = 0; j < 6; j++) {
                if ( (combo & WQ[j]) > 0) {
                    bingLayout[locations[j]] = 2;
                } else {
                    k1 = k2;
                    k2 = locations[j];
                }
            }
            bingLayout[k1] = bingLayout[k2] = 0;
            ret[i] = bingLayout;
        }
        return ret;
    }

    public static void test() {
        int sum = 0;
        for (int i = 0; i < 6; i++) {
            int hCount = i;
            LayoutEnumerator enumerator = new LayoutEnumerator(hCount);
            System.out.print(enumerator.getPrintLayout2());
            int total = 1;
            while ((enumerator.next() != null)) {
                total ++;
//                System.out.print(enumerator.getPrintLayout2());
            }
            System.out.println(String.format("%d横式共有%d节点", hCount, total));
            sum += total;
        }
        System.out.println(String.format("华容道共有%d节点", sum));
    }

    public String getPrintLayout2() {
        String layoutStr = "";
        for (int i = 0; i < 20; i++) {
            layoutStr += layout[i] + "\t";
            if (i % 4 == 3) {
                layoutStr += "\n";
            }
        }
        layoutStr = layoutStr.substring(0, layoutStr.length() - 1);
        layoutStr += "\n---------" + count + "-----------\n\n";
        return layoutStr;
    }

    public static String getPrintLayout(int[] layout, int count) {
        String layoutStr = "";
        for (int i = 0; i < 20; i++) {
            layoutStr += layout[i] + "\t";
            if (i % 4 == 3) {
                layoutStr += "\n";
            }
        }
        layoutStr = layoutStr.substring(0, layoutStr.length() - 1);
        layoutStr += "\n---------" + count + "-----------\n\n";
        return layoutStr;
    }

    public static String getPrintLayout2(int[] layout, int count) {
        String layoutStr = "";
        for (int i = 0; i < 20; i++) {
            layoutStr += layout[i] + ", ";
            if (i % 4 == 3) {
                layoutStr += "\n";
            }
        }
        layoutStr = layoutStr.substring(0, layoutStr.length() - 1);
        layoutStr += "\n---------" + count + "-----------\n\n";
        return layoutStr;
    }

    static public String toLayoutString(int[] layout2) {
        String layout = "";
        for (int i = 0; i < 20; i++) {
            layout += layout2[i];
            if (i % 4 == 3) {
                layout += ", ";
            } else {
                layout += " ";
            }
        }
        layout = layout.substring(0, layout.length() - 2);
        return layout;
    }

    // 3 5 5 3 | 0  1   2  3
    // 1 1 1 1 | 4  5   6  7
    // 3 4 4 3 | 8  9   10 11
    // 1 0 0 1 | 12 13  14 15
    // 0 0 0 0 | 16 17  18 19

    // 0 3 4 7 8 11 12 13 14 15 >
    // 0 3 4 7 8 11 12
    // 3 4 7 8 11 12 13
    // 8 11 12 13 14
    // 11 12 13 14 15

    // 3 3 3 3 | 0  1   2  3
    // 1 1 1 1 | 4  5   6  7
    // 0 4 4 0 | 8  9   10 11
    // 5 5 0 0 | 12 13  14 15
    // 1 1 0 0 | 16 17  18 19

    // 0 1 2 3 4 7 11 14 15 >
    // 0 1 2 3 4
    // 1 2 3 4 7
    // 2 3 4 7 11 14
    // 4 7 11 14 15

    // 4 4 4 4 | 0  1   2  3
    // 5 5 4 4 | 4  5   6  7
    // 1 1 0 0 | 8  9   10 11
    // 0 0 0 0 | 12 13  14 15
    // 0 0 0 0 | 16 17  18 19

    // 0 1 2 6 10 12 13 14 16 17 18 >
    // 0 1 2 6 10 12 13 14
    // 2 6 10 12 13 14 16
    // 6 10 12 13 14 16 17 18
}

代码讲解:
movableS,movableH是二维数组
最核心的代码方法,
boolean initVerticalArrs() 初始化所有竖将的可行位置数组,movableS, 所有竖将放置成功则返回true
initHorizontalArrs()初始化所有横将的可行位置数组,movableH

initVerticalArrs的功能:
如果给定一个布局的大王位置,横将位置,则所有竖将能放置在哪些位置都要尽可能地确定下来,确定每个竖将最小和最大的可放置位置。

initHorizontalArrs的功能:
如果给定一个布局的大王位置,则所有横将能放置在哪些位置都要尽可能地确定下来。

重要的代码方法
int[] sCursors, hCursors; 分别表示竖将的游标,横将的游标
sNext() 移动竖将,成功则返回true,当所有竖将移动到头时,返回false,负责更新竖将的游标
hNext() 移动横将,返回值同sNext(),负责更新横将的游标

举个例子

    // 3 5 5 3 | 0  1   2  3
    // 1 1 1 1 | 4  5   6  7
    // 3 4 4 3 | 8  9   10 11
    // 1 0 0 1 | 12 13  14 15
    // 0 0 0 0 | 16 17  18 19

    // 0 3 4 7 8 11 12 13 14 15 >
    // 0 3 4 7 8 11 12
    // 3 4 7 8 11 12 13
    // 8 11 12 13 14
    // 11 12 13 14 15

对于横刀立马布局,已经给定了大王,关羽的位置,那么所有竖将的可选位置是 // 0 3 4 7 8 11 12 13 14 15 >
第一个竖将 0 3 4 7 8 11 12
第二个竖将 3 4 7 8 11 12 13
第三个竖将 8 11 12 13 14
第四个竖将 11 12 13 14 15
initVerticalArrs() 负责把movableS初始化为以上那个二维数组,
不仅要推出所有竖将的可选位置,还要知道针对每个竖将,哪些位置是已经被其他竖将占用了,不能在放置了,如第一个一定不能放置在13 14 15因为已经被后三个占用了
第二个一定不能放0 14 15,第三第四个同理
还有三点要强调,(都是走过的坑)
第一点,initVerticalArrs为什么是递归的,我曾经试图把它改为非递归的,结果几个小时都没有搞定,而且各种错误,非常烦,因为其中的局部变量 rArr, 是动态的,不能在一次循环中,正确地初始化,必须要在前面竖将已经放置好的情况下才能,initVerticalArrs3() 方法中去初始化rArr是不正确的。
比如

    // 5 5 0 0 | 0  1   2  3
    // 1 1 0 0 | 4  5   6  7
    // 4 4 3 0 | 8  9   10 11
    // 3 3 1 3 | 12 13  14 15
    // 1 1 0 1 | 16 17  18 19

如果第二个竖将不确定放置在11 还是 12,那么最后两个竖将也不能确定怎么放

第二点,如何更新二维数组movableS ?还是以上面的横刀立马为例,假设
第三个竖将 8 11 12 13 14
第四个竖将 11 12 13 14 15
第四个竖将已经走到15,第三个竖将 需要从8走到11,那么第四个竖将的可放置位置都要改变,并且其对应的游标置0,sCursors[3] = 0, movableS[3]的所有元素都要更新,
如果第一个竖将走到下一个位置,第二三四个竖将所有的可放置数组都要更新,并且相应地游标置0

如果横将走到下一个位置,整个二维数组movableS都要改,sCursors的所有元素置0

第三点,为什么initVerticalArrs() 有时为返回false,因为大王,横将的摆放,有时会使得竖将放不下了,比如如下二横式,怎么放第三个竖将?无解,那么横将必须的进行下一种摆放

    // 0 0 0 0 | 0  1   2  3
    // 5 5 4 4 | 4  5   6  7
    // 1 1 0 0 | 8  9   10 11
    // 0 0 4 4 | 12 13  14 15
    // 0 0 0 0 | 16 17  18 19

如果曹操走到下一个位置,整个二维数组movableS和movableH都要改,sCursors和hCursors的所有元素置0

针对哈希布局的盘面编码类

package game.hrd.game.hrd.refactor;

import game.hrd.LocalConst;

//针对哈希布局的盘面编码类
public class HashLayoutEncoder {

    //组合序号表
    short[] Hz;     //横将
    short[] Sz;     //竖将
    short[] Bz;     //小兵

    //权值表
    int[] Hw;
    int[] Sw;
    int[] Mw;

    public int bmTotal;

    public HashLayoutEncoder(int[] layout) {
        Hz = new short[4096 * 3];
        Sz = new short[4096 * 3];
        Bz = new short[128];
        //C12取5=792
        Hw = new int[792*2];
        Sw = new int[792];
        int i,j,k;
        int Hn=0, Bn=0, Sn=0; //各类子数目,大王默认为1不用计数
        for(i=0;i<20;i++){   //计算各种棋子的个数
            if(layout[i] == LocalConst.B) Bn++;
            if(layout[i] == LocalConst.H) Hn++;
            if(layout[i] == LocalConst.S) Sn++;
        }
        Hn /= 2;
        int[] WQ2 = LocalConst.WQ2;
//        int Hmax=WQ2[11],Smax=WQ2[12-Hn*2],Bmax=WQ2[16-(Hn+Sn)*2]; //各种子的最大二进位数
        int Hmax=WQ2[11],Smax=WQ2[12],Bmax=WQ2[6]; //各种子的最大二进位数
        short Hx=0,Sx=0,Bx=0; //各种棋子组合的最大序号
        //初始化组合序号表
        for(i=0; i<4096; i++){
            for(j=0,k=0;j<12;j++) {
                if((i & WQ2[j]) > 0) {
                    k++; //计算1的个数
                }
            }
            if(k==Hn && i<Hmax) {
                Hz[i] = Hx++;
            }
            if(k==Sn && i<Smax) {
                Sz[i]=Sx++;
            }
            if(k==Bn && i<Bmax) {
                Bz[i]=Bx++;
            }
        }
        int Sq=Bx,Hq=Bx*Sx,Mq=Bx*Sx*Hx; //竖将位权,横将位权,王位权
        Mw = new int[12];
        Hw = new int[Hx];
        Sw = new int[Sx];
        for(i=0;i<12;i++) Mw[i]=i*Mq; //初始化大王权值表
        for(i=0;i<Hx;i++) Hw[i]=i*Hq; //初始化横将权值表
        for(i=0;i<Sx;i++) Sw[i]=i*Sq; //初始化竖将权值表
        bmTotal = Mq*12;
    }

    //盘面编码
    public int encode(int[] layout) {
        int Bb=0,Bd=-1; //空位序号记录器
        int Sb=0,Sd=-1; //竖条序号记录器
        int Hb=0,Hd=-1; //横条序号记录器
        int Mb = 0;         //大王序号记录器
        int c;
        int[] f = new int[20];

        for(int i = 0; i < 20; i++){
            c=layout[i];
            if(c == LocalConst.K) { //大王定序
                if(f[i] == 0) {
                    Mb = i - LocalConst.ROW[i];
                    f[i] = f[i + 1] = f[i + 4] = f[i + 5] = 1;
                }
                continue;
            }
            if (LocalConst.COL[i]<3 && layout[i+1] <= LocalConst.H) {
                if (LocalConst.ROW[i] < 1) {
                    Hd++; //横条位置序号(编号)
                } else if (layout[i-4] <= LocalConst.H &&
                    layout[i-3] <= LocalConst.H) {
                    Hd++; //横条位置序号(编号)
                }
            }
            if (c == LocalConst.H) {//横将定序,转为二进制进行询址得Hb
                if(f[i] == 0) {
                    Hb += LocalConst.WQ2[Hd];
                    f[i] = f[i+1] = 1;
                }
                continue;
            }
            if (LocalConst.ROW[i]<4 && layout[i+4]<=LocalConst.S) {
                if (LocalConst.ROW[i] < 1) {
                    Sd++; //竖将位置序号(编号)
                } else if (layout[i-4] <= LocalConst.H) {
                    Sd++; //竖将位置序号(编号)
                }
            }
            if (c == LocalConst.S) { //竖条定序,转为二进制进行询址得Sb
                if(f[i] == 0) {
                    Sb += LocalConst.WQ2[Sd];
                    f[i] = f[i+4] = 1;
                }
                continue;
            }
            if(c == LocalConst.B || c == 0)
                Bd++;  //小兵位置序号(编号)
            if(c == LocalConst.B)
                Bb += LocalConst.WQ2[Bd]; //小兵定序,转为二进制进行询址得Bb
        }
        //Hb,Sb,Bb为组合序号,"横刀立马"最大值为小兵C(6,4)-1=15-1,竖条C(10,4)-1=210-1
        Bb=Bz[Bb];
        Sb=Sz[Sb];
        Hb=Hz[Hb];//询址后得得Bb,Sb,Hb组合序号
        return Bb+Sw[Sb]+Hw[Hb]+Mw[Mb]; //用位权编码,其中Bb的位权为1
    }

    //按左右对称规则考查棋盘,对其编码
    public int symmetricEncode(int[] q){
        char i;
        int[] q2 = new int[20];
        for(i=0; i<20; i+=4) {
            q2[i]=q[i+3];
            q2[i+1]=q[i+2];
            q2[i+2]=q[i+1];
            q2[i+3]=q[i];
        }
        return encode(q2);
    }

    public static String getPrintLayout(int[] layout) {
        String layoutStr = "";
        for (int i = 0; i < 20; i++) {
            layoutStr += layout[i] + "\t";
            if (i % 4 == 3) {
                layoutStr += "\n";
            }
        }
        layoutStr = layoutStr.substring(0, layoutStr.length() - 1);
        return layoutStr;
    }
}
package game.hrd.game.hrd.refactor;

import game.hrd.LocalConst;

/**
 * Created by huangcongjie on 2017/12/20.
 */
public class HashLayoutAnalyzer implements IAnalyzer {
    private int k1 = -1; //空格1位置
    private int k2 = -1; //空格2位置
    private int h;  //两空格的联合类型, {0:不联合,1:竖联合,2:横联合}

    @Override
    public void analyze(HrdNode hrdLayout) {
        int i=0; //i,列
        k1 = k2 = -1;
        h = 0;
        int[] layout = hrdLayout.layout;
        for(i=0; i<20; i++){
            if(layout[i] == 0) {
                if (k1 == -1) {
                    k1 = i;
                } else {
                    k2 = i;
                    break;
                }
            }
        }
        if (k1 + 4 == k2) {
            h = 1;  //空格竖联合
        }
        if (k1 + 1 == k2 && LocalConst.COL[k1] < 3) {
            h = 2;  //空格横联合
        }
        int col1 = LocalConst.COL[k1];
        int col2 = LocalConst.COL[k2];
        if (col1 > 0) {
            i = k1 - 1;
            zinb(hrdLayout, i, k1);
            if (layout[i] == 3) {
                if (h == 1)
                    hrdLayout.addChoice(i, k1);
            }
            if (layout[i] == 5) {
                if (h == 1)
                    hrdLayout.addChoice(i - 1, k1 - 1);
            }
            if (layout[i] == 4) {
                if (h == 2) {
                    hrdLayout.addChoice(i - 1, k2 - 1);
                }
                hrdLayout.addChoice(i - 1, k1 - 1);
            }
        }
        if (col1 < 3) {
            i = k1 + 1;
            zinb(hrdLayout, i, k1);
            if (layout[i] == 3) {
                if (h == 1)
                    hrdLayout.addChoice(i, k1);
            }
            if (layout[i] == 5) {
                if (h == 1)
                    hrdLayout.addChoice(i, k1);
            }
            if (layout[i] == 4) {
                hrdLayout.addChoice(i, k1); //如果横联合,k1不是第一空,所以不用判断h
            }
        }
        if (k1 > 3) {
            i = k1 - 4;
            zinb(hrdLayout, i, k1);
            if (layout[i] == 4 && layout[i+1] == 4 &&
                    (col1 != 1 || layout[i-1] != 4) ) {
                if (h == 2)
                    hrdLayout.addChoice(i, k1);
            }
            if (layout[i] == 1) {
                if (layout[i-4] == 3) {
                    if (h == 1) {
                        hrdLayout.addChoice(i - 4, k2 - 4);
                    }
                    hrdLayout.addChoice(i - 4, k1 - 4);
                }
                if (layout[i-4] == 5 && layout[i-3] == 5) {
                    if (h == 2)
                        hrdLayout.addChoice(i - 4, k1 - 4);
                }
            }
        }
        if (k1 < 16) {
            i = k1 + 4;
            zinb(hrdLayout, i, k1);
            if (layout[i] == 3)
                hrdLayout.addChoice(i, k1);
            if (layout[i] == 4 && i < 19 && layout[i+1] == 4 &&
                    (col1 != 1 || layout[i-1] != 4) ) {
                if (h == 2) {
                    hrdLayout.addChoice(i, k1);
                }
            }
            if (layout[i] == 5 && layout[i+1] == 5) {
                if (h == 2)
                    hrdLayout.addChoice(i, k1);
            }
        }
        if (col2 > 0) {
            i = k2 - 1;
            zinb(hrdLayout, i, k2);
            if (layout[i] == 4) {
                hrdLayout.addChoice(i - 1, k2 - 1);
            }
        }
        if(k2>3)  {
            i = k2 - 4;
            zinb(hrdLayout, i, k2);
            if(layout[i]==1 && layout[i-4] == 3) {
                hrdLayout.addChoice(i - 4, k2 - 4);
            }
        }
        if(col2<3) {
            i = k2 + 1;
            zinb(hrdLayout, i, k2);
            if(layout[i]==4) {
                if(h==2) {
                    hrdLayout.addChoice(i, k1);
                }
                hrdLayout.addChoice(i, k2);
            }
        }
        if(k2<16) {
            i = k2 + 4;
            zinb(hrdLayout, i, k2);
            if(layout[i]==3) {
                if(h==1) {
                    hrdLayout.addChoice(i, k1);
                }
                hrdLayout.addChoice(i, k2);
            }
        }
    }

    private void zinb(HrdNode hrdLayout, int src, int dst) {
        if (hrdLayout.layout[src] == 2) {
            //小兵
            if (h > 0) {
                hrdLayout.addChoice(src, k1);
                hrdLayout.addChoice(src, k2);
            } else {
                hrdLayout.addChoice(src, dst);
            }
        }
    }

    //走一步函数
    @Override
    public void step(int[] layout, int src, int dst) {
        int c = layout[src];
        int lx = c;
        if (c == 1) {
            lx = layout[src-4];
        }
        switch(lx) {
            case 2:     //兵
                layout[src] = 0;
                layout[dst] = c;
                break;
            case 3:     //竖
                layout[src] = layout[src+4] = 0;
                layout[dst] = c;
                layout[dst+4] = 1;
                break;
            case 4:     //横
                layout[src] = layout[src+1]=0;
                layout[dst] = layout[dst+1]=c;
                break;
            case 5:     //王
                layout[src] = layout[src+1]= layout[src+4]=layout[src+5]=0;
                layout[dst] = layout[dst+1]= c;
                layout[dst+4]=layout[dst+5]= 1;
                break;
        }
    }
}
package game.hrd.game.hrd.refactor;

public class HrdNode {
    public int[] layout;
    public int level = 0;
    public HrdNode parent = null;
    //记录上次移动的索引
    private int preMovedIndex = -1;

    //原位置,目标位置,最多只会有10步
    public int[] srcArr = new int[10];
    public int[] dstArr = new int[10];
    public int totalChoices = 0;

    private IAnalyzer analyzer;
    private boolean isHashLayout;

    public int nodeNum=1;
    public boolean outable = false;
    public int hNum;
    private int dstCode = -1;
    public int bmCode = -2;

    public void setGoal(int code) {
        dstCode = code;
    }

    public HrdNode(int[] layout, IAnalyzer analyzer, boolean useHash) {
        this.layout = layout;
        this.analyzer = analyzer;
        isHashLayout = useHash;
        analyzer.analyze(this);
    }

    public void addChoice(int src, int dst) {
        srcArr[totalChoices] = src;
        dstArr[totalChoices] = dst;
        totalChoices++;
    }

    public boolean isGoal() {
        if (dstCode >= 0)  {
            return bmCode == dstCode;
        }
        return layout[13] == 5 && layout[14] == 5;
    }

    public HrdNode giveBirth(IChecker encoder, Solver.IListener listener) {
        HrdNode answer = null;
        for (int i = 0; i < totalChoices; i++) {
            if (srcArr[i] == preMovedIndex) {
                //和上次移动同一个棋子时不搜索,可提速20%左右
                continue;
            }
            int[] childLayout = HrdUtil.copy(layout);
            analyzer.step(childLayout, srcArr[i], dstArr[i]);
            if (encoder.checkAndModify(childLayout)) {
                //get permission to giveBirth
                HrdNode child = new HrdNode(childLayout, analyzer, isHashLayout);
                child.bmCode = encoder.getCurrentCode();
                child.parent = this;
                child.setGoal(dstCode);
                child.level = this.level + 1;
                child.setPreMovedIndex(dstArr[i]);
                if (listener != null) {
                    listener.validNodeCreated(child);
                    if (child.isGoal()) {
                        answer = child;
                    }
                }
            }
        }
        return answer;
    }

//    public int getValidChildrenNum() {
//        return validChildrenNum;
//    }

    public boolean isHashLayout() {
        return isHashLayout;
    }

    public void setPreMovedIndex(int preMovedIndex) {
        this.preMovedIndex = preMovedIndex;
    }
}
package game.hrd.game.hrd.refactor;

import game.hrd.PmBm;

import java.util.ArrayList;

/**
 * Created by huangcongjie on 2017/12/29.
 */
public class TreeFinder {
    HrdNode[] queue;
    int searchAmount = 0;
    int processingIndex = 0;
    int maxAmount = 500000;
    private int[] flagMap;
    private int[][] layoutMap = new int[500000][20];
    private int[] codeSnMap;    //根据编码找序列号

    private void addToQueue(HrdNode node, int nodeCode, int dcCode) {
        if (searchAmount >= maxAmount) {
            System.out.println("对溢出");
            return;
        }
        queue[searchAmount] = node;
        searchAmount++;
        markSearched(nodeCode);
        markSearched(dcCode);
    }

    //--广度优先--
    private HrdNode find(int[] layout) {
        long t1 = System.currentTimeMillis();
        long cost = 0;
        IAnalyzer analyzer = new HashLayoutAnalyzer();
        HrdNode root = new HrdNode(layout, analyzer, false);
        if (root.isGoal()) {
            root.outable = true;
        }
        IChecker checker = new LayoutToCode(root.layout);
        maxAmount = ((LayoutToCode) checker).codeTotalNum / 10;
        queue = new HrdNode[maxAmount];
        searchAmount = processingIndex = 0;
        HrdNode curNode = root;
        checker.checkAndModify(root.layout);
        addToQueue(root, checker.getCurrentCode(), checker.getSymmetricCode());

        while (processingIndex < searchAmount) {
//            System.out.println("searchAmount: " + searchAmount);
            HrdNode answer = curNode.giveBirth(checker, new Solver.IListener() {
                @Override
                public void validNodeCreated(HrdNode child) {
                    addToQueue(child, checker.getCurrentCode(), checker.getSymmetricCode());
                }
            });
            if (answer != null) {
//                cost = System.currentTimeMillis() - t1;
//                System.out.println("found answer! total steps: " + answer.level + " cost: " + cost + "ms");
//                displaySolution(answer);
                root.outable = true;
            }
            processingIndex++;
            curNode = queue[processingIndex];
        }
        cost = System.currentTimeMillis() - t1;
        root.nodeNum = searchAmount;
        return root;
    }

    private void init(int hCount) {
//        for (int m = 0; m < 6; m++) {
            LayoutEnumerator enumerator = new LayoutEnumerator(hCount);
            int count = 0;
            System.out.print(enumerator.getPrintLayout(enumerator.getLayout(), count));

            int[] first = enumerator.getBin15Layouts()[0];
            HashLayoutEncoder encoder = new HashLayoutEncoder(first);
            int[] JD = new int[encoder.bmTotal];   //节点字典
            codeSnMap = new int[encoder.bmTotal];

            while ((enumerator != null)) {
                int[][] layouts = enumerator.getBin15Layouts();
                for (int i = 0; i < layouts.length; i++) {
                    int code = encoder.encode(layouts[i]);
                    if (JD[code] == 0) {
                        JD[code] = 1;
                        codeSnMap[code] = count;
                        layoutMap[count] = layouts[i];
                    } else {
                        System.out.println("error! detected duplicate layout, count: " + count +
                                ", code: " + code);
                    }
                    count++;
                }
                enumerator = enumerator.next();
            }
            System.out.println(String.format("%d横式共有%d节点", hCount, count));
            flagMap = new int[count];
//        } //for
    }

    public void test(int hCount) {
        init(hCount);
        int solvableCount = 0;
        int playableCount = 0;
        ArrayList<HrdNode> trees = new ArrayList<>();
        int[] curLayout = getUnSearchedLayout();
        while (curLayout != null) {
            HrdNode tree = find(curLayout);
            trees.add(tree);
//            System.out.println("found tree: " + tree.nodeNum + ", remain: " + getUnsearchedCount()
//            + ", solvable: " + tree.outable);
            if (tree.outable) {
                solvableCount++;
                if (tree.nodeNum > 500) {
                    playableCount++;
                }
//                System.out.print(LayoutEnumerator.getPrintLayout2(tree.layout, trees.size()));
//                System.out.println("found tree: " + tree.nodeNum + ", remain: " + getUnsearchedCount());
            }
            curLayout = getUnSearchedLayout();
        }
        System.out.println(String.format("%d横式共有%d颗树, 其中%d个树有解, %d个树可玩性好",
                hCount, trees.size(), solvableCount, playableCount));
    }

    private void markSearched(int code) {
        if (code >= 0) {
            int serialNum = codeSnMap[code];
            flagMap[serialNum] = 1;
        }
    }

    private int[] getUnSearchedLayout() {
        for (int i = 0; i < flagMap.length; i++ ) {
            if (flagMap[i] != 1) {
                return layoutMap[i];
            }
        }
        return null;
    }

    private int getUnsearchedCount() {
        int count = 0;
        for (int i = 0; i < flagMap.length; i++ ) {
            if (flagMap[i] != 1) {
                count++;
            }
        }
        return count;
    }
}
package game.hrd.game.hrd.refactor;

import java.util.ArrayList;

/**
 * Created by huangcongjie on 2017/12/20.
 */
public class Solver {
    HrdNode[] queue;
    int searchAmount = 0;
    int processingIndex = 0;
    int maxAmount = 500000;

    private void addToQueue(HrdNode node) {
        if (searchAmount >= maxAmount) {
            System.out.println("对溢出");
            return;
        }
        queue[searchAmount] = node;
        searchAmount++;
    }

    //--广度优先--
    public int bfs(int[] layout, boolean useHash) {
//        System.out.println("use hash checker: " + useHash);
        long t1 = System.currentTimeMillis();
        long cost = 0;
        IAnalyzer analyzer = new HashLayoutAnalyzer();
        HrdNode root = new HrdNode(layout, analyzer, useHash);
        IChecker checker = useHash ? new LayoutToHash() : new LayoutToCode(root.layout);
        if (useHash) {
            maxAmount = 40 * 1024;
        } else {
            maxAmount = ((LayoutToCode) checker).codeTotalNum / 10;
        }
        queue = new HrdNode[maxAmount];
        HrdNode curNode = root;
        addToQueue(root);

        while (processingIndex < searchAmount) {
//            System.out.println("searchAmount: " + searchAmount);
            HrdNode answer = curNode.giveBirth(checker, new IListener() {
                @Override
                public void validNodeCreated(HrdNode child) {
                    addToQueue(child);
                }
            });
            if (answer != null) {
                cost = System.currentTimeMillis() - t1;
                System.out.println("found answer! total steps: " + answer.level + " cost: " + cost + "ms");
                if (checker instanceof LayoutToHash) {
                    System.out.println("hash conflicts: " + ((LayoutToHash) checker).cht );
                }
//                displaySolution(answer);
                return 1;
            }
            processingIndex++;
            curNode = queue[processingIndex];
        }
        cost = System.currentTimeMillis() - t1;
        System.out.println("already searched all nodes, cannot found answer! cost: " + cost + "ms");
        return 0;
    }

    public int reachable(int[] layout1, int[] layout2) {
        long t1 = System.currentTimeMillis();
        long cost = 0;
        IAnalyzer analyzer = new HashLayoutAnalyzer();
        HrdNode root = new HrdNode(layout1, analyzer, false);
        LayoutToCode checker = new LayoutToCode(root.layout);
        int dstCode = checker.encode(layout2);
        root.setGoal(dstCode);
        maxAmount = checker.codeTotalNum / 10;
        queue = new HrdNode[maxAmount];
        HrdNode curNode = root;
        addToQueue(root);

        while (processingIndex < searchAmount) {
//            System.out.println("searchAmount: " + searchAmount);
            HrdNode answer = curNode.giveBirth(checker, new IListener() {
                @Override
                public void validNodeCreated(HrdNode child) {
                    addToQueue(child);
                }
            });
            if (answer != null) {
                cost = System.currentTimeMillis() - t1;
                System.out.println("found answer! total steps: " + answer.level + " cost: " + cost + "ms");
                displaySolution(answer);
                return 1;
            }
            processingIndex++;
            curNode = queue[processingIndex];
        }
        cost = System.currentTimeMillis() - t1;
        System.out.println("already searched all nodes, cannot found answer! cost: " + cost + "ms");
        return 0;
    }

    public interface IListener {
        void validNodeCreated(HrdNode child);
    }

    private void displaySolution(HrdNode hrdObj) {
        ArrayList<HrdNode> list = new ArrayList<>();
        list.add(hrdObj);
        HrdNode node = hrdObj;
        while (node.parent != null) {
            list.add(node.parent);
            node = node.parent;
        }
        for (int i = list.size() - 1; i >= 0; i--) {
            displayStep(list.get(i));
        }
    }

    private void displayStep(HrdNode hrdObj) {
        System.out.println("第" + hrdObj.level + "步");
        int[] layout = hrdObj.isHashLayout() ? HrdUtil.convertToScreen(hrdObj.layout) :
                hrdObj.layout;
        for (int i = 0; i < layout.length; i += 4) {
            System.out.println(String.format("%d\t%d\t%d\t%d", layout[i], layout[i+1], layout[i+2], layout[i+3]));
        }
        System.out.println();
    }
}

测试代码

public class TestHrdEnumerator {
    public static void main(String args[]) {
//        testBin15Layouts();
//        testPmHx();
//        testEncoder();
//        testLayoutEncoder();

        for (int i = 0; i < 6; i++) {
            TreeFinder treeFinder = new TreeFinder();
            treeFinder.test(i);
        }
//        testReachable();
    }

    public static void testBin15Layouts() {
        int hCount = 1;
        LayoutEnumerator enumerator = new LayoutEnumerator(hCount);
        int count = 0;
        System.out.print(enumerator.getPrintLayout(enumerator.getLayout(), count));

        int[][] layouts = enumerator.getBin15Layouts();
        for (int i = 0; i < layouts.length; i++) {
            count++;
            System.out.print(LayoutEnumerator.getPrintLayout(layouts[i], count));
        }
    }

    public static void testPmHx() {
        int hCount = 1;
        LayoutEnumerator enumerator = new LayoutEnumerator(hCount);
        int count = 0;
        System.out.print(enumerator.getPrintLayout(enumerator.getLayout(), count));

        PmHx hx = new PmHx();
        while ((enumerator != null)) {
            int[][] layouts = enumerator.getBin15Layouts();
            for (int i = 0; i < layouts.length; i++) {
                count++;
                if ( hx.check(layouts[i]) != 1 ) {
                    System.out.println("error! detected duplicate layout, count: " + count);
                }
            }
            enumerator = enumerator.next();
        }
        System.out.println(String.format("%d横式共有%d节点, 发生哈希冲突%d", hCount, count, hx.cht));
    }

    public static void testEncoder() {
//        int[] qp = {
//                6, 15, 15, 7,
//                6, 15, 15, 7,
//                8, 11, 11, 5,
//                8, 3,   4, 5,
//                2, 0,   0, 1
//        };
//        int[] qp = {
//                6, 15, 15, 7,
//                6, 15, 15, 7,
//                3, 11, 11, 5,
//                4, 10, 10, 5,
//                2, 0,   0, 1
//        };
//        int[] qp = {
//                10, 10, 5, 7,
//                11, 11, 5, 7,
//                12, 12, 3, 4,
//                15, 15, 1, 2,
//                15, 15, 0, 0
//        };
        int[] qp = {
                8, 6, 5, 7,
                8, 6, 5, 7,
                1, 2, 3, 9,
                15, 15, 4, 9,
                15, 15, 0, 0
        };
        PmBm pmBm = new PmBm(qp);
//        int code1 = pmBm.Bm(qp);
        int[] qp2 = HrdUtil.convertToHashLayout(qp);
        HashLayoutEncoder encoder = new HashLayoutEncoder(qp2);
//        int code2 = encoder.encode(qp2);
//        System.out.println(code1 + " == " +code2);

        int[] qp3 = {
                5, 5, 3, 3,
                1, 1, 1, 1,
                3, 3, 2, 2,
                1, 1, 2, 3,
                2, 0, 0, 1};
        int[] qp4 = {
                5, 5, 3, 3,
                1, 1, 1, 1,
                3, 2, 3, 2,
                1, 2, 1, 3,
                2, 0, 0, 1};
        int[] n1 = HrdUtil.hashLayoutConvertToNormal(qp3);
        int code4 = pmBm.Bm(n1);
        int code3 = encoder.encode(qp3);
        int code2 = encoder.encode(qp4);
        System.out.println(code3 + " != " +code2 + ", " + code4 + " == " + code3);
    }

    public static void testLayoutEncoder() {
        for (int m = 0; m < 6; m++) {
            int hCount = m;
            LayoutEnumerator enumerator = new LayoutEnumerator(hCount);
            int count = 0;
            System.out.print(enumerator.getPrintLayout(enumerator.getLayout(), count));

            int[] first = enumerator.getBin15Layouts()[0];
            HashLayoutEncoder encoder = new HashLayoutEncoder(first);
            PmBm pmBm = new PmBm(HrdUtil.hashLayoutConvertToNormal(first));
            int[] JD = new int[encoder.bmTotal];   //节点字典

            while ((enumerator != null)) {
                int[][] layouts = enumerator.getBin15Layouts();
                for (int i = 0; i < layouts.length; i++) {
                    count++;
                    int code = encoder.encode(layouts[i]);
//                int[] normalLayout = HrdUtil.hashLayoutConvertToNormal(layouts[i]);
//                int code = pmBm.Bm(normalLayout);
                    if (JD[code] == 0) {
                        JD[code] = 1;
                    } else {
                        System.out.println("error! detected duplicate layout, count: " + count +
                                ", code: " + code);
                    }
                }
                enumerator = enumerator.next();
            }
            System.out.println(String.format("%d横式共有%d节点", hCount, count));
        }
    }

    static public String toLayoutString(int[] layout2) {
        String layout = "";
        for (int i = 0; i < 20; i++) {
            layout += layout2[i];
            if (i % 4 == 3) {
                layout += ",|";
            } else {
                layout += ", ";
            }
        }
        layout = layout.substring(0, layout.length() - 2);
        return layout;
    }

    static public void testReachable() {
        int[] qp1 = {
                5, 5, 4, 4,
                1, 1, 3, 3,
                3, 3, 1, 1,
                1, 1, 2, 2,
                2, 2, 0, 0
        };
        int[] qp2 = {
                5, 5, 3, 3,
                1, 1, 1, 1,
                4, 4, 3, 3,
                2, 2, 1, 1,
                2, 2, 0, 0
        };
        Solver solver = new Solver();
        solver.reachable(qp1, qp2);
    }
}

最终结果,

5   5   3   3   
1   1   1   1   
3   3   3   0   
1   1   1   0   
0   0   0   0   
---------0-----------

0横式共有15660节点
0横式共有80颗树, 其中1个树有解, 1个树可玩性好
5   5   4   4   
1   1   3   3   
3   3   1   1   
1   1   0   0   
0   0   0   0   
---------0-----------

1横式共有65880节点
1横式共有898颗树, 其中52个树有解, 2个树可玩性好
5   5   4   4   
1   1   4   4   
3   3   3   0   
1   1   1   0   
0   0   0   0   
---------0-----------

2横式共有109260节点
2横式共有2653颗树, 其中230个树有解, 1个树可玩性好
5   5   4   4   
1   1   4   4   
4   4   3   3   
0   0   1   1   
0   0   0   0   
---------0-----------

3横式共有106800节点
3横式共有2609颗树, 其中146个树有解, 1个树可玩性好
5   5   4   4   
1   1   4   4   
4   4   4   4   
3   0   0   0   
1   0   0   0   
---------0-----------

4横式共有51660节点
4横式共有1729颗树, 其中107个树有解, 1个树可玩性好
5   5   4   4   
1   1   4   4   
4   4   4   4   
4   4   0   0   
0   0   0   0   
---------0-----------

5横式共有14220节点
5横式共有505颗树, 其中19个树有解, 1个树可玩性好

“可玩性好”表示树的节点不能少于500个,不然就太简单了

猜你喜欢

转载自blog.csdn.net/mu399/article/details/78943829