宝塔服务器面板,一键全能部署及管理,送你10850元礼包,点我领取

最近想做一个象棋游戏,但是AI把我难住了。这是这几天的成果:

象棋程序通过使用“搜索”函数来寻找着法。搜索函数获得棋局信息,然后寻找对于程序一方来说最好的着法。

一,最小-最大搜索Minimax Search

首先:最小与最大是相对的,且只针对一方,AI中即为有利于AI 
象棋AI中的最小最大搜索:  简单来讲就是该AI走了,穷举这个过程中对于AI来说的最佳(最大)走法对于我来说最差(最小)的走法。
而这个走法就是我们所要找的AI的最佳走法。

这个过程就跟你与别人下象棋时猜测对方走法然后下棋一样,只不过,电脑可以多想几步,这里的步数就是下面的搜索深度

举个例子:假设搜索深度为4. 那么AI走一步(他认为最佳,记为步数1,搜索深度4)时,会先考虑如果他走这一步1,那我肯定会走相对于这一步来讲
最差的一步2(搜索深度3),然后ai再假设出根据步数2来讲的最佳步数3(搜索深度2),继续考虑我根据步数3走的最差步数4(搜索深度1)
接下,搜索深度为0,给出此时的局面评价函数
             

他们之间彼此递归的调用,所以说这种搜索思路是相对的

这里只是浅显的讲下最小最大搜索的原理,还不能实现具体的AI功能

下面是实现代码

int Max(int depth) {
 int best = -INFINITY;
 if (depth <= 0) {
  return Evaluate();
 }
 GenerateLegalMoves();       //产生所有合理走法
 while (MovesLeft()) { 
  MakeNextMove();             //走这一步时          
  val = Min(depth – 1);       //接受一个相对最小值
  UnmakeMove();
  if (val > best) {
   best = val;
  }
 }
 return best;                 //返回一个相对最大的评价(AI 认为的最佳着法)
}
 
int Min(int depth) {
 int best = INFINITY; // 注意这里不同于“最大”算法
 if (depth <= 0) {
  return Evaluate();
 }
 GenerateLegalMoves();
 while (MovesLeft()) {
  MakeNextMove();
  val = Max(depth – 1);   //接受一个相对最大值
  UnmakeMove();
  if (val < best) {  // 注意这里不同于“最大”算法
   best = val;
  }
 }
 return best;            //返回一个相对最小的评价 (对方,人认为的最差走法)
}
 
  上面的代码可以这样调用:
 
val = MinMax(5);
 
  这样可以返回当前局面的评价,它是向前看5步的结果。 相关解释看注释就好

上面这种算法代码长,而且只是利于一方来推测(即对一方来说最佳最差走法),下面将介绍一种优化过的算法

————————————————————————–华丽丽的分割线
二,负值最大函数  Negamax Search

int NegaMax(int depth) {
 int best = -INFINITY;
 if (depth <= 0) {
  return Evaluate();
 }
 GenerateLegalMoves();
 while (MovesLeft()) {
  MakeNextMove();
  val = -NegaMax(depth – 1); // 注意这里有个负号。
  UnmakeMove();
  if (val > best) {            //始终最优值
   best = val;
  }
 }
 return best;
}

从这个函数可以看出,这个函数求得始终是当前节点的最优值(即始终求对当前节点的最佳走法),只是当变换节点(即由AI变换到人时),对函数结果取负值,变成人的最佳着法对于AI的评价,这样就省去了求min函数的步骤,减少了代码量

———————————————————————————————————————华丽丽的分割线

三,Alpha-Beta搜索

最小最大运行时要检查整个博弈树,然后尽可能选择最好的线路,但因为分枝因子太大导致效率非常低,无法做到很深的搜索。
Alpha-Beta搜索好处在于裁剪了不必要的分枝因子

举个例子(口袋的例子):
比如你的死敌面前有很多口袋,他和你打赌赌输了,因此他必须从中给你一样东西,而挑选规则却非常奇怪:
  每个口袋里有几件物品,你能取其中的一件,你来挑这件物品所在的口袋,而他来挑这个口袋里的物品。你要赶紧挑出口袋并离开,因为你不愿意一直做在那里翻口袋而让你的死敌盯着你。
  假设你一次只能找一只口袋,在找口袋时一次只能从里面摸出一样东西。

分析:你很容易将最小最大原理运用到这个问题上——你挑出最好的口袋,你死敌从里面挑出最差的物品。所以你的目标是——
挑出在诸多最糟糕物品中最好的物品所在的口袋

假设口袋内物品请况
我们从第一个口袋开始,看每一件物品,并对口袋作出评价。比方说口袋里有一只花生黄油三明治和一辆新汽车的钥匙。你知道三明治更糟,因此如果你挑了这只口袋就会得到三明治。
事实上只要我们假设对手也会跟我们一样正确评价物品,那么口袋里的汽车钥匙就是无关紧要的了。
现在你开始翻第二个口袋,这次你采取的方案就和最小-最大方案不同了。你每次看一件物品,并跟你能得到的最好的那件物品(三明治)去比较。只要物品比三明治更好,那么你就按照最小-最大方案来办——
去找最糟的,或许最糟的要比三明治更好,那么你就可以挑这个口袋,它比装有三明治的那个口袋好。
  比方这个口袋里的第一件物品是一张20美元的钞票,它比三明治好。如果包里其他东西都没比这个更糟了,那么如果你选了这个口袋,它就是对手必须给你的物品,这个口袋就成了你的选择。
    这个口袋里的下一件物品是六合装的流行唱片。你认为它比三明治好,但比20美元差,那么这个口袋仍旧可以选择。再下一件物品是一条烂鱼,这回比三明治差了。于是你就说“不谢了”,把口袋放回去,不再考虑它了。
  无论口袋里还有什么东西,或许还有另一辆汽车的钥匙,也没有用了,因为你会得到那条烂鱼。或许还有比烂鱼更糟的东西(那么你看着办吧)。无论如何烂鱼已经够糟的了,而你知道挑那个有三明治的口袋肯定会更好。

物品情况如图所示

象棋AI算法(一)-风君雪科技博客
排序后如图所示
象棋AI算法(一)-风君雪科技博客
节点2中最小值200,节点3中150<200.而节点1下第一个子节点只有170,小于200,而第二个子节点比170还要小,所以就不用再拿他跟200比较,剪裁,节点4类似,第一个子节点50,后面的就都不用再看了。这里只有alpha剪枝。

  对于一个MIN节点(第二层),若能估计出其倒推值的上确界Beta(170和50),并且这个Beta值不大于MIN的父节点(MAX节点)的估计倒推值的下确界Alpha(200),即Alpha≥Beta,则就不必再扩展该MIN节点的其余子节点(画x的子节点)了,因为这些节点的估值对MIN父节点的倒推值已无任何影响了,这一过程称为Alpha剪枝。

当然还有beta剪枝:

对于一个MAX节点,若能估计出其倒推值的下确界Alpha,并且这个Alpha值不小于MAX的父节点(MIN节点)的估计倒推值的上确界Beta,即Alpha≥Beta,则就不必再扩展该MAX节点的其余子节点了,因为这些节点的估值对MAX父节点的倒推值已无任何影响了。这一过程称为Beta剪枝。

一个MAX节点的Alpha值等于其后继节点当前最大的最终倒推值,一个MIN节点的Beta值等于其后继节点当前最小的最终倒推值

 

算法
搜索中传递两个值,第一个值是Alpha,即搜索到的最好值,体现在if (val > alpha) {alpha = val;}
第二个值是beta,即对于对手来说最坏的值,如果某个着法的结果大于或等于Beta,那么整个结点就作废了

体现在:if (val >= beta) {return beta;}
   
  

代码:
int AlphaBeta(int depth,int alpha,int beta) {
 if (depth == 0) {
  return Evaluate();
 }
 GenerateLegalMoves();
 while (MovesLeft()) {
  MakeNextMove();
  val = -AlphaBeta(depth – 1,-beta, -alpha);      //Alpha和Beta是不断互换的。当函数递归时,Alpha和Beta不但取负                                                //   数而且位置交换了
  UnmakeMove();
  if (val >= beta) {
   return beta;
  }
  if (val > alpha) {
   alpha = val;
  }
 }
 return alpha;
}
把醒目的部分去掉,剩下的就是最小-最大函数。
这个函数需要传递的参数有:需要搜索的深度,负无穷大即Alpha,以及正无穷大即Beta:

可能的弱点:

这个算法严重依赖于着法的寻找顺序。如果你总是先去搜索最坏的着法,那么Beta截断就不会发生,因此该算法就如同最小-最大一样,效率非常低。该算法最终会找遍整个博弈树,就像最小-最大算法一样。 

所以,生成全部着法后,排序很重要~ ~ ~

 

结语:

算法原理方面就到此为止了,比较浅显,大神勿喷~