chess-ai-kong
Version:
Chess AI based on Alpha-Beta pruning algorithm.
200 lines (171 loc) • 6.36 kB
JavaScript
;
var chessRules = require('chess-rules');
//Search
var alphaBetaData = require('./alpha-beta-data');
var AlphaBetaData = alphaBetaData.AlphaBetaData;
var sorter = require('./quick-sort');
//Evaluation
var evaluatorMoves = require('./../evaluation/evaluator-moves');
var evaluatorBoard = require('./../evaluation/evaluator-board');
//Monitoring
var _monitor = require('./../monitoring/monitoring');
//Settings
var aiDepth = 3;
var aiTimeout = 10000;
var aiStrategy = 'basic';
/**
* Set the timeout around which the search shall return a move.
*
* @param timeout The timeout in millisecond
*/
function setTimeout(timeout) {
if(timeout === undefined || typeof timeout != 'number' || timeout%1 != 0) {
throw new Error('timeout value type!');
} else {
aiTimeout = timeout;
}
}
/**
* Set the strategy to use in the evaluation.
* @param strategyName The strategy name ('basic' by default, 'random')
*/
function setStrategy(strategyName) {
if(strategyName === undefined || typeof strategyName !== 'string') {
throw new Error('strategy value type!');
} else {
aiStrategy = strategyName;
}
}
/**
* Set the depth of the alpha-beta algorithm.
* @param depth The depth (2 by default)
*/
function setDepth(depth) {
if(depth === undefined || typeof depth != 'number' || depth%1 != 0) {
throw new Error('depth value type!');
} else {
aiDepth = depth;
}
}
/**
* Get the AI next move for the position passed in, this method follow the alpha beta max algorithm.
* @param position The position and AI turn
* @returns {*} The move
*/
function getNextMove(position) {
_monitor.clear();
_monitor.startWatch('getNextMove');
_monitor.startWatch('setup');
var alpha = -1000000;
var beta = 1000000;
var bestMove = null;
var startTime = new Date().getTime();
//Initialize the data for AlphaBeta Search
_monitor.stopWatch('setup');
//Get the available moves
_monitor.startWatch('availableMoves');
var availableMoves = chessRules.getAvailableMoves(position);
_monitor.stopWatch('availableMoves');
//Evaluate the moves
var evaluatedMoves = evaluatorMoves.evaluateMoves(availableMoves, position, aiStrategy);
//Order moves to enhance pruning
evaluatedMoves = sorter.sortMoves(evaluatedMoves);
evaluatedMoves.some(function (move) {
var _path;
if(_monitor.isEnabled()) {
_monitor.startWatch('pgn');
_path = chessRules.moveToPgn(position, move.move);
_monitor.stopWatch('pgn');
}
_monitor.startWatch('applyMove');
var nextPosition = chessRules.applyMove(position, move.move);
_monitor.stopWatch('applyMove');
//var score = alphaBetaMin(nextPosition, alpha, beta, aiDepth - 1,
// new AlphaBetaData(_pgn, startTime));
var score = -alphaBeta(nextPosition, -beta, -alpha, aiDepth - 1,
new AlphaBetaData(_path, startTime));
//Use of alpha-beta max for the first step
if(score >= beta) {
//Cut-off
_monitor.addCutoffNode(_path, alpha, beta, 'max', score);
alpha = beta;
_monitor.stopWatch('return');
return true;
}
if(score > alpha) {
//we have found a better best move (a new max)
alpha = score;
bestMove = move;
}
_monitor.stopWatch('return');
return false;
});
_monitor.stopWatch('getNextMove');
_monitor.dumpLogs(true, false);
return bestMove == null ? null : bestMove.move;
}
function isTerminal(position) {
return chessRules.getGameStatus(position) !== 'OPEN';
}
/**
* Single alpha-beta algorithm (Negamax).
*
* @param position The current position
* @param alpha The current best score
* @param beta The current worst score
* @param depth The depth
* @param alphaBetaData Data gathered at recursion depth+1
* @returns {number} The score evaluated
*/
function alphaBeta(position, alpha, beta, depth, alphaBetaData) {
if(depth == 0
|| new Date().getTime() - alphaBetaData.startTime > aiTimeout*0.98-200
|| isTerminal(position)) {
/**
* TODO: Enhance with Quiescence algorithm.
*/
_monitor.startWatch('return');
var score = evaluatorBoard.evaluateBoard(position, depth, aiStrategy);
_monitor.addSearchNode(alphaBetaData.path, alpha, beta, (aiDepth-depth)%2==0?'max':'min', score);
return score;
}
//Get the available moves
_monitor.startWatch('availableMoves');
var availableMoves = chessRules.getAvailableMoves(position);
_monitor.stopWatch('availableMoves');
//Evaluate the moves
var evaluatedMoves = evaluatorMoves.evaluateMoves(availableMoves, position, aiStrategy);
//Order moves to enhance pruning
evaluatedMoves = sorter.sortMoves(evaluatedMoves);
evaluatedMoves.some(function (move) {
var _path;
if(_monitor.isEnabled()) {
_monitor.startWatch('pgn');
_path = chessRules.moveToPgn(position, move.move);
_monitor.stopWatch('pgn');
}
_monitor.startWatch('applyMove');
var nextPosition = chessRules.applyMove(position, move.move);
_monitor.stopWatch('applyMove');
var score = -alphaBeta(nextPosition, -beta, -alpha, depth - 1, alphaBetaData.next(_path));
//Cut off
if (score >= beta) {
_monitor.addCutoffNode(alphaBetaData.path, alpha, beta, (aiDepth-depth)%2==0?'max':'min', score);
alpha = beta;
_monitor.stopWatch('return');
return true;
}
//we have found a better best move
if(score > alpha) {
alpha = score;
}
_monitor.stopWatch('return');
return false;
});
_monitor.addSearchNode(alphaBetaData.path, alpha, beta, (aiDepth-depth)%2==0?'max':'min', alpha);
return alpha;
}
module.exports.setDepth = setDepth;
module.exports.setStrategy = setStrategy;
module.exports.setTimeout = setTimeout;
module.exports.getNextMove = getNextMove;