上篇文章发布了源码,这里先将代码做个整体介绍,以方便读者了解整个程序结构。

 

Board类负责棋盘绘制工作,棋盘是正方形,横竖各19条线,再加上四周边界,所以线与线之间的间隔就是棋盘宽度除以20,

这个宽度也是棋子的直径,另外还要在棋盘上画九个小黑点,标示出星位,代码不再赘述。

 

再看棋子Stone类,它的主要属性是横纵坐标X、Y,都是[1,19]间的整数,还有手数Number,因为黑棋先下,所以黑棋的手数总是奇数,

而白棋的手数总是偶数,棋子的Color属性就是根据这个规则计算的。另外还有一个重要属性就是KilledStone,用来保存被该棋子吃掉的子,

这主要用在悔棋及复盘时的后退功能,因为当把一个已经下上去的棋子拿掉的话,也要相应的把它吃掉的子再放回棋盘,

另外在判断是否是打劫局面的时候也会用到该属性。Stone类里的主要方法就是Draw()和Die()了,用来绘制和移除棋子。

 

程序的主要控制类是WeiQi类,只有一个属性runningMode,是运行模式,包括对弈与复盘两种模式。主要职责是负责管理系统资源,

如棋盘、棋子列表、棋谱、行棋手数等,还要响应用户输入,其中最主要的就是鼠标单击事件,也就是用户在棋盘上落子。代码如下:

 

鼠标单击
 1 void CvsBoard_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
 2         {
 3             if (runningMode == RunningMode.Study)
 4             {
 5                 GoNext();
 6                 return;
 7             }
 8 
 9             Point p = e.GetPosition(_cvsBoard);
10             double gap = _cvsBoard.Width / 20;
11             Coordinate cod = Coordinate.GetCoordinateByPoint(p, gap);
12             if (!cod.IsValid())
13             {
14                 return;
15             }
16             if (_stoneList.FindStone(cod.X,cod.Y) != null)
17             {
18                 return;
19             }
20 
21             Stone stn = new Stone(++_stoneNumber, cod.X, cod.Y, _cvsBoard);
22             if (_stoneList.Add(stn))
23             {
24                 _stoneRecord.Add(cod);
25                 DrawText(stn);
26             }
27             else
28             {
29                 _stoneNumber--;
30             }
31         }

 

先判断运行模式,如果是复盘模式,则单击相当于点击下一步按钮。如果是对弈模式,则根据点击位置获得要下子的坐标,

如果该坐标无效或该位置已经有棋子则返回,否则生成新棋子加入棋子列表。如果这步棋是有效行棋,则会添加成功,从而要加入棋谱,

并在该棋子上绘制手数,否则说明是无效行棋,手数再回退一步。至于如何判断行棋有效性,会在下面StoneList里面讲到。

 

棋谱管理类是StoneRecord,所谓棋谱就是所有有效行棋的坐标列表,按行棋顺序排列,里面提供了前进与后退操作,复盘模式下会用到。

另外还提供了棋谱的打开与保存,采用的是silverlight文件操作功能,代码容易理解,不再赘述。

 

程序中最重要的类就是StoneList了,用来保存棋盘上当前局面下的棋子,所以关于行棋有效性、吃子、悔棋、数目等算法都是在这儿实现的。

先说行棋有效性。什么样的位置才可以落子呢?当然首先是这个位置不能有子,然而只是满足这个条件还不够。总结起来应该是这样的:

如果一个棋子下在棋盘上是有气的,那么它一定是有效的;否则的话要看它是否能吃掉其它子,如果可以提子,一般来说也是有效的,除了打劫的情况。

从这儿可以看出,要判断行棋是否有效,首先要判断棋盘上的一个棋子是否有气,这也是很多核心算法的基础。棋子是否有气,首先可以看它相邻的位置,

如果这些位置里面有空位,则这个棋子一定有气,否则要看和它相连的同颜色的棋子是否有气,所以算法应该是个递归处理,代码如下:

 

判断棋子是否有气
 1 bool IsLive(Stone stn,List<Stone> lstLinkedStone)
 2         {
 3             if (lstLinkedStone == null)
 4             {
 5                 lstLinkedStone = new List<Stone>();
 6             }
 7 
 8             int leftX = stn.X - 1;
 9             int rightX = stn.X + 1;
10             int topY = stn.Y - 1;
11             int bottomY = stn.Y + 1;
12             Stone leftStone = null;
13             Stone rightStone = null;
14             Stone topStone = null;
15             Stone bottomStone = null;
16 
17             if(leftX > 0)
18             {
19                 leftStone = FindStone(leftX, stn.Y);
20                 if (leftStone == null)
21                 {
22                     return true;
23                 }
24             }
25             if (rightX < 20)
26             {
27                 rightStone = FindStone(rightX, stn.Y);
28                 if (rightStone == null)
29                 {
30                     return true;
31                 }
32             }
33             if (topY > 0)
34             {
35                 topStone = FindStone(stn.X, topY);
36                 if (topStone == null)
37                 {
38                     return true;
39                 }
40             }
41             if (bottomY < 20)
42             {
43                 bottomStone = FindStone(stn.X, bottomY);
44                 if (bottomStone == null)
45                 {
46                     return true;
47                 }
48             }
49 
50             if (lstLinkedStone.IndexOf(stn) < 0)
51             {
52                 lstLinkedStone.Add(stn);
53             }
54 
55             if (leftX > 0)
56             {
57                 if (leftStone.Color == stn.Color && lstLinkedStone.IndexOf(leftStone) < 0)
58                 {
59                     if (IsLive(leftStone, lstLinkedStone))
60                     {
61                         return true;
62                     }
63                 }
64             }
65             if (rightX < 20)
66             {
67                 if (rightStone.Color == stn.Color && lstLinkedStone.IndexOf(rightStone) < 0)
68                 {
69                     if (IsLive(rightStone, lstLinkedStone))
70                     {
71                         return true;
72                     }
73                 }
74             }
75             if (topY > 0)
76             {
77                 if (topStone.Color == stn.Color && lstLinkedStone.IndexOf(topStone) < 0)
78                 {
79                     if (IsLive(topStone, lstLinkedStone))
80                     {
81                         return true;
82                     }
83                 }
84             }
85             if (bottomY < 20)
86             {
87                 if (bottomStone.Color == stn.Color && lstLinkedStone.IndexOf(bottomStone) < 0)
88                 {
89                     if (IsLive(bottomStone, lstLinkedStone))
90                     {
91                         return true;
92                     }
93                 }
94             }
95             return false;
96         }

 

代码先判断棋子上下左右四个相邻位置是否为空,如果有空位则返回true,否则在四个方向上递归处理和它同颜色的棋子,只要一旦发现某棋子有气,

则说明该棋子也有气。需要注意的是在递归的过程中,不能重复处理一个棋子,否则会出现死循环,因为如果a和b相连,那么b也和a相连。

所以算法在递归过程中,同时保存了和这个棋子相连的棋子,一方面可以解决这个问题,另外如果一个棋子没气,那所有和它相连的子也没气,

提子时就需要一起提掉,实际上吃子算法就是利用了这一点。有了这个函数,行棋有效性和吃子就容易实现了,代码如下:

 

行棋有效性判断
 1 public bool IsValid(Stone stn)
 2         {
 3             if (!IsLive(stn, null))
 4             {
 5                 List<Stone> lstKilledStone = GetKilledStone(stn);
 6                 if (lstKilledStone.Count < 1)
 7                 {
 8                     return false;
 9                 }
10                 if (lstKilledStone.Count == 1)
11                 {
12                     Stone killedStone = lstKilledStone[0];
13                     if (killedStone.Number == stn.Number - 1)
14                     {
15                         if (killedStone.KilledStone.Count == 1)
16                         {
17                             return false;
18                         }
19                     }
20                 }
21             }
22             return true;
23         }

 

里面有两种情况会返回false,一是如果该棋子没气,又没有吃掉其它棋子;另外一种就是虽然吃掉了一个棋子,但这个棋子刚好是上一步下的,而且还吃掉了一个子,

显然这就是打劫的情况。至于吃子的算法,也容易理解了,代码如下:

 

 

计算被新下的棋子吃掉的棋
 1 List<Stone> GetKilledStone(Stone newStone)
 2         {
 3             List<Stone> lstDeadStone = new List<Stone>();
 4 
 5             Stone stn = FindStone(newStone.X - 1, newStone.Y);
 6             if (stn != null && stn.Color != newStone.Color)
 7             {
 8                 CheckAndSaveDeadStone(stn, lstDeadStone);
 9             }
10 
11             stn = FindStone(newStone.X + 1, newStone.Y);
12             if (stn != null && stn.Color != newStone.Color)
13             {
14                 CheckAndSaveDeadStone(stn, lstDeadStone);
15             }
16 
17             stn = FindStone(newStone.X, newStone.Y-1);
18             if (stn != null && stn.Color != newStone.Color)
19             {
20                 CheckAndSaveDeadStone(stn, lstDeadStone);
21             }
22 
23             stn = FindStone(newStone.X, newStone.Y+1);
24             if (stn != null && stn.Color != newStone.Color)
25             {
26                 CheckAndSaveDeadStone(stn, lstDeadStone);
27             }
28 
29             return lstDeadStone;
30         }
31 
32 void CheckAndSaveDeadStone(Stone stn,List<Stone> lstDeadStone)
33         {
34             List<Stone> lstLinkedStone = new List<Stone>();
35             if (!IsLive(stn, lstLinkedStone))
36             {
37                 foreach (Stone deadStone in lstLinkedStone)
38                 {
39                     if (lstDeadStone.IndexOf(deadStone) < 0)
40                     {
41                         lstDeadStone.Add(deadStone);
42                     }
43                 }
44             }
45         }

 

就是在上下左右四个相邻位置上判断相反颜色的棋是否没有气,如果没气,那连同与这个棋子相连接的棋子也全都是死棋。所以在判断棋子是否有气的函数中,

顺便保存与这个棋子相连的棋子,避免了重复计算,是很有效率的处理方式。

 

最后再说一下数目算法。这里做了简化处理,假定用户已经把残子全部提掉,并收完了全部单官。这样的话,如果一个空点四周全是同颜色的棋,

那这个空点就算作该方的一目。当然这样处理比较简单,毕竟要判断残子的死活还是比较复杂的。代码如下:

 

数目算法
 1  public string Calculate()
 2         {
 3             int black = 0;
 4             int white = 0;
 5 
 6             for (int x = 1; x < 20; x++)
 7             {
 8                 for (int y = 1; y < 20; y++)
 9                 {
10                     Stone stn = FindStone(x, y);
11                     if (stn != null)
12                     {
13                         if (stn.Color == StoneColor.Black) { black++; }
14                         else { white++; }
15                     }
16                     else
17                     {
18                         int result = IsWhite(x, y);
19                         if (result == 0) { black++; }
20                         else if (result == 1) { white++; }
21                     }
22                 }
23             }
24             return string.Format("黑方:{0},白方:{1}",black,white);
25         }
26 
27         private int IsWhite(int x, int y)
28         {
29             Stone firstStone = null;
30             Stone tempStone = null;
31             for (int left = x - 1; left > 0; left--)
32             {
33                 tempStone = FindStone(left, y);
34                 if (tempStone != null)
35                 {
36                     firstStone = tempStone;
37                     break;
38                 }
39             }
40             for (int right = x + 1; right < 20; right++)
41             {
42                 tempStone = FindStone(right, y);
43                 if (tempStone != null)
44                 {
45                     if (firstStone == null) { firstStone = tempStone; }
46                     else if (firstStone.Color != tempStone.Color) { return -1; }
47                     break;
48                 }
49             }
50             for (int top = y - 1; top > 0; top--)
51             {
52                 tempStone = FindStone(x, top);
53                 if (tempStone != null)
54                 {
55                     if (firstStone == null) { firstStone = tempStone; }
56                     else if (firstStone.Color != tempStone.Color) { return -1; }
57                     break;
58                 }
59             }
60             for (int bottom = y + 1; bottom < 20; bottom++)
61             {
62                 tempStone = FindStone(x, bottom);
63                 if (tempStone != null)
64                 {
65                     if (firstStone == null) { firstStone = tempStone; }
66                     else if (firstStone.Color != tempStone.Color) { return -1; }
67                     break;
68                 }
69             }
70             if (firstStone == null) { return -1; }
71             if (firstStone.Color == StoneColor.Black) { return 0; }
72             return 1;
73         }

 

 

posted on 2010-11-14 21:54  arbin98  阅读(1984)  评论(5编辑  收藏  举报