UNPKG

jointjs

Version:

JavaScript diagramming library

1,652 lines (1,385 loc) 82.8 kB
'use strict'; // Perf TODO: // Merge material updating with psq values // Put move scoring inline in generator // Remove need for fliptable in psq tables. Access them by color // Optimize pawn move generation // Non-perf todo: // Checks in first q? // Pawn eval. // Better king evaluation // Better move sorting in PV nodes (especially root) var g_timeout = 40; function GetFen(){ var result = ''; for (var row = 0; row < 8; row++) { if (row != 0) result += '/'; var empty = 0; for (var col = 0; col < 8; col++) { var piece = g_board[((row + 2) << 4) + col + 4]; if (piece == 0) { empty++; } else { if (empty != 0) result += empty; empty = 0; var pieceChar = [' ', 'p', 'n', 'b', 'r', 'q', 'k', ' '][(piece & 0x7)]; result += ((piece & colorWhite) != 0) ? pieceChar.toUpperCase() : pieceChar; } } if (empty != 0) { result += empty; } } result += g_toMove == colorWhite ? ' w' : ' b'; result += ' '; if (g_castleRights == 0) { result += '-'; } else { if ((g_castleRights & 1) != 0) result += 'K'; if ((g_castleRights & 2) != 0) result += 'Q'; if ((g_castleRights & 4) != 0) result += 'k'; if ((g_castleRights & 8) != 0) result += 'q'; } result += ' '; if (g_enPassentSquare == -1) { result += '-'; } else { result += FormatSquare(g_enPassentSquare); } return result; } function GetMoveSAN(move, validMoves) { var from = move & 0xFF; var to = (move >> 8) & 0xFF; if (move & moveflagCastleKing) return 'O-O'; if (move & moveflagCastleQueen) return 'O-O-O'; var pieceType = g_board[from] & 0x7; var result = ['', '', 'N', 'B', 'R', 'Q', 'K', ''][pieceType]; var dupe = false, rowDiff = true, colDiff = true; if (validMoves == null) { validMoves = GenerateValidMoves(); } for (var i = 0; i < validMoves.length; i++) { var moveFrom = validMoves[i] & 0xFF; var moveTo = (validMoves[i] >> 8) & 0xFF; if (moveFrom != from && moveTo == to && (g_board[moveFrom] & 0x7) == pieceType) { dupe = true; if ((moveFrom & 0xF0) == (from & 0xF0)) { rowDiff = false; } if ((moveFrom & 0x0F) == (from & 0x0F)) { colDiff = false; } } } if (dupe) { if (colDiff) { result += FormatSquare(from).charAt(0); } else if (rowDiff) { result += FormatSquare(from).charAt(1); } else { result += FormatSquare(from); } } else if (pieceType == piecePawn && (g_board[to] != 0 || (move & moveflagEPC))) { result += FormatSquare(from).charAt(0); } if (g_board[to] != 0 || (move & moveflagEPC)) { result += 'x'; } result += FormatSquare(to); if (move & moveflagPromotion) { if (move & moveflagPromoteBishop) result += '=B'; else if (move & moveflagPromoteKnight) result += '=N'; else if (move & moveflagPromoteQueen) result += '=Q'; else result += '=R'; } MakeMove(move); if (g_inCheck) { result += GenerateValidMoves().length == 0 ? '#' : '+'; } UnmakeMove(move); return result; } function FormatSquare(square) { var letters = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']; return letters[(square & 0xF) - 4] + ((9 - (square >> 4)) + 1); } function FormatMove(move) { var result = FormatSquare(move & 0xFF) + FormatSquare((move >> 8) & 0xFF); if (move & moveflagPromotion) { if (move & moveflagPromoteBishop) result += 'b'; else if (move & moveflagPromoteKnight) result += 'n'; else if (move & moveflagPromoteQueen) result += 'q'; else result += 'r'; } return result; } function GetMoveFromString(moveString) { var moves = GenerateValidMoves(); for (var i = 0; i < moves.length; i++) { if (FormatMove(moves[i]) == moveString) { return moves[i]; } } alert('busted! ->' + moveString + ' fen:' + GetFen()); } function PVFromHash(move, ply) { if (ply == 0) return ''; if (move == 0) { if (g_inCheck) return 'checkmate'; return 'stalemate'; } var pvString = ' ' + GetMoveSAN(move); MakeMove(move); var hashNode = g_hashTable[g_hashKeyLow & g_hashMask]; if (hashNode != null && hashNode.lock == g_hashKeyHigh && hashNode.bestMove != null) { pvString += PVFromHash(hashNode.bestMove, ply - 1); } UnmakeMove(move); return pvString; } // // Searching code // var g_startTime; var g_nodeCount; var g_qNodeCount; var g_searchValid; function Search(finishMoveCallback, maxPly, finishPlyCallback) { var alpha = minEval; var beta = maxEval; g_nodeCount = 0; g_qNodeCount = 0; g_searchValid = true; var bestMove = 0; var value; g_startTime = (new Date()).getTime(); var i; for (i = 1; i <= maxPly && g_searchValid; i++) { var tmp = AlphaBeta(i, 0, alpha, beta); if (!g_searchValid) break; value = tmp; if (value > alpha && value < beta) { alpha = value - 500; beta = value + 500; if (alpha < minEval) alpha = minEval; if (beta > maxEval) beta = maxEval; } else if (alpha != minEval) { alpha = minEval; beta = maxEval; i--; } if (g_hashTable[g_hashKeyLow & g_hashMask] != null) { bestMove = g_hashTable[g_hashKeyLow & g_hashMask].bestMove; } if (finishPlyCallback != null) { finishPlyCallback(bestMove, value, (new Date()).getTime() - g_startTime, i); } } if (finishMoveCallback != null) { finishMoveCallback(bestMove, value, (new Date()).getTime() - g_startTime, i - 1); } } var minEval = -2000000; var maxEval = +2000000; var minMateBuffer = minEval + 2000; var maxMateBuffer = maxEval - 2000; var materialTable = [0, 800, 3350, 3450, 5000, 9750, 600000]; var pawnAdj = [ 0, 0, 0, 0, 0, 0, 0, 0, -25, 105, 135, 270, 270, 135, 105, -25, -80, 0, 30, 176, 176, 30, 0, -80, -85, -5, 25, 175, 175, 25, -5, -85, -90, -10, 20, 125, 125, 20, -10, -90, -95, -15, 15, 75, 75, 15, -15, -95, -100, -20, 10, 70, 70, 10, -20, -100, 0, 0, 0, 0, 0, 0, 0, 0 ]; var knightAdj = [-200, -100, -50, -50, -50, -50, -100, -200, -100, 0, 0, 0, 0, 0, 0, -100, -50, 0, 60, 60, 60, 60, 0, -50, -50, 0, 30, 60, 60, 30, 0, -50, -50, 0, 30, 60, 60, 30, 0, -50, -50, 0, 30, 30, 30, 30, 0, -50, -100, 0, 0, 0, 0, 0, 0, -100, -200, -50, -25, -25, -25, -25, -50, -200 ]; var bishopAdj = [ -50,-50,-25,-10,-10,-25,-50,-50, -50,-25,-10, 0, 0,-10,-25,-50, -25,-10, 0, 25, 25, 0,-10,-25, -10, 0, 25, 40, 40, 25, 0,-10, -10, 0, 25, 40, 40, 25, 0,-10, -25,-10, 0, 25, 25, 0,-10,-25, -50,-25,-10, 0, 0,-10,-25,-50, -50,-50,-25,-10,-10,-25,-50,-50 ]; var rookAdj = [ -60, -30, -10, 20, 20, -10, -30, -60, 40, 70, 90,120,120, 90, 70, 40, -60, -30, -10, 20, 20, -10, -30, -60, -60, -30, -10, 20, 20, -10, -30, -60, -60, -30, -10, 20, 20, -10, -30, -60, -60, -30, -10, 20, 20, -10, -30, -60, -60, -30, -10, 20, 20, -10, -30, -60, -60, -30, -10, 20, 20, -10, -30, -60 ]; var kingAdj = [ 50, 150, -25, -125, -125, -25, 150, 50, 50, 150, -25, -125, -125, -25, 150, 50, 50, 150, -25, -125, -125, -25, 150, 50, 50, 150, -25, -125, -125, -25, 150, 50, 50, 150, -25, -125, -125, -25, 150, 50, 50, 150, -25, -125, -125, -25, 150, 50, 50, 150, -25, -125, -125, -25, 150, 50, 150, 250, 75, -25, -25, 75, 250, 150 ]; var emptyAdj = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]; var pieceSquareAdj = new Array(8); // Returns the square flipped var flipTable = new Array(256); function Mobility(color) { var result = 0; var from, to, mob, pieceIdx; var enemy = color == 8 ? 0x10 : 0x8; var mobUnit = color == 8 ? g_mobUnit[0] : g_mobUnit[1]; // Knight mobility mob = -3; pieceIdx = (color | 2) << 4; from = g_pieceList[pieceIdx++]; while (from != 0) { mob += mobUnit[g_board[from + 31]]; mob += mobUnit[g_board[from + 33]]; mob += mobUnit[g_board[from + 14]]; mob += mobUnit[g_board[from - 14]]; mob += mobUnit[g_board[from - 31]]; mob += mobUnit[g_board[from - 33]]; mob += mobUnit[g_board[from + 18]]; mob += mobUnit[g_board[from - 18]]; from = g_pieceList[pieceIdx++]; } result += 65 * mob; // Bishop mobility mob = -4; pieceIdx = (color | 3) << 4; from = g_pieceList[pieceIdx++]; while (from != 0) { to = from - 15; while (g_board[to] == 0) { to -= 15; mob++; } if (g_board[to] & enemy) { mob++; if (!(g_board[to] & piecePawn)) { to -= 15; while (g_board[to] == 0) to -= 15; mob += mobUnit[g_board[to]] << 2; } } to = from - 17; while (g_board[to] == 0) { to -= 17; mob++; } if (g_board[to] & enemy) { mob++; if (!(g_board[to] & piecePawn)) { to -= 17; while (g_board[to] == 0) to -= 17; mob += mobUnit[g_board[to]] << 2; } } to = from + 15; while (g_board[to] == 0) { to += 15; mob++; } if (g_board[to] & enemy) { mob++; if (!(g_board[to] & piecePawn)) { to += 15; while (g_board[to] == 0) to += 15; mob += mobUnit[g_board[to]] << 2; } } to = from + 17; while (g_board[to] == 0) { to += 17; mob++; } if (g_board[to] & enemy) { mob++; if (!(g_board[to] & piecePawn)) { to += 17; while (g_board[to] == 0) to += 17; mob += mobUnit[g_board[to]] << 2; } } from = g_pieceList[pieceIdx++]; } result += 44 * mob; // Rook mobility mob = -4; pieceIdx = (color | 4) << 4; from = g_pieceList[pieceIdx++]; while (from != 0) { to = from - 1; while (g_board[to] == 0) { to--; mob++;} if (g_board[to] & enemy) mob++; to = from + 1; while (g_board[to] == 0) { to++; mob++; } if (g_board[to] & enemy) mob++; to = from + 16; while (g_board[to] == 0) { to += 16; mob++; } if (g_board[to] & enemy) mob++; to = from - 16; while (g_board[to] == 0) { to -= 16; mob++; } if (g_board[to] & enemy) mob++; from = g_pieceList[pieceIdx++]; } result += 25 * mob; // Queen mobility mob = -2; pieceIdx = (color | 5) << 4; from = g_pieceList[pieceIdx++]; while (from != 0) { to = from - 15; while (g_board[to] == 0) { to -= 15; mob++; } if (g_board[to] & enemy) mob++; to = from - 17; while (g_board[to] == 0) { to -= 17; mob++; } if (g_board[to] & enemy) mob++; to = from + 15; while (g_board[to] == 0) { to += 15; mob++; } if (g_board[to] & enemy) mob++; to = from + 17; while (g_board[to] == 0) { to += 17; mob++; } if (g_board[to] & enemy) mob++; to = from - 1; while (g_board[to] == 0) { to--; mob++; } if (g_board[to] & enemy) mob++; to = from + 1; while (g_board[to] == 0) { to++; mob++; } if (g_board[to] & enemy) mob++; to = from + 16; while (g_board[to] == 0) { to += 16; mob++; } if (g_board[to] & enemy) mob++; to = from - 16; while (g_board[to] == 0) { to -= 16; mob++; } if (g_board[to] & enemy) mob++; from = g_pieceList[pieceIdx++]; } result += 22 * mob; return result; } function Evaluate() { var curEval = g_baseEval; var evalAdjust = 0; // Black queen gone, then cancel white's penalty for king movement if (g_pieceList[pieceQueen << 4] == 0) evalAdjust -= pieceSquareAdj[pieceKing][g_pieceList[(colorWhite | pieceKing) << 4]]; // White queen gone, then cancel black's penalty for king movement if (g_pieceList[(colorWhite | pieceQueen) << 4] == 0) evalAdjust += pieceSquareAdj[pieceKing][flipTable[g_pieceList[pieceKing << 4]]]; // Black bishop pair if (g_pieceCount[pieceBishop] >= 2) evalAdjust -= 500; // White bishop pair if (g_pieceCount[pieceBishop | colorWhite] >= 2) evalAdjust += 500; var mobility = Mobility(8) - Mobility(0); if (g_toMove == 0) { // Black curEval -= mobility; curEval -= evalAdjust; } else { curEval += mobility; curEval += evalAdjust; } return curEval; } function ScoreMove(move){ var moveTo = (move >> 8) & 0xFF; var captured = g_board[moveTo] & 0x7; var piece = g_board[move & 0xFF]; var score; if (captured != 0) { var pieceType = piece & 0x7; score = (captured << 5) - pieceType; } else { score = historyTable[piece & 0xF][moveTo]; } return score; } function QSearch(alpha, beta, ply) { g_qNodeCount++; var realEval = g_inCheck ? (minEval + 1) : Evaluate(); if (realEval >= beta) return realEval; if (realEval > alpha) alpha = realEval; var moves = new Array(); var moveScores = new Array(); var wasInCheck = g_inCheck; if (wasInCheck) { // TODO: Fast check escape generator and fast checking moves generator GenerateCaptureMoves(moves); GenerateAllMoves(moves); for (var i = 0; i < moves.length; i++) { moveScores[i] = ScoreMove(moves[i]); } } else { GenerateCaptureMoves(moves); for (i = 0; i < moves.length; i++) { var captured = g_board[(moves[i] >> 8) & 0xFF] & 0x7; var pieceType = g_board[moves[i] & 0xFF] & 0x7; moveScores[i] = (captured << 5) - pieceType; } } for (i = 0; i < moves.length; i++) { var bestMove = i; for (var j = moves.length - 1; j > i; j--) { if (moveScores[j] > moveScores[bestMove]) { bestMove = j; } } { var tmpMove = moves[i]; moves[i] = moves[bestMove]; moves[bestMove] = tmpMove; var tmpScore = moveScores[i]; moveScores[i] = moveScores[bestMove]; moveScores[bestMove] = tmpScore; } if (!wasInCheck && !See(moves[i])) { continue; } if (!MakeMove(moves[i])) { continue; } var value = -QSearch(-beta, -alpha, ply - 1); UnmakeMove(moves[i]); if (value > realEval) { if (value >= beta) return value; if (value > alpha) alpha = value; realEval = value; } } /* Disable checks... Too slow currently if (ply == 0 && !wasInCheck) { moves = new Array(); GenerateAllMoves(moves); for (var i = 0; i < moves.length; i++) { moveScores[i] = ScoreMove(moves[i]); } for (var i = 0; i < moves.length; i++) { var bestMove = i; for (var j = moves.length - 1; j > i; j--) { if (moveScores[j] > moveScores[bestMove]) { bestMove = j; } } { var tmpMove = moves[i]; moves[i] = moves[bestMove]; moves[bestMove] = tmpMove; var tmpScore = moveScores[i]; moveScores[i] = moveScores[bestMove]; moveScores[bestMove] = tmpScore; } if (!MakeMove(moves[i])) { continue; } var checking = g_inCheck; UnmakeMove(moves[i]); if (!checking) { continue; } if (!See(moves[i])) { continue; } MakeMove(moves[i]); var value = -QSearch(-beta, -alpha, ply - 1); UnmakeMove(moves[i]); if (value > realEval) { if (value >= beta) return value; if (value > alpha) alpha = value; realEval = value; } } } */ return realEval; } function StoreHash(value, flags, ply, move, depth) { if (value >= maxMateBuffer) value += depth; else if (value <= minMateBuffer) value -= depth; g_hashTable[g_hashKeyLow & g_hashMask] = new HashEntry(g_hashKeyHigh, value, flags, ply, move); } function IsHashMoveValid(hashMove) { var from = hashMove & 0xFF; var to = (hashMove >> 8) & 0xFF; var ourPiece = g_board[from]; var pieceType = ourPiece & 0x7; if (pieceType < piecePawn || pieceType > pieceKing) return false; // Can't move a piece we don't control if (g_toMove != (ourPiece & 0x8)) return false; // Can't move to a square that has something of the same color if (g_board[to] != 0 && (g_toMove == (g_board[to] & 0x8))) return false; if (pieceType == piecePawn) { if (hashMove & moveflagEPC) { return false; } // Valid moves are push, capture, double push, promotions var dir = to - from; if ((g_toMove == colorWhite) != (dir < 0)) { // Pawns have to move in the right direction return false; } var row = to & 0xF0; if (((row == 0x90 && !g_toMove) || (row == 0x20 && g_toMove)) != (hashMove & moveflagPromotion)) { // Handle promotions return false; } if (dir == -16 || dir == 16) { // White/Black push return g_board[to] == 0; } else if (dir == -15 || dir == -17 || dir == 15 || dir == 17) { // White/Black capture return g_board[to] != 0; } else if (dir == -32) { // Double white push if (row != 0x60) return false; if (g_board[to] != 0) return false; if (g_board[from - 16] != 0) return false; } else if (dir == 32) { // Double black push if (row != 0x50) return false; if (g_board[to] != 0) return false; if (g_board[from + 16] != 0) return false; } else { return false; } return true; } else { // This validates that this piece type can actually make the attack if (hashMove >> 16) return false; return IsSquareAttackableFrom(to, from); } } function IsRepDraw() { var stop = g_moveCount - 1 - g_move50; stop = stop < 0 ? 0 : stop; for (var i = g_moveCount - 5; i >= stop; i -= 2) { if (g_repMoveStack[i] == g_hashKeyLow) return true; } return false; } function MovePicker(hashMove, depth, killer1, killer2) { this.hashMove = hashMove; this.depth = depth; this.killer1 = killer1; this.killer2 = killer2; this.moves = new Array(); this.losingCaptures = null; this.moveCount = 0; this.atMove = -1; this.moveScores = null; this.stage = 0; this.nextMove = function() { if (++this.atMove == this.moveCount) { this.stage++; if (this.stage == 1) { if (this.hashMove != null && IsHashMoveValid(hashMove)) { this.moves[0] = hashMove; this.moveCount = 1; } if (this.moveCount != 1) { this.hashMove = null; this.stage++; } } if (this.stage == 2) { GenerateCaptureMoves(this.moves); this.moveCount = this.moves.length; this.moveScores = new Array(this.moveCount); // Move ordering for (var i = this.atMove; i < this.moveCount; i++) { var captured = g_board[(this.moves[i] >> 8) & 0xFF] & 0x7; var pieceType = g_board[this.moves[i] & 0xFF] & 0x7; this.moveScores[i] = (captured << 5) - pieceType; } // No moves, onto next stage if (this.atMove == this.moveCount) this.stage++; } if (this.stage == 3) { if (IsHashMoveValid(this.killer1) && this.killer1 != this.hashMove) { this.moves[this.moves.length] = this.killer1; this.moveCount = this.moves.length; } else { this.killer1 = 0; this.stage++; } } if (this.stage == 4) { if (IsHashMoveValid(this.killer2) && this.killer2 != this.hashMove) { this.moves[this.moves.length] = this.killer2; this.moveCount = this.moves.length; } else { this.killer2 = 0; this.stage++; } } if (this.stage == 5) { GenerateAllMoves(this.moves); this.moveCount = this.moves.length; // Move ordering for (i = this.atMove; i < this.moveCount; i++) this.moveScores[i] = ScoreMove(this.moves[i]); // No moves, onto next stage if (this.atMove == this.moveCount) this.stage++; } if (this.stage == 6) { // Losing captures if (this.losingCaptures != null) { for (i = 0; i < this.losingCaptures.length; i++) { this.moves[this.moves.length] = this.losingCaptures[i]; } for (i = this.atMove; i < this.moveCount; i++) this.moveScores[i] = ScoreMove(this.moves[i]); this.moveCount = this.moves.length; } // No moves, onto next stage if (this.atMove == this.moveCount) this.stage++; } if (this.stage == 7) return 0; } var bestMove = this.atMove; for (var j = this.atMove + 1; j < this.moveCount; j++) { if (this.moveScores[j] > this.moveScores[bestMove]) { bestMove = j; } } if (bestMove != this.atMove) { var tmpMove = this.moves[this.atMove]; this.moves[this.atMove] = this.moves[bestMove]; this.moves[bestMove] = tmpMove; var tmpScore = this.moveScores[this.atMove]; this.moveScores[this.atMove] = this.moveScores[bestMove]; this.moveScores[bestMove] = tmpScore; } var candidateMove = this.moves[this.atMove]; if ((this.stage > 1 && candidateMove == this.hashMove) || (this.stage > 3 && candidateMove == this.killer1) || (this.stage > 4 && candidateMove == this.killer2)) { return this.nextMove(); } if (this.stage == 2 && !See(candidateMove)) { if (this.losingCaptures == null) { this.losingCaptures = new Array(); } this.losingCaptures[this.losingCaptures.length] = candidateMove; return this.nextMove(); } return this.moves[this.atMove]; }; } function AllCutNode(ply, depth, beta, allowNull) { if (ply <= 0) { return QSearch(beta - 1, beta, 0); } if ((g_nodeCount & 127) == 127) { if ((new Date()).getTime() - g_startTime > g_timeout) { // Time cutoff g_searchValid = false; return beta - 1; } } g_nodeCount++; if (IsRepDraw()) return 0; // Mate distance pruning if (minEval + depth >= beta) return beta; if (maxEval - (depth + 1) < beta) return beta - 1; var hashMove = null; var hashNode = g_hashTable[g_hashKeyLow & g_hashMask]; if (hashNode != null && hashNode.lock == g_hashKeyHigh) { hashMove = hashNode.bestMove; if (hashNode.hashDepth >= ply) { var hashValue = hashNode.value; // Fixup mate scores if (hashValue >= maxMateBuffer) hashValue -= depth; else if (hashValue <= minMateBuffer) hashValue += depth; if (hashNode.flags == hashflagExact) return hashValue; if (hashNode.flags == hashflagAlpha && hashValue < beta) return hashValue; if (hashNode.flags == hashflagBeta && hashValue >= beta) return hashValue; } } // TODO - positional gain? if (!g_inCheck && allowNull && beta > minMateBuffer && beta < maxMateBuffer) { // Try some razoring if (hashMove == null && ply < 4) { var razorMargin = 2500 + 200 * ply; if (g_baseEval < beta - razorMargin) { var razorBeta = beta - razorMargin; var v = QSearch(razorBeta - 1, razorBeta, 0); if (v < razorBeta) return v; } } // TODO - static null move // Null move if (ply > 1 && g_baseEval >= beta - (ply >= 4 ? 2500 : 0) && // Disable null move if potential zugzwang (no big pieces) (g_pieceCount[pieceBishop | g_toMove] != 0 || g_pieceCount[pieceKnight | g_toMove] != 0 || g_pieceCount[pieceRook | g_toMove] != 0 || g_pieceCount[pieceQueen | g_toMove] != 0)) { var r = 3 + (ply >= 5 ? 1 : ply / 4); if (g_baseEval - beta > 1500) r++; g_toMove = 8 - g_toMove; g_baseEval = -g_baseEval; g_hashKeyLow ^= g_zobristBlackLow; g_hashKeyHigh ^= g_zobristBlackHigh; var value = -AllCutNode(ply - r, depth + 1, -(beta - 1), false); g_hashKeyLow ^= g_zobristBlackLow; g_hashKeyHigh ^= g_zobristBlackHigh; g_toMove = 8 - g_toMove; g_baseEval = -g_baseEval; if (value >= beta) return beta; } } var moveMade = false; var realEval = minEval - 1; var movePicker = new MovePicker(hashMove, depth, g_killers[depth][0], g_killers[depth][1]); for (;;) { var currentMove = movePicker.nextMove(); if (currentMove == 0) { break; } var plyToSearch = ply - 1; if (!MakeMove(currentMove)) { continue; } var doFullSearch = true; if (g_inCheck) { // Check extensions plyToSearch++; } else { var reduced = plyToSearch - (movePicker.atMove > 14 ? 2 : 1); // Futility pruning /* if (movePicker.stage == 5 && !inCheck) { if (movePicker.atMove >= (15 + (1 << (5 * ply) >> 2)) && realEval > minMateBuffer) { UnmakeMove(currentMove); continue; } if (ply < 7) { var reducedPly = reduced <= 0 ? 0 : reduced; var futilityValue = -g_baseEval + (900 * (reducedPly + 2)) - (movePicker.atMove * 10); if (futilityValue < beta) { if (futilityValue > realEval) { realEval = futilityValue; } UnmakeMove(currentMove); continue; } } }*/ // Late move reductions if (movePicker.stage == 5 && movePicker.atMove > 5 && ply >= 3) { value = -AllCutNode(reduced, depth + 1, -(beta - 1), true); doFullSearch = (value >= beta); } } if (doFullSearch) { value = -AllCutNode(plyToSearch, depth + 1, -(beta - 1), true); } moveMade = true; UnmakeMove(currentMove); if (!g_searchValid) { return beta - 1; } if (value > realEval) { if (value >= beta) { var histTo = (currentMove >> 8) & 0xFF; if (g_board[histTo] == 0) { var histPiece = g_board[currentMove & 0xFF] & 0xF; historyTable[histPiece][histTo] += ply * ply; if (historyTable[histPiece][histTo] > 32767) { historyTable[histPiece][histTo] >>= 1; } if (g_killers[depth][0] != currentMove) { g_killers[depth][1] = g_killers[depth][0]; g_killers[depth][0] = currentMove; } } StoreHash(value, hashflagBeta, ply, currentMove, depth); return value; } realEval = value; hashMove = currentMove; } } if (!moveMade) { // If we have no valid moves it's either stalemate or checkmate if (g_inCheck) // Checkmate. return minEval + depth; else // Stalemate return 0; } StoreHash(realEval, hashflagAlpha, ply, hashMove, depth); return realEval; } function AlphaBeta(ply, depth, alpha, beta) { if (ply <= 0) { return QSearch(alpha, beta, 0); } g_nodeCount++; if (depth > 0 && IsRepDraw()) return 0; // Mate distance pruning var oldAlpha = alpha; alpha = alpha < minEval + depth ? alpha : minEval + depth; beta = beta > maxEval - (depth + 1) ? beta : maxEval - (depth + 1); if (alpha >= beta) return alpha; var hashMove = null; var hashFlag = hashflagAlpha; var hashNode = g_hashTable[g_hashKeyLow & g_hashMask]; if (hashNode != null && hashNode.lock == g_hashKeyHigh) { hashMove = hashNode.bestMove; } var inCheck = g_inCheck; var moveMade = false; var realEval = minEval; var movePicker = new MovePicker(hashMove, depth, g_killers[depth][0], g_killers[depth][1]); for (;;) { var currentMove = movePicker.nextMove(); if (currentMove == 0) { break; } var plyToSearch = ply - 1; if (!MakeMove(currentMove)) { continue; } if (g_inCheck) { // Check extensions plyToSearch++; } var value; if (moveMade) { value = -AllCutNode(plyToSearch, depth + 1, -alpha, true); if (value > alpha) { value = -AlphaBeta(plyToSearch, depth + 1, -beta, -alpha); } } else { value = -AlphaBeta(plyToSearch, depth + 1, -beta, -alpha); } moveMade = true; UnmakeMove(currentMove); if (!g_searchValid) { return alpha; } if (value > realEval) { if (value >= beta) { var histTo = (currentMove >> 8) & 0xFF; if (g_board[histTo] == 0) { var histPiece = g_board[currentMove & 0xFF] & 0xF; historyTable[histPiece][histTo] += ply * ply; if (historyTable[histPiece][histTo] > 32767) { historyTable[histPiece][histTo] >>= 1; } if (g_killers[depth][0] != currentMove) { g_killers[depth][1] = g_killers[depth][0]; g_killers[depth][0] = currentMove; } } StoreHash(value, hashflagBeta, ply, currentMove, depth); return value; } if (value > oldAlpha) { hashFlag = hashflagExact; alpha = value; } realEval = value; hashMove = currentMove; } } if (!moveMade) { // If we have no valid moves it's either stalemate or checkmate if (inCheck) // Checkmate. return minEval + depth; else // Stalemate return 0; } StoreHash(realEval, hashFlag, ply, hashMove, depth); return realEval; } // // Board code // // This somewhat funky scheme means that a piece is indexed by it's lower 4 bits when accessing in arrays. The fifth bit (black bit) // is used to allow quick edge testing on the board. var colorBlack = 0x10; var colorWhite = 0x08; var pieceEmpty = 0x00; var piecePawn = 0x01; var pieceKnight = 0x02; var pieceBishop = 0x03; var pieceRook = 0x04; var pieceQueen = 0x05; var pieceKing = 0x06; var g_vectorDelta = new Array(256); var g_bishopDeltas = [-15, -17, 15, 17]; var g_knightDeltas = [31, 33, 14, -14, -31, -33, 18, -18]; var g_rookDeltas = [-1, +1, -16, +16]; var g_queenDeltas = [-1, +1, -15, +15, -17, +17, -16, +16]; var g_castleRightsMask = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7,15,15,15, 3,15,15,11, 0, 0, 0, 0, 0, 0, 0, 0,15,15,15,15,15,15,15,15, 0, 0, 0, 0, 0, 0, 0, 0,15,15,15,15,15,15,15,15, 0, 0, 0, 0, 0, 0, 0, 0,15,15,15,15,15,15,15,15, 0, 0, 0, 0, 0, 0, 0, 0,15,15,15,15,15,15,15,15, 0, 0, 0, 0, 0, 0, 0, 0,15,15,15,15,15,15,15,15, 0, 0, 0, 0, 0, 0, 0, 0,15,15,15,15,15,15,15,15, 0, 0, 0, 0, 0, 0, 0, 0,13,15,15,15,12,15,15,14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; var moveflagEPC = 0x2 << 16; var moveflagCastleKing = 0x4 << 16; var moveflagCastleQueen = 0x8 << 16; var moveflagPromotion = 0x10 << 16; var moveflagPromoteKnight = 0x20 << 16; var moveflagPromoteQueen = 0x40 << 16; var moveflagPromoteBishop = 0x80 << 16; function MT() { var N = 624; var M = 397; var MAG01 = [0x0, 0x9908b0df]; this.mt = new Array(N); this.mti = N + 1; this.setSeed = function() { var a = arguments; switch (a.length) { case 1: if (a[0].constructor === Number) { this.mt[0]= a[0]; for (var i = 1; i < N; ++i) { var s = this.mt[i - 1] ^ (this.mt[i - 1] >>> 30); this.mt[i] = ((1812433253 * ((s & 0xffff0000) >>> 16)) << 16) + 1812433253 * (s & 0x0000ffff) + i; } this.mti = N; return; } this.setSeed(19650218); var l = a[0].length; i = 1; var j = 0; for (var k = N > l ? N : l; k != 0; --k) { s = this.mt[i - 1] ^ (this.mt[i - 1] >>> 30); this.mt[i] = (this.mt[i] ^ (((1664525 * ((s & 0xffff0000) >>> 16)) << 16) + 1664525 * (s & 0x0000ffff))) + a[0][j] + j; if (++i >= N) { this.mt[0] = this.mt[N - 1]; i = 1; } if (++j >= l) { j = 0; } } for (var k = N - 1; k != 0; --k) { var s = this.mt[i - 1] ^ (this.mt[i - 1] >>> 30); this.mt[i] = (this.mt[i] ^ (((1566083941 * ((s & 0xffff0000) >>> 16)) << 16) + 1566083941 * (s & 0x0000ffff))) - i; if (++i >= N) { this.mt[0] = this.mt[N-1]; i = 1; } } this.mt[0] = 0x80000000; return; default: var seeds = new Array(); for (i = 0; i < a.length; ++i) { seeds.push(a[i]); } this.setSeed(seeds); return; } }; this.setSeed(0x1BADF00D); this.next = function(bits) { if (this.mti >= N) { var x = 0; for (var k = 0; k < N - M; ++k) { x = (this.mt[k] & 0x80000000) | (this.mt[k + 1] & 0x7fffffff); this.mt[k] = this.mt[k + M] ^ (x >>> 1) ^ MAG01[x & 0x1]; } for (k = N - M; k < N - 1; ++k) { x = (this.mt[k] & 0x80000000) | (this.mt[k + 1] & 0x7fffffff); this.mt[k] = this.mt[k + (M - N)] ^ (x >>> 1) ^ MAG01[x & 0x1]; } x = (this.mt[N - 1] & 0x80000000) | (this.mt[0] & 0x7fffffff); this.mt[N - 1] = this.mt[M - 1] ^ (x >>> 1) ^ MAG01[x & 0x1]; this.mti = 0; } var y = this.mt[this.mti++]; y ^= y >>> 11; y ^= (y << 7) & 0x9d2c5680; y ^= (y << 15) & 0xefc60000; y ^= y >>> 18; return (y >>> (32 - bits)) & 0xFFFFFFFF; }; } // Position variables var g_board = new Array(256); // Sentinel 0x80, pieces are in low 4 bits, 0x8 for color, 0x7 bits for piece type var g_toMove; // side to move, 0 or 8, 0 = black, 8 = white var g_castleRights; // bitmask representing castling rights, 1 = wk, 2 = wq, 4 = bk, 8 = bq var g_enPassentSquare; var g_baseEval; var g_hashKeyLow, g_hashKeyHigh; var g_inCheck; // Utility variables var g_moveCount = 0; var g_moveUndoStack = new Array(); var g_move50 = 0; var g_repMoveStack = new Array(); var g_hashSize = 1 << 22; var g_hashMask = g_hashSize - 1; var g_hashTable; var g_killers; var historyTable = new Array(32); var g_zobristLow; var g_zobristHigh; var g_zobristBlackLow; var g_zobristBlackHigh; // Evaulation variables var g_mobUnit; var hashflagAlpha = 1; var hashflagBeta = 2; var hashflagExact = 3; function HashEntry(lock, value, flags, hashDepth, bestMove, globalPly) { this.lock = lock; this.value = value; this.flags = flags; this.hashDepth = hashDepth; this.bestMove = bestMove; } function MakeSquare(row, column) { return ((row + 2) << 4) | (column + 4); } function MakeTable(table) { var result = new Array(256); for (var i = 0; i < 256; i++) { result[i] = 0; } for (var row = 0; row < 8; row++) { for (var col = 0; col < 8; col++) { result[MakeSquare(row, col)] = table[row * 8 + col]; } } return result; } function ResetGame() { g_killers = new Array(128); for (var i = 0; i < 128; i++) { g_killers[i] = [0, 0]; } g_hashTable = new Array(g_hashSize); for (i = 0; i < 32; i++) { historyTable[i] = new Array(256); for (var j = 0; j < 256; j++) historyTable[i][j] = 0; } var mt = new MT(/*0x1badf00d*/); g_zobristLow = new Array(256); g_zobristHigh = new Array(256); for (i = 0; i < 256; i++) { g_zobristLow[i] = new Array(16); g_zobristHigh[i] = new Array(16); for (var j = 0; j < 16; j++) { g_zobristLow[i][j] = mt.next(32); g_zobristHigh[i][j] = mt.next(32); } } g_zobristBlackLow = mt.next(32); g_zobristBlackHigh = mt.next(32); for (var row = 0; row < 8; row++) { for (var col = 0; col < 8; col++) { var square = MakeSquare(row, col); flipTable[square] = MakeSquare(7 - row, col); } } pieceSquareAdj[piecePawn] = MakeTable(pawnAdj); pieceSquareAdj[pieceKnight] = MakeTable(knightAdj); pieceSquareAdj[pieceBishop] = MakeTable(bishopAdj); pieceSquareAdj[pieceRook] = MakeTable(rookAdj); pieceSquareAdj[pieceQueen] = MakeTable(emptyAdj); pieceSquareAdj[pieceKing] = MakeTable(kingAdj); var pieceDeltas = [[], [], g_knightDeltas, g_bishopDeltas, g_rookDeltas, g_queenDeltas, g_queenDeltas]; for (i = 0; i < 256; i++) { g_vectorDelta[i] = new Object(); g_vectorDelta[i].delta = 0; g_vectorDelta[i].pieceMask = new Array(2); g_vectorDelta[i].pieceMask[0] = 0; g_vectorDelta[i].pieceMask[1] = 0; } // Initialize the vector delta table for (row = 0; row < 0x80; row += 0x10) for (var col = 0; col < 0x8; col++) { square = row | col; // Pawn moves var index = square - (square - 17) + 128; g_vectorDelta[index].pieceMask[colorWhite >> 3] |= (1 << piecePawn); index = square - (square - 15) + 128; g_vectorDelta[index].pieceMask[colorWhite >> 3] |= (1 << piecePawn); index = square - (square + 17) + 128; g_vectorDelta[index].pieceMask[0] |= (1 << piecePawn); index = square - (square + 15) + 128; g_vectorDelta[index].pieceMask[0] |= (1 << piecePawn); for (var i = pieceKnight; i <= pieceKing; i++) { for (var dir = 0; dir < pieceDeltas[i].length; dir++) { var target = square + pieceDeltas[i][dir]; while (!(target & 0x88)) { index = square - target + 128; g_vectorDelta[index].pieceMask[colorWhite >> 3] |= (1 << i); g_vectorDelta[index].pieceMask[0] |= (1 << i); var flip = -1; if (square < target) flip = 1; if ((square & 0xF0) == (target & 0xF0)) { // On the same row g_vectorDelta[index].delta = flip * 1; } else if ((square & 0x0F) == (target & 0x0F)) { // On the same column g_vectorDelta[index].delta = flip * 16; } else if ((square % 15) == (target % 15)) { g_vectorDelta[index].delta = flip * 15; } else if ((square % 17) == (target % 17)) { g_vectorDelta[index].delta = flip * 17; } if (i == pieceKnight) { g_vectorDelta[index].delta = pieceDeltas[i][dir]; break; } if (i == pieceKing) break; target += pieceDeltas[i][dir]; } } } } InitializeEval(); InitializeFromFen('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'); } function InitializeEval() { g_mobUnit = new Array(2); for (var i = 0; i < 2; i++) { g_mobUnit[i] = new Array(); var enemy = i == 0 ? 0x10 : 8; var friend = i == 0 ? 8 : 0x10; g_mobUnit[i][0] = 1; g_mobUnit[i][0x80] = 0; g_mobUnit[i][enemy | piecePawn] = 1; g_mobUnit[i][enemy | pieceBishop] = 2; g_mobUnit[i][enemy | pieceKnight] = 2; g_mobUnit[i][enemy | pieceRook] = 4; g_mobUnit[i][enemy | pieceQueen] = 6; g_mobUnit[i][enemy | pieceKing] = 6; g_mobUnit[i][friend | piecePawn] = 0; g_mobUnit[i][friend | pieceBishop] = 0; g_mobUnit[i][friend | pieceKnight] = 0; g_mobUnit[i][friend | pieceRook] = 0; g_mobUnit[i][friend | pieceQueen] = 0; g_mobUnit[i][friend | pieceKing] = 0; } } function SetHash() { var result = new Object(); result.hashKeyLow = 0; result.hashKeyHigh = 0; for (var i = 0; i < 256; i++) { var piece = g_board[i]; if (piece & 0x18) { result.hashKeyLow ^= g_zobristLow[i][piece & 0xF]; result.hashKeyHigh ^= g_zobristHigh[i][piece & 0xF]; } } if (!g_toMove) { result.hashKeyLow ^= g_zobristBlackLow; result.hashKeyHigh ^= g_zobristBlackHigh; } return result; } function InitializeFromFen(fen) { var chunks = fen.split(' '); for (var i = 0; i < 256; i++) g_board[i] = 0x80; var row = 0; var col = 0; var pieces = chunks[0]; for (i = 0; i < pieces.length; i++) { var c = pieces.charAt(i); if (c == '/') { row++; col = 0; } else { if (c >= '0' && c <= '9') { for (var j = 0; j < parseInt(c); j++) { g_board[MakeSquare(row, col)] = 0; col++; } } else { var isBlack = c >= 'a' && c <= 'z'; var piece = isBlack ? colorBlack : colorWhite; if (!isBlack) c = pieces.toLowerCase().charAt(i); switch (c) { case 'p': piece |= piecePawn; break; case 'b': piece |= pieceBishop; break; case 'n': piece |= pieceKnight; break; case 'r': piece |= pieceRook; break; case 'q': piece |= pieceQueen; break; case 'k': piece |= pieceKing; break; } g_board[MakeSquare(row, col)] = piece; col++; } } } InitializePieceList(); g_toMove = chunks[1].charAt(0) == 'w' ? colorWhite : 0; var them = 8 - g_toMove; g_castleRights = 0; if (chunks[2].indexOf('K') != -1) { if (g_board[MakeSquare(7, 4)] != (pieceKing | colorWhite) || g_board[MakeSquare(7, 7)] != (pieceRook | colorWhite)) { return 'Invalid FEN: White kingside castling not allowed'; } g_castleRights |= 1; } if (chunks[2].indexOf('Q') != -1) { if (g_board[MakeSquare(7, 4)] != (pieceKing | colorWhite) || g_board[MakeSquare(7, 0)] != (pieceRook | colorWhite)) { return 'Invalid FEN: White queenside castling not allowed'; } g_castleRights |= 2; } if (chunks[2].indexOf('k') != -1) { if (g_board[MakeSquare(0, 4)] != (pieceKing | colorBlack) || g_board[MakeSquare(0, 7)] != (pieceRook | colorBlack)) { return 'Invalid FEN: Black kingside castling not allowed'; } g_castleRights |= 4; } if (chunks[2].indexOf('q') != -1) { if (g_board[MakeSquare(0, 4)] != (pieceKing | colorBlack) || g_board[MakeSquare(0, 0)] != (pieceRook | colorBlack)) { return 'Invalid FEN: Black queenside castling not allowed'; } g_castleRights |= 8; } g_enPassentSquare = -1; if (chunks[3].indexOf('-') == -1) { col = chunks[3].charAt(0).charCodeAt() - 'a'.charCodeAt(); row = 8 - (chunks[3].charAt(1).charCodeAt() - '0'.charCodeAt()); g_enPassentSquare = MakeSquare(row, col); } var hashResult = SetHash(); g_hashKeyLow = hashResult.hashKeyLow; g_hashKeyHigh = hashResult.hashKeyHigh; g_baseEval = 0; for (i = 0; i < 256; i++) { if (g_board[i] & colorWhite) { g_baseEval += pieceSquareAdj[g_board[i] & 0x7][i]; g_baseEval += materialTable[g_board[i] & 0x7]; } else if (g_board[i] & colorBlack) { g_baseEval -= pieceSquareAdj[g_board[i] & 0x7][flipTable[i]]; g_baseEval -= materialTable[g_board[i] & 0x7]; } } if (!g_toMove) g_baseEval = -g_baseEval; g_move50 = 0; g_inCheck = IsSquareAttackable(g_pieceList[(g_toMove | pieceKing) << 4], them); // Check for king capture (invalid FEN) if (IsSquareAttackable(g_pieceList[(them | pieceKing) << 4], g_toMove)) { return 'Invalid FEN: Can capture king'; } // Checkmate/stalemate if (GenerateValidMoves().length == 0) { return g_inCheck ? 'Checkmate' : 'Stalemate'; } return ''; } var g_pieceIndex = new Array(256); var g_pieceList = new Array(2 * 8 * 16); var g_pieceCount = new Array(2 * 8); function InitializePieceList() { for (var i = 0; i < 16; i++) { g_pieceCount[i] = 0; for (var j = 0; j < 16; j++) { // 0 is used as the terminator for piece lists g_pieceList[(i << 4) | j] = 0; } } for (i = 0; i < 256; i++) { g_pieceIndex[i] = 0; if (g_board[i] & (colorWhite | colorBlack)) { var piece = g_board[i] & 0xF; g_pieceList[(piece << 4) | g_pieceCount[piece]] = i; g_pieceIndex[i] = g_pieceCount[piece]; g_pieceCount[piece]++; } } } function MakeMove(move){ var me = g_toMove >> 3; var otherColor = 8 - g_toMove; var flags = move & 0xFF0000; var to = (move >> 8) & 0xFF; var from = move & 0xFF; var captured = g_board[to]; var piece = g_board[from]; var epcEnd = to; if (flags & moveflagEPC) { epcEnd = me ? (to + 0x10) : (to - 0x10); captured = g_board[epcEnd]; g_board[epcEnd] = pieceEmpty; } g_moveUndoStack[g_moveCount] = new UndoHistory(g_enPassentSquare, g_castleRights, g_inCheck, g_baseEval, g_hashKeyLow, g_hashKeyHigh, g_move50