代码先公布:http://download.csdn.net/source/891878
到现在为止,我只实现了一个棋盘,确切的说是在棋盘上随机走棋的速度测试程序,我借鉴了lib-ego,在上面做了一些改进,现在这个棋盘可以使用围棋规则或者五子棋规则。我的目标是让我的AI程序用同样的算法来对待围棋、五子棋甚至小时候玩过的黑白棋,它不需要任何棋类知识,你只要告诉它下棋的规则。我们的脑细胞可曾了解究竟什么是围棋?它们只是机械的执行自己的职能,而亿万个细胞堆叠在一起就使人类会下棋了。
上面说的三种棋的棋盘有一些共同的特点:棋盘是由n行n列的平行线段交叉组成的格子,棋子分黑白两种颜色,棋手分为两方,分执一种颜色的棋子。双方轮流下子,每次下一个子,棋子要下在空的交叉点上(黑白棋似乎是下在格子里,但是应该没有本质区别)。
根据这些特点我们开始设计棋盘的结构。
一、比特棋盘
很想在围棋中使用比特棋盘,就像国际象棋中那样,用一个64bit的数就描述了棋盘上的一种棋子。围棋上尽管也可以做到,例如用一个361bit的数来描述棋盘上的黑棋,另一个361bit数描述白棋,但是没见过谁这么做。
一般还是用传统的数组来描述棋盘,数组的每个元素有三个状态:黑(black)、白(white)、空(empty)。
为何计算机不是三进制的?我以前曾经这么想过,如果计算机是三进制的,会不会能更好的描述围棋?
后来我发现,其实棋盘上的点不只三个状态,还漏掉了一个off_board,也就是棋盘外的点。因此棋盘其实是4进制的,和2进制的计算机还是契合的不错的。
如何理解off_board也是一种状态?我们可以观察一下棋盘的边界,边界再往外就是off_board了,对围棋来说,通常的一颗子有4口气,但是到边界上就变成三口气或者两口气了,就仿佛边界外有敌人的子一样。对于五子棋,如果对方冲四冲到边界上,就不用挡了,就好像棋盘外有自己的棋子给它挡住了一样。
我按这种物理意义来为这些状态指派2进制数:
empty 00
black 01
white 10
off_board 11
这里empty就是没有棋子,black和white分别有一个棋子,而off_board则是同时有两个棋子,哪方的棋子靠近它,它就表现为另一方。
这样做的好处是,我可以用一个8bit的数来描述一个棋子的邻点,8bit总共256种情况,非常适合查表,通过查表,我就能得知任何情况下交叉点的“气”了。
关于计算交叉点的“气”,lib-ego中采用的另一种方法,它仅仅只增量计算交叉点周围黑、白、空三种情况的数量(off_board就分摊到黑白两种情况上了),而不管具体分布情况。目前我还没有发现我的方法表现出来的优势,但是我坚信我的方法比lib-ego中的好,因为它合乎道。
看起来,可以用一个8bit的数来存4个位置的状态,那么整个棋盘总共需要56个64bit数,比国际象棋没多太多,然而最终我没有贯彻比特棋盘的思想,因为我觉得那样不自然,我仍然选用传统的数组方式。
二、代码优化
许多人都指出优化应该晚做。但是对一份已经优化过的代码,如果不了解其优化手段,很难明白一些代码的意义。
1 使用编译期常量来代替变量。
例如棋盘的尺寸这个量,棋子的坐标计算依赖于它,为一些结构分配多大空间也与这个量相关。为了避免运行期再去计算这些东西,我们可以用宏或者const int来定义它:
- const uint board_size = 9;
但是我们希望