zoukankan      html  css  js  c++  java
  • [转]象棋AI算法(一)

    本文转自:http://blog.csdn.net/u012723995/article/details/47133693

    参考文献:http://www.xqbase.com/computer/search_minimax.htm

    http://www.xqbase.com/computer/search_alphabeta.htm

    最近想做一个象棋游戏,但是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美元差,那么这个口袋仍旧可以选择。再下一件物品是一条烂鱼,这回比三明治差了。于是你就说“不谢了”,把口袋放回去,不再考虑它了。   无论口袋里还有什么东西,或许还有另一辆汽车的钥匙,也没有用了,因为你会得到那条烂鱼。或许还有比烂鱼更糟的东西(那么你看着办吧)。无论如何烂鱼已经够糟的了,而你知道挑那个有三明治的口袋肯定会更好。
    物品情况如图所示
    排序后如图所示 节点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);      //AlphaBeta是不断互换的。当函数递归时,AlphaBeta不但取负                                                 //   数而且位置交换了   UnmakeMove();   if (val >= beta) {    return beta;   }   if (val > alpha) {    alpha = val;   }  }  return alpha; } 把醒目的部分去掉,剩下的就是最小-最大函数。 这个函数需要传递的参数有:需要搜索的深度,负无穷大即Alpha,以及正无穷大即Beta:

    可能的弱点:

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

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

    结语:

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

  • 相关阅读:
    洛谷【P1109 学生分组】 题解
    卡特兰数
    并查集
    深度优先搜索DFS;递归
    【71】序列模型和注意力机制
    c/c++ 常用的几个安全函数
    win32 Ui 编程 收集
    vc获取特殊路径(SpecialFolder)
    std::map 自定义排序
    16-----BBS论坛
  • 原文地址:https://www.cnblogs.com/freeliver54/p/6898377.html
Copyright © 2011-2022 走看看