Silverlight围棋单机版源码介绍

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

 

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

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

 

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

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

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

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

 

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

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

 

ExpandedBlockStart.gif 鼠标单击
 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了,用来保存棋盘上当前局面下的棋子,所以关于行棋有效性、吃子、悔棋、数目等算法都是在这儿实现的。

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

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

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

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

 

ExpandedBlockStart.gif 判断棋子是否有气
 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相连。

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

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

 

ExpandedBlockStart.gif 行棋有效性判断
 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,一是如果该棋子没气,又没有吃掉其它棋子;另外一种就是虽然吃掉了一个棋子,但这个棋子刚好是上一步下的,而且还吃掉了一个子,

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

 

 

ExpandedBlockStart.gif 计算被新下的棋子吃掉的棋
 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          }

 

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

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

 

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

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

 

ExpandedBlockStart.gif 数目算法
 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          }

 

 

转载于:https://www.cnblogs.com/arbin98/archive/2010/11/14/1877145.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值