zoukankan      html  css  js  c++  java
  • 围棋规则的计算机实现

      版权申明:本文为博主窗户(Colin Cai)原创,欢迎转帖。如要转贴,必须注明原文网址
      http://www.cnblogs.com/Colin-Cai/p/7502410.html 
      作者:窗户
      QQ:6679072
      E-mail:6679072@qq.com
    

      

      提到这个名字,很多人会想到前段时间让全世界振奋的围棋人工智能Alphago,想曾经我也了解过一些围棋的AI。我也正想花点时间说说alphago相关的东西,包括alphago的架构以及模型引申等,不过这篇文章里我只说围棋规则的实现,和人工智能无关。

      规则

      说到围棋规则的实现不得不先说围棋规则,一般来说,至少有三种围棋规则:中国规则,日本规则,应氏规则。其实还有中国古代规则,和这三种规则都有一点差别。应氏规则和中国规则实际差距非常非常小,小到很多人认为可以忽略不计。但中国规则和日本规则的差别有些大,个人认为中国规则更科学,日本规则不收单官导致了很多问题,比如盘角曲四算死棋(这一点个人觉得挺让人吐血,因为如果盘角曲四和双活同在,那盘角曲四的死毫无道理),再比如不提三目(这个简直就是强盗逻辑了,至于什么叫不提三目,请自行搜索)。从这一点上,至少中国规则不会导致这样的争议,一切实战解决。另外一点,日本规则的双活不算目,这个给计算机数目带来了问题,并且不容易解决。所以,本篇还是基于中国规则。

      基本数据结构

      很自然的就可以想到,可以用一个19X19的二维数组来代表棋盘上当前的棋面(一般称枰面)。棋盘上的每个点可以有三种状态:无子、黑子、白子。那么,这个19X19的二维数组就是基本的数据结构。

      下棋

      从第一步开始,黑白轮流下,无论对于谁下,其实都是要判断这个二维数组所下坐标下的点的状态是不是无子,如果不是无子,当然是不允许下的。

      另外一点,还有一个气紧的问题,就是说,把自己的一块棋走成没有气是不允许的(应氏规则除外,它可自杀),除非可以吃子。气紧和吃子最终可以归结为一个算法:判断连通的一块棋有没有气。这里连通的一块棋是狭义的,只是通过横竖紧密的连在一起的才是一块棋。

      

      如上图,左边7个黑子紧密的连在一起,我们称之未一块。右边这个中心标记为红色的黑子,却不是属于这一块的。

      看起来稍微形式化一点的定义如下:

      先定义坐标相邻,(A,B)与(C,D)相邻的意思是A=C且|B-D|=1,或者B=D且|A-C|=1

      坐标(A,B)所在的一块棋是一个坐标的集合S;

      坐标(A,B)在S内,坐标(C,D)与(A,B)相邻,并且(C,D)坐标上有棋子且棋子颜色和(A,B)一致,那么(C,D)也在S内。

      以上的内容很像连通图的定义,实际上,如果把相邻的同色子的连线当成图的边,那么连通的一块棋实际上就是连通图,那么判断一块棋有没有气可以利用连通图的遍历,只是如果发现在遍历的过程中找到一颗棋子有气,那么整块棋子都有气。

      要注意打劫,打劫的时候不可立即回提。

      

      如上图即为打劫。

      打劫至少有两种简单的判断手段:

      (1)当出现提1子时,记录当前子的坐标和提子的坐标;若下棋的时候,只提一子,并且上一步对方也是提一子,并且当前子的坐标就是上一步提子坐标,当前提子坐标就是上一步下的棋子坐标,那么则是打劫回提,是犯规的。

      (2)每一步都记录当前的棋面。如果当前下完棋子之后,棋面和上一步没下时一模一样,则是打劫回提。

      打劫是一种绝对需要避免的同局再现,至于三劫、四劫、长生、双提这一类导致无胜负的局面,则可以用记录每一次的棋面,然后与几次之前的进行对比,如果存在相同,也就是同局再现,可以判断是无胜负,基本同判断打劫的算法2,只是不是和上一步的比。

      计算

      最终计算胜负的时候,自动算十分复杂,之前网络上的围棋对战平台程序也是反复改进了很久才准确。我们这里只讨论手动的方式。

      首先是点掉死子。手动一个个的点掉死子自然可以,但效率太低,一般都是一点就点掉“连同”的一片死子。

      

      如图中两块死子,是希望清除其中一个子就清除掉所有其他“连通”的。

      这里的连通概念和上面连通的一块棋有点不同,这里的连通是同一个颜色或者空格在一起的一块,而之前的只强调一个颜色的一块。

      比如上面的图的上面那块权掉白棋死子所在的连通块是5个白子加旁边六个空格。

      

      其实依然是图,只是遍历图的时候边的定义改了一下,之前是相邻棋子颜色相同则是边,现在是相邻两个左边不出现不是死子颜色的是边。

      这样就可以遍历死子,确定一个死子坐标,就可以扫掉所有与之“相连”的死子。

      点掉所有死子之后数空定胜负。数空还是利用连通图,只是这里连同图指的是空格。如果空格的连通图在遍历中发现只和黑子相邻则是黑子的空,只和白子相邻则是白子的空,和黑白都有相邻则是公气,计算时得一方一半才可。数空是要依次遍历所有的空格连通图,直到整个棋盘上所有的空格都属于某个遍历出来的连通图。

      遍历连通图

      上面基本所有的算法都可以归结于连通图的遍历。图的遍历一般有深度遍历和广度遍历,围棋这里算连通图采用广度遍历比较方便。

      需要一个数据结构来记录哪些坐标被遍历过了,防止重复遍历,每次遍历了坐标之后就记录下,这个数据结构以二维数组最合适。

      建立一个空队列,然后把开始遍历的第一个坐标进队。

      从遍历的第一个位置开始,每次把这个位置相邻的坐标中所有与之相连并且没有遍历过的点(注意相连在不同的判断里意义不同)进队,并把当前坐标出队,同时记录该坐标已遍历。

      如此循环,直到队空,则已遍历了整个连通图。

      

      

  • 相关阅读:
    数据链路层
    补码加减法
    matlab函数
    HDU2159_二维完全背包问题
    HDU2844买表——多重背包初探
    HDU1025贫富平衡
    最大m段子段和
    01背包浮点数情况
    第K大01背包
    HDU2955 01背包
  • 原文地址:https://www.cnblogs.com/Colin-Cai/p/7502410.html
Copyright © 2011-2022 走看看