這次我又給自己挖了一個更大的坑,已經確定華容道一共可分爲六個領域,或者叫森林。分別爲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 && 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
== 0) {
JD= 1;
codeSnMap= 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;
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
== 0) {
JD= 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]