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截断就不会发生,因此该算法就如同最小-最大一样,效率非常低。该算法最终会找遍整个博弈树,就像最小-最大算法一样。 

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

     

    结语:

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

  • 相关阅读:
    数独高阶技巧入门之六——ALS
    数独高阶技巧入门之七——AIC & Nice Loop
    数独-链的理解顺序
    数独高阶技巧入门之三——Fish
    数独·唯一性技巧(Uniqueness)-2
    游戏剧本从入门到放弃
    Electron和NW.js入门笔记
    Spring boot Access-Control-Allow-Origin 问题解决
    Materialize -- 基于Material Design的主流前端响应式框架
    Ubuntu 安装 nvm
  • 原文地址:https://www.cnblogs.com/hanson1/p/7102065.html
Copyright © 2011-2022 走看看