You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Minimax算法异常求助:首步固定选择、搜索深度适配错误

棋类AI Minimax算法异常问题求助

我在开发棋类AI时遇到以下Minimax算法异常问题:

  • 当己方棋子无威胁时,算法总是选择首个可用走法,导致行为可预测
  • 调整搜索深度后出现新问题:深度设为4时maxEval恒为9999,深度为3时算法会选择极小化玩家的棋子作为最优走法

以下是相关代码:

Min Max方法

int minMax(List<String> piecesPos, int depth, bool isMaximizing, int alpha, int beta) {
    // Base case: if depth is 0 or the game is over, return the evaluation
    if (depth == 0 || isGameOver(piecesPos)) {
      return evaluateBoard(piecesPos);
    }

    if (isMaximizing) {
      int maxEval = -9999; // Initialize to a very low value
      for (int i = 0; i < piecesPos.length; i++) {
        if (piecesPos[i][0] == "B" || piecesPos[i][0] == "O") {
          List<int> possibleMoves = getPossibleMoves(piecesPos, i);
          for (int move in possibleMoves) {
            // Save the current state
            List<String> saveState = List.from(piecesPos);

            // Make the move
            makeMove(piecesPos, i, move);

            // Recursive call
            int eval = minMax(piecesPos, depth - 1, false, alpha, beta);

            // Restore the state
            piecesPos = List.from(saveState);

            // Update maxEval
            maxEval = max(maxEval, eval);
            alpha = max(alpha, eval);

            // Alpha-Beta Pruning
            if (beta <= alpha) {
              break;
            }
          }
        }
      }
      return maxEval;
    } else {
      int minEval = 9999; // Initialize to a very high value
      for (int i = 0; i < piecesPos.length; i++) {
        if (piecesPos[i][0] == "W" || piecesPos[i][0] == "Q") {
          List<int> possibleMoves = getPossibleMoves(piecesPos, i);
          for (int move in possibleMoves) {
            // Save the current state
            List<String> saveState = List.from(piecesPos);

            // Make the move
            makeMove(piecesPos, i, move);

            // Recursive call
            int eval = minMax(piecesPos, depth - 1, true, alpha, beta);

            // Restore the state
            piecesPos = List.from(saveState);

            // Update minEval
            minEval = min(minEval, eval);
            beta = min(beta, eval);

            // Alpha-Beta Pruning
            if (beta <= alpha) {
              break;
            }
          }
        }
      }
      return minEval;
    }
  }

最优走法调用逻辑

void playBestMove(List<String> piecesPosCopy, int depth) {
    int bestEval = -9999;
    int bestMovePrev = -1;
    int bestMoveIndex = -1;
    // Save the current state before call
    List<String> defaultState = List.from(piecesPos);

    for (int i = 0; i < piecesPos.length; i++) {
      if (piecesPos[i][0] == "B" || piecesPos[i][0] == "O") {
        List<int> possibleMoves = getPossibleMoves(piecesPos, i);
        for (int move in possibleMoves) {
          // Save the current state
          List<String> saveState = List.from(piecesPos);

          // Make the move
          makeMove(piecesPos, i, move);

          // Evaluate the move
          int eval = minMax(piecesPos, depth - 1, false, -9999, 9999);

          // Restore the state
          piecesPos = List.from(saveState);

          // Update best move
          if (eval > bestEval) {
            bestEval = eval;
            bestMovePrev = i;
            bestMoveIndex = move;
          }
        }
      }
    }
    print("BBB The best max is $bestEval prev is $bestMovePrev and index is $bestMoveIndex");
    //Restored state to original State before making best move
    piecesPos = List.from(defaultState);

    // Play the best move
    if (bestMovePrev != -1 && bestMoveIndex != -1) {
      //makeMove(piecesPos, bestMovePrev, bestMoveIndex);
      recentlyCrowned = false;
      if(animatePieceMovement){performMultitakeAnim = true;}
      if(!vsComputer){
        undoMove = List.from(piecesPos);
        saveMovesState.add(undoMove);
      }
      //Allow to check for Win, loose and draw.
      checkForWinLooseDraw = true;
      //MakeMove
      makeBotMove(bestMovePrev, bestMoveIndex);
    }
  }

棋盘评估方法

int evaluateBoard(List<String> piecesPos) {
    // Implement the evaluation function
    // This function should return a score based on the current state of the board
    // For example, the difference in the number of pieces between the two players
    int totalMyPiece = 0;
    int totalOppPiece = 0;

    //Piece Weight
    for (int i = 0; i < piecesPos.length; i++) {
      if (piecesPos[i][0] == "W"){
        totalOppPiece = totalOppPiece + 10;
      }else
      if(piecesPos[i][0] == "Q"){
        totalOppPiece = totalOppPiece + 20;
      }else
      if (piecesPos[i][0] == "B"){
        totalMyPiece = totalMyPiece + 10;
      }else
      if(piecesPos[i][0] == "O"){
        totalMyPiece = totalMyPiece + 20;
      }
    }
    // center control
    for(int b = 0; b < centerControlArea.length; b++){
      if (piecesPos[centerControlArea[b]][0] == "W" || piecesPos[centerControlArea[b]][0] == "Q"){
        totalOppPiece = totalOppPiece + 3;
      }
    }
    // Threats (pieces under attack)
    totalOppPiece = totalOppPiece - squaresWithTakes_OPP.length * 5;

    // center control
    for(int b = 0; b < centerControlArea.length; b++){
      if (piecesPos[centerControlArea[b]][0] == "B" || piecesPos[centerControlArea[b]][0] == "O"){
        totalMyPiece = totalMyPiece + 3;
      }
    }
    // Threats (pieces under attack)
    totalMyPiece = totalMyPiece - squaresWithTakes.length * 5;

    print("VVV $totalMyPiece");
    print("VVV $totalOppPiece");

    return totalMyPiece - totalOppPiece;
  }

问题分析与修复方案

1. 无威胁时固定选择首个走法

原因:playBestMove中仅当eval > bestEval时更新最优走法,多个走法评估分值相同时,只会保留第一个遇到的走法。
修复:将判断条件改为eval >= bestEval,或在分值相同时随机选择走法增加多样性:

// Update best move
if (eval >= bestEval) {
  // 分值相同时随机替换,避免固定行为
  if (eval == bestEval && Random().nextBool()) {
    bestEval = eval;
    bestMovePrev = i;
    bestMoveIndex = move;
  } else if (eval > bestEval) {
    bestEval = eval;
    bestMovePrev = i;
    bestMoveIndex = move;
  }
}

2. 深度4时maxEval恒为9999

排查方向:

  • 检查isGameOver函数,确认深度4递归时是否错误判定游戏结束,且对应结束状态的评估分值为9999
  • 检查evaluateBoard是否存在未处理的边界情况,导致返回极端值9999
  • 补充无合法走法的处理逻辑:当max玩家无走法时,应返回极低分而非初始值:
if (isMaximizing) {
  int maxEval = -9999;
  bool hasValidMoves = false;
  for (int i = 0; i < piecesPos.length; i++) {
    if (piecesPos[i][0] == "B" || piecesPos[i][0] == "O") {
      List<int> possibleMoves = getPossibleMoves(piecesPos, i);
      for (int move in possibleMoves) {
        hasValidMoves = true;
        // 原有逻辑...
      }
    }
  }
  // 无合法走法时返回极低分,表示己方劣势
  return hasValidMoves ? maxEval : -9999;
}

3. 深度3时选择极小化玩家棋子

原因:评估函数中威胁计算逻辑可能混淆了己方与对方的威胁状态
修复:确认squaresWithTakes是己方被威胁的棋子集合,squaresWithTakes_OPP是对方被威胁的棋子集合,调整评估逻辑:

// 己方棋子被威胁,扣分
totalMyPiece = totalMyPiece - squaresWithTakes.length * 5;
// 对方棋子被威胁,己方加分(或对方扣分)
totalOppPiece = totalOppPiece - squaresWithTakes_OPP.length * 5;

同时验证minMax中玩家角色对应关系:isMaximizing=true对应己方(B/O),需最大化totalMyPiece - totalOppPiece;isMaximizing=false对应对方(W/Q),需最小化该值。

内容的提问来源于stack exchange,提问作者Franklyn Oreben

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 16:22:01