Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a problem with my Negamax algorithm with alpha-beta pruning?

I'm trying to build a chess AI. My negamax function with alpha-beta pruning (ABP) runs much slower (about 8 times) than separate min and max functions also with ABP, though the moves returned are equal.

My board evaluation function always returns a value with respect to the red player, i.e. the higher the better for red. For Negamax only, this value is multiplied by -1 for the black player when returning at depth 0.

My Negamax function:

int alphaBeta(Board board, int depth, int alpha, int beta) {
    if (depth <= 0 || board.isGameOver()) { // game over == checkmate/stalemate
        int color = board.getCurrPlayer().getAlliance().isRed() ? 1 : -1;
        return BoardEvaluator.evaluate(board, depth) * color;
    }

    int bestVal = Integer.MIN_VALUE + 1;
    for (Move move : MoveSorter.simpleSort(board.getCurrPlayer().getLegalMoves())) {
        MoveTransition transition = board.getCurrPlayer().makeMove(move);
        if (transition.getMoveStatus().isAllowed()) { // allowed == legal && non-suicidal
            int val = -alphaBeta(transition.getNextBoard(), depth - 1, -beta, -alpha);
            if (val >= beta) {
                return val; // fail-soft
            }
            if (val > bestVal) {
                bestVal = val;
                alpha = Math.max(alpha, val);
            }
        }
    }        

    return bestVal;
}

The root call:

-alphaBeta(transition.getNextBoard(), searchDepth - 1,
                        Integer.MIN_VALUE + 1, Integer.MAX_VALUE); // +1 to avoid overflow when negating

My min and max functions:

int min(Board board, int depth, int alpha, int beta) {
    if (depth <= 0 || board.isGameOver()) {
        return BoardEvaluator.evaluate(board, depth);
    }

    int minValue = Integer.MAX_VALUE;
    for (Move move : MoveSorter.simpleSort(board.getCurrPlayer().getLegalMoves())) {
        MoveTransition transition = board.getCurrPlayer().makeMove(move);
        if (transition.getMoveStatus().isAllowed()) {
            minValue = Math.min(minValue, max(transition.getNextBoard(), depth - 1, alpha, beta));
            beta = Math.min(beta, minValue);
            if (alpha >= beta) break; // cutoff
        }
    }

    return minValue; 
}

int max(Board board, int depth, int alpha, int beta) {
    if (depth <= 0 || board.isGameOver()) {
        return BoardEvaluator.evaluate(board, depth);   
    }

    int maxValue = Integer.MIN_VALUE;
    for (Move move : MoveSorter.simpleSort(board.getCurrPlayer().getLegalMoves())) {
        MoveTransition transition = board.getCurrPlayer().makeMove(move);
        if (transition.getMoveStatus().isAllowed()) {
            maxValue = Math.max(maxValue, min(transition.getNextBoard(), depth - 1, alpha, beta));
            alpha = Math.max(alpha, maxValue);
            if (alpha >= beta) break; // cutoff
        }
    }

    return maxValue;
}

The root calls for red and black players respectively:

min(transition.getNextBoard(), searchDepth - 1, Integer.MIN_VALUE, Integer.MAX_VALUE);
max(transition.getNextBoard(), searchDepth - 1, Integer.MIN_VALUE, Integer.MAX_VALUE);

I'm guessing there's a bug with the cutoff in the Negamax function although I followed the pseudocode from here. Any help is appreciated, thanks!

EDIT: alphaBeta() is called about 6 times more than min() and max() combined, while the number of beta cutoffs is only about 2 times more.

like image 636
wayne Avatar asked Oct 18 '25 13:10

wayne


1 Answers

Solved. I should have posted my full code for the root calls as well -- didn't realise I wasn't passing in the new value for beta. Alpha/beta was actually being updated in the root method for separate min-max.

Updated root method for Negamax:

Move bestMove = null;
int bestVal = Integer.MIN_VALUE + 1;

for (Move move : MoveSorter.simpleSort(currBoard.getCurrPlayer().getLegalMoves())) {
    MoveTransition transition = currBoard.getCurrPlayer().makeMove(move);
    if (transition.getMoveStatus().isAllowed()) {
        int val = -alphaBeta(transition.getNextBoard(), searchDepth - 1, Integer.MIN_VALUE + 1, -bestVal);
        if (val > bestVal) {
            bestVal = val;
            bestMove = move;
        }
    }
}

return bestMove;

Apologies for the lack of information provided in my question -- I didn't expect the bug to be there.

like image 172
wayne Avatar answered Oct 21 '25 03:10

wayne