这次我又给自己挖了一个更大的坑,已经确定华容道一共可分为六个领域,或者叫森林。分别为0到5 横式,比如0横式,表示布局为一个曹操,0个横将,5个竖将,4个兵; 2横式,表示布局为一个曹操,2个横将,3个竖将,4个兵;
六个森林,一定是没有任何关系的,独立的,因为对于滑块类游戏的规则,横将不可能变化为竖将,只有位置可以变化。
1) 现在我想找出每个森林共有多少个布局?
2) 每个森林又有多少颗相互独立的树?
相互独立的树是什么?假设一横式的森林有树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个兵的组合序号表了,直接拿来用。
这个枚举规则,保证两个空格总是逐渐地 从高位移动到低位,
而相应地,大王,横将竖将总是逐渐地 从低位移动到高位。
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 && iif(k==Sn && iif(k==Bn && iint 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//初始化横将权值表
for(i=0;i//初始化竖将权值表
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 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 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个,不然就太简单了。
对于任意一颗华容道的树,如何找到一种节点布局,使得以此节点为根,生成的树,层数最少?
如何找到另有一种节点,使得以此节点为根,生成的树,层数最多?
这两个问题是新的坑,太闲了再来研究,
联系邮箱: [email protected]