Preface and Acknowledgements
Placeholder: This section will include a preface and acknowledgements.
Computer Chess Overview
Placeholder: Introduce computer chess concepts and history.
/*
Bit Map With Coordinates
a8:56 b8:57 c8:58 d8:59 e8:60 f8:61 g8:62 h8:63
a7:48 b7:49 c7:50 d7:51 e7:52 f7:53 g7:54 h7:55
a6:40 b6:41 c6:42 d6:43 e6:44 f6:45 g6:46 h6:47
a5:32 b5:33 c5:34 d5:35 e5:36 f5:37 g5:38 h5:39
a4:24 b4:25 c4:26 d4:27 e4:28 f4:29 g4:30 h4:31
a3:16 b3:17 c3:18 d3:19 e3:20 f3:21 g3:22 h3:23
a2:8 b2:9 c2:10 d2:11 e2:12 f2:13 g2:14 h2:15
a1:0 b1:1 c1:2 d1:3 e1:4 f1:5 g1:6 h1:7
Bit Map Clean
56 57 58 59 60 61 62 63
48 49 50 51 52 53 54 55
40 41 42 43 44 45 46 47
32 33 34 35 36 37 38 39
24 25 26 27 28 29 30 31
16 17 18 19 20 21 22 23
8 9 10 11 12 13 14 15
0 1 2 3 4 5 6 7
*/
Computer Chess Challenges
Placeholder: Discuss the main challenges in building a chess engine.
export const NOT_A = (FILE[1]|FILE[2]|FILE[3]|FILE[4]|FILE[5]|FILE[6]|FILE[7]);
export const NOT_AB = (FILE[2]|FILE[3]|FILE[4]|FILE[5]|FILE[6]|FILE[7]);
export const NOT_B = (FILE[0]|FILE[2]|FILE[3]|FILE[4]|FILE[5]|FILE[6]|FILE[7]);
export const NOT_C = (FILE[0]|FILE[1]|FILE[3]|FILE[4]|FILE[5]|FILE[6]|FILE[7]);
export const NOT_D = (FILE[0]|FILE[1]|FILE[2]|FILE[4]|FILE[5]|FILE[6]|FILE[7]);
export const NOT_E = (FILE[0]|FILE[1]|FILE[2]|FILE[3]|FILE[5]|FILE[6]|FILE[7]);
export const NOT_F = (FILE[0]|FILE[1]|FILE[2]|FILE[3]|FILE[4]|FILE[6]|FILE[7]);
export const NOT_G = (FILE[0]|FILE[1]|FILE[2]|FILE[3]|FILE[4]|FILE[5]|FILE[7]);
export const NOT_H = (FILE[0]|FILE[1]|FILE[2]|FILE[3]|FILE[4]|FILE[5]|FILE[6]);
export const NOT_GH = (FILE[0]|FILE[1]|FILE[2]|FILE[3]|FILE[4]|FILE[5]);
export const NOT_1 = ( RANK[1]| RANK[2]| RANK[3]| RANK[4]| RANK[5]| RANK[6]| RANK[7]);
export const NOT_1_2 = ( RANK[2]| RANK[3]| RANK[4]| RANK[5]| RANK[6]| RANK[7]);
export const NOT_8 = ( RANK[0]| RANK[1]| RANK[2]| RANK[3]| RANK[4]| RANK[5]| RANK[6]);
export const NOT_7_8 = ( RANK[0]| RANK[1]| RANK[2]| RANK[3]| RANK[4]| RANK[5]);
export function getWhitePawnThreatMap(pawnMap){
return ((pawnMap & BoardState.NOT_A) << 7n) | ((pawnMap & BoardState.NOT_H) << 9n);
}
export function getBlackPawnThreatMap(pawnMap){
return ((pawnMap & BoardState.NOT_A) >> 9n) | ((pawnMap & BoardState.NOT_H) >> 7n);
}
Simple Approaches To Computer Chess
Placeholder: Overview of simple algorithms for chess AI.
export const PIECE_TYPE = {
pawn: 0,
knight: 1,
bishop: 2,
rook: 3,
queen: 4,
king: 5,
};
export const PIECE_COLOR = {
white : 0,
black : 1,
};
export const MOVE_TYPE = {
normal: 0,
castleKingside : 1,
castleQueenside: 2,
promotion: 3,
enPassant: 4,
};
Computational Issues With Simple Approaches
Placeholder: Explain why simple methods are computationally expensive.
function generateChessRays(rookRays){
const rayBuffer = new ArrayBuffer(4096*8);
let rays = new BigUint64Array(rayBuffer).fill(0n);
for(let start = 0; start < 64; start++){
for(let end = 0; end < 64; end++){
let index = (start * 64) + end;
if (start == end) {
rays[index] = 0n;
continue;
}
let sign = 1;
if (end < start) {sign = -1;}
let success = false;
let newRay = 0n;
if (rookRays){
//Try horizontal ray
[newRay, success] = tryCreateRayInclusive(start, end, 1, sign, false);
if (success){
rays[index] = newRay;
//console.log("horizontal ray created from: ", start, " to ", end);
continue;
}
//Try vertical ray
[newRay, success] = tryCreateRayInclusive(start, end, 8, sign, true);
if (success){
rays[index] = newRay;
//console.log("vertical ray created from: ", start, " to ", end);
continue;
}
} else {
//Try up-right diagonal ray
[newRay, success] = tryCreateRayInclusive(start, end, 9, sign, false);
if (success){
rays[index] = newRay;
//console.log("diagonal ray created from: ", start, " to ", end);
continue;
}
//Try down-left diagonal ray
[newRay, success] = tryCreateRayInclusive(start, end, 7,sign, false);
if (success){
rays[index] = newRay;
//console.log("diagonal ray created from: ", start, " to ", end);
continue;
}
}
//No ray was able to be created
//console.log("no ray created from: ", start, " to ", end);
rays[index] = 0n;
}
}
return rays;
}
Bitwise Computations
Placeholder: Introduce bitwise operations used in chess programming.
export function isBlackPawnAtMask(board, mask){
return (board.blackPawns & mask) !== 0n;
}
//Black Knight
export function isBlackKnightAtMask(board, mask){
return (board.blackKnights & mask) !== 0n;
}
//Black Bishop
export function isBlackBishopAtMask(board, mask){
return (board.blackBishops & mask) !== 0n;
}
//Black Rook
export function isBlackRookAtMask(board, mask){
return (board.blackRooks & mask) !== 0n;
}
//Black Queen
export function isBlackQueenAtMask(board, mask){
return (board.blackQueens & mask) !== 0n;
}
//Black King
export function isBlackKingAtMask(board, mask){
return (board.blackKing & mask) !== 0n;
}
//All Pieces
export function isAnyPieceAtMask(board, mask){
return (board.allPieces & mask) !== 0n;
}
//White Pieces
export function isWhitePieceAtMask(board, mask){
return (board.whitePieces & mask) !== 0n;
}
//Black Pieces
export function isBlackPieceAtMask(board, mask){
return (board.blackPieces & mask) !== 0n;
}
Overview of Bitboards
Placeholder: Explain bitboards as 64-bit integers representing chessboards.
export class ChessBoard {
// Starting Positions
constructor(){
this.whiteRooks = 0b0000000000000000000000000000000000000000000000000000000010000001n; // bits 0 and 7
this.blackRooks = 0b1000000100000000000000000000000000000000000000000000000000000000n; // bits 56 and 63
this.whiteKnights = 0b0000000000000000000000000000000000000000000000000000000001000010n; // bits 1 and 6
this.blackKnights = 0b0100001000000000000000000000000000000000000000000000000000000000n; // bits 57 and 62
this.blackBishops = 0b0010010000000000000000000000000000000000000000000000000000000000n; // bits 58 and 61
this.whiteBishops = 0b0000000000000000000000000000000000000000000000000000000000100100n; // bits 2 and 5
this.blackQueens = 0b0000100000000000000000000000000000000000000000000000000000000000n; // bit 59
this.whiteQueens = 0b0000000000000000000000000000000000000000000000000000000000001000n; // bit 5
this.blackKing = 0b0001000000000000000000000000000000000000000000000000000000000000n; // bit 60
this.whiteKing = 0b0000000000000000000000000000000000000000000000000000000000010000n; // bit 4
this.blackPawns = 0b0000000011111111000000000000000000000000000000000000000000000000n; // bits 48 through 55
this.whitePawns = 0b0000000000000000000000000000000000000000000000001111111100000000n; // bits 8 through 15
this.whitePieces = 0b0000000000000000000000000000000000000000000000000000000000000000n; // no bits to start
this.blackPieces = 0b0000000000000000000000000000000000000000000000000000000000000000n; // no bits to start
this.allPiecesNoWhiteKing = 0b0000000000000000000000000000000000000000000000000000000000000000n; // no bits to start
this.allPiecesNoBlackKing = 0b0000000000000000000000000000000000000000000000000000000000000000n; // no bits to start
this.allPieces = 0b0000000000000000000000000000000000000000000000000000000000000000n; // no bits to start
this.notWhitePieces = ALL_ONES; // inverse of whitePieces
this.notBlackPieces = ALL_ONES; // inverse of blackPieces
this.emptySquares = ALL_ONES; // Set default to all Squares
this.checkMask = ALL_ONES; //Set default to all squares.
this.whiteAttackMap = 0b0000000000000000000000000000000000000000000000000000000000000000n; // no bits to start
this.blackAttackMap = 0b0000000000000000000000000000000000000000000000000000000000000000n; // no bits to start
this.pinMasks = [
0b0000000000000000000000000000000000000000000000000000000000000000n, // no bits to start
0b0000000000000000000000000000000000000000000000000000000000000000n, // no bits to start
0b0000000000000000000000000000000000000000000000000000000000000000n, // no bits to start
0b0000000000000000000000000000000000000000000000000000000000000000n, // no bits to start
0b0000000000000000000000000000000000000000000000000000000000000000n, // no bits to start
0b0000000000000000000000000000000000000000000000000000000000000000n, // no bits to start
0b0000000000000000000000000000000000000000000000000000000000000000n, // no bits to start
0b0000000000000000000000000000000000000000000000000000000000000000n, // no bits to start
];
this.pinMapFull = 0b0000000000000000000000000000000000000000000000000000000000000000n; // no bits to start
this.totalPins = 0;
this.whiteToMove = 1;
this.whiteKingIndex = 4;
this.blackKingIndex = 60;
this.whiteKingIndexBig = 4n;
this.blackKingIndexBig = 60n;
this.totalChecks = 0;
this.checkmate = 1; //0 is checkmate, 1 is not checkmate
this.whiteCanCastleKingside = 1;
this.whiteCanCastleQueenside = 1;
this.blackCanCastleKingside = 1;
this.blackCanCastleQueenside = 1;
this.enPassantTarget = 0b0000000000000000000000000000000000000000000000000000000000000000n; // no bits to start
}
}
board.whitePieces = board.whitePawns | board.whiteKnights | board.whiteBishops | board.whiteRooks | board.whiteQueens;
board.blackPieces = board.blackPawns | board.blackKnights | board.blackBishops | board.blackRooks | board.blackQueens;
board.allPieces = board.whitePieces | board.blackPieces;
board.allPiecesNoWhiteKing = board.allPieces | board.blackKing;
board.allPiecesNoBlackKing = board.allPieces | board.whiteKing;
board.whitePieces |= board.whiteKing;
board.blackPieces |= board.blackKing;
board.allPieces |= board.whiteKing | board.blackKing;
// Update precomputed inverse masks
board.notWhitePieces = ALL_ONES ^ board.whitePieces;
board.notBlackPieces = ALL_ONES ^ board.blackPieces;
board.emptySquares = ALL_ONES ^ board.allPieces;
function moveWhiteKnight(board, fromMask, toMask){
board.whiteKnights &= ~fromMask; //Remove the old location
board.whiteKnights |= toMask; //Add to the new location
captureBlackPiece(board, toMask);
}
Fancy Magic Numbers
Placeholder: Explain magic numbers for fast move generation.
export function getBishopAttacks(occupancyMask, locationIndex){
const occupancy = BishopMove.blockerMaskTable[locationIndex] & occupancyMask;
const attackIndex = BigInt.asUintN(64, occupancy * BishopMagics.bishopMagics[locationIndex])>> BishopMagics.bishopShifts[locationIndex];
const attackIndexOffset = attackIndex + BishopMagics.bishopOffsets[locationIndex];
return BishopMagics.bishopAttackData[Number(attackIndexOffset)];
}
function generateAllMagicLookups(isRook, isXrayTable) {
const occupancyMasks = isRook ? generateRookBlockerMaskTable() : generateBishopBlockerMaskTable();
let magics = [];
let shifts = [];
let indexOffsets = [];
currentOffset = 0;
for (let squareIndex = 0; squareIndex < 64; squareIndex++) {
console.log("Looking for magics for: " + squareIndex);
indexOffsets.push(BigInt(currentOffset));
let { magic, shift } = findMagic(squareIndex, occupancyMasks[squareIndex], isRook, isXrayTable);
magics.push(magic);
shifts.push(shift);
}
if(isXrayTable){
if (isRook){
RookXRayMagics.setAttackData(rookXRayFlatAttackTable);
RookXRayMagics.setOffsets(indexOffsets);
RookXRayMagics.setShifts(shifts);
RookXRayMagics.setMagics(magics);
} else {
BishopXRayMagics.setAttackData(bishopXRayFlatAttackTable);
BishopXRayMagics.setOffsets(indexOffsets);
BishopXRayMagics.setShifts(shifts);
BishopXRayMagics.setMagics(magics);
}
} else {
if (isRook){
RookMagics.setAttackData(rookFlatAttackTable);
RookMagics.setOffsets(indexOffsets);
RookMagics.setShifts(shifts);
RookMagics.setMagics(magics);
} else {
BishopMagics.setAttackData(bishopFlatAttackTable);
BishopMagics.setOffsets(indexOffsets);
BishopMagics.setShifts(shifts);
BishopMagics.setMagics(magics);
}
}
return { magics, shifts, offsets: indexOffsets};
}
Non-Sliding Pieces
Placeholder: Handling knights and pawns efficiently.
export function getKnightAttacks(locationIndex){
return KnightMove.moveTable[locationIndex];
}
//Move Table Snippet
moveTable[0] = 0b0000000000000000000000000000000000000000000000100000010000000000n;
moveTable[1] = 0b0000000000000000000000000000000000000000000001010000100000000000n;
moveTable[2] = 0b0000000000000000000000000000000000000000000010100001000100000000n;
moveTable[3] = 0b0000000000000000000000000000000000000000000101000010001000000000n;
moveTable[4] = 0b0000000000000000000000000000000000000000001010000100010000000000n;
moveTable[5] = 0b0000000000000000000000000000000000000000010100001000100000000000n;
//etc...
Handling Checks
Placeholder: Explain check detection in the engine.
export function countChecksOnWhiteKing(board){
let checkingPieces = (PawnMove.captureTableWhite[board.whiteKingIndex] & board.blackPawns);
checkingPieces |= getKnightAttacks(board.whiteKingIndex) & board.blackKnights;
let lsb = checkingPieces & -checkingPieces;
let locationIndex = index64n[Number(((lsb * deBruijn64) >> 58n)& 63n)];
detectKnightPawnCheckFunctions[Number(checkingPieces >> locationIndex)](board, checkingPieces);
checkingPieces = getBishopAttacks(board.allPieces, board.whiteKingIndex) & (board.blackBishops | board.blackQueens);
checkingPieces |= getRookAttacks(board.allPieces, board.whiteKingIndex) & (board.blackRooks | board.blackQueens);
while(checkingPieces){
lsb = checkingPieces & -checkingPieces;
locationIndex = index64[Number(((lsb * deBruijn64) >> 58n)& 63n)];
board.totalChecks += 1;
board.checkMask = ChessRays.allInclusiveRays[(board.whiteKingIndex*64)+locationIndex];
checkingPieces ^= lsb;
}
detectDoubleCheckFunctions[board.totalChecks](board);
}
export function setWhiteAttackMap(board){
let whiteKnightBits = board.whiteKnights;
let whiteBishopQueenBits = board.whiteBishops | board.whiteQueens;
let whiteRookQueenBits = board.whiteRooks | board.whiteQueens;
board.whiteAttackMap = ((board.whitePawns & BoardState.NOT_A) << 7n) | ((board.whitePawns & BoardState.NOT_H) << 9n);
board.whiteAttackMap |= KingMove.moveTable[board.whiteKingIndex];
let lsb = 0
let locationIndex = 0;
while(whiteKnightBits){
lsb = whiteKnightBits & -whiteKnightBits;
locationIndex = index64[Number(((lsb * deBruijn64) >> 58n)& 63n)];
board.whiteAttackMap |= KnightMove.moveTable[locationIndex];
whiteKnightBits ^= lsb;
}
while(whiteBishopQueenBits){
lsb = whiteBishopQueenBits & -whiteBishopQueenBits;
locationIndex = index64[Number(((lsb * deBruijn64) >> 58n)& 63n)];
board.whiteAttackMap |= getBishopAttacks(board.allPiecesNoBlackKing, locationIndex);
whiteBishopQueenBits ^= lsb;
}
while(whiteRookQueenBits){
lsb = whiteRookQueenBits & -whiteRookQueenBits;
locationIndex = index64[Number(((lsb * deBruijn64) >> 58n)& 63n)];
board.whiteAttackMap |= getRookAttacks(board.allPiecesNoBlackKing, locationIndex);
whiteRookQueenBits ^= lsb;
}
}
The Issue of Pins
Placeholder: How pins affect legal move generation.
function setAllPinsOnBlackKing(board){
let occupancy = BishopMove.blockerMaskTable[board.blackKingIndex] & board.allPieces;
let xRayIndex = BigInt.asUintN(64, occupancy * BishopXRayMagics.bishopXRayMagics[board.blackKingIndex])>> BishopXRayMagics.bishopXRayShifts[board.blackKingIndex];
let xRayIndexOffset = xRayIndex + BishopXRayMagics.bishopXRayOffsets[board.blackKingIndex];
let xRayMap = BishopXRayMagics.bishopXRayAttackData[Number(xRayIndexOffset)]& (board.whiteBishops | board.whiteQueens);
occupancy = RookMove.blockerMaskTable[board.blackKingIndex] & board.allPieces;
xRayIndex = BigInt.asUintN(64, occupancy * RookXRayMagics.rookXRayMagics[board.blackKingIndex])>> RookXRayMagics.rookXRayShifts[board.blackKingIndex];
xRayIndexOffset = xRayIndex + RookXRayMagics.rookXRayOffsets[board.blackKingIndex];
xRayMap |= RookXRayMagics.rookXRayAttackData[Number(xRayIndexOffset)] & (board.whiteRooks | board.whiteQueens);
let pinCount = 0;
while (xRayMap){
const lsb = xRayMap & -xRayMap;
const pinIndex = index64[Number(((lsb * deBruijn64) >> 58n)& 63n)];
xRayMap ^= lsb;
board.pinMasks[pinCount] = ChessRays.allInclusiveRays[(board.blackKingIndex*64)+pinIndex];
board.pinMapFull |= board.pinMasks[pinCount];
pinCount++;
}
board.totalPins = pinCount;
const enIndex = index64n[Number(((board.enPassantTarget * deBruijn64) >> 58n)& 63n)];
checkBlackEnPassantLegalityFunctions[Number(board.enPassantTarget >> enIndex)](board);
}
Fancy X-Ray Magic Numbers
Placeholder: Advanced techniques for sliding pieces and x-ray attacks.
// Code Snippet
export function generateRookXRayTable(square, occupancyMask) {
const { file, rank } = indexToCoord(square);
let xRayTargets = 0n;
let potentialPinnedPieces = 0;
// Right (file++)
for (let f = file + 1; f < 8; f++) {
const idx = coordToIndex(f, rank);
if (bitAt(idx, occupancyMask)) {
if (potentialPinnedPieces == 1){
xRayTargets |= 1n << BigInt(idx);
break;
}
potentialPinnedPieces++;
continue;
}
if ((f == 7) && (potentialPinnedPieces == 1)){
xRayTargets |= 1n << BigInt(idx);
break;
}
}
potentialPinnedPieces = 0;
// Left (file--)
for (let f = file - 1; f >= 0; f--) {
const idx = coordToIndex(f, rank);
if (bitAt(idx, occupancyMask)) {
if (potentialPinnedPieces == 1){
xRayTargets |= 1n << BigInt(idx);
break;
}
potentialPinnedPieces++;
continue;
}
if ((f == 0) && (potentialPinnedPieces == 1)){
xRayTargets |= 1n << BigInt(idx);
break;
}
}
potentialPinnedPieces = 0;
// Up (rank++)
for (let r = rank + 1; r < 8; r++) {
const idx = coordToIndex(file, r);
if (bitAt(idx, occupancyMask)) {
if (potentialPinnedPieces == 1){
xRayTargets |= 1n << BigInt(idx);
break;
}
potentialPinnedPieces++;
continue;
}
if ((r == 7) && (potentialPinnedPieces == 1)){
xRayTargets |= 1n << BigInt(idx);
break;
}
}
potentialPinnedPieces = 0;
// Down (rank--)
for (let r = rank - 1; r >= 0; r--) {
const idx = coordToIndex(file, r);
if (bitAt(idx, occupancyMask)) {
if (potentialPinnedPieces == 1){
xRayTargets |= 1n << BigInt(idx);
break;
}
potentialPinnedPieces++;
continue;
}
if ((r == 0) && (potentialPinnedPieces == 1)){
xRayTargets |= 1n << BigInt(idx);
break;
}
}
return xRayTargets;
}
Detecting Checkmate
Placeholder: Logic for detecting checkmate positions.
// Code Snippet
function detectSingleCheckmateWhite(board){
let blockOnly = board.checkMask & board.emptySquares;
const captureOnly = board.checkMask & board.blackPieces;
//All pawn moves can be grabbed in one batch
let pawnBlocks = (board.whitePawns << 8n) & board.emptySquares;
pawnBlocks |= ((pawnBlocks & BoardState.RANK[2]) << 8n) & board.emptySquares;
let legalMoveMask = pawnBlocks & blockOnly;
//Chek for knight and sliding piece blockers
while(blockOnly){
const lsb = blockOnly & -blockOnly;
const squareBlock = index64[Number(((lsb * deBruijn64) >> 58n)& 63n)];
legalMoveMask |= getKnightAttacks(squareBlock) & board.whiteKnights; //Knight block
legalMoveMask |= getBishopAttacks(board.allPieces, squareBlock) & (board.whiteBishops | board.whiteQueens); //Bishop/Queen Block
legalMoveMask |= getRookAttacks(board.allPieces, squareBlock) & (board.whiteRooks | board.whiteQueens); //Rook/Queen Block
blockOnly ^= lsb; //Pop the bit
}
const lsb = captureOnly & -captureOnly;
const squareBlock = index64[Number(((lsb * deBruijn64) >> 58n)& 63n)];
legalMoveMask |= getKnightAttacks(squareBlock) & board.whiteKnights; //Knight Capture
legalMoveMask |= getBishopAttacks(board.allPieces, squareBlock) & (board.whiteBishops | board.whiteQueens); //Bishop/Queen Capture
legalMoveMask |= getRookAttacks(board.allPieces, squareBlock) & (board.whiteRooks | board.whiteQueens); //Rook/Queen Capture
let legalPawnCaptureMask = ((board.enPassantTarget >> 8n) & captureOnly) << 8n;
legalPawnCaptureMask |= captureOnly;
legalMoveMask |= getBlackPawnThreatMap(legalPawnCaptureMask) & board.whitePawns; //Pawn Capture stops mate
//Get rid of any pinned pieces
legalMoveMask &= ~board.pinMapFull;
//Add any legal king moves
legalMoveMask |= getLegalWhiteKingMoves(board, board.whiteKingIndex);
const lsbC = legalMoveMask & -legalMoveMask;
//Set the checkmate
board.checkmate = lsbC >> index64n[Number(((lsbC * deBruijn64) >> 58n)& 63n)];
}
function detectDoubleCheckmateWhite(board){
const legalMoves = getLegalWhiteKingMoves(board, board.whiteKingIndex);
const lsb = legalMoves & -legalMoves;
board.checkmate = lsb >> index64n[Number(((lsb * deBruijn64) >> 58n)& 63n)];
}
Chess and Edge Cases
Placeholder: Discuss the challenges that the special rules of chess pose upon efficient computation.
function checkEnPassantPinsOnBlackKing(board){
//Ensure en passant cannot be done if king is horizontal to the potential pin
const ignoreEnPassantPawn = (board.enPassantTarget << 8n);
const occupancy = RookMove.blockerMaskTable[board.blackKingIndex] & (board.allPieces ^ ignoreEnPassantPawn) & BoardState.RANK_OF_INDEX[board.blackKingIndex]; //Only horizontal pieces matter
const xRayIndex = BigInt.asUintN(64, occupancy * RookXRayMagics.rookXRayMagics[board.blackKingIndex])>> RookXRayMagics.rookXRayShifts[board.blackKingIndex];
const xRayIndexOffset = xRayIndex + RookXRayMagics.rookXRayOffsets[board.blackKingIndex];
let xRayMap = RookXRayMagics.rookXRayAttackData[Number(xRayIndexOffset)] & (board.whiteRooks | board.whiteQueens);
let enPassantPinMap = 0n;
while (xRayMap){
const lsb = xRayMap & -xRayMap;
const pinIndex = index64[Number(((lsb * deBruijn64) >> 58n)& 63n)];
xRayMap ^= lsb;
enPassantPinMap |= ignoreEnPassantPawn & ChessRays.allInclusiveRays[(board.blackKingIndex*64)+pinIndex];
}
//Remove any en passant capture that would result in a check
let enPassantPinIndex = index64n[Number(((enPassantPinMap * deBruijn64) >> 58n)& 63n)];
board.enPassantTarget = board.enPassantTarget * (1n - (enPassantPinMap>>enPassantPinIndex));
}
export function getDisambiguationTypeFromLocationArray(possibleLocations, start){
let rankDisambiguation = true;
let fileDisambiguation = true;
for(let i = 0; i < possibleLocations.length; i++){
if (sameRank(start, possibleLocations[i])){ rankDisambiguation = false;}
if (sameFile(start, possibleLocations[i])){ fileDisambiguation = false;}
}
if (!rankDisambiguation && !fileDisambiguation){return 3;}
if (fileDisambiguation) {return 2;}
if (rankDisambiguation) {return 1;}
return 0;
}
export function getDisambiguationTypeFromPossiblePiecesMask(possiblePiecesMask, start){
let possibleLocations = [];
let foundStart = false;
while (possiblePiecesMask){
const lsb = possiblePiecesMask & -possiblePiecesMask;
const pieceIndex = index64[Number(((lsb * deBruijn64) >> 58n)& 63n)];
possiblePiecesMask ^= lsb;
if (pieceIndex == start){
foundStart = true;
} else {
possibleLocations.push(pieceIndex);
}
}
if (!foundStart){return 0;}
if (possibleLocations){
if (possibleLocations.length >= 1){
return getDisambiguationTypeFromLocationArray(possibleLocations, start);
}
}
return 0;
}
Castling
Placeholder: Rules and implementation of castling in the engine.
export const whiteKingsideRookStart = 0b0000000000000000000000000000000000000000000000000000000010000000n; // bit 7
export const whiteQueensideRookStart = 0b0000000000000000000000000000000000000000000000000000000000000001n; // bit 0
export const blackKingsideRookStart = 0b1000000000000000000000000000000000000000000000000000000000000000n; // bits 63
export const blackQueensideRookStart = 0b0000000100000000000000000000000000000000000000000000000000000000n; // bits 56
export const whiteKingsideCastleTarget = 0b0000000000000000000000000000000000000000000000000000000001000000n; // bit 6
export const whiteQueensideCastleTarget = 0b0000000000000000000000000000000000000000000000000000000000000100n; // bit 2
export const blackKingsideCastleTarget = 0b0100000000000000000000000000000000000000000000000000000000000000n; // bit 62
export const blackQueensideCastleTarget = 0b0000010000000000000000000000000000000000000000000000000000000000n; // bits 58
export const whiteRookKingsideCastleTarget = 0b0000000000000000000000000000000000000000000000000000000000100000n; // bit 5
export const whiteRookQueensideCastleTarget = 0b0000000000000000000000000000000000000000000000000000000000001000n; // bit 3
export const blackRookKingsideCastleTarget = 0b0010000000000000000000000000000000000000000000000000000000000000n; // bit 61
export const blackRookQueensideCastleTarget = 0b0000100000000000000000000000000000000000000000000000000000000000n; // bits 59
export const whiteKingSideCastleEmpty = 0b0000000000000000000000000000000000000000000000000000000001100000n;
export const whiteKingSideCastleSafe = 0b0000000000000000000000000000000000000000000000000000000001110000n;
export const whiteQueenSideCastleEmpty = 0b0000000000000000000000000000000000000000000000000000000000001110n;
export const whiteQueenSideCastleSafe = 0b0000000000000000000000000000000000000000000000000000000000011100n;
export const blackKingSideCastleEmpty = 0b0110000000000000000000000000000000000000000000000000000000000000n;
export const blackKingSideCastleSafe = 0b0111000000000000000000000000000000000000000000000000000000000000n;
export const blackQueenSideCastleEmpty = 0b0000111000000000000000000000000000000000000000000000000000000000n;
export const blackQueenSideCastleSafe = 0b0001110000000000000000000000000000000000000000000000000000000000n;
Pawn Promotion
Placeholder: Handling pawn promotion logic.
function promoteWhitePawnMove(board, fromMask, toMask){
let pawnPromotionType = index64[Number(((toMask * deBruijn64) >> 58n)& 63n)];
pawnPromotionType = ((pawnPromotionType - (pawnPromotionType % 8)-32) / 8);
pawnPromotionType = 3-pawnPromotionType;
invokeWhitePawnPromotionFunctions[pawnPromotionType](board,fromMask,toMask);
board.enPassantTarget = 0n;
}
function promoteToWhiteQueen(board, fromMask, toMask){
board.whitePawns &= ~fromMask; //Remove the old location
board.whiteQueens |= toMask; //Add to the new location
captureBlackPiece(board, toMask);
}
function promoteToWhiteRook(board, fromMask, toMask){
board.whitePawns &= ~fromMask; //Remove the old location
board.whiteRooks |= (toMask << 8n); //Add to the new location
captureBlackPiece(board, toMask << 8n);
}
function promoteToWhiteBishop(board, fromMask, toMask){
board.whitePawns &= ~fromMask; //Remove the old location
board.whiteBishops |= (toMask << 16n); //Add to the new location
captureBlackPiece(board, toMask << 16n);
}
function promoteToWhiteKnight(board, fromMask, toMask){
board.whitePawns &= ~fromMask; //Remove the old location
board.whiteKnights |= (toMask << 24n); //Add to the new location
captureBlackPiece(board, toMask << 24n);
}
promotionSquares = [];
const file = promotionSquareSelected % 8n;
for (let i = 0n; i < 64n; i++) {
if ((validMoves >> i) & 1n) {
const { row, col } = indexToSquareCoord(Number(i));
const square = squareGrid[row][col];
if (square && (Number(i % 8n) == file )) {
promotionSquares.push(i);
square.classList.add('promotion-option');
//Add the promotion piece render to the highlighted squares
//Get the color
let promotionColor = 'white';
if (piece.color == 1){ promotionColor = 'black'; }
//Get the piece
let promotionPiece = 'queen';
if ((i >= 48n && i <= 55n) || (i >= 8n && i <= 15n)) {promotionPiece = 'rook';}
if ((i >= 40n && i <= 47n) || (i >= 16n && i <= 23n)) {promotionPiece = 'bishop';}
if ((i >= 32n && i <= 39n) || (i >= 24n && i <= 31n)) {promotionPiece = 'knight';}
//get the image source
const imgSrc = `images/${PIECE_IMAGE[promotionColor][promotionPiece]}`;
//Create the element
const promotionImg = document.createElement('img');
promotionImg.src = imgSrc;
promotionImg.alt = `${promotionColor} ${promotionPiece}`;
square.appendChild(promotionImg);
}
}
}
En Passant
Placeholder: Implementing en passant captures.
export function getLegalWhitePawnMoves(board, locationIndex){
let legalMask = (1n << BigInt(locationIndex+8)) & board.emptySquares;
legalMask |= ((legalMask & BoardState.RANK[2]) << 8n) & board.emptySquares;
legalMask |= getWhitePawnAttacks(locationIndex) & (board.blackPieces | board.enPassantTarget);
const enPassantCheckMask = ((board.enPassantTarget >> 8n) & board.checkMask) << 8n;
legalMask = applyPins(legalMask, board, locationIndex) & (board.checkMask | enPassantCheckMask);
const promotionSquares = legalMask & BoardState.RANK[7];
legalMask |= (promotionSquares >> 8n) | (promotionSquares >> 16n) | (promotionSquares >> 24n);
return legalMask;
}
function regularWhitePawnMove(board, fromMask, toMask){
board.whitePawns &= ~fromMask; //Remove the old location
board.whitePawns |= toMask; //Add to the new location
//Check for en passant capture
const enPassantCapture = (toMask & board.enPassantTarget) >> 8n; //Capture the pawn 1 rank behind the target
captureBlackPawn(board, enPassantCapture);
//Set en passant target
board.enPassantTarget = (fromMask << 8n) & (toMask >> 8n) & getBlackPawnThreatMap(board.blackPawns); //This will be zero unless the pawn moved two squares and did not capture //It will also be zero if there are no potential captures
captureBlackPiece(board, toMask);
}
Limitations of JavaScript
Placeholder: Discuss performance or language limitations.
const getLegalMoveFunctions = [getLegalWhitePawnMoves, getLegalWhiteKnightMoves, getLegalWhiteBishopMoves, getLegalWhiteRookMoves, getLegalWhiteQueenMoves, getLegalWhiteKingMoves, getLegalBlackPawnMoves, getLegalBlackKnightMoves, getLegalBlackBishopMoves, getLegalBlackRookMoves, getLegalBlackQueenMoves, getLegalBlackKingMoves];
const moveWhitePieceFunctions = [moveWhitePawn, moveWhiteKnight, moveWhiteBishop, moveWhiteRook, moveWhiteQueen, moveWhiteKing];
const moveBlackPieceFunctions = [moveBlackPawn, moveBlackKnight, moveBlackBishop, moveBlackRook, moveBlackQueen, moveBlackKing];
const captureWhitePieceFunctions =[captureWhitePawn, captureWhiteKnight, captureWhiteBishop, captureWhiteRook, captureWhiteQueen, captureWhiteKing];
const captureBlackPieceFunctions = [captureBlackPawn, captureBlackKnight, captureBlackBishop, captureBlackRook, captureBlackQueen, captureBlackKing];
const capturePieceFunctions = [captureWhitePieceFunctions, captureBlackPieceFunctions];
const movePieceFunctions = [moveWhitePieceFunctions, moveBlackPieceFunctions];
const attackMapFunctions = [setWhiteAttackMap, setBlackAttackMap];
const setPinMapFunctions = [setAllPinsOnBlackKing, setAllPinsOnWhiteKing];
const applyPinFunctions = [noPin, applyPin];
const pinApplicationFunctions = [noPinsApplied, pinsApplied];
const detectKnightPawnCheckFunctions = [applyNoChecks, applyKnightPawnChecks];
const detectDoubleCheckFunctions = [noDoubleCheck, setSingleCheck, setDoubleCheck, setDoubleCheck, setDoubleCheck, setDoubleCheck, setDoubleCheck, setDoubleCheck, setDoubleCheck, setDoubleCheck, setDoubleCheck, setDoubleCheck];
const detectSingleCheckmateFunctions = [detectSingleCheckmateBlack, detectSingleCheckmateWhite];
const detectDoubleCheckmateFunctions = [detectDoubleCheckmateBlack, detectDoubleCheckmateWhite];
const detectCheckOnWhiteKingFunctions = [applyNoChecks, countChecksOnWhiteKing];
const detectCheckOnBlackKingFunctions = [applyNoChecks, countChecksOnBlackKing];
const detectCheckFunctions = [checkOnBlackKing, checkOnWhiteKing];
const flipWhiteToMove = [setWhiteToMove, setBlackToMove];
const revokeWhiteKingsideCastleFunctions = [noRevoke, revokeWhiteKingsideCastle];
const revokeWhiteQueensideCastleFunctions = [noRevoke, revokeWhiteQueensideCastle];
const revokeBlackKingsideCastleFunctions = [noRevoke, revokeBlackKingsideCastle];
const revokeBlackQueensideCastleFunctions = [noRevoke, revokeBlackQueensideCastle];
const invokeWhiteKingsideCastleFunctions = [noCastle, invokeWhiteKingsideCastle];
const invokeWhiteQueensideCastleFunctions = [noCastle, invokeWhiteQueensideCastle];
const invokeBlackKingsideCastleFunctions = [noCastle, invokeBlackKingsideCastle];
const invokeBlackQueensideCastleFunctions = [noCastle, invokeBlackQueensideCastle];
const checkWhiteKingsideCastlingMovesFunctions = [addNoCastlingMoves, checkWhiteKingsideCastling];
const checkWhiteQueensideCastlingMovesFunctions = [addNoCastlingMoves, checkWhiteQueensideCastling];
const checkBlackKingsideCastlingMovesFunctions = [addNoCastlingMoves, checkBlackKingsideCastling];
const checkBlackQueensideCastlingMovesFunctions = [addNoCastlingMoves, checkBlackQueensideCastling];
const addWhiteKingSideCastlingMoveFunctions = [addWhiteKingsideCastlingMove, castlingNotLegal];
const addWhiteQueenSideCastlingMoveFunctions = [addWhiteQueensideCastlingMove, castlingNotLegal];
const addBlackKingSideCastlingMoveFunctions = [addBlackKingsideCastlingMove, castlingNotLegal];
const addBlackQueenSideCastlingMoveFunctions = [addBlackQueensideCastlingMove, castlingNotLegal];
const checkWhiteEnPassantLegalityFunctions = [noEnPassantToCheck, checkEnPassantPinsOnWhiteKing];
const checkBlackEnPassantLegalityFunctions = [noEnPassantToCheck, checkEnPassantPinsOnBlackKing];
const checkWhitePawnPromotionFunctions = [regularWhitePawnMove, promoteWhitePawnMove];
const checkBlackPawnPromotionFunctions = [regularBlackPawnMove, promoteBlackPawnMove];
const invokeWhitePawnPromotionFunctions = [promoteToWhiteQueen, promoteToWhiteRook, promoteToWhiteBishop, promoteToWhiteKnight];
const invokeBlackPawnPromotionFunctions = [promoteToBlackQueen, promoteToBlackRook, promoteToBlackBishop, promoteToBlackKnight];
Chess Engine UI
Placeholder: Overview of user interface for the engine.
unction createDisplayBoard(){
for (let row = 0; row < 8; row++) {
for (let col = 0; col < 8; col++) {
const square = document.createElement('div');
square.classList.add('square');
// Add light/dark class
if ((row + col) % 2 === 0) {
square.classList.add('light');
} else {
square.classList.add('dark');
}
// Add coordinate data
square.dataset.row = row;
square.dataset.col = col;
square.dataset.coord = getSquareName(row,col);
squareGrid[row][col] = square;
}
}
}
FEN Import and Export
Placeholder: Handling FEN notation for board positions.
// Code Snippet
export function generateFEN(board){
let FEN = '';
//Generate the chars for the pieces for the FEN
let emptyCounter = 0;
//Loop through the FEN pieces string to extract the pieces
for(let rank = 7; rank >= 0; rank--)
{
if (rank != 7) {FEN += '/';}
for (let file = 0; file < 8; file++){
const index = (rank * 8) + file;
let currentPiece = BitBoardFunctions.getPieceAndTypeAtIndex(board, index);
if (currentPiece.type == -1){
emptyCounter++;
continue;
}
if (emptyCounter != 0){
FEN += emptyCounter;
emptyCounter = 0;
}
if (currentPiece.color == 0){
FEN += getPieceCharUpper(currentPiece.type);
} else {
FEN += getPieceCharLower(currentPiece.type);
}
}
if (emptyCounter != 0){
FEN += emptyCounter;
emptyCounter = 0;
}
}
//Add the white to move indicator
FEN += ' ';
FEN += (board.whiteToMove === 1 ? 'w':'b');
//Add the castling rights
FEN += ' ';
let castlingRights = '';
castlingRights += (board.whiteCanCastleKingside === 1 ? 'K':'');
castlingRights += (board.whiteCanCastleQueenside === 1 ? 'Q':'');
castlingRights += (board.blackCanCastleKingside === 1 ? 'k':'');
castlingRights += (board.blackCanCastleQueenside === 1 ? 'q':'');
if (castlingRights === ""){castlingRights = '-';}
FEN += castlingRights;
//Add the en passant target
FEN += ' ';
if (board.enPassantTarget == 0n){
FEN += '-';
} else {
const lsb = board.enPassantTarget & -board.enPassantTarget;
const enIndex = index64[Number(((lsb * deBruijn64) >> 58n)& 63n)];
FEN += getSquareNameFromIndex(enIndex);
}
//Add the half move counter
FEN += ' ';
FEN += 0;
//Add the full move counter
FEN += ' ';
FEN += fullMoveCounter;
return FEN;
}
PGN Import and Export
Placeholder: Handling PGN notation and game variations.
// Code Snippet
export function getMovePGN(previousBoardState, currentBoardState, piece, from, to){
let movePGN = '';
let capture = false;
let promotion = false;
let disambiguation = 0;
//Check for promotion first to fix capture square issues
let promotionString = '';
//Check for promotion
if (piece.type == 0){
if (piece.color == 0){ //White Pawn
if (from >= 48 && from <=55){ //Comes from 7th rank
promotion = true;
promotionString+= getFileFromIndex(to);
promotionString+= '8=';
if (to >= 56 && to <= 63){
promotionString+= 'Q';
} else if (to >= 48 && to <= 55){
promotionString+= 'R';
to += 8;
} else if (to >= 40 && to <= 47){
promotionString+= 'B';
to += 16;
} else if (to >= 32 && to <= 39){
promotionString+= 'N';
to += 24;
}
}
} else { //Black Pawn
if (from >= 8 && from <= 15){ //Comes from 2nd rank
promotion = true;
promotionString+= getFileFromIndex(to);
promotionString+= '1=';
if (to >= 0 && to <= 7){
promotionString+= 'Q';
} else if (to >= 8 && to <= 15){
promotionString+= 'R';
to -= 8;
} else if (to >= 16 && to <= 23){
promotionString+= 'B';
to -= 16;
} else if (to >= 24 && to <= 31){
promotionString+= 'N';
to -= 24;
}
}
}
}
//Check if capture
if (BitBoardFunctions.isAnyPieceAtIndex(previousBoardState, to)){
capture = true;
}
//Check if en passant capture
if (piece.type == 0){
const moveMask = 1n << BigInt(to);
const enPassantCapture = moveMask & previousBoardState.enPassantTarget;
if (enPassantCapture != 0n){
capture = true;
}
}
//Get the movement piece type
if (piece.type != 0){
movePGN += getPieceCharUpper(piece.type);
}
//Check for disambiguation
if (piece.type != 0 && piece.type != 5){
disambiguation = BitBoardFunctions.getDisambiguationType(previousBoardState, piece, from, to);
}
switch(disambiguation){
case 1: //Rank Disambiguation
movePGN += getRankFromIndex(from);
break;
case 2: //File Disambiguation
movePGN += getFileFromIndex(from);
break;
case 3: //Rank and file Disambiguation
movePGN += getSquareNameFromIndex(from);
break;
default:
break;
}
//Check if pawn capture
if (piece.type == 0 && capture){
movePGN += getFileFromIndex(from);
movePGN += 'x';
}
//Check if regular capture
if (piece.type != 0 && capture){
movePGN += 'x';
}
if (promotion){
movePGN += promotionString;
} else {
movePGN += getSquareNameFromIndex(to);
}
//Overide everything else if the move is a castling move
//6, 2 // 62, 58 //Castling Target Indexes
//4 //60 //King Start Pos
//Check for castling
if (piece.type == 5){ //Check if a king move
if (piece.color == 0 && from == 4){ //White Castle
if (to == 6){
movePGN = 'O-O';
} else if (to == 2){
movePGN = 'O-O-O';
}
} else if (piece.color == 1 && from == 60){ //Black Castle
if (to == 62){
movePGN = 'O-O';
} else if (to == 58){
movePGN = 'O-O-O';
}
}
}
if (currentBoardState.checkmate == 0){
movePGN += '#';
} else if (currentBoardState.totalChecks > 0){ //Add the check if not checkmate
//Add checkmate
movePGN += '+';
}
return {rawPGN: movePGN, newTo: to};
}
On the Horizon
Placeholder: Future improvements or features for the engine.