华容道自动求解 java版

本文介绍了一种高效的华容道自动求解方法,利用Java实现了盘面编码、解码和广度优先搜索算法,详细解释了如何通过组合数学原理减少搜索空间。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

以前自学程序设计时, 研究过华容道的自动求解,已经是几年前的事了。

当时找到一个高人写的程序,效率非常高,但是,是C语言的代码,代码可读性不好,以前弄明白过这个程序,现在又忘记了,故而这次把C语言的代码改成java版的,有重新理解一遍,并记下来,以后不怕在忘了。

C 和 javascript版的代码

http://www.fjptsz.com/xxjs/xjw/rj/110.htm

对以上代码,我着重理解了盘面是如何编码的,还有原作者如何用数组实现广度优先搜索,如何找到最终答案的。

package com.global;

public class LocalConst {
	//用1-15表示各棋子,空位用0表示,兵1-4,竖将5-9,横将10-14,大王15
	//大王只能1个,将必须5个(横竖合计),兵必须为4个
//	static final public String U = "ABBBBCCCCCHHHHHM";
	static final public int[] U = {'A','B','B','B','B','C','C','C','C','C','H','H','H','H','H','M'};
	static final public int[] COL = {0,1,2,3,0,1,2,3,0,1,2,3,0,1,2,3,0,1,2,3};   //列号表
	static final public int[] ROW = {0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4};   //行号表
	static final public int[] WQ2 = {1,2,4,8,16,32,64,128,256,512,1024,2048,4096}; //二进制位权表(12个)
	
	//使用128k(17位)哈希表,如果改用更大的表,相应的哈希计算位数也要改
	static final public int hsize = 128 * 1024;
}

盘面是如何编码?

盘面的状态(节点)数量是十分有限的,状态总数不会超过50万种。(横刀立马为例)

曹操的走法只有12种,任你如何排放,只有12种,不是20种。

横将(关羽)的排法最多只有11种

接下来对4个竖将排列(组合),排列第一个竖将的排法最多10种,第二个8种,第三个6种,第四个4种。组合数是10*8*6*4/4!=80,后来为来编程方便,做了更多冗于,组合数用C10取4,即C(10,4)=10*9*8*7/4!=210,这样,4个竖将的某一排列组合必对应0—209中的一个数,这个数就是我们所要的竖将组合编码值。

同理小兵的组合为C(6,4)=15,编码范围在0—14

因此对这4种(10个)棋子全排列,种数最多为12*11*210*15=415800,即4百多K。

最后易得盘面编码:各种棋子的编码值乘以码权,然后取和。

码权只需遵照排列规律,随你定,是比较简单的。可设兵的码权为1,竖将则是15,横将则为15*210,曹操为15*210*11。

要如何对各种棋子高速有效的编码呢?如“横刀立马”开局,如何编码?

这又变成一个组合问题。

我们一个一个的排放“横刀立马”棋子并演示编码过程。

曹操有12个可排放位置,这12个位置编号为0-11,曹操位置在1,注意,首个是0。

关羽有11个可排放位置,这11个位置编号为0-10,关羽位置在1个。

竖将有10个可排放的位置,编号为0-9,一将是0,二将是1,三将是4,四将是5。

小兵有6个可排放的位置,编号为0-5,一兵是0,二兵是1,三兵是2,四兵是5。


组合序号表是什么意思?

比如小兵只有4个, 可以占用6个位置,前面的14个位置已经被大王,横将,竖将占了

小兵的组合序号表Bz的内容为 001111 (序号为0), 011101 (序号为1), 011011 (序号为2),010111 (序号为3), ...

在Bz[i]=Bx++; 处设置断点,有利于理解程序。

组合序号表就是对各种组合进行一个编号。

比如C4 取2的组合,其组合序号表为

0:  0011

1:  0101

2:  0110

3:  1001

4:  1010

5:  1100

共有六个元素,实际上,把下面代码的变量 Hz, Sz, Bz用字典来存储,代码可读性更强。

PmBm构造函数的那个二重循环,容易让人看得不知所云,实际上就是用来初始化组合序号表的,

特别的,当横将或在竖将只有一个时,完全无必要用组合序号表这种字典,组合序号表的作用就是把多于一个棋子位置摆放仅用一个数字来描述,

如果不用组合序号表,描述四个兵的位置,就需要更多的比特,而现在只需要六个比特,比一个字符占的空间(8比特)还要小。


PmBm构造函数出现很多变量,多次让我懵逼,为什么会多次呢?因为编码过程本身有些复杂,加上代码命名方式不友好。

比如
Hz = new short[4096 * 3];
Sz = new short[4096];
Bz = new short[128];
//C12取5=792
Hw = new int[792*2];
Sw = new int[792];

为什么,五个数组初始化这么大,其实不用纠结,Bz的大小其实 2 的 6 次方, 64 就够了,
Hz 的大小应该是 2 的 11 次方,关羽这种横将 在 大王之后摆放,只有11种位置选择,
Sz 的大小应该是 2 的 10 次方,  每个竖将最多有10个位置选择

以“横刀立马”为例子
Bx = 15 就是6 中 取 4 的组合数

Sw = [0, 15, 30, ...] length = 210, 就是10 中 取 4 的组合数

Hmax = 2048, Smax = 1024,

Bx = 15 ,  Sx = 210, 10个中取4个的组合数, Hq = 15 * 210, Mq = 15 * 210 * 11
Bx四个兵的组合数目, Sx竖将的组合数目,Hx横将的组合数目

Mw, Hw, Sw这三个数组,用查表来替代乘法

package com.gamedata;

import com.global.LocalConst;

//盘面编码类
public class PmBm {
	
	//组合序号表
	short[] Hz;  	//横将
	short[] Sz;		//竖将
	short[] Bz;		//小兵
	
	//权值表
	int[] Hw;
	int[] Sw;
	int[] Mw;
	
	public int bmTotal;
	
	public PmBm(int[] qiPan) {
		Hz = new short[4096 * 3];
		Sz = new short[4096];
		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(LocalConst.U[qiPan[i]]=='B') Bn++;
			if(LocalConst.U[qiPan[i]]=='H') Hn++;
			if(LocalConst.U[qiPan[i]]=='C') Sn++;
		}
		Hn /= 2;
		Sn /= 2;
		int[] WQ2 = LocalConst.WQ2;
		int Hmax=WQ2[11],Smax=WQ2[12-Hn*2],Bmax=WQ2[16-(Hn+Sn)*2]; //各种子的最大二进位数
		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 Bm(int[] qiPan) {
		int Bb=0,Bd=-1; //空位序号记录器
		int Sb=0,Sd=-1; //竖条序号记录器
		int Hb=0,Hd=-1; //横条序号记录器
		int Mb = 0;         //大王序号记录器
		int c,lx;
		int[] f = new int[16];
		
		for(int i = 0; i < 20; i++){
			c=qiPan[i];
			lx = LocalConst.U[c]; //当前的值
			if(lx=='M') { //大王定序
		       if(f[c] == 0) {
		    	   Mb = i - LocalConst.ROW[i];
		    	   f[c] = 1;
		       }
		       continue;
		     }
		     if (LocalConst.COL[i]<3 && LocalConst.U[qiPan[i+1]] <= 'H') {
		    	 Hd++; //横条位置序号(编号)
		     }
		     if (lx == 'H') {//横将定序,转为二进制进行询址得Hb
		       if(f[c] == 0) {
		    	   Hb += LocalConst.WQ2[Hd];
		    	   f[c]=1;
		       }
		       continue;
		     }
		     if (LocalConst.ROW[i]<4 && LocalConst.U[qiPan[i+4]]<='C') {
		    	 Sd++; //竖将位置序号(编号)
		     }
		     if (lx=='C') { //竖条定序,转为二进制进行询址得Sb
		       if(f[c] == 0) {
		    	   Sb += LocalConst.WQ2[Sd];
		    	   f[c]=1;
		       }
		       continue;
		     }
		     if(lx<='B') Bd++;  //小兵位置序号(编号)
		     if(lx=='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 dcBM(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 Bm(q2);
	}
}


PMZB 这个类是负责盘面走步的,非常简单,就是一些判断,容易理解

package com.gamelogic;

import com.global.LocalConst;

public class PMZB {
	//原位置,目标位置,最多只会有10步
	int[] src = new int[10];
	int[] dst = new int[10];
	//总步数
	int n;
	
	private void zbIn(PMZB zb, int s, int d) {
		zb.src[n] = s;
		zb.dst[n] = d;
		zb.n++;
	}

	public void analyze(int[] qiPan, PMZB zb) {
		int i,col,k1=0,k2=0,h=0; //i,列,空格1位置,空格2位置,h为两空格的联合类型
		int c,lx;
		//f[]记录已判断过的棋字
		int[] f = new int[16];
		zb.n=0; //计步复位
		for(i=0; i<20; i++){
			if(qiPan[i] == 0) {
				k1=k2;
				k2=i; //查空格的位置
			}
		}
		if (k1 + 4 == k2) {
			h = 1;	//空格竖联合
		}
		if (k1 + 1 == k2 && LocalConst.COL[k1] < 3) {
			h = 2;	//空格横联合
		}
		for (i = 0; i < 20; i++) {
			c = qiPan[i];
			lx = LocalConst.U[c];
			col = LocalConst.COL[i];
			if (f[c] == 1) {
				continue;
			}
			switch (lx) {
			case 'M'://曹操可能的走步
				if (i + 8 == k1 && h == 2) {
					//向下
					zbIn(zb, i, i + 4);
				}
				if (i - 4 == k1 && h == 2) {
					//向上
					zbIn(zb, i, i - 4);
				}
				if (col < 2 && i + 2 == k1 && h == 1) {
					//向右
					zbIn(zb, i, i + 1);
				}
				if (col > 0 && i - 1 == k1 && h == 1) {
					//向左
					zbIn(zb, i, i - 1);
				}
				f[c] = 1;
				break;
			case 'H':
				//关羽可能的走步
				if (i + 4 == k1 && h == 2) {
					zbIn(zb, i, i+4);
				}
				if (i - 4 == k1 && h == 2) {
					zbIn(zb, i, i - 4);
				}
				if (col < 2 && (i + 2 == k1 || i + 2 == k2)) {
					zbIn(zb, i, i + 1);
					if (h == 2) {
						zbIn(zb, i, k1);
					}
				}
				if (col > 0 && (i - 1 == k1 || i - 1 == k2)) {
					zbIn(zb, i, i - 1);
					if (h == 2) {
						zbIn(zb, i, k1);
					}
				}
				f[c] = 1;
				break;
			case 'C':
				//张飞,马超,赵云,黄忠可能的走步
				if (i + 8 == k1 || i + 8 == k2) {
					zbIn(zb, i, i + 4);
					if (h == 1) {
						zbIn(zb, i, k1);
					}
				}
				if (i - 4 == k1 || i - 4 == k2) {
					zbIn(zb, i, i - 4);
					if (h == 1) {
						zbIn(zb, i, k1);
					}
				}
				if (col < 3 && i + 1 == k1 && h == 1) {
					zbIn(zb, i, i+1);
				}
				if (col > 0 && i - 1 == k1 && h == 1) {
					zbIn(zb, i, i-1);
				}
				f[c] = 1;
				break;
			case 'B':
				if (i + 4 == k1 || i + 4 == k2) {
					if (h > 0) {
						zbIn(zb, i, k1);
						zbIn(zb, i, k2);
					} else {
						zbIn(zb, i, i + 4);
					}
				}
				if (i - 4 == k1 || i - 4 == k2) {
					if (h > 0) {
						zbIn(zb, i, k1);
						zbIn(zb, i, k2);
					} else {
						zbIn(zb, i, i - 4);
					}
				}
				if (col < 3 && (i + 1 == k1 || i + 1 == k2)) {
					if (h > 0) {
						zbIn(zb, i, k1);
						zbIn(zb, i, k2);
					} else {
						zbIn(zb, i, i + 1);
					}
				}
				if (col > 0 && (i - 1 == k1 || i - 1 == k2)) {
					if (h > 0) {
						zbIn(zb, i, k1);
						zbIn(zb, i, k2);
					} else {
						zbIn(zb, i, i - 1);
					}
				}
				break;
			}
		}
	}
	
	//走一步函数
	void zb(int[] qiPan, int src, int dst) {
		int c = qiPan[src];
		int lx = LocalConst.U[c];
		switch(lx) {
		case 'B':
			qiPan[src] = 0;
			qiPan[dst] = c;
			break;
		case 'C':
			qiPan[src] = qiPan[src+4] = 0;
			qiPan[dst] = qiPan[dst+4] = c;
			break;
		case 'H':
			qiPan[src] = qiPan[src+1]=0;
			qiPan[dst] = qiPan[dst+1]=c;
			break;
		case 'M':
			qiPan[src] = qiPan[src+1]= qiPan[src+4]=qiPan[src+5]=0;
			qiPan[dst] = qiPan[dst+1]= qiPan[dst+4]=qiPan[dst+5]=c;
			break;
		}
	}
}


ZBD,走步队,这个类不太容易理解

变量n 表示当前队的长度

变量m 表示当前入队的布局,它的父亲布局在队列z 中的索引

比如,我们有一个初始布局 0, 布局0 通过PMZB类的分析, 有 3个儿子节点,分别命名为a1, a2, a3

首先把初始布局 0 放入队列z 中,这时m=0, 把m放入数组 hs 中, 接着把儿子 a1, a2, a3 都放入 队列z中,这时

z = [0, a1, a2, a3], hs = [0, 0, 0, 0], 然后m = m + 1;  假设 a1 有儿子节点b1, b2,  a2 有儿子 c1, a3 有儿子d1, d2, 

b1有儿子 e1, e2, b2 有儿子 f1, f2, f3,  c1有儿子 g1, g2, 

如下:


如果搞清了队列z, 数组hs, n, m的变化过程,就理解了代码

最终

00123456789101112131415
z=0a1a2a3b1b2c1d1d2e1e2f1f2f3g1g2
hs=0000112334455566


如何知道g1节点是怎么来的,通过hs 知道g2节点是 队列z的第6个节点的儿子,也就是c1, 又c1的父亲是第2个节点 a2, a2的父亲是初始布局 0


ZBD.bfs() 中的变量 i 为何能代表层数,以上图为例,

假设,z.z队列中为root, a1, a2, a3, b1, b2, c1,d1
 k=4, z.m=3, 执行到代码第42行后,当前布局为d2, 
m值为4,等于k,跳出while循环,执行代码第96行,z.zbrd(),将d2加入z.z队列,这时n从7变为8,
k = 8,这里,队列的第8个节点就是该层的最后一个节点,然后执行z.zbcd(),
执行到代码第50行后,当前布局从d2变为b1.

下列代码的第50行有两种作用,
1. 还原当前布局为其父布局
2. 刚刚换层时,i刚刚变化时,把当前层的第一个布局赋值给当前布局

z.n表示队列中有 n 个布局(节点)
其中z.m之前节点都是已经处理过的,z.m之后节点为待处理的,
索引为z.m的节点,表示正在处理,怎么处理? 首先对其进行走法分析,然后对其产生的所有子布局
进行编码或者求其hash值,如果已经在全局字典中则丢弃,否则放入z.z队列,并放入全局字典


package com.gamelogic;

import com.gamedata.PmBm;
import com.gamedata.PmHx;

public class ZBD {

	public int[][] z;	//队列,变量命应该为queue
	PMZB zbj;
	int n;       //队长度
	int[] hs;//回溯用的指针及棋子
	int[] hss;
	int m,cur;   //队头及队头内步集游标,用于广度搜索
	int max;     //最大队长
	int[] res;//结果
	int ren;
	 
	private void reset() {
		n=0;
		m=0;
		cur=-1;
		hss[0]=-1;
		ren=0;
	}
	
	public ZBD(int k) {
		zbj = new PMZB();
		z = new int[k][20];
		hs = new int[k*2 + 500];
		hss = new int[k];
		res = new int[k];
		max = k;
		reset();
	}
	//走步出队
	int zbcd(int[] qiPan) {
		if (cur == -1) {
			zbj.analyze(z[m], zbj);
		}
		cur++;
		if (cur >= zbj.n) {
			m++;
			cur = -1;
			return 1;
		}
		if (hss[m] == zbj.src[cur]) {
			//和上次移动同一个棋子时不搜索,可提速20%左右
			return 1;
		}
		qpcpy(qiPan, z[m]);  //还原当前布局为其父布局 或者 刚刚换层时,i刚刚变化时,把当前层的第一个布局赋值给当前布局
		zbj.zb(qiPan, zbj.src[cur], zbj.dst[cur]); //改变当前布局
		return 0;
	}
	
	//走步入队
	void zbrd(int[] qiPan) {
		 if (n >= max) {
			 System.out.println("对溢出");
			 return;
		 }
		 qpcpy(z[n], qiPan);	//入队
		 if (cur >= 0) {
			 hss[n] = zbj.dst[cur];//记录移动的子(用于回溯)
		 }
		 hs[n++] = m;//记录回溯点
	}
	
	//参数:层数
	void hui(int cs) {
		int k = cs - 2;
		ren = cs;
		res[cs - 1] = m;
		for (; k >=0; k--) {
			res[k] = hs[res[k+1]]; //回溯
		}
	}
	
	//取第n步盘面
	int[] getre(int n) {
		return z[res[n]];
	}
	private static void qpcpy(int[] q1, int[] q2) {
		for (int i = 0; i < q1.length; i++) {
			q1[i] = q2[i];
		}
	}
	
	//--广度优先--
	public static int bfs(int[] qiPan, int dep) {
		int i,j,k,bm,v;
		int js = 0;
		int js2 = 0;
		PmBm coder = new PmBm(qiPan);
		int[] JD = new int[coder.bmTotal];//建立节点数组
		ZBD z = new ZBD(coder.bmTotal / 10);
		for (z.zbrd(qiPan), i=1; i <= dep; i++) {
			k = z.n;
//多次执行z.zbcd() 后会产生当前布局的若干个儿子布局,对于每个儿子布局,如果子布局之前从来没有出现过,则其编码放入全局字典,
//并且入队(获得了繁衍后代的资格),否则丢弃子布局
			while (z.m < k) {
				if (z.zbcd(qiPan) == 1) {
					continue;//返回1说明是步集出队,不是步出队
				}
				js++;
				if (qiPan[17] == 15 && qiPan[18] == 15) {
					//大王出来了
					z.hui(i);
					for (int h=0; h<z.ren; h++) {
						prt(z.getre(h));	//输出结果
					}
					prt(qiPan);
					return 1;
				}
				if (i == dep) {
					//到了最后一层可以不再入队了
					continue;
				}
				bm = coder.Bm(qiPan);
				if (JD[bm] == 0) {
					js2++;	//js搜索总次数计数和js2遍历的实结点个数
					JD[bm] = 1;
					JD[coder.dcBM(qiPan)] = 1;
					z.zbrd(qiPan);
				}
			}
		}
		return 0;
	}
	//打印棋盘
	static private void prt(int[] qipan) {
		for (int i = 0; i < 5; i++) {
			for (int j = 0; j < 4; j++) {
				System.out.print(qipan[i*4+j] + "\t");
			}
			System.out.println();
		}
		System.out.println();
	}
	
	public static void test() {
		int[] qp = {
			6, 15, 15, 7,
			6, 15, 15, 7,
			8, 11, 11, 5,
			8, 3, 4, 5,
			2, 0, 0, 1
		};
		int ret = bfs(qp, 200);
		if (ret == 1) {
			
		}
	}
	
	public static int bfs_hx(int[] qiPan, int dep) {
		int all = 0;
		if (dep > 500 || dep <= 0) {
			dep = 200;
		}
		int[] q = new int[20];
		qpcpy(q, qiPan);
		int i,k;
		int js = 0;
		int js2 = 0;
		PmHx hx = new PmHx();
		ZBD worker = new ZBD(50000);
		for (worker.zbrd(q), i=1; i<=dep; i++) {
			//一层一层的搜索
			k = worker.n;
			if (worker.m == k) {
				return -1;
			}
			while (worker.m < k) {
				//广度优先
				if (worker.zbcd(q) == 1) {
					continue;     //返回1说明是步集出队,不是步出队
				}
				js ++; //遍历总次数计数
				if (q[17] == 5 && q[18] == 5) {
					//大王出来了
					worker.hui(i);
					for (int h = 0; h < worker.ren; h++) {
						prt(worker.getre(h));	//输出结果
					}
					prt(q);
					return 1;
				}
				
				if (i < dep && hx.check(q) == 1) {
					//js2遍历的实结点个数,js2不包括起始点,出队时阻止了回头步,始节点不可能再遍历
					js2++;  
					//对称节点做哈希处理
					hx.check2(q);
					worker.zbrd(q);
				}
			}
		}
		return 0;
	}
	
}


盘面编码,解码,以及测试代码

package game.hrd;

//盘面编码类
public class PmBm {

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

    //根据序号 找 组合数, 用于解码
    int[] rHz = new int[792];
    int[] rSz = new int[792]; //C12取5=792
    int[] rBz = new int[16];

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

    int Sq; //竖将位权,
    int Hq; //横将位权,
    int Mq; //王位权
    int Hn=0, Bn=0, Sn=0; //各类子数目,大王默认为1不用计数

    public int bmTotal;

    public PmBm(int[] qiPan) {
        Hz = new short[4096 * 3];
        Sz = new short[4096];
        Bz = new short[128];
        //C12取5=792
        Hw = new int[792*2];
        Sw = new int[792];
        int i,j,k;

        for(i=0;i<20;i++){   //计算各种棋子的个数
            if(LocalConst.U[qiPan[i]]=='B') Bn++;
            if(LocalConst.U[qiPan[i]]=='H') Hn++;
            if(LocalConst.U[qiPan[i]]=='C') Sn++;
        }
        Hn /= 2;
        Sn /= 2;
        int[] WQ2 = LocalConst.WQ2;
        int Hmax=WQ2[11],Smax=WQ2[12-Hn*2],Bmax=WQ2[16-(Hn+Sn)*2]; //各种子的最大二进位数
        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;
                rHz[Hx] = i;
                Hx++;
            }
            if(k==Sn && i<Smax) {
                Sz[i]=Sx;
                rSz[Sx] = i;
                Sx++;
            }
            if(k==Bn && i<Bmax) {
                Bz[i]=Bx;
                rBz[Bx] = i;
                Bx++;
            }
        }
        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 Bm(int[] qiPan) {
        int Bb=0,Bd=-1; //空位序号记录器
        int Sb=0,Sd=-1; //竖条序号记录器
        int Hb=0,Hd=-1; //横条序号记录器
        int Mb = 0;         //大王序号记录器
        int c,lx;
        int[] f = new int[16];

        for(int i = 0; i < 20; i++){
            c=qiPan[i];
            lx = LocalConst.U[c]; //当前的值
            if(lx=='M') { //大王定序
                if(f[c] == 0) {
                    Mb = i - LocalConst.ROW[i];
                    f[c] = 1;
                }
                continue;
            }
            if (LocalConst.COL[i]<3 && LocalConst.U[qiPan[i+1]] <= 'H') {
                Hd++; //横条位置序号(编号)
            }
            if (lx == 'H') {//横将定序,转为二进制进行询址得Hb
                if(f[c] == 0) {
                    Hb += LocalConst.WQ2[Hd];
                    f[c]=1;
                }
                continue;
            }
            if (LocalConst.ROW[i]<4 && LocalConst.U[qiPan[i+4]]<='C') {
                Sd++; //竖将位置序号(编号)
            }
            if (lx=='C') { //竖条定序,转为二进制进行询址得Sb
                if(f[c] == 0) {
                    Sb += LocalConst.WQ2[Sd];
                    f[c]=1;
                }
                continue;
            }
            if(lx<='B') Bd++;  //小兵位置序号(编号)
            if(lx=='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 dcBM(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 Bm(q2);
    }

    public int[] decode(int code) {
        int mb = code / Mq;
        int left = code % Mq;
        int hbCombo = rHz[left / Hq];
        left = left % Hq;
        int sbCombo = rSz[left / Sq];
        left = left % Sq;
        int bCombo = rBz[left];
        int[] ret = new int[20];
        ret[mb] = ret[mb + 1] = 5;
        ret[mb + 4] = ret[mb + 5] = 1;
        int[] WQ = LocalConst.WQ2;

        int[] hArr = new int[Hn];
        int a = 0;
        for (int i = 0; i < WQ.length; i++) {
            if ( (hbCombo & WQ[i]) > 0) {
                int count = 0;
                for (int j = 0; j < ret.length; j++) {
                    if (LocalConst.COL[j] < 3 &&
                            ret[j] == 0 && ret[j+1] == 0) {
                        if (count == i) {
                            hArr[a] = j;
                            a++;
                            break;
                        }
                        count++;
                    }
                }
            }
        }
        for (int i = 0; i < hArr.length; i++) {
            int loc = hArr[i];
            ret[loc] = ret[loc+1] = 4;
        }

        int[] sArr = new int[Sn];
        a = 0;
        for (int i = 0; i < WQ.length; i++) {
            if ( (sbCombo & WQ[i]) > 0) {
                int count = 0;
                for (int j = 0; j < ret.length; j++) {
                    if (LocalConst.ROW[j] < 4 &&
                            ret[j] == 0 && ret[j+4] == 0) {
                        if (count == i) {
                            sArr[a] = j;
                            a++;
                            break;
                        }
                        count++;
                    }
                }
            }
        }
        for (int i = 0; i < sArr.length; i++) {
            int loc = sArr[i];
            ret[loc] = 3;
            ret[loc+4] = 1;
        }

        int[] bArr = new int[Bn];
        a=0;
        for (int i = 0; i < WQ.length; i++) {
            if ( (bCombo & WQ[i]) > 0) {
                int count = 0;
                for (int j = 0; j < ret.length; j++) {
                    if (ret[j] == 0) {
                        if (count == i) {
                            bArr[a]=j;
                            a++;
                            break;
                        }
                        count++;
                    }
                }
            }
        }
        for (int i = 0; i < bArr.length; i++) {
            int loc = bArr[i];
            ret[loc] = 2;
        }

        return ret;
    }
}

测试代码

import game.hrd.LocalConst;
import game.hrd.PmBm;
import game.hrd.ZBD;
import game.hrd.game.hrd.refactor.HrdUtil;
import game.hrd.game.hrd.refactor.LayoutEnumerator;
import game.hrd.game.hrd.refactor.LayoutToCode;
import game.hrd.game.hrd.refactor.Solver;

/**
 * Created by huangcongjie on 2017/12/17.
 */
public class HuaRongDao {
    public static void main(String args[]) {
        int[] qp = {
                6, 15, 15, 4,
                6, 15, 15, 3,
                8, 11, 11, 5,
                8, 12, 12, 5,
                2, 0,   0, 1
        };
        PmBm coder = new PmBm(qp);
        int[] qp2 = {
                6, 15, 15, 5,
                6, 15, 15, 5,
                8, 11, 11, 0,
                8, 3,   4, 2,
                12, 12, 1, 0
        };
        int code = coder.Bm(qp2);
        int[] decoded = coder.decode(code);
        printLayout(decoded);
    }

    static public void printLayout(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);
        System.out.print(layoutStr);
    }
}



                      ┌────┐
                      │root│
                      └────┘
         ┌──────────────┼──────────┐
         a1             a2         a3
    ┌────┴────┐         │       ┌──┴──┐
   b1         b2        c1      d1    d2
┌──┴──┐   ┌───┼───┐  ┌──┴──┐ 
e1    e2 f1  f2  f3  g1    g2


                                      ┌────┐
                                      │root│
                                      └────┘
               ┌────────────────────────┼────────────┐
              ┌──┐                     ┌──┐         ┌──┐
              │a1│                     │a2│         │a3│
              └──┘                     └──┘         └──┘
      ┌────────┴───────┐                │         ┌──┴──┐
    ┌──┐              ┌──┐             ┌──┐      ┌──┐  ┌──┐ 
    │b1│              │b2│             │c1│      │d1│  │d2│
    └──┘              └──┘             └──┘      └──┘  └──┘
  ┌──┴───┐     ┌───────┼──────┐      ┌──┴──┐ 
┌──┐   ┌──┐   ┌──┐   ┌──┐   ┌──┐   ┌──┐   ┌──┐
│e1│   │e2│   │f1│   │f2│   │f3│   │g1│   │g2│
└──┘   └──┘   └──┘   └──┘   └──┘   └──┘   └──┘


<!DOCTYPE html>
<html>
<head>
    <title>jQuery UI</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    
    <!--    jQuery UI font sizes are relative to document's,
            so set a base size here. -->
    <style type="text/css">
        body {
            font-size:12px;
            font-family:sans-serif
        }
    </style>
    
    <!-- Load the jQuery UI style sheet. -->
    <!-- href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.11/themes/start/jquery-ui.css" -->
    <link rel="stylesheet"
        href="https://code.jquery.com/ui/1.12.1/themes/start/jquery-ui.css"
        type="text/css" media="all" />
    
    <!-- Load jQuery. -->
    <script src="https://code.jquery.com/jquery-1.12.4.js"
        type="text/javascript"></script> 
    
    <!-- Load jQuery UI. -->
    <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"
        type="text/javascript"></script> 
    
   
    <script>
        // On DOM loaded, initialize a date picker widget on the input element
        // with id of 'datepicker'.
        $(function() {
            $( "#datepicker" ).datepicker();
        });
	</script>
        
</head>
<body>
    <!-- The following input element will be turned into a date picker -->
    <p>Enter Date: <input type="text" id="datepicker"></p>
</body>
</html>



评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值