admin管理员组

文章数量:1402839

I'm implementing the Minimax algorithm for a two-player board game using Dart. My approach involves two nested for loops to simulate the maximizing and minimizing players' turns. I've also developed a heuristic evaluation function that considers several key factors to assess the board state. These factors include:

Pawn Count: Weighting the number of pawns each player possesses.

King Count: Prioritizing the acquisition of king pieces.

Center Control: Evaluating the degree to which a player controls the center of the board.

Threatened Pieces: Penalizing positions where a player's pieces are under threat.

Piece Mobility: Rewarding positions with greater piece movement options.

My intention is to create a Minimax implementation that can play reasonably well, aiming for at least a 50% win rate against a basic opponent. While the code executes without errors, the AI's performance is significantly below my expectations. It often makes seemingly poor moves, failing to capitalize on obvious advantages and falling into simple traps.

This method is used to call the minMax and find the best move to play:

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

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

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

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

          // Update best move
          if (eval > bestEval) {
            bestEval = eval;
            bestMovePrev = i;
            bestMoveIndex = move;
          }
        }
      }
    }
    //Restored state to original State before making best move
    piecesPos = List.from(defaultState);

    // Play the best move
    if (bestMovePrev != -1 && bestMoveIndex != -1) {
      //MakeMove
      makeBotMove(bestMovePrev, bestMoveIndex);
    }
  }

This is the minmax code

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;
    }
  }

I'm implementing the Minimax algorithm for a two-player board game using Dart. My approach involves two nested for loops to simulate the maximizing and minimizing players' turns. I've also developed a heuristic evaluation function that considers several key factors to assess the board state. These factors include:

Pawn Count: Weighting the number of pawns each player possesses.

King Count: Prioritizing the acquisition of king pieces.

Center Control: Evaluating the degree to which a player controls the center of the board.

Threatened Pieces: Penalizing positions where a player's pieces are under threat.

Piece Mobility: Rewarding positions with greater piece movement options.

My intention is to create a Minimax implementation that can play reasonably well, aiming for at least a 50% win rate against a basic opponent. While the code executes without errors, the AI's performance is significantly below my expectations. It often makes seemingly poor moves, failing to capitalize on obvious advantages and falling into simple traps.

This method is used to call the minMax and find the best move to play:

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

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

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

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

          // Update best move
          if (eval > bestEval) {
            bestEval = eval;
            bestMovePrev = i;
            bestMoveIndex = move;
          }
        }
      }
    }
    //Restored state to original State before making best move
    piecesPos = List.from(defaultState);

    // Play the best move
    if (bestMovePrev != -1 && bestMoveIndex != -1) {
      //MakeMove
      makeBotMove(bestMovePrev, bestMoveIndex);
    }
  }

This is the minmax code

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;
    }
  }
Share Improve this question edited Mar 23 at 15:17 desertnaut 60.5k32 gold badges155 silver badges181 bronze badges asked Mar 21 at 8:05 Franklyn OrebenFranklyn Oreben 3083 silver badges15 bronze badges 1
  • Disable the alpha-beta pruning entirely and let it run a pure brute force at modest search depths to establish that the fault isn't actually in the evaluation function that isn't shown here. I'm fairly convinced there is a serious error in the alpha-beta pruning code see below... – Martin Brown Commented Mar 29 at 19:28
Add a comment  | 

2 Answers 2

Reset to default 0

It don't speak Dart but I think I can follow the syntax well enough to have a good chance of suggesting a correction to the above minMax code that I think ought to work. Obviously I can't test it.

I think the computation of alpha/beta is using the wrong eval value at least some of the time. I think it should be testing against and replacing the cutoff values with the most recent maxEval/minEval. Trivial modification and hard to spot but should make a big difference.


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);      // should be maxEval
            alpha = max(alpha, maxEval); 

            // 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);  // should be minEval
            beta = min(beta, minEval);

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

Rather than have two copies of what is essentially the same code I prefer to use the convention that side = 1 or -1 and a variant of alpha-beta algorithm that is now called [Negascout](https://en.wikipedia./wiki/Negamax). Exploiting the simplification to have code that only implements maximise eval and then using the identity that min(a, b) = -max(-b, -a)

I had to save the board state for each move made when Minimax was called and analyze them individually. This allowed me to track the moves and notice that the board state was not being updated correctly. I’ve now resolved the issue. The problem was related to how I was passing my board state (piecesPos). I was retrieving and passing the wrong board state, which caused Minimax to make incorrect or suboptimal moves. Thank you all for your contributions; it is greatly appreciated.

Renaming to piecesPosCopy and using piecesPos

This was getting the actual board state to use when min max is called.

int minMax(List<String> piecesPosCopy, 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
            performMultitakeAnim = false;
            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
            performMultitakeAnim = false;
            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;
    }
  }

本文标签: dartMinimax Implementation with Heuristic Unexpectedly Poor PerformanceStack Overflow