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

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          }

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

Silverlight围棋单机版源码介绍相关推荐

  1. 4- vue django restful framework 打造生鲜超市 -restful api 与前端源码介绍

    使用Python3.6与Django2.0.2(Django-rest-framework)以及前端vue开发的前后端分离的商城网站 项目支持支付宝支付(暂不支持微信支付),支持手机短信验证码注册, ...

  2. c语言 临时文件作用,c语言函数mktemp()产生唯一临时文件名实例源码介绍

    c语言函数mktemp()产生唯一临时文件名实例源码介绍.有关的函数:tmpfile引入的头文件:#include 定义函数mktemp():char * mktemp(char * template ...

  3. c语言复制粘贴源码,c语言函数memccpy()如何复制内存中的内容实例源码介绍

    c语言函数memccpy()如何复制内存中的内容实例源码介绍.引入的头文件:#include memccpy()函数定义:void * memccpy(void *dest, const void * ...

  4. Weka开发[8]-ID3源码介绍

    为什么80%的码农都做不了架构师?>>>    这次介绍一下Id3源码,这次用Weka的源码介绍一下.首先Id3是继承于Classifier的: public class Id3 e ...

  5. c语言比较函数memcmp,c语言函数memcmp()如何比较内存前n个字节实例源码介绍

    c语言函数memcmp()如何比较内存前n个字节实例源码介绍.引入头文件:#include 定义memcmp()函数:int memcmp (const void *s1, const void *s ...

  6. robot_localization中EKF源码介绍

    robot_localization中EKF源码介绍 /** Copyright (c) 2014, 2015, 2016 Charles River Analytics, Inc.* Copyrig ...

  7. Live555源码阅读笔记(一):源码介绍文档 及 源码目录结构

    目录 一.Live555介绍 1.Live555项目介绍 2.官网及帮助文档介绍 二.源码目录结构 1.UsageEnvironment 2.BasicUsageEnvironment 3.group ...

  8. android做拨号程序代码,Android开发手机拨号程序实现实例源码介绍

    Android开发手机拨号程序实现实例源码介绍,在上一篇文章中,我们实现了第一个程序:helloWorld,并成功测试完成.还给大家介绍了Android项目结构和说明.现在写一个手机拨号程序: 首先, ...

  9. cocos creator2.1.3 仙剑奇侠传单机版源码

    CocosCreator-仙剑奇侠传单机版 亲测cocos creator2.1.3 仙剑奇侠传单机版源码下载.完整可玩.拿来学习很不错. 一款rpg类型的经典老游戏, cereator实现,源码极具 ...

最新文章

  1. 话里话外:新顾问答疑解惑对话大公开
  2. 浅入浅出TensorFlow 8 - 行人分割
  3. 库函数、系统调用和内核函数的区别
  4. android 自定义频谱,android – 如何从实时音频开发频谱分析仪?
  5. Linux内核里的“智能指针” (续)
  6. C符号之逻辑运算符 左移与右移 自增自减
  7. ug编程内公差和外公差是什么_工厂老师傅自学数控编程多年心得,希望你们少走弯路...
  8. Bootstrap CSS 编码规范之简写形式的属性声明
  9. 坐标或测量值超出范围
  10. 估值飙至 280 亿美元,Databricks G 轮融资 10 亿美元,谁说开源不挣钱?
  11. python基础9-常见难点
  12. 【转】Nodejs链接Mysql批量添加 insert into
  13. java程序员简历范文
  14. BP神经网络(BPNN)
  15. ios 开发证书导出p12文件_iOS 证书(.p12)和描述文件(.mobileprovision)的导出和使用方法...
  16. 微型计算机中央处理器又称为,微处理器又称为什么
  17. element组件官网
  18. Spring AOP术语
  19. 【软考软件评测师】第三十三章 数据库系统应用
  20. 送书 | 哈佛大学单细胞课程:笔记汇总前篇

热门文章

  1. 【Java】用etcd做服务注册和发现
  2. 1Sigma~3sigma
  3. python itertools功能详解
  4. 误删除备忘录怎么恢复
  5. python求两条线段的交点
  6. Switch多条件循环
  7. 2023华为OD机试真题C++实现【密室逃生游戏】
  8. vb6 webclass是做什么的_牛奶变酸奶,都发生了什么变化?哪些营养多了?哪些营养少了?...
  9. 开曼群岛:注册互联网公司的天堂
  10. yy-mm-dd hh:mm:ss转换时间戳