whisper.cat/weboasis/arcade/chess/js/AI/garbochess.js
2023-10-05 23:28:32 +11:00

2738 lines
72 KiB
JavaScript

/*
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);
}
}
}