I'm implementing a search algorithm into the search function with Negamax with alpha-beta pruning. However, it often misses forced checkmate.
(Note: "Mate in X" counts whole turns, while "depth" and "move(s)" relies on half moves.)
The position with the following FEN: 1k1r4/pp1b1R2/3q2pp/4p3/2B5/4Q3/PPP2B2/2K5 b - - 0 1
has a Mate in 3 (depth of 5 to the algorithm).
It goes Qd1+, Kxd1, Bg4+, Kc1/Ke1 (Doesn't matter), Rd1#.
It can spot the checkmate from 1 move away, but fails at higher depths.
It could be a typo, a misused type
, or even a complete misunderstanding of the method, as all of it happened before.
I've make some part of the code code easier to read. (eg. remove std::
, turns multiple lines into function).
Shouldn't changes the functionalities though.
pieceMove searchBestMove (gameState currentState, int depth) {
//Calls the Negamax search
pieceColor sideToMove = whoseTurnIsIt();
vector<pieceMove> moveList = generateLegalMoves(currentState, sideToMove);
pieceMove bestMove;
signed int bestEval = numeric_limits<signed int>::max();
for (const auto move : moveList) {
signed int evaluation = negaMax(applyMove(currentState, move), numeric_limits<signed int>::min(), numeric_limits<signed int>::max(), depth - 1, 1);
if (evaluation < bestEval) {
bestMove = move;
bestEval = evaluation;
}
}
return bestMove;
}
signed int negaMax (gameState currentState, signed int alpha, signed int beta, int depth, int rootDepth) {
//Main Negamax search
//Terminal node
if (depth == 0) {
return evaluates(currentState); //Replace this line with the one below to enable the extended search
//return quiescenceSearch(currentState, alpha, beta);
}
//Mate distance pruning
signed int mateDistScore = numeric_limits<signed int>::max() - rootDepth;
alpha = max(alpha, -mateDistScore);
beta = min(beta, mateDistScore - 1);
if (alpha >= beta) return alpha;
vector<pieceMove> moveList = generateLegalMoves(currentState);
//If no moves are allowed, then it's either checkmate or stalemate
if (moveList.size() == 0) return evaluates(currentState)
orderMoves(currentState, moveList);
for (const auto move : moveList) {
signed int score = -negaMax(applyMove(currentState, move), -beta, -alpha, depth - 1, rootDepth + 1);
if (score >= beta) return beta; //Bata cutoff
alpha = max(score, alpha);
}
return alpha;
}
signed int quiescenceSearch (gameState currentState, signed int alpha, signed int beta) {
//Searches only captures
//Terminal node
int evaluation = evaluates(currentState);
if (evaluation >= beta) return beta;
alpha = max(alpha, evaluation);
vector<pieceMove> moveList = generateCaptureMoves(currentState);
//If no moves are allowed, then it's either checkmate or stalemate
if (moveList.size() == 0) return evaluates(currentState);
orderMoves(currentState, moveList);
for (const auto move : moveList) {
signed int score = -quiescenceSearch(applyMove(currentState, move), -beta, -alpha);
if (score >= beta) return beta; //Bata cutoff
alpha = max(score, alpha);
}
return alpha;
}
I think you need to call the function "quiescenceSearch" when the depth is 0 in "negaMax". Also you need to check for "checks" too in "quiescenceSearch" along with captures since they are not quiet moves. Also Matedistance pruning works only when positions are properly scored(https://www.chessprogramming.org/Mate_Distance_Pruning#Mating_Value). May be checking if your evaluation function is evaluating properly could also help.