I have an alpha-beta pruning chess AI that I have implemented using chessjs and chessboardjs. Below is my AI algorithm - it includes move ordering on the first two depths, using the boardScore value at that position as the heuristic, and a hash table of past minimizing and maximizing values for FEN strings at different depths, as a transposition table:
var minAI = function(depth, alpha, beta) {
if (minimaxDict[game.fen() + ' [ ' + depth + ' minimizing ]'])
return minimaxDict[game.fen() + ' [ ' + depth + ' minimizing ]'];
var options = game.ugly_moves();
if (depth <= 0 || options.length === 0)
return boardScore('b', game, depth);
if (maxDepth - 1 === depth) //only perform move ordering on "shallow" iteration
orderMoves(options, depth, false);
var bestMin = 99999;
for (var i = 0; i < options.length; i ++)
{
game.ugly_move(options[i]);
if (game.in_threefold_repetition())
{
game.undo();
continue;
}
bestMin = Math.min(bestMin, maxAI(depth - 1, alpha, beta));
beta = Math.min(beta, bestMin);
game.undo();
if (beta <= alpha)
break;
}
minimaxDict[game.fen() + ' [ ' + depth + ' minimizing ]'] = bestMin;
return bestMin;
}
var maxAI = function(depth, alpha, beta) {
if (minimaxDict[game.fen() + ' [ ' + depth + ' maximizing ]'])
return minimaxDict[game.fen() + ' [ ' + depth + ' maximizing ]'];
var options = game.ugly_moves();
if (depth <= 0 || options.length === 0)
return boardScore('b', game, depth);
var bestMax = -99999;
for (var i = 0; i < options.length; i ++)
{
game.ugly_move(options[i]);
if (game.in_threefold_repetition())
{
game.undo();
continue;
}
bestMax = Math.max(bestMax, minAI(depth - 1, alpha, beta));
alpha = Math.max(alpha, bestMax);
game.undo();
if (beta <= alpha)
break;
}
minimaxDict[game.fen() + ' [ ' + depth + ' maximizing ]'] = bestMax;
return bestMax;
}
var bestAI = function() {
var options = game.ugly_moves();
if (!options.length) return;
orderMoves(options, maxDepth, true);
var bestScore = -99999;
var moveIndex = -1;
var alpha = -99999;
var beta = 99999;
for (i = 0; i < options.length; i++)
{
game.ugly_move(options[i]);
var moveScore = minAI(maxDepth - 1, alpha, beta);
if (moveScore > bestScore)
{
moveIndex = i;
bestScore = moveScore;
alpha = Math.max(alpha, bestScore);
};
game.undo();
if (beta <= alpha)
break;
}
game.ugly_move(options[moveIndex]);
board.position(game.fen());
if (game.in_checkmate())
window.setTimeout(alert("Checkmate!"), 250);
else if (game.in_draw())
window.setTimeout(alert("Draw!"), 250);
else if (game.in_stalemate())
window.setTimeout(alert("Stalemate!"), 250);
else if (game.in_threefold_repetition())
window.setTimeout(alert("Threefold repetition!"), 250);
else if (game.insufficient_material())
window.setTimeout(alert("Draw by insufficient material!"), 250);
}
Here is my game running on maxDepth = 5 after 5 moves, without the transposition table hash being used. At the bottom of the picture are the setTimeout violations, which (though they aren't supposed to be used as timers) show the time it took for each move to be made.
And here is my game with the transposition table hashing:
Comparing the two move calculation durations, it doesn't look like my transposition table is helping too much, and is sometimes slowing me down. Is my implementation of the transposition table incorrect? Is the move ordering algorithm hampering me in some way?