var board, game = new Chess(); /*The "AI" part starts here */ var minimaxRoot =function(depth, game, isMaximisingPlayer) { var newGameMoves = game.ugly_moves(); var bestMove = -9999; var bestMoveFound; for(var i = 0; i < newGameMoves.length; i++) { var newGameMove = newGameMoves[i] game.ugly_move(newGameMove); var value = minimax(depth - 1, game, -10000, 10000, !isMaximisingPlayer); game.undo(); if(value >= bestMove) { bestMove = value; bestMoveFound = newGameMove; } } return bestMoveFound; }; var minimax = function (depth, game, alpha, beta, isMaximisingPlayer) { positionCount++; if (depth === 0) { return -evaluateBoard(game.board()); } var newGameMoves = game.ugly_moves(); if (isMaximisingPlayer) { var bestMove = -9999; for (var i = 0; i < newGameMoves.length; i++) { game.ugly_move(newGameMoves[i]); bestMove = Math.max(bestMove, minimax(depth - 1, game, alpha, beta, !isMaximisingPlayer)); game.undo(); alpha = Math.max(alpha, bestMove); if (beta <= alpha) { return bestMove; } } return bestMove; } else { var bestMove = 9999; for (var i = 0; i < newGameMoves.length; i++) { game.ugly_move(newGameMoves[i]); bestMove = Math.min(bestMove, minimax(depth - 1, game, alpha, beta, !isMaximisingPlayer)); game.undo(); beta = Math.min(beta, bestMove); if (beta <= alpha) { return bestMove; } } return bestMove; } }; var evaluateBoard = function (board) { var totalEvaluation = 0; for (var i = 0; i < 8; i++) { for (var j = 0; j < 8; j++) { totalEvaluation = totalEvaluation + getPieceValue(board[i][j], i ,j); } } return totalEvaluation; }; var reverseArray = function(array) { return array.slice().reverse(); }; var pawnEvalWhite = [ [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [5.0, 5.0, 5.0, 5.0, 5.0, 5.0, 5.0, 5.0], [1.0, 1.0, 2.0, 3.0, 3.0, 2.0, 1.0, 1.0], [0.5, 0.5, 1.0, 2.5, 2.5, 1.0, 0.5, 0.5], [0.0, 0.0, 0.0, 2.0, 2.0, 0.0, 0.0, 0.0], [0.5, -0.5, -1.0, 0.0, 0.0, -1.0, -0.5, 0.5], [0.5, 1.0, 1.0, -2.0, -2.0, 1.0, 1.0, 0.5], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0] ]; var pawnEvalBlack = reverseArray(pawnEvalWhite); var knightEval = [ [-5.0, -4.0, -3.0, -3.0, -3.0, -3.0, -4.0, -5.0], [-4.0, -2.0, 0.0, 0.0, 0.0, 0.0, -2.0, -4.0], [-3.0, 0.0, 1.0, 1.5, 1.5, 1.0, 0.0, -3.0], [-3.0, 0.5, 1.5, 2.0, 2.0, 1.5, 0.5, -3.0], [-3.0, 0.0, 1.5, 2.0, 2.0, 1.5, 0.0, -3.0], [-3.0, 0.5, 1.0, 1.5, 1.5, 1.0, 0.5, -3.0], [-4.0, -2.0, 0.0, 0.5, 0.5, 0.0, -2.0, -4.0], [-5.0, -4.0, -3.0, -3.0, -3.0, -3.0, -4.0, -5.0] ]; var bishopEvalWhite = [ [ -2.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -2.0], [ -1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -1.0], [ -1.0, 0.0, 0.5, 1.0, 1.0, 0.5, 0.0, -1.0], [ -1.0, 0.5, 0.5, 1.0, 1.0, 0.5, 0.5, -1.0], [ -1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, -1.0], [ -1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, -1.0], [ -1.0, 0.5, 0.0, 0.0, 0.0, 0.0, 0.5, -1.0], [ -2.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -2.0] ]; var bishopEvalBlack = reverseArray(bishopEvalWhite); var rookEvalWhite = [ [ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [ 0.5, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.5], [ -0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.5], [ -0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.5], [ -0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.5], [ -0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.5], [ -0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.5], [ 0.0, 0.0, 0.0, 0.5, 0.5, 0.0, 0.0, 0.0] ]; var rookEvalBlack = reverseArray(rookEvalWhite); var evalQueen = [ [ -2.0, -1.0, -1.0, -0.5, -0.5, -1.0, -1.0, -2.0], [ -1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -1.0], [ -1.0, 0.0, 0.5, 0.5, 0.5, 0.5, 0.0, -1.0], [ -0.5, 0.0, 0.5, 0.5, 0.5, 0.5, 0.0, -0.5], [ 0.0, 0.0, 0.5, 0.5, 0.5, 0.5, 0.0, -0.5], [ -1.0, 0.5, 0.5, 0.5, 0.5, 0.5, 0.0, -1.0], [ -1.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0.0, -1.0], [ -2.0, -1.0, -1.0, -0.5, -0.5, -1.0, -1.0, -2.0] ]; var kingEvalWhite = [ [ -3.0, -4.0, -4.0, -5.0, -5.0, -4.0, -4.0, -3.0], [ -3.0, -4.0, -4.0, -5.0, -5.0, -4.0, -4.0, -3.0], [ -3.0, -4.0, -4.0, -5.0, -5.0, -4.0, -4.0, -3.0], [ -3.0, -4.0, -4.0, -5.0, -5.0, -4.0, -4.0, -3.0], [ -2.0, -3.0, -3.0, -4.0, -4.0, -3.0, -3.0, -2.0], [ -1.0, -2.0, -2.0, -2.0, -2.0, -2.0, -2.0, -1.0], [ 2.0, 2.0, 0.0, 0.0, 0.0, 0.0, 2.0, 2.0 ], [ 2.0, 3.0, 1.0, 0.0, 0.0, 1.0, 3.0, 2.0 ] ]; var kingEvalBlack = reverseArray(kingEvalWhite); var getPieceValue = function (piece, x, y) { if (piece === null) { return 0; } var getAbsoluteValue = function (piece, isWhite, x ,y) { if (piece.type === 'p') { return 10 + ( isWhite ? pawnEvalWhite[y][x] : pawnEvalBlack[y][x] ); } else if (piece.type === 'r') { return 50 + ( isWhite ? rookEvalWhite[y][x] : rookEvalBlack[y][x] ); } else if (piece.type === 'n') { return 30 + knightEval[y][x]; } else if (piece.type === 'b') { return 30 + ( isWhite ? bishopEvalWhite[y][x] : bishopEvalBlack[y][x] ); } else if (piece.type === 'q') { return 90 + evalQueen[y][x]; } else if (piece.type === 'k') { return 900 + ( isWhite ? kingEvalWhite[y][x] : kingEvalBlack[y][x] ); } throw "Unknown piece type: " + piece.type; }; var absoluteValue = getAbsoluteValue(piece, piece.color === 'w', x ,y); return piece.color === 'w' ? absoluteValue : -absoluteValue; }; /* board visualization and games state handling */ var onDragStart = function (source, piece, position, orientation) { if (game.in_checkmate() === true || game.in_draw() === true || piece.search(/^b/) !== -1) { return false; } }; var makeBestMove = function () { var bestMove = getBestMove(game); game.ugly_move(bestMove); board.position(game.fen()); renderMoveHistory(game.history()); if (game.game_over()) { alert('Game over'); } }; var positionCount; var getBestMove = function (game) { if (game.game_over()) { alert('Game over'); } positionCount = 0; var depth = parseInt($('#search-depth').find(':selected').text()); var d = new Date().getTime(); var bestMove = minimaxRoot(depth, game, true); var d2 = new Date().getTime(); var moveTime = (d2 - d); var positionsPerS = ( positionCount * 1000 / moveTime); $('#position-count').text(positionCount); $('#time').text(moveTime/1000 + 's'); $('#positions-per-s').text(positionsPerS); return bestMove; }; var renderMoveHistory = function (moves) { var historyElement = $('#move-history').empty(); historyElement.empty(); for (var i = 0; i < moves.length; i = i + 2) { historyElement.append('' + moves[i] + ' ' + ( moves[i + 1] ? moves[i + 1] : ' ') + '
') } historyElement.scrollTop(historyElement[0].scrollHeight); }; var onDrop = function (source, target) { var move = game.move({ from: source, to: target, promotion: 'q' }); removeGreySquares(); if (move === null) { return 'snapback'; } renderMoveHistory(game.history()); window.setTimeout(makeBestMove, 250); }; var onSnapEnd = function () { board.position(game.fen()); }; var onMouseoverSquare = function(square, piece) { var moves = game.moves({ square: square, verbose: true }); if (moves.length === 0) return; greySquare(square); for (var i = 0; i < moves.length; i++) { greySquare(moves[i].to); } }; var onMouseoutSquare = function(square, piece) { removeGreySquares(); }; var removeGreySquares = function() { $('#board .square-55d63').css('background', ''); }; var greySquare = function(square) { var squareEl = $('#board .square-' + square); var background = '#a9a9a9'; if (squareEl.hasClass('black-3c85d') === true) { background = '#696969'; } squareEl.css('background', background); }; var cfg = { draggable: true, position: 'start', onDragStart: onDragStart, onDrop: onDrop, onMouseoutSquare: onMouseoutSquare, onMouseoverSquare: onMouseoverSquare, onSnapEnd: onSnapEnd }; board = ChessBoard('board', cfg);