Board类负责棋盘绘制工作,棋盘是正方形,横竖各19条线,再加上四周边界,所以线与线之间的间隔就是棋盘宽度除以20,
这个宽度也是棋子的直径,另外还要在棋盘上画九个小黑点,标示出星位,代码不再赘述。
再看棋子Stone类,它的主要属性是横纵坐标X、Y,都是[1,19]间的整数,还有手数Number,因为黑棋先下,所以黑棋的手数总是奇数,
而白棋的手数总是偶数,棋子的Color属性就是根据这个规则计算的。另外还有一个重要属性就是KilledStone,用来保存被该棋子吃掉的子,
这主要用在悔棋及复盘时的后退功能,因为当把一个已经下上去的棋子拿掉的话,也要相应的把它吃掉的子再放回棋盘,
另外在判断是否是打劫局面的时候也会用到该属性。Stone类里的主要方法就是Draw()和Die()了,用来绘制和移除棋子。
程序的主要控制类是WeiQi类,只有一个属性runningMode,是运行模式,包括对弈与复盘两种模式。主要职责是负责管理系统资源,
如棋盘、棋子列表、棋谱、行棋手数等,还要响应用户输入,其中最主要的就是鼠标单击事件,也就是用户在棋盘上落子。代码如下:
先判断运行模式,如果是复盘模式,则单击相当于点击下一步按钮。如果是对弈模式,则根据点击位置获得要下子的坐标,
如果该坐标无效或该位置已经有棋子则返回,否则生成新棋子加入棋子列表。如果这步棋是有效行棋,则会添加成功,从而要加入棋谱,
并在该棋子上绘制手数,否则说明是无效行棋,手数再回退一步。至于如何判断行棋有效性,会在下面StoneList里面讲到。
棋谱管理类是StoneRecord,所谓棋谱就是所有有效行棋的坐标列表,按行棋顺序排列,里面提供了前进与后退操作,复盘模式下会用到。
另外还提供了棋谱的打开与保存,采用的是silverlight文件操作功能,代码容易理解,不再赘述。
程序中最重要的类就是StoneList了,用来保存棋盘上当前局面下的棋子,所以关于行棋有效性、吃子、悔棋、数目等算法都是在这儿实现的。
先说行棋有效性。什么样的位置才可以落子呢?当然首先是这个位置不能有子,然而只是满足这个条件还不够。总结起来应该是这样的:
如果一个棋子下在棋盘上是有气的,那么它一定是有效的;否则的话要看它是否能吃掉其它子,如果可以提子,一般来说也是有效的,除了打劫的情况。
从这儿可以看出,要判断行棋是否有效,首先要判断棋盘上的一个棋子是否有气,这也是很多核心算法的基础。棋子是否有气,首先可以看它相邻的位置,
如果这些位置里面有空位,则这个棋子一定有气,否则要看和它相连的同颜色的棋子是否有气,所以算法应该是个递归处理,代码如下:
代码先判断棋子上下左右四个相邻位置是否为空,如果有空位则返回true,否则在四个方向上递归处理和它同颜色的棋子,只要一旦发现某棋子有气,
则说明该棋子也有气。需要注意的是在递归的过程中,不能重复处理一个棋子,否则会出现死循环,因为如果a和b相连,那么b也和a相连。
所以算法在递归过程中,同时保存了和这个棋子相连的棋子,一方面可以解决这个问题,另外如果一个棋子没气,那所有和它相连的子也没气,
提子时就需要一起提掉,实际上吃子算法就是利用了这一点。有了这个函数,行棋有效性和吃子就容易实现了,代码如下:
里面有两种情况会返回false,一是如果该棋子没气,又没有吃掉其它棋子;另外一种就是虽然吃掉了一个棋子,但这个棋子刚好是上一步下的,而且还吃掉了一个子,
显然这就是打劫的情况。至于吃子的算法,也容易理解了,代码如下:
就是在上下左右四个相邻位置上判断相反颜色的棋是否没有气,如果没气,那连同与这个棋子相连接的棋子也全都是死棋。所以在判断棋子是否有气的函数中,
顺便保存与这个棋子相连的棋子,避免了重复计算,是很有效率的处理方式。
最后再说一下数目算法。这里做了简化处理,假定用户已经把残子全部提掉,并收完了全部单官。这样的话,如果一个空点四周全是同颜色的棋,
那这个空点就算作该方的一目。当然这样处理比较简单,毕竟要判断残子的死活还是比较复杂的。代码如下: