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

    最近想做一个象棋游戏,但是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);      //Alpha和Beta是不断互换的。当函数递归时,Alpha和Beta不但取负                                                //   数而且位置交换了
      UnmakeMove();
      if (val >= beta) {
       return beta;
      }
      if (val > alpha) {
       alpha = val;
      }
     }
     return alpha;
    }
    把醒目的部分去掉,剩下的就是最小-最大函数。
    这个函数需要传递的参数有:需要搜索的深度,负无穷大即Alpha,以及正无穷大即Beta:

    可能的弱点:

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

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

     

    结语:

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

  • 相关阅读:
    微信小程序HTTPS
    微信商城-1简介
    va_list
    Event log c++ sample.
    EVENT LOGGING
    Analyze Program Runtime Stack
    unknow table alarmtemp error when drop database (mysql)
    This application has request the Runtime to terminate it in an unusual way.
    How to check if Visual Studio 2005 SP1 is installed
    SetUnhandledExceptionFilter
  • 原文地址:https://www.cnblogs.com/hanson1/p/7102065.html
Copyright © 2011-2022 走看看