/* This Chess AI comes from here https://github.com/glinscott/Garbochess-JS Copyright (c) 2011 Gary Linscott All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ // 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) // ECMAScript 5 strict mode /* jshint globalstrict: true*/ /* jslint newcap: true */ /* global postMessage, self */ "use strict"; var g_debug = false; var g_timeout = 40; var g_maxply = 99; var worker = false; try { // not sure this will work on any browser // and since it's just for debug, // a try catch should be good enough if ( self.constructor.name === "DedicatedWorkerContext") { worker = true; } } catch(e) {} debug ("worker = "+worker); 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 === undefined) { 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 !== undefined && hashNode.lock === g_hashKeyHigh && hashNode.bestMove !== undefined) { pvString += PVFromHash(hashNode.bestMove, ply - 1); } UnmakeMove(move); return pvString; } // // Searching code // var g_startTime; var g_nodeCount; var g_qNodeCount; var g_searchValid; var g_globalPly = 0; function Search(finishMoveCallback, maxPly, finishPlyCallback) { debug("start search with maxPly:"+maxPly+" and timeout "+g_timeout); if (GenerateValidMoves().length === 0) { debug( g_inCheck ? 'Checkmate' : 'Stalemate' ); return false; } var lastEval; var alpha = minEval; var beta = maxEval; g_globalPly++; 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] !== undefined) { bestMove = g_hashTable[g_hashKeyLow & g_hashMask].bestMove; } if (finishPlyCallback !== undefined) { finishPlyCallback(bestMove, value, (new Date()).getTime() - g_startTime, i); } } if (finishMoveCallback !== undefined) { 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 PawnEval(color) { var pieceIdx = (color | 1) << 4; var from = g_pieceList[pieceIdx++]; while (from !== 0) { from = g_pieceList[pieceIdx++]; } } 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 = []; var moveScores = []; var wasInCheck = g_inCheck; var i; if (wasInCheck) { // TODO: Fast check escape generator and fast checking moves generator GenerateCaptureMoves(moves, null); GenerateAllMoves(moves); for (i = 0; i < moves.length; i++) { moveScores[i] = ScoreMove(moves[i]); } } else { GenerateCaptureMoves(moves, null); 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 = []; 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 = []; this.losingCaptures = null; this.moveCount = 0; this.atMove = -1; this.moveScores = null; this.stage = 0; this.nextMove = function () { var i; if (++this.atMove == this.moveCount) { this.stage++; if (this.stage == 1) { if (this.hashMove !== undefined && IsHashMoveValid(hashMove)) { this.moves[0] = hashMove; this.moveCount = 1; } if (this.moveCount != 1) { this.hashMove = undefined; this.stage++; } } if (this.stage == 2) { GenerateCaptureMoves(this.moves, null); this.moveCount = this.moves.length; this.moveScores = new Array(this.moveCount); // Move ordering for (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 = []; } 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 !== undefined && 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? var value; if (!g_inCheck && allowNull && beta > minMateBuffer && beta < maxMateBuffer) { // Try some razoring if (hashMove === undefined && 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; 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 inCheck = g_inCheck; 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; } value = null; 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 !== undefined && 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 moveflagPromoteRook = 0x00 << 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; var i,s,k; switch (a.length) { case 1: if (a[0].constructor === Number) { this.mt[0]= a[0]; for (i = 1; i < N; ++i) { 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; var j = 0; i = 1; for (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 (k = N - 1; k !== 0; --k) { 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 = []; for (i = 0; i < a.length; ++i) { seeds.push(a[i]); } this.setSeed(seeds); return; } }; this.setSeed(0x1BADF00D); this.next = function (bits) { var k; if (this.mti >= N) { var x = 0; for (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 = []; var g_move50 = 0; var g_repMoveStack = []; 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(fen) { var i,j; var row, col; var square; g_killers = new Array(128); for (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 (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 (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 (row = 0; row < 8; row++) { for (col = 0; col < 8; col++) { 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] = {}; 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 (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 (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] = []; 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 = {}; 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 i; var row = 0; var col = 0; var chunks = fen.split(' '); for (i = 0; i < 256; i++) g_board[i] = 0x80; 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,10); 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() { var i; for (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){ // move format: // --flags-- ---to---- --from--- // 0000 0000 0000 0000 0000 0000 // g_board is a 256x256 array // representing the chess board // right in the middle of it // moveflagEPC = 0000 0010 0000 0000 0000 0000 // moveflagCastleKing = 0000 0100 0000 0000 0000 0000 // moveflagCastleQueen = 0000 1000 0000 0000 0000 0000 // moveflagPromotion = 0001 0000 0000 0000 0000 0000 // moveflagPromoteRook = 0000 0000 0000 0000 0000 0000 // moveflagPromoteKnight = 0010 0000 0000 0000 0000 0000 // moveflagPromoteQueen = 0100 0000 0000 0000 0000 0000 // moveflagPromoteBishop = 1000 0000 0000 0000 0000 0000 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; var rook, rookIndex; 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, captured); g_moveCount++; g_enPassentSquare = -1; if (flags) { if (flags & moveflagCastleKing) { if (IsSquareAttackable(from + 1, otherColor) || IsSquareAttackable(from + 2, otherColor)) { g_moveCount--; return false; } rook = g_board[to + 1]; g_hashKeyLow ^= g_zobristLow[to + 1][rook & 0xF]; g_hashKeyHigh ^= g_zobristHigh[to + 1][rook & 0xF]; g_hashKeyLow ^= g_zobristLow[to - 1][rook & 0xF]; g_hashKeyHigh ^= g_zobristHigh[to - 1][rook & 0xF]; g_board[to - 1] = rook; g_board[to + 1] = pieceEmpty; g_baseEval -= pieceSquareAdj[rook & 0x7][me === 0 ? flipTable[to + 1] : (to + 1)]; g_baseEval += pieceSquareAdj[rook & 0x7][me === 0 ? flipTable[to - 1] : (to - 1)]; rookIndex = g_pieceIndex[to + 1]; g_pieceIndex[to - 1] = rookIndex; g_pieceList[((rook & 0xF) << 4) | rookIndex] = to - 1; } else if (flags & moveflagCastleQueen) { if (IsSquareAttackable(from - 1, otherColor) || IsSquareAttackable(from - 2, otherColor)) { g_moveCount--; return false; } rook = g_board[to - 2]; g_hashKeyLow ^= g_zobristLow[to -2][rook & 0xF]; g_hashKeyHigh ^= g_zobristHigh[to - 2][rook & 0xF]; g_hashKeyLow ^= g_zobristLow[to + 1][rook & 0xF]; g_hashKeyHigh ^= g_zobristHigh[to + 1][rook & 0xF]; g_board[to + 1] = rook; g_board[to - 2] = pieceEmpty; g_baseEval -= pieceSquareAdj[rook & 0x7][me === 0 ? flipTable[to - 2] : (to - 2)]; g_baseEval += pieceSquareAdj[rook & 0x7][me === 0 ? flipTable[to + 1] : (to + 1)]; rookIndex = g_pieceIndex[to - 2]; g_pieceIndex[to + 1] = rookIndex; g_pieceList[((rook & 0xF) << 4) | rookIndex] = to + 1; } } if ((captured & 0x7) > 0) { // Remove our piece from the piece list var capturedType = captured & 0xF; g_pieceCount[capturedType]--; var lastPieceSquare = g_pieceList[(capturedType << 4) | g_pieceCount[capturedType]]; g_pieceIndex[lastPieceSquare] = g_pieceIndex[epcEnd]; g_pieceList[(capturedType << 4) | g_pieceIndex[lastPieceSquare]] = lastPieceSquare; g_pieceList[(capturedType << 4) | g_pieceCount[capturedType]] = 0; g_baseEval += materialTable[captured & 0x7]; g_baseEval += pieceSquareAdj[captured & 0x7][me ? flipTable[epcEnd] : epcEnd]; g_hashKeyLow ^= g_zobristLow[epcEnd][capturedType]; g_hashKeyHigh ^= g_zobristHigh[epcEnd][capturedType]; g_move50 = 0; } else if ((piece & 0x7) == piecePawn) { var diff = to - from; if (diff < 0) diff = -diff; if (diff > 16) { g_enPassentSquare = me ? (to + 0x10) : (to - 0x10); } g_move50 = 0; } g_hashKeyLow ^= g_zobristLow[from][piece & 0xF]; g_hashKeyHigh ^= g_zobristHigh[from][piece & 0xF]; g_hashKeyLow ^= g_zobristLow[to][piece & 0xF]; g_hashKeyHigh ^= g_zobristHigh[to][piece & 0xF]; g_hashKeyLow ^= g_zobristBlackLow; g_hashKeyHigh ^= g_zobristBlackHigh; g_castleRights &= g_castleRightsMask[from] & g_castleRightsMask[to]; g_baseEval -= pieceSquareAdj[piece & 0x7][me === 0 ? flipTable[from] : from]; // Move our piece in the piece list g_pieceIndex[to] = g_pieceIndex[from]; g_pieceList[((piece & 0xF) << 4) | g_pieceIndex[to]] = to; if (flags & moveflagPromotion) { var newPiece = piece & (~0x7); if (flags & moveflagPromoteKnight) newPiece |= pieceKnight; else if (flags & moveflagPromoteQueen) newPiece |= pieceQueen; else if (flags & moveflagPromoteBishop) newPiece |= pieceBishop; else newPiece |= pieceRook; g_hashKeyLow ^= g_zobristLow[to][piece & 0xF]; g_hashKeyHigh ^= g_zobristHigh[to][piece & 0xF]; g_board[to] = newPiece; g_hashKeyLow ^= g_zobristLow[to][newPiece & 0xF]; g_hashKeyHigh ^= g_zobristHigh[to][newPiece & 0xF]; g_baseEval += pieceSquareAdj[newPiece & 0x7][me === 0 ? flipTable[to] : to]; g_baseEval -= materialTable[piecePawn]; g_baseEval += materialTable[newPiece & 0x7]; var pawnType = piece & 0xF; var promoteType = newPiece & 0xF; g_pieceCount[pawnType]--; var lastPawnSquare = g_pieceList[(pawnType << 4) | g_pieceCount[pawnType]]; g_pieceIndex[lastPawnSquare] = g_pieceIndex[to]; g_pieceList[(pawnType << 4) | g_pieceIndex[lastPawnSquare]] = lastPawnSquare; g_pieceList[(pawnType << 4) | g_pieceCount[pawnType]] = 0; g_pieceIndex[to] = g_pieceCount[promoteType]; g_pieceList[(promoteType << 4) | g_pieceIndex[to]] = to; g_pieceCount[promoteType]++; } else { g_board[to] = g_board[from]; g_baseEval += pieceSquareAdj[piece & 0x7][me === 0 ? flipTable[to] : to]; } g_board[from] = pieceEmpty; g_toMove = otherColor; g_baseEval = -g_baseEval; if ((piece & 0x7) == pieceKing || g_inCheck) { if (IsSquareAttackable(g_pieceList[(pieceKing | (8 - g_toMove)) << 4], otherColor)) { UnmakeMove(move); return false; } } else { var kingPos = g_pieceList[(pieceKing | (8 - g_toMove)) << 4]; if (ExposesCheck(from, kingPos)) { UnmakeMove(move); return false; } if (epcEnd != to) { if (ExposesCheck(epcEnd, kingPos)) { UnmakeMove(move); return false; } } } g_inCheck = false; if (flags <= moveflagEPC) { var theirKingPos = g_pieceList[(pieceKing | g_toMove) << 4]; // First check if the piece we moved can attack the enemy king g_inCheck = IsSquareAttackableFrom(theirKingPos, to); if (!g_inCheck) { // Now check if the square we moved from exposes check on the enemy king g_inCheck = ExposesCheck(from, theirKingPos); if (!g_inCheck) { // Finally, ep. capture can cause another square to be exposed if (epcEnd != to) { g_inCheck = ExposesCheck(epcEnd, theirKingPos); } } } } else { // Castle or promotion, slow check g_inCheck = IsSquareAttackable(g_pieceList[(pieceKing | g_toMove) << 4], 8 - g_toMove); } g_repMoveStack[g_moveCount - 1] = g_hashKeyLow; g_move50++; return true; } function UnmakeMove(move){ g_toMove = 8 - g_toMove; g_baseEval = -g_baseEval; g_moveCount--; g_enPassentSquare = g_moveUndoStack[g_moveCount].ep; g_castleRights = g_moveUndoStack[g_moveCount].castleRights; g_inCheck = g_moveUndoStack[g_moveCount].inCheck; g_baseEval = g_moveUndoStack[g_moveCount].baseEval; g_hashKeyLow = g_moveUndoStack[g_moveCount].hashKeyLow; g_hashKeyHigh = g_moveUndoStack[g_moveCount].hashKeyHigh; g_move50 = g_moveUndoStack[g_moveCount].move50; var otherColor = 8 - g_toMove; var me = g_toMove >> 3; var them = otherColor >> 3; var flags = move & 0xFF0000; var captured = g_moveUndoStack[g_moveCount].captured; var to = (move >> 8) & 0xFF; var from = move & 0xFF; var piece = g_board[to]; var rook,rookIndex; if (flags) { if (flags & moveflagCastleKing) { rook = g_board[to - 1]; g_board[to + 1] = rook; g_board[to - 1] = pieceEmpty; rookIndex = g_pieceIndex[to - 1]; g_pieceIndex[to + 1] = rookIndex; g_pieceList[((rook & 0xF) << 4) | rookIndex] = to + 1; } else if (flags & moveflagCastleQueen) { rook = g_board[to + 1]; g_board[to - 2] = rook; g_board[to + 1] = pieceEmpty; rookIndex = g_pieceIndex[to + 1]; g_pieceIndex[to - 2] = rookIndex; g_pieceList[((rook & 0xF) << 4) | rookIndex] = to - 2; } } if (flags & moveflagPromotion) { piece = (g_board[to] & (~0x7)) | piecePawn; g_board[from] = piece; var pawnType = g_board[from] & 0xF; var promoteType = g_board[to] & 0xF; g_pieceCount[promoteType]--; var lastPromoteSquare = g_pieceList[(promoteType << 4) | g_pieceCount[promoteType]]; g_pieceIndex[lastPromoteSquare] = g_pieceIndex[to]; g_pieceList[(promoteType << 4) | g_pieceIndex[lastPromoteSquare]] = lastPromoteSquare; g_pieceList[(promoteType << 4) | g_pieceCount[promoteType]] = 0; g_pieceIndex[to] = g_pieceCount[pawnType]; g_pieceList[(pawnType << 4) | g_pieceIndex[to]] = to; g_pieceCount[pawnType]++; } else { g_board[from] = g_board[to]; } var epcEnd = to; if (flags & moveflagEPC) { if (g_toMove == colorWhite) epcEnd = to + 0x10; else epcEnd = to - 0x10; g_board[to] = pieceEmpty; } g_board[epcEnd] = captured; // Move our piece in the piece list g_pieceIndex[from] = g_pieceIndex[to]; g_pieceList[((piece & 0xF) << 4) | g_pieceIndex[from]] = from; if (captured) { // Restore our piece to the piece list var captureType = captured & 0xF; g_pieceIndex[epcEnd] = g_pieceCount[captureType]; g_pieceList[(captureType << 4) | g_pieceCount[captureType]] = epcEnd; g_pieceCount[captureType]++; } } function ExposesCheck(from, kingPos){ var index = kingPos - from + 128; // If a queen can't reach it, nobody can! if ((g_vectorDelta[index].pieceMask[0] & (1 << (pieceQueen))) !== 0) { var delta = g_vectorDelta[index].delta; var pos = kingPos + delta; while (g_board[pos] === 0) pos += delta; var piece = g_board[pos]; if (((piece & (g_board[kingPos] ^ 0x18)) & 0x18) === 0) return false; // Now see if the piece can actually attack the king var backwardIndex = pos - kingPos + 128; return (g_vectorDelta[backwardIndex].pieceMask[(piece >> 3) & 1] & (1 << (piece & 0x7))) !== 0; } return false; } function IsSquareOnPieceLine(target, from) { var index = from - target + 128; var piece = g_board[from]; return (g_vectorDelta[index].pieceMask[(piece >> 3) & 1] & (1 << (piece & 0x7))) ? true : false; } function IsSquareAttackableFrom(target, from){ var index = from - target + 128; var piece = g_board[from]; if (g_vectorDelta[index].pieceMask[(piece >> 3) & 1] & (1 << (piece & 0x7))) { // Yes, this square is pseudo-attackable. Now, check for real attack var inc = g_vectorDelta[index].delta; do { from += inc; if (from == target) return true; } while (g_board[from] === 0); } return false; } function IsSquareAttackable(target, color) { // Attackable by pawns? var inc = color ? -16 : 16; var pawn = (color ? colorWhite : colorBlack) | 1; if (g_board[target - (inc - 1)] == pawn) return true; if (g_board[target - (inc + 1)] == pawn) return true; // Attackable by pieces? for (var i = 2; i <= 6; i++) { var index = (color | i) << 4; var square = g_pieceList[index]; while (square !== 0) { if (IsSquareAttackableFrom(target, square)) return true; square = g_pieceList[++index]; } } return false; } function GenerateMove(from, to) { return from | (to << 8); } function GenerateMove(from, to, flags){ return from | (to << 8) | flags; } function GenerateValidMoves() { var moveList = []; var allMoves = []; GenerateCaptureMoves(allMoves, null); GenerateAllMoves(allMoves); for (var i = allMoves.length - 1; i >= 0; i--) { if (MakeMove(allMoves[i])) { moveList[moveList.length] = allMoves[i]; UnmakeMove(allMoves[i]); } else { } } return moveList; } function GenerateAllMoves(moveStack) { var from, to, piece, pieceIdx; // Pawn quiet moves pieceIdx = (g_toMove | 1) << 4; from = g_pieceList[pieceIdx++]; while (from !== 0) { GeneratePawnMoves(moveStack, from); from = g_pieceList[pieceIdx++]; } // Knight quiet moves pieceIdx = (g_toMove | 2) << 4; from = g_pieceList[pieceIdx++]; while (from !== 0) { to = from + 31; if (g_board[to] === 0) moveStack[moveStack.length] = GenerateMove(from, to); to = from + 33; if (g_board[to] === 0) moveStack[moveStack.length] = GenerateMove(from, to); to = from + 14; if (g_board[to] === 0) moveStack[moveStack.length] = GenerateMove(from, to); to = from - 14; if (g_board[to] === 0) moveStack[moveStack.length] = GenerateMove(from, to); to = from - 31; if (g_board[to] === 0) moveStack[moveStack.length] = GenerateMove(from, to); to = from - 33; if (g_board[to] === 0) moveStack[moveStack.length] = GenerateMove(from, to); to = from + 18; if (g_board[to] === 0) moveStack[moveStack.length] = GenerateMove(from, to); to = from - 18; if (g_board[to] === 0) moveStack[moveStack.length] = GenerateMove(from, to); from = g_pieceList[pieceIdx++]; } // Bishop quiet moves pieceIdx = (g_toMove | 3) << 4; from = g_pieceList[pieceIdx++]; while (from !== 0) { to = from - 15; while (g_board[to] === 0) { moveStack[moveStack.length] = GenerateMove(from, to); to -= 15; } to = from - 17; while (g_board[to] === 0) { moveStack[moveStack.length] = GenerateMove(from, to); to -= 17; } to = from + 15; while (g_board[to] === 0) { moveStack[moveStack.length] = GenerateMove(from, to); to += 15; } to = from + 17; while (g_board[to] === 0) { moveStack[moveStack.length] = GenerateMove(from, to); to += 17; } from = g_pieceList[pieceIdx++]; } // Rook quiet moves pieceIdx = (g_toMove | 4) << 4; from = g_pieceList[pieceIdx++]; while (from !== 0) { to = from - 1; while (g_board[to] === 0) { moveStack[moveStack.length] = GenerateMove(from, to); to--; } to = from + 1; while (g_board[to] === 0) { moveStack[moveStack.length] = GenerateMove(from, to); to++; } to = from + 16; while (g_board[to] === 0) { moveStack[moveStack.length] = GenerateMove(from, to); to += 16; } to = from - 16; while (g_board[to] === 0) { moveStack[moveStack.length] = GenerateMove(from, to); to -= 16; } from = g_pieceList[pieceIdx++]; } // Queen quiet moves pieceIdx = (g_toMove | 5) << 4; from = g_pieceList[pieceIdx++]; while (from !== 0) { to = from - 15; while (g_board[to] === 0) { moveStack[moveStack.length] = GenerateMove(from, to); to -= 15; } to = from - 17; while (g_board[to] === 0) { moveStack[moveStack.length] = GenerateMove(from, to); to -= 17; } to = from + 15; while (g_board[to] === 0) { moveStack[moveStack.length] = GenerateMove(from, to); to += 15; } to = from + 17; while (g_board[to] === 0) { moveStack[moveStack.length] = GenerateMove(from, to); to += 17; } to = from - 1; while (g_board[to] === 0) { moveStack[moveStack.length] = GenerateMove(from, to); to--; } to = from + 1; while (g_board[to] === 0) { moveStack[moveStack.length] = GenerateMove(from, to); to++; } to = from + 16; while (g_board[to] === 0) { moveStack[moveStack.length] = GenerateMove(from, to); to += 16; } to = from - 16; while (g_board[to] === 0) { moveStack[moveStack.length] = GenerateMove(from, to); to -= 16; } from = g_pieceList[pieceIdx++]; } // King quiet moves pieceIdx = (g_toMove | 6) << 4; from = g_pieceList[pieceIdx]; to = from - 15; if (g_board[to] === 0) moveStack[moveStack.length] = GenerateMove(from, to); to = from - 17; if (g_board[to] === 0) moveStack[moveStack.length] = GenerateMove(from, to); to = from + 15; if (g_board[to] === 0) moveStack[moveStack.length] = GenerateMove(from, to); to = from + 17; if (g_board[to] === 0) moveStack[moveStack.length] = GenerateMove(from, to); to = from - 1; if (g_board[to] === 0) moveStack[moveStack.length] = GenerateMove(from, to); to = from + 1; if (g_board[to] === 0) moveStack[moveStack.length] = GenerateMove(from, to); to = from - 16; if (g_board[to] === 0) moveStack[moveStack.length] = GenerateMove(from, to); to = from + 16; if (g_board[to] === 0) moveStack[moveStack.length] = GenerateMove(from, to); if (!g_inCheck) { var castleRights = g_castleRights; if (!g_toMove) castleRights >>= 2; if (castleRights & 1) { // Kingside castle if (g_board[from + 1] == pieceEmpty && g_board[from + 2] == pieceEmpty) { moveStack[moveStack.length] = GenerateMove(from, from + 0x02, moveflagCastleKing); } } if (castleRights & 2) { // Queenside castle if (g_board[from - 1] == pieceEmpty && g_board[from - 2] == pieceEmpty && g_board[from - 3] == pieceEmpty) { moveStack[moveStack.length] = GenerateMove(from, from - 0x02, moveflagCastleQueen); } } } } function GenerateCaptureMoves(moveStack, moveScores) { var from, to, piece, pieceIdx; var inc = (g_toMove == 8) ? -16 : 16; var enemy = g_toMove == 8 ? 0x10 : 0x8; // Pawn captures pieceIdx = (g_toMove | 1) << 4; from = g_pieceList[pieceIdx++]; while (from !== 0) { to = from + inc - 1; if (g_board[to] & enemy) { MovePawnTo(moveStack, from, to); } to = from + inc + 1; if (g_board[to] & enemy) { MovePawnTo(moveStack, from, to); } from = g_pieceList[pieceIdx++]; } if (g_enPassentSquare !== -1) { inc = (g_toMove === colorWhite) ? -16 : 16; var pawn = g_toMove | piecePawn; from = g_enPassentSquare - (inc + 1); if ((g_board[from] & 0xF) === pawn) { moveStack[moveStack.length] = GenerateMove(from, g_enPassentSquare, moveflagEPC); } from = g_enPassentSquare - (inc - 1); if ((g_board[from] & 0xF) === pawn) { moveStack[moveStack.length] = GenerateMove(from, g_enPassentSquare, moveflagEPC); } } // Knight captures pieceIdx = (g_toMove | 2) << 4; from = g_pieceList[pieceIdx++]; while (from !== 0) { to = from + 31; if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); to = from + 33; if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); to = from + 14; if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); to = from - 14; if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); to = from - 31; if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); to = from - 33; if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); to = from + 18; if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); to = from - 18; if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); from = g_pieceList[pieceIdx++]; } // Bishop captures pieceIdx = (g_toMove | 3) << 4; from = g_pieceList[pieceIdx++]; while (from !== 0) { to = from; do { to -= 15; } while (g_board[to] === 0); if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); to = from; do { to -= 17; } while (g_board[to] === 0); if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); to = from; do { to += 15; } while (g_board[to] === 0); if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); to = from; do { to += 17; } while (g_board[to] === 0); if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); from = g_pieceList[pieceIdx++]; } // Rook captures pieceIdx = (g_toMove | 4) << 4; from = g_pieceList[pieceIdx++]; while (from !== 0) { to = from; do { to--; } while (g_board[to] === 0); if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); to = from; do { to++; } while (g_board[to] === 0); if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); to = from; do { to -= 16; } while (g_board[to] === 0); if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); to = from; do { to += 16; } while (g_board[to] === 0); if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); from = g_pieceList[pieceIdx++]; } // Queen captures pieceIdx = (g_toMove | 5) << 4; from = g_pieceList[pieceIdx++]; while (from !== 0) { to = from; do { to -= 15; } while (g_board[to] === 0); if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); to = from; do { to -= 17; } while (g_board[to] === 0); if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); to = from; do { to += 15; } while (g_board[to] === 0); if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); to = from; do { to += 17; } while (g_board[to] === 0); if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); to = from; do { to--; } while (g_board[to] === 0); if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); to = from; do { to++; } while (g_board[to] === 0); if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); to = from; do { to -= 16; } while (g_board[to] === 0); if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); to = from; do { to += 16; } while (g_board[to] === 0); if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); from = g_pieceList[pieceIdx++]; } // King captures pieceIdx = (g_toMove | 6) << 4; from = g_pieceList[pieceIdx]; to = from - 15; if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); to = from - 17; if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); to = from + 15; if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); to = from + 17; if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); to = from - 1; if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); to = from + 1; if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); to = from - 16; if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); to = from + 16; if (g_board[to] & enemy) moveStack[moveStack.length] = GenerateMove(from, to); } function MovePawnTo(moveStack, start, square) { var row = square & 0xF0; if ((row == 0x90) || (row == 0x20)) { moveStack[moveStack.length] = GenerateMove(start, square, moveflagPromotion | moveflagPromoteQueen); moveStack[moveStack.length] = GenerateMove(start, square, moveflagPromotion | moveflagPromoteKnight); moveStack[moveStack.length] = GenerateMove(start, square, moveflagPromotion | moveflagPromoteBishop); moveStack[moveStack.length] = GenerateMove(start, square, moveflagPromotion); } else { moveStack[moveStack.length] = GenerateMove(start, square, 0); } } function GeneratePawnMoves(moveStack, from) { var piece = g_board[from]; var color = piece & colorWhite; var inc = (color === colorWhite) ? -16 : 16; // Quiet pawn moves var to = from + inc; if (g_board[to] === 0) { MovePawnTo(moveStack, from, to, pieceEmpty); // Check if we can do a 2 square jump if ((((from & 0xF0) === 0x30) && color !== colorWhite) || (((from & 0xF0) === 0x80) && color === colorWhite)) { to += inc; if (g_board[to] === 0) { moveStack[moveStack.length] = GenerateMove(from, to); } } } } function UndoHistory(ep, castleRights, inCheck, baseEval, hashKeyLow, hashKeyHigh, move50, captured) { this.ep = ep; this.castleRights = castleRights; this.inCheck = inCheck; this.baseEval = baseEval; this.hashKeyLow = hashKeyLow; this.hashKeyHigh = hashKeyHigh; this.move50 = move50; this.captured = captured; } var g_seeValues = [ 0, 1, 3, 3, 5, 9, 900, 0, 0, 1, 3, 3, 5, 9, 900, 0]; function See(move) { var from = move & 0xFF; var to = (move >> 8) & 0xFF; var fromPiece = g_board[from]; var fromValue = g_seeValues[fromPiece & 0xF]; var toValue = g_seeValues[g_board[to] & 0xF]; var pieceType,pieceValue,i; if (fromValue <= toValue) { return true; } if (move >> 16) { // Castles, promotion, ep are always good return true; } var us = (fromPiece & colorWhite) ? colorWhite : 0; var them = 8 - us; // Pawn attacks // If any opponent pawns can capture back, this capture is probably not worthwhile (as we must be using knight or above). var inc = (fromPiece & colorWhite) ? -16 : 16; // Note: this is capture direction from to, so reversed from normal move direction if (((g_board[to + inc + 1] & 0xF) == (piecePawn | them)) || ((g_board[to + inc - 1] & 0xF) == (piecePawn | them))) { return false; } var themAttacks = []; // Knight attacks // If any opponent knights can capture back, and the deficit we have to make up is greater than the knights value, // it's not worth it. We can capture on this square again, and the opponent doesn't have to capture back. var captureDeficit = fromValue - toValue; SeeAddKnightAttacks(to, them, themAttacks); if (themAttacks.length !== 0 && captureDeficit > g_seeValues[pieceKnight]) { return false; } // Slider attacks g_board[from] = 0; for (pieceType = pieceBishop; pieceType <= pieceQueen; pieceType++) { if (SeeAddSliderAttacks(to, them, themAttacks, pieceType)) { if (captureDeficit > g_seeValues[pieceType]) { g_board[from] = fromPiece; return false; } } } // Pawn defenses // At this point, we are sure we are making a "losing" capture. The opponent can not capture back with a // pawn. They cannot capture back with a minor/major and stand pat either. So, if we can capture with // a pawn, it's got to be a winning or equal capture. if (((g_board[to - inc + 1] & 0xF) === (piecePawn | us)) || ((g_board[to - inc - 1] & 0xF) === (piecePawn | us))) { g_board[from] = fromPiece; return true; } // King attacks SeeAddSliderAttacks(to, them, themAttacks, pieceKing); // Our attacks var usAttacks = []; SeeAddKnightAttacks(to, us, usAttacks); for (pieceType = pieceBishop; pieceType <= pieceKing; pieceType++) { SeeAddSliderAttacks(to, us, usAttacks, pieceType); } g_board[from] = fromPiece; // We are currently winning the amount of material of the captured piece, time to see if the opponent // can get it back somehow. We assume the opponent can capture our current piece in this score, which // simplifies the later code considerably. var seeValue = toValue - fromValue; for (; ; ) { var capturingPieceValue = 1000; var capturingPieceIndex = -1; // Find the least valuable piece of the opponent that can attack the square for (i = 0; i < themAttacks.length; i++) { if (themAttacks[i] !== 0) { pieceValue = g_seeValues[g_board[themAttacks[i]] & 0x7]; if (pieceValue < capturingPieceValue) { capturingPieceValue = pieceValue; capturingPieceIndex = i; } } } if (capturingPieceIndex == -1) { // Opponent can't capture back, we win return true; } // Now, if seeValue < 0, the opponent is winning. If even after we take their piece, // we can't bring it back to 0, then we have lost this battle. seeValue += capturingPieceValue; if (seeValue < 0) { return false; } var capturingPieceSquare = themAttacks[capturingPieceIndex]; themAttacks[capturingPieceIndex] = 0; // Add any x-ray attackers SeeAddXrayAttack(to, capturingPieceSquare, us, usAttacks, themAttacks); // Our turn to capture capturingPieceValue = 1000; capturingPieceIndex = -1; // Find our least valuable piece that can attack the square for (i = 0; i < usAttacks.length; i++) { if (usAttacks[i] !== 0) { pieceValue = g_seeValues[g_board[usAttacks[i]] & 0x7]; if (pieceValue < capturingPieceValue) { capturingPieceValue = pieceValue; capturingPieceIndex = i; } } } if (capturingPieceIndex == -1) { // We can't capture back, we lose :( return false; } // Assume our opponent can capture us back, and if we are still winning, we can stand-pat // here, and assume we've won. seeValue -= capturingPieceValue; if (seeValue >= 0) { return true; } capturingPieceSquare = usAttacks[capturingPieceIndex]; usAttacks[capturingPieceIndex] = 0; // Add any x-ray attackers SeeAddXrayAttack(to, capturingPieceSquare, us, usAttacks, themAttacks); } } function SeeAddXrayAttack(target, square, us, usAttacks, themAttacks) { var index = square - target + 128; var delta = -g_vectorDelta[index].delta; if (delta === 0) return; square += delta; while (g_board[square] === 0) { square += delta; } if ((g_board[square] & 0x18) && IsSquareOnPieceLine(target, square)) { if ((g_board[square] & 8) == us) { usAttacks[usAttacks.length] = square; } else { themAttacks[themAttacks.length] = square; } } } // target = attacking square, us = color of knights to look for, attacks = array to add squares to function SeeAddKnightAttacks(target, us, attacks) { var pieceIdx = (us | pieceKnight) << 4; var attackerSq = g_pieceList[pieceIdx++]; while (attackerSq !== 0) { if (IsSquareOnPieceLine(target, attackerSq)) { attacks[attacks.length] = attackerSq; } attackerSq = g_pieceList[pieceIdx++]; } } function SeeAddSliderAttacks(target, us, attacks, pieceType) { var pieceIdx = (us | pieceType) << 4; var attackerSq = g_pieceList[pieceIdx++]; var hit = false; while (attackerSq !== 0) { if (IsSquareAttackableFrom(target, attackerSq)) { attacks[attacks.length] = attackerSq; hit = true; } attackerSq = g_pieceList[pieceIdx++]; } return hit; } function BuildPVMessage(bestMove, value, timeTaken, ply) { var totalNodes = g_nodeCount + g_qNodeCount; return "Ply:" + ply + " Score:" + value + " Nodes:" + totalNodes + " NPS:" + ((totalNodes / (timeTaken / 1000)) | 0) + " " + PVFromHash(bestMove, 15); } ////////////////////////////////////////////////// // Test Harness ////////////////////////////////////////////////// function FinishPlyCallback(bestMove, value, timeTaken, ply) { postMessage("pv " + BuildPVMessage(bestMove, value, timeTaken, ply)); } function FinishMoveLocalTesting(bestMove, value, timeTaken, ply) { if (bestMove !== undefined) { MakeMove(bestMove); postMessage(FormatMove(bestMove)); } } var needsReset = true; self.onmessage = function (e) { if (e.data == "go" || needsReset) { ResetGame(); needsReset = false; if (e.data == "go") return; } if (e.data.match("^position") == "position") { ResetGame(); var result = InitializeFromFen(e.data.substr(9, e.data.length - 9)); if (result.length !== 0) { postMessage("message " + result); } } else if (e.data.match("^search") == "search") { var level = e.data.substr(7,e.data.length-7).split(","); g_timeout = parseInt(level[0], 10); g_maxply = parseInt(level[1], 10); Search(FinishMoveLocalTesting, g_maxply, FinishPlyCallback); } else if (e.data == "analyze") { g_timeout = 99999999999; Search(null, g_maxply, FinishPlyCallback); } else { MakeMove(GetMoveFromString(e.data)); } }; function debug(msg) { if (g_debug) { if(worker) { postMessage("console: "+msg); } else { console.log(msg); } } }