黑白棋程式简史

在1980年代,电脑并不普及,在黑白棋界里,最强的仍然是棋手(人类)。

到了1990年代初,电脑的速度以几何级数增长,写出来的黑白棋程式虽然仍然有点笨拙,但由于计算深度(电脑的速度快)和尾局的准确性,所以已经很强。1990年代初最有名的程式就是Thor,在那时候是最强的程式(还是DOS模式年代),棋力能比得上世界级棋手,在这个时期的程式都是人工地加入行动力、位置策略、偶数重要性等等,但又因为这些策略是直接编写在程式里,那程式的棋力很依赖程式员本人的棋力,程式下起来比较像人类的下法。而且程式里遗留了人类棋手的弱点、策略的不完整性等等的问题。因为早期Thor的通用性,影响到现在黑白棋界里的统一棋谱数据库都是用Thor database。

到了1994、1995年,黑白棋程式的编写方法有了突破性的发展,首先是有了一个称为IOS的网站(GGS的前身),让不同的程式同时连上去互相对局,突破了以往那种闭门造车的日子,在同期Michael Buro做出了能由程式自我学习的Logistello, 在差不多的时间,大家都学起来,用相似的方法来编写程式。程式员不再把人工的策略和下棋方法等死硬地写在程式里,而是由程式自我学习,程式会记录好、坏的 形状(patterns),根据实战的结果自动调整策略,又会把不同的开局棋谱根据实战的过程来评分、保存,有些程式能保存几十万步的开局棋谱。

就 是因为Logistello在根本方法的改进、先进的算法、编写方面的高效率和准确性等等,一直在黑白棋界保持为世界程式冠军。在1993 年~1997年的25场比赛中,Logistello有18场获得冠军、6场获得亚军。1997年8月,Logistello击败了1996年的世界冠军村上健(Takeshi Murakami),从此黑白棋程式把人类棋手远远甩在后面。直到Michael在1998年1月宣布Logistello退休为止。现在已经有数个程式在棋力上超越已退休的Logistello,包括edax, cyrano, saio,主要突破点在于它们把算法改善为多线程执行(multithreading)。

WZebra

黑白棋程序“斑马”(WZebra)是Zebra的Windows版本,除了可以下棋外,还提供了打谱、复盘、棋局分析、自我学习等功能,也可以加载Thor棋谱文件,进行针对性训练。在标准比赛时间(2x15分钟)内,其搜索深度可达中局18至27步、终局24至31步。WZebra是自由软件,提供了中文版本,并提供中文帮助。

S.A.I.O.

Saio是System Artificial Intelligence Othello缩写,是黑白棋程式之一。并不开放下载。

Problem——Source 来自中山大学ACM校赛中的一些问题,这里,我们要模拟黑白棋的流程,并实现一个简单的AI,可以判断——对于任意一个给定的局面(假设十步以内就是可以 结束的),黑白双方都以最NB的策略进行对弈(这里注明一下,对于十步以内的胜负几乎就要见分晓的局面中,这种黑白之间的“最优策略”是存在的,这有点类 似于围棋中的“局部手筋”),每次还是先给出棋盘的局面,分别用w和b表示白方和黑方,另外,在下一行给出是轮到白先下棋还是黑先下棋。

  博弈树是神马??!

极大极小博弈树(Minimax Game Tree,简写为MGT,译者注)用于编写电脑之间的游戏程序,这类程序由两个游戏者轮流,每次执行一个步骤。当然,所有可能的步骤构成了一个树的结构。例如下面的图就是一个MGT,它表示了Tic-Tac-Toe游戏的前两步所有可能的步骤。

(Tic-Tac-Toe是一种简单的九宫格游戏,玩法是使用3*3的9个方格子,每人下一次看谁先连成一行3个,以下称ttt游戏,译者注)

我们注意到这棵树不同于其他的树结构,比如二叉树,2 3树以及堆树(heap tree,译者注),根据游戏规则,一个MGT节点上可能有很多个子节点。

图 中的树有三级,不过在编码中,极大极小树的级通常被称作层(级:level,层:ply,译者注)。在每一层中,“转换”开关指向另一个游戏者。两个不同 的游戏者通常被称作马克思(MAX,即最大,译者注)和米恩(Min,即最小,译者注)。(下面将简短的解释这些陌生的名称)

对 一棵完整的极大极小树来说,计算机能够向前遍历每一步,直到找到最佳步骤为止。当然,正如你在例图中看到的那样,仅仅几个步骤也会令这棵树变得异常庞大, 对于一台普通的计算机来说,预测五个步骤就足以令其迅速崩溃。因此,对于像国际象棋和围棋这样的大型博弈游戏来说,计算机程序不可能遍历所有结果,而是仅 仅通过最上层的几个步骤来判断胜负。此外,程序员们也提出了很多算法和技巧来减少节点数目,比如阿尔法 贝塔剪枝算法(Alpha-Beta pruning),Negascout搜索算法以及MTD(全称是:Memory enhanced Test Driver,即记忆增强测试驱动,译者注)方法。

MGT 当然不能预测所有计算机游戏的可能步骤。比如扑克游戏,计算机在判断对手动向的时候将会非常吃力,因为因为计算机不能看到对方手中的牌。因此,仅仅对于两 个游戏者都能看到全部博弈形式的游戏来说,MGT才是最好的选择。这些游戏包括国际跳棋、五子棋、国际象棋和围棋,这些游戏被称作完全信息博弈(原文为 games of perfect information,译者注)。

极 大极小博弈树是因描绘这种结构的一种简单算法而得名。我们来对ttt游戏的结果分配一下分值。如果叉(X)获胜,则分值为1。如果圈(O)获胜,则分值为 -1。现在,叉将试图获得最大化的分值,而圈将试图最小化分值。于是,第一位研究此问题的研究者决定把游戏者叉命名为马克思,并且把游戏者圈命名为米恩。 因此,这个完整的数据结构就被命名为极大(Max,马克思,译者注)极小(Min,米恩,译者注)博弈树。

极 大极小逻辑也被用于其它博弈,比如国际象棋中。然而,在这些更复杂的博弈中,程序仅仅能搜索极大极小树中的一部分;由于树太过庞大,程序往往不能搜索到博 弈最终的结局。计算机一般是搜索某几个节点之后就停止了。然后程序在某个节点上评估博弈的胜负,这些评估结果被换算成博弈形势的分值。如果计算机是马克思 一方,程序会试图使博弈形势的分值最大化,同时为获胜结局(将死)赋最大值(比如说这个值是一百万)。如果计算机是米恩一方,显然程序将试图最小化分值, 并为获胜结局赋最小分值(例如负一百万)。游戏双方将在两个最大值之间博弈,数值越接近哪一方则哪一方获利(象不象拔河?译者)。

极大极小算法背后的策略假定参与博弈的游戏者都尽自己最大的努力获得好结果。因此,无论对方选择有利或有害的步骤,计算机都将会根据对手的着法选择最于己有利的步骤。

这个简单浅显的概念就是极大极小树的最大奥妙。比如,对马克思的程序来说,无论米恩怎么做,最佳的步骤或步骤序列一定会得到最高分值的结果。而米恩显然将选择那些让它获得最低分值的结果。

从 某种意义上说,叶子节点(the bottom nodes,最下层节点,译者注)是唯一需要评估位置分值的节点,因为它们代表最终的结局。比如在马克思的博弈变化中,叶子节点始终处在同一位置。程序将 假定米恩将从可能的步骤中选择最低分值的步骤行动,那么任何马克思节点的最大最小值都会等同于米恩节点的最低分值子节点。

最 后,像人类的棋类游戏一样,程序的能力高低取决于计算机对所处形势的评估能力,以及程序搜索的深度。一位国际象棋大师对形势的估计误差要大大小于余位业余 选手,而且象棋大师对于棋局的预测也远比一般人更远。计算机同样也可以对棋局做出很长远的预测,并且它着棋不会失误,因为它会看到对手由于失误而做出的回 应。

有 很多算法可以帮助极大极小算法提高搜索效率。其中一种被称作阿尔法 贝塔剪枝算法。在使用这种算法进行的搜索中,计算机所要搜索的节点数大约只是不使用这种技术所需搜索节点数的平方根那么多。也就是说,如果程序原来需要搜 索四百个节点,使用新的算法后它只需要搜索二十个。

其 它的一些工具包括置换表(原文为Transposition table,译者注),记载搜索结果的纪录被放在一张可以快速存取的很小的表中。通常来说,不同的步骤序列可能达到相同的位置(结果)。这两个位置(结 果)就可以互换。该表可以帮助计算机认识目前棋局的形势,因为它已经付出了内存存取时间的代价对其进行了审查。

同时,这些技术也允许计算机搜索更多的节点,并模拟策略思考。尽管其它的技术也开始崭露头角(比如神经网络),但极大极小树仍然是该类程序的最佳心脏。

Solve: 很精彩的一段代码啊!看完有一种对称美的感觉,在很多地方都出现过了,我这里也不想对极大极小博弈做出什么评价,只是觉得这里的数组就用得非常巧妙,设置 empty[10][2]之余还设置enable[10],另外,吃子的描述也很精彩。运行黑棋搜索或者白棋搜索之后,就会陷入黑套白,白套黑这样的局 面,双方各自博弈的详细过程我表示看得一知半解,对博弈值的理解,我认为和每次吃的子的数目有关,但绝对不是单一的每次比较谁吃的子多!我认为这里面逻辑 还比较复杂,需要请教高手了。

  1 #include<fstream.h> //文件流的输入输出
  2  #include<string.h>
  3  #include<assert.h> //断言,这个主要是为了调试的方便
  4  
  5  ifstream filein("stone.in"); //文件读入
  6  ofstream fileout("stone.out"); //文件输出
  7  
  8  int grid[8][8]; //定义整个棋盘,其中的grid就是网格的意思
  9  int num,empty[10][2],enable[10]; //由于问题中说明了10步以内下完全局,enable[]这里代表的是还可以装填的数目
 10  const int path[8][2]={0,1,0,-1,1,0,-1,0,1,1,1,-1,-1,1,-1,-1}; //这里用path[8][2]标识了八个方向
 11  
 12  //计算黑白的数目并且判断输赢,其中,白赢则定义为1,黑赢则定义为-1,如果是平局则定义为0
 13  int cal()
 14  {
 15    int i,j,whitecount=0,blackcount=0;
 16    for(i=0;i<8;i++)
 17      for(j=0;j<8;j++)
 18      {
 19        if(grid[i][j]==1) whitecount++;
 20        else if(grid[i][j]==-1) blackcount++;
 21        else assert(grid[i][j]==0);//这个可以不管,只是为了方便调试,向调试器发送的信号                
 22      }    
 23    if(whitecount>blackcount) return 1;
 24    else if(whitecount==blackcount) return 0;
 25    else return -1;
 26  }
 27  
 28  int black(); //这里不声明也是可以的,毕竟函数写在main函数的前面
 29  
 30  int white()
 31  {
 32    int i,j,k,u,v,loop,temp1,temp2,res=-1,tempres;
 33    int rem[32][2],remnum;
 34    int tag=0;
 35    //考虑每一个可能的空位,虽然是指数级别的,但是由于在10步以内,空间还是比较小
 36    for(i=0;i<num;i++)
 37      if(enable[i])
 38      {
 39        //重新标记回来
 40        enable[i]=0;
 41        //获取空位点的x,y的坐标
 42        u=empty[i][0]; v=empty[i][1];
 43        grid[u][v]=1;
 44        remnum=0;//统计吃子的个数
 45        //朝着八个方向开始搜索
 46        for(loop=0;loop<8;loop++)
 47        {  
 48          //棋子对该位置不超过棋盘,而且,朝向的前面一个格子不为空(否则无法吃掉)
 49          for(j=u,k=v;j+path[loop][0]>=0&&j+path[loop][0]<8&&k+path[loop][1]>=0&&k+path[loop][1]<8&&grid[j+path[loop][0]][k+path[loop][1]];)
 50          {
 51            j+=path[loop][0];
 52            k+=path[loop][1];
 53            //判断是否可以吃子,这里我还是很看了一下才看懂的!是这样的,注意到前面那个for循环,最后的执行是一个空的,就是说,如果遇到朝着那个方向不相同
 54            //的子的话,就直接加过去,直到遇到了一个与之颜色相同的棋子为止。这个时候,还没有完,我们还需要判断一下这个中间是否"夹着"一些棋子,这里可以将
 55            //只朝着方向移动一格的子和终点比较,如果坐标不同的话,就说明符合条件了,并统计可以吃的子的个数,并最后以remnum作为统计
 56            if(grid[j][k]==grid[u][v])
 57            {
 58              for(temp1=u+path[loop][0],temp2=v+path[loop][1];(temp1!=j||temp2!=k);temp1+=path[loop][0],temp2+=path[loop][1])
 59              {
 60                rem[remnum][0]=temp1;
 61                rem[remnum][1]=temp2;
 62                remnum++;                                                                                                                 
 63              }          
 64              break;                    
 65            }                                                                                                                                    
 66          }                         
 67        }           
 68        if(remnum>0)
 69        {
 70          for(loop=0;loop<remnum;loop++)
 71          {
 72            j=rem[loop][0];
 73            k=rem[loop][1];
 74            //调整每个子的颜色,这也就是我们为什么要设置1表示白子而-1表示黑子了
 75            grid[j][k]=-grid[j][k];                              
 76          }              
 77          tempres=black();
 78          tag=1;
 79          if(tempres>res) res=tempres; //白棋希望博弈值越大越好,如果过小的话,白方不满意
 80          //这里返回到初始的状态
 81          for(loop=0;loop<remnum;loop++)
 82          {
 83            j=rem[loop][0];
 84            k=rem[loop][1];
 85            grid[j][k]=-grid[j][k];                              
 86          }
 87        }   
 88        grid[u][v]=0;
 89        enable[i]=1;
 90      }   
 91    if(tag==0) reutrn cal();
 92    else return res;
 93  }
 94  
 95  int black()
 96  {
 97    //黑方下棋的规则和白方类似,这里略去一些注释
 98    int i,j,k,u,v,loop,temp1,temp2,res=1,tempres;
 99    int rem[32][2],remnum;
100    int tag=0;
101    for(i=0;i<num;i++)
102      if(enable[i])
103      {
104        enable[i]=0;
105        u=empty[i][0],v=empty[i][1];
106        grid[u][v]=-1;
107        remnum=0;
108        for(loop=0;loop<8;loop++)
109        {
110          for(j=u,k=v;j+=path[loop][0]>=0&&j+path[loop][0]<8&&k+path[loop][1]>=0&&k+path[loop][1]<8&&grid[j+path[loop][0]][k+path[loop][1]])
111          {
112            j+=path[loop][0];
113            k+=path[loop][1];
114            if(grid[j][k]==grid[u][v])
115            {
116              for(temp1=u+path[loop][0],temp2=v+path[loop][1];(tenmp1!=j||temp2!=k);temp1+=path[loop][0],temp2+=path[loop][1])
117              {
118                rem[remnum][0]=temp1;
119                rem[remnum][1]=temp2;
120                remnum++;                                                                                                                
121              }          
122              break;                
123            }                                                                                                                                    
124          }                         
125        }           
126        if(remnum>0)
127        {
128          for(loop=0;loop<remnum;loop++)
129          {
130            j=rem[loop][0];
131            k=rem[loop][1];
132            grid[j][k]=-grid[j][k];                              
133          }            
134          tempres=white();
135          tag=1;
136          if(tempres<res) res=tempres; //黑方则希望博弈值越小越好,如果过大的话,则会不满意
137          //跳回到原来的状态
138          for(loop=0;loop<remnum;loop++)
139          {
140            j=rem[loop][0];
141            k=rem[loop][1];
142            grid[j][k]=-grid[j][k];                              
143          }
144        }  
145        grid[u][v]=0;
146        enable[i]=1;
147      }    
148    if(tag==0) return cal();
149    else return res;
150  }
151  
152  int main()
153  {
154    char ch,str[100];
155    int i,j;
156    for(; ;)//这个,用while(1)可能看起来更舒服一些吧!
157    {
158      //由于是读str,这里是读一行
159      filein>>str;
160      if(strcmp(str,"EndOfInput")==0) break; //读文件结束
161      for(i=0;i<8;i++)
162      {
163        if(str[i]=='w') grid[0][i]=1; //白方置1
164        else if(str[i]=='b') grid[0][i]=-1; //黑方置-1
165        else if(str[i]=='e') grid[0][i]=0; //空白置0                 
166      }        
167      for(i=1;i<8;i++)
168        for(j=0;j<8;j++)
169        {
170          //由于是读ch,这里是一个字符一个字符地读
171          filein>>ch;
172          if(ch=='w') grid[i][j]=1;
173          else if(ch=='b') grid[i][j]=-1;
174          else if(ch=='e') grid[i][j]=0;                
175        }
176      num=0;
177      for(i=0;i<8;i++)
178        for(j=0;j<8;j++)
179          if(grid[i][j]==0)
180          {
181            empty[num][0]=i;
182            empty[num][1]=j;
183            enable[num]=1;
184            num++;                 
185          }
186      filestr>>str;
187      if(strcmp(str,"WHITE")==0)
188      {
189        i=white();
190        if(i==1) fileout<<"White"<<endl;
191        else if(i==0) fileout<<"Draw"<<endl;
192        else if(i==-1) fileout<<"Black"<<endl;
193        else assert(0); //实在不行,就报错了,这里相当于异常处理                         
194      }
195      else if(strcmp(str,"Black")==0)
196      {
197        i=Black();
198        if(i==1) fileout<<"White"<<endl;
199        else if(i==0) fileout<<"Draw"<<endl;
200        else if(i==-1) fileout<<"Black"<<endl;
201        else assert(0);    
202      }
203      else assert(0);
204    }     
205    return 0;
206  }

转载于:https://www.cnblogs.com/tuanzang/archive/2013/02/27/2935837.html

吴昊品游戏核心算法 Round 9 —— 正统黑白棋AI(博弈树)相关推荐

  1. 吴昊品游戏核心算法 Round 16 ——吴昊教你玩口袋妖怪 第三弹 地洞谜题

    这样的场景我们应该经常遇到的吧,哈哈! 口袋妖怪的地洞要算是最令人讨厌的了,因为,有些地洞是全黑的,你即使用了闪光灯(必选道具#03),你有时也只能用GBA外壳的荧光屏作为道具才能将整个地洞看清楚. ...

  2. 吴昊品游戏核心算法 Round 17 —— 吴昊教你玩拼图游戏 序

    如图所示,此人就是<死亡笔记>中最终击败夜神月的尼亚(当然,他在击败夜神月的过程中,利用了梅洛的一些帮助),尼亚喜欢玩各种玩具,比如乐高啊,多米诺骨牌啊等等,当然,他最喜欢的仍然是拼图.他 ...

  3. 吴昊品游戏核心算法 Round 10 —— 吴昊教你下围棋(利用递归来解决吃子的问题)...

    如图所示,此即为日本动漫棋魂中的千年佐为,也就是SAI.众所周知,围棋的规则相比于中国象棋,国际象棋等等都简单许多,真是因为更简单的规则,才诞生 了更复杂的逻辑.目前的围棋AI还很不行,最NB的应该是 ...

  4. 吴昊品游戏核心算法 Round 16 —— 吴昊教你玩口袋妖怪 第四弹 拉帝亚斯?!拉帝欧斯?!...

    作为讲述口袋妖怪的Round 16,这里,我会采用"夹叙夹议,夹议夹叙"的模式进行编排.也就是在议论一些口袋妖怪中的小游戏的核心算法的同时,叙述一下我对口袋妖怪的理解以及我与这一系 ...

  5. 吴昊品游戏核心算法 Round 9 —— 黑白棋AI系列之西洋跳棋(第二弹)(双向BFS+STL)(POJ 1198)...

    接上回,如图所示,这是黑白棋的一个变种,Solitaire也是一种在智能手机上普遍存在的一种游戏.和翻转棋(Flip Game)一样,西洋跳棋(Solitaire)也没有正统的黑白棋(奥赛罗,又称Ot ...

  6. 吴昊品游戏核心算法 Round 18 —— 吴昊教你玩Zen Puzzle Garden

    如果你认为无法因为玩一个电脑游戏而达到精神的顿悟,你可能是正确的.不过你完全可以试着解决一个禅宗花园发生的难题,从而达到静心的精神状态.   这个游戏是获得2003年独立游戏节提名的精品游戏,在注重游 ...

  7. 吴昊品游戏核心算法 Round 15 —— (转载)德州扑克中的心理战

    在我的Round 15中的德州扑克的AI中,设计者曾经赋予了德州扑克一种不错的心理恐吓算法,具体地说,是如下的战术: 如果RR<0.8,那么95%选择弃牌,0%选择叫牌,5%选择加倍(这里加倍的 ...

  8. 吴昊品游戏核心算法 Round 16 —— 吴昊教你玩口袋妖怪 第九弹 冰系道馆

    道馆之战!!!这一次,道馆由电系变为了冰系,和龙系的道馆不一样,冰系的道馆有冰系的规定.如图所示,一共有三层,上面一层总是比下面一层要复杂一些.在冰系道馆中,有如下的两个规则:(1)我们不会在冰面上划 ...

  9. 吴昊品游戏核心算法 Round 5 ——(转载)关于无禁手下先手必胜的证明

    关于五子棋先手必胜的证明,用人工的方式过于复杂,其难度相当于证明四色定理的正确性或者是若儿当定理的正确性.但是,如果采用计算机来解决,则复杂程度 会降低许多.由于很难地毯式地枚举到所有可能的情形,这一 ...

最新文章

  1. Ubuntu 安装 CUDA 和 cuDNN 详细步骤
  2. Spark UDF用户自定义函数
  3. 疾风之刃鸿蒙炸裂,疾风之刃二周年版本12月1日上线_疾风之刃天武僧二觉率先开放_游戏堡...
  4. powerbi visualization
  5. 表现层(jsp)、持久层(类似dao)、业务层(逻辑层、service层)、模型(javabean)、控制层(action)...
  6. RuntimeError: Model class cmdb.models.UserInfo doesn't declare an explicit app_label
  7. Oracle Stream配置详细步骤
  8. JAVA入门级教学之(classpath的配置)
  9. python动态执行语句_Python Language
  10. coredump产生与分析
  11. 杨元:CSS浮动(float,clear)通俗讲解
  12. diff和pacth
  13. 100个人奇数枪毙Java,在JAVA中用for循环编写程序计算1~100之间的奇数之和
  14. 用matlab做仿真实验难不难,SIMULINK仿真实验心得体会
  15. Python_Pandas_分组汇总数据和创建数据透视表
  16. matlab怎么输入学号姓名,matlab如何创建一个构架数组用于统计学生的情况包括学生的姓名学号各科成绩等??...
  17. IT从业人员面试经典70问答
  18. javascript(一)
  19. 如何验证office是否已永久激活。
  20. 李宏毅机器学习2020笔记(二)Classification

热门文章

  1. 【JAVA】06 封装、继承、多态 总结(初级)
  2. vue 引用网络css_Vue 引入外部CSS文件
  3. 带你来看全国3000所高等院校分布
  4. 【Azure DevOps系列】什么是Azure DevOps
  5. 上大学后才知道的14件事!!!
  6. Adobe Acrobat插件开发的基本流程
  7. codevs3147 矩阵乘法 2 (推导小公式)
  8. 【笔记】MATLAB文件中文乱码问题:使用记事本打开,保存成不同的格式,然后再使用Matlab打开观察结果
  9. MATLAB实现自编码器(一)——Autoencoder类和训练设置
  10. DirectX游戏编程入门——第一部分(Windows和DirectX游戏编程引言) —— 初识DirectX