象棋ai算法(极小-极大值搜索、负极大值函数、Alpha-Beta剪枝算法、渴望算法)

ai的思考过程是怎样的?自然就是遍历所有的可能,找出相对最好的一种着法。我们首先要实现这个功能,之后再优化算法,使得效率更高。

本文介绍的算法:

极小-极大值搜索

负极大值函数

Alpha-Beta剪枝算法

渴望算法

极小-极大值搜索:

轮到ai下棋时,它首先会思考全部可能的着法。然后还要思考每一种着法下玩家全部可能的着法,然后思考玩家全部可能的着法下ai全部可能的着法,如此类推直到达到设定的搜索深度。然后对此时的棋局进行评分。然后根据评分选择合适着法。

但要注意:并非是最终哪个棋局评分最高ai就要选择对应路线的那步着法,因为是ai、玩家交替下棋的。我们让ai假定在玩家层玩家会选择对自己最有利即对ai最不利的着法(即评分最低)。如此从对棋局评分那层再层层倒推,最终得到ai这步最好的着法。

如下图表示一个搜索深度为4的搜索,每一个节点代表一个局面。每一个节点由上一个节点下完一步后产生。其中圆节点代表是ai下棋,ai的不同着法产生多个方节点。方节点又因玩家的不同着法产生多个圆节点。叶节点则不再下,而是进行评分。除叶节点外的节点不直接评分,而是通过子节点得到评分。(当然,实际的节点数远多于图示)

对于下图的第3层,由于是玩家层,玩家会选择对ai最不利的着法。其子节点是由玩家不同着法产生的局面,所以玩家选择最低评分的子节点对应的着法。而方节点的评分便是其子节点中评分最低的那个节点的评分。

 同样,ai会选择其子节点中评分最高的节点对应的着法。其评分也是其最高评分的子节点对应的评分。

 通过反复最小、最大值的选择,最终得到顶层圆节点对应的着法,即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;//返回该节点评分

}

 

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;

}

使用Max(4)可以调用以上方法进行深度为4的搜索。

负极大值函数:

不难发现最大值函数和最小值函数高度相仿,只是初始值相反和比较时使用大于号和小于号。所以这二个函数完全能合并成一个函数。只是在递归时的返回值取负。(在使用负极大值函数和之后介绍的算法时,对叶节点评分时需要考虑这是玩家层还是ai层,同样的棋局对玩家来说和对ai来说评分是相反的)

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;

}

Alpha-Beta剪枝算法:

假设你要从对方多个口袋中挑选一件物品拿走。规则是你挑选一个口袋,对方从该口袋中选择一样给你的物品。你希望得到尽量好的物品,对方希望给尽量差的。也就是你要挑选最合适的口袋,对方挑选出其中最差的。

当查看第一个口袋时,有价值5、8、10的物品。当查看第2个口袋时,看到一件价值3的物品,低于价值5。那么第二个口袋余下物品就没有查看价值了。

如下图。对于右边玩家方节点,只有值大于2才可能取代根节点的2。但子节点已经有一个1,由于玩家节点会选择最小评分的子节点作为自身评分,自身最大只可能为1。所以余下节点已经没有计算的必要。

Alpha-Beta剪枝用于裁剪搜索树中没有意义的不需要搜索的树枝,以提高运算速度。假设α为下界,β为上界,对于α ≤ N ≤ β:

若 α ≤ β 则N有解。(该节点还有继续计算的价值)

若 α > β 则N无解。(停止该节点剩余内容的计算,也不会再更新α、β值)

α-β初始为负无穷到正无穷,算法过程中不断的缩小α-β范围。Max层增加本节点的α,Min层减小本节点的β。

一个节点的初始α、β值来自于父节点。父节点如果还没有相应值,子节点的相应值则是初始的正负无穷。

对于ai圆节点(Max层),希望挑选子节点中的最大节点。所以会更新最小值α(因为每得到一个符合条件的更大的值时,以后的新值必须更大才会被选择)。新的最小值α来自于已经计算的子节点(中的最大值)。但最大不能大于于父节点的β。

对于玩家方节点(Min层),希望挑选子节点中的最小节点。所以会更新最大值β。新的最大值β来自于已经计算的子节点(中的最小值)。但最小不能小于父节点的α。 

极大极小算法的剪枝算法:

int Max(int depth, int alpha, int beta)

{

    int best = -INFINITY;//横线表示原极大极小值算法有,这里删除

    if (depth <= 0)

    {

        return Evaluate();

    }

    GenerateLegalMoves();

    while (MovesLeft())

    {

        MakeNextMove();

        val = Min(depth - 1, int alpha, int beta);

        UnmakeMove();

        if (val > beta)

        {

            return beta;

        }

        if (val > alpha)

        {

            alpha = val;

        }

    }

    return best;

}

int Min(int depth, int alpha, int beta)

{

    int best = INFINITY;

    if (depth <= 0)

    {

        return Evaluate();

    }

    GenerateLegalMoves();

    while (MovesLeft())

    {

        MakeNextMove();

        val = Max(depth - 1);

        UnmakeMove();

        if (val < alpha)

        { 

            return alpha;

        }

        if (val < beta)

        { 

            beta = val;

        }

    }

    return best;

}

负值最大函数的剪枝算法:

搜索中传递两个值,第一个值是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剪枝算法,剪掉的枝较多时则效率较高,当剪掉的枝较少时则效率较低。如何能剪掉更多的枝呢?渴望算法就是对Alpha-Beta剪枝算法的进一步优化。

渴望算法:

在Alpha-Beta剪枝算法中,我们不断的缩小α- β的范围,最终得到需要的值。如果一开始就知道最终的值,就可以大大提高剪枝的效率。当然这是不可能的。但我们可以设定一个不太大的范围,并使这个范围有较大概率包含最终值。这样我们可以根据这个范围进行剪枝从而大大提高效率。

步骤:

1:进行一次深度为N-1的搜索(N为需要的搜索深度),得到结果X

2:  得到值可能的范围(X-w/2,X+w/2),w为设定的窗口宽度。

3:根据范围进行剪枝搜索,并对值超出范围的情况进行处理。

下例中的current的作用:普通的Alpha-Beta剪枝算法是先判断是否发生了Alpha>Beta,如果发生了就进行剪枝并且此时不会更新Alpha、Beta的值。而渴望算法则是在剪枝前先用current记录下了Alpha。如果最终该着法被选中,但current却超过了选定的范围,则证明选定的范围不合理,需要选一个新范围。

代码:

void AspirationSearch()

{

    int X;//n-1层的最佳得分

    int current;//用于判断当前所选区域是否合适

    X = FAlphaBeta(N - 1, -INFINITY, INFINITY);

    current = FAlphaBeta(N, X - 50, X + 50);//这里窗口范围设置为了100,可根据自己需要定

    if (current < X - 50)//如果current小于窗口最小值,设(-INFINITY, X - 50)为新范围

    {

        FAlphaBeta(searchDepth, -INFINITY, X - 50);

    }

    else if (current > X + 50)//如果current大于窗口最大值,设(X + 50, INFINITY)为新范围

    {

        FAlphaBeta(searchDepth, X + 50, INFINITY);

    }

}

int FAlphaBeta(int depth, int alpha, int beta)

{

    int current = -INFINITY;//从负无穷变大,记录最大值。用于和假定的窗口比较,判断当前所选区域是否合适

    if (depth <= 0)

    {

        return Eveluate();//达到搜索深度时进行评分,这个例子忽略了未达搜索深度将帅就死亡的情况

    }

    GenerateLegalMoves();//得到该棋局所有可能下法

    while (MovesLeft())//遍历全部着法

    {

        val = -FAlphaBeta(depth - 1, -beta, -alpha);

        if (val > current)

        {

            current = val;

            if (val > alpha)

            {

                alpha = val;

                if (depth == searchDepth)

                {

                    bestMove = gameManager.movingOfPiece.moveList[depth, i];

                }

            }

            if (alpha >= beta)

            {

                break;

            }

        }

    }

    return current;

}

猜你喜欢

转载自blog.csdn.net/qq_21315789/article/details/125119140
今日推荐