十一、 Layout的设计
Layout在我的程序中充当"布局",也就是说记录当前棋盘的状态。棋盘状态其实无外乎就是10个棋子的位置以及2个空格的位置而已。所以在Layout类中包含了两个成员:
{
private Chessman[] _chessmen = new Chessman[10];
private BlankPosition _blankPosition;
…………
}
同时,Layout应当具有将自己转换为4字节整数的功能。所以提供了ToInt函数。关于转换的方式,可以参考《华容道与数据结构 (1)》中的内容。我这里需要说的是如何将Layout转换为整数。
由于Layout中的每个棋子都有其坐标值,例如如下布局:
为了正确对棋子编号,我们需要一个排序操作,而这个排序的依据就是先按纵坐标排,纵坐标相同的按横坐标排。排序后就可以根据每个棋子的类型生成对应的整数。就上面这个例子来说,我们可以得到如下表:
棋子 |
类型 |
Y |
X |
Y×10+X |
顺序号 |
赵云 | VChessman | 0 | 0 | 0 | 1 |
曹操 | General | 0 | 1 | 1 | A |
张飞 | VChessman | 0 | 3 | 3 | 2 |
马超 | VChessman | 2 | 0 | 20 | 3 |
黄忠 | VChessman | 2 | 3 | 23 | 6 |
关羽 | HChessman | 4 | 1 | 41 | 10 |
卒1 | Solider | 2 | 1 | 21 | 4 |
卒2 | Solider | 2 | 2 | 22 | 5 |
卒3 | Solider | 3 | 1 | 31 | 7 |
卒4 | Solider | 3 | 2 | 32 | 8 |
空格1 | Blank | 4 | 0 | 40 | 9 |
空格2 | Blank | 4 | 3 | 43 | 11 |
按照Y×10+X公式排序后得到的顺序号正好是我们棋子的编号。剩下的事情就是将这些棋子串起来,形成一个整数值。在这里我们使用左移运算符。前面的设计中用二进制的00表示空格,01表示卒,10表示竖放,11表示横放,这与ChessmanTypeEnum中各棋子的整数值正好相对应。所以我们可以如此完成串接过程(仅仅以8位二进制位来演示):
a = 2; //二进制的10,竖放棋子
b = 3; //二进制的11,横放棋子
c = 1; //二进制的01,卒
result = a; //二进制的00000010
result = result << 2; //左移两位00001000
result += b; //得到结果00001011
result = result << 2; //左移两位00101100
result += c; //得到结果00101101
其中左移运算给新棋子的加入腾出位置。具体Layout中的实现可以参考代码中的ToInt方法,这里就不再多说。
Layout中另外一处需要注意的问题就是在类的定义中有这样的两个数组:
private Chessman[] sortedChessmen = new Chessman[12];
其中的sortedChessmen起到什么作用?我们知道数组是引用型的变量,为了兼顾排序求Int以及Layout的复制操作,我们需要两个数组,一个是不排序的,而另外一个是排序的,但它们都指向同一内存中的对象。这样在求整数以及追踪第几个棋子被移动的过程中,我们使用sortedChessmen,在复制Layout时我们使用_chessmen,但最终引用的对象确是同一个对象。如下图所示:
sortedChessmen在Layout的InitLayoutMap方法中被初始化,使用快速排序法进行排序操作(有关快速排序法将在下部分内容中加以介绍)。
Layout中还有一个只读属性IsFinished属性,用来判断当前棋局是否可以结束,其实就是判断曹操是否移动到了指定位置而已。该属性在ToInt方法中被初始化。