I am currently testing null move pruning in my engine and it seems a bit odd to me that it gives me worse results (around -17elo in a 1k game match against the version without null move pruning) when I use it.
It is very weird, especially considering that everyone says it's probably the most important pruning technique.
I should also mention that I tried tweaking the reduction depth and the material threshold, but it performs even worse than this.
Here's a the code of my alphaBeta function:
Code: Select all
int alphaBeta(int alpha, int beta, short depth, short ply, bool doNull) {
assert(depth >= 0);
int pvIndex = ply * (2 * N + 1 - ply) / 2;
int pvNextIndex = pvIndex + N - ply;
memset(pvArray + pvIndex, NO_MOVE, sizeof(int) * (pvNextIndex - pvIndex));
if(!(nodesSearched & 4095) && !infiniteTime) {
long long currTime = chrono::duration_cast<chrono::milliseconds>(chrono::system_clock::now().time_since_epoch()).count();
if(currTime >= stopTime) timeOver = true;
}
if(timeOver) return 0;
nodesSearched++;
int hashFlag = HASH_F_ALPHA;
bool isInCheck = board.isInCheck();
if(isInCheck) depth++;
// --- MATE DISTANCE PRUNING ---
int matedScore = - MATE_EVAL + ply;
if(alpha < matedScore) {
alpha = matedScore;
if(beta <= matedScore) return matedScore;
}
int mateScore = MATE_EVAL - ply;
if(beta > mateScore) {
beta = mateScore;
if(alpha >= mateScore) return mateScore;
}
if(alpha >= beta) return alpha;
if(board.isDraw()) return 0;
bool isPV = (beta - alpha > 1);
// retrieving the hashed move and evaluation if there is any
int hashScore = probeHash(depth, alpha, beta);
if(hashScore != VAL_UNKNOWN) {
// we return hashed info only if it is an exact hit in pv nodes
if(!isPV || (hashScore > alpha && hashScore < beta)) {
return hashScore;
}
}
int moves[256];
int num = board.generateLegalMoves(moves);
if(num == 0) {
if(isInCheck) return -mateScore; // checkmate
return 0; // stalemate
}
if(depth <= 0) {
int q = quiesce(alpha, beta);
if(timeOver) return 0;
return q;
}
int currBestMove = NO_MOVE;
// --- NULL MOVE PRUNING ---
const int ENDGAME_MATERIAL = 16; // this is equivalent to all pieces on the table except the queens
if(doNull && (!isPV) && (isInCheck == false) && ply && (depth >= 3) && (gamePhase() >= ENDGAME_MATERIAL) && (evaluate() >= beta)) {
board.makeMove(NO_MOVE);
short R = 2;
int score = -alphaBeta(-beta, -beta + 1, depth - R - 1, ply + 1, false);
board.unmakeMove(NO_MOVE);
if(timeOver) return 0;
if(score >= beta) return beta;
}
int movesSearched = 0;
sortMoves(moves, num, ply);
for(int idx = 0; idx < num; idx++) {
if(alpha >= beta) return alpha;
board.makeMove(moves[idx]);
// --- PRINCIPAL VARIATION SEARCH ---
int score;
if(!movesSearched) {
score = -alphaBeta(-beta, -alpha, depth-1, ply+1, true);
} else {
// --- LATE MOVE REDUCTION ---
if(movesSearched >= 2 && !isCapture(moves[idx]) && !isPromotion(moves[idx]) && !isInCheck && depth >= 3 && !board.isInCheck()) {
int reductionDepth = int(sqrt(double(depth-1)) + sqrt(double(movesSearched-1)));
if(isPV) reductionDepth = (reductionDepth * 2) / 3;
reductionDepth = (reductionDepth < depth-1 ? reductionDepth : depth-1);
score = -alphaBeta(-alpha-1, -alpha, depth - reductionDepth - 1, ply+1, true);
} else {
score = alpha + 1; // hack to ensure that full-depth search is done
}
if(score > alpha) {
score = -alphaBeta(-alpha-1, -alpha, depth-1, ply+1, true);
if(score > alpha && score < beta) {
score = -alphaBeta(-beta, -alpha, depth-1, ply+1, true);
}
}
}
board.unmakeMove(moves[idx]);
movesSearched++;
if(timeOver) return 0;
if(score > alpha) {
currBestMove = moves[idx];
pvArray[pvIndex] = moves[idx];
copyPv(pvArray + pvIndex + 1, pvArray + pvNextIndex, N - ply - 1);
assert(pvArray[pvIndex] != NO_MOVE);
assert(pvIndex < pvNextIndex);
assert(pvNextIndex < (N * N + N) / 2);
if(score >= beta) {
recordHash(depth, beta, HASH_F_BETA, currBestMove);
if(!isCapture(moves[idx]) && !isPromotion(moves[idx])) {
storeKiller(ply, moves[idx]);
updateHistory(moves[idx], depth);
}
return beta;
}
hashFlag = HASH_F_EXACT;
alpha = score;
}
}
if(timeOver) return 0;
recordHash(depth, alpha, hashFlag, currBestMove);
return alpha;
}