js-chess-engine 1.0.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +586 -205
- package/dist/adapters/APIAdapter.d.ts +88 -0
- package/dist/adapters/APIAdapter.d.ts.map +1 -0
- package/dist/adapters/APIAdapter.js +225 -0
- package/dist/adapters/APIAdapter.js.map +1 -0
- package/dist/ai/AIEngine.d.ts +42 -0
- package/dist/ai/AIEngine.d.ts.map +1 -0
- package/dist/ai/AIEngine.js +62 -0
- package/dist/ai/AIEngine.js.map +1 -0
- package/dist/ai/Evaluator.d.ts +48 -0
- package/dist/ai/Evaluator.d.ts.map +1 -0
- package/dist/ai/Evaluator.js +248 -0
- package/dist/ai/Evaluator.js.map +1 -0
- package/dist/ai/MoveOrdering.d.ts +60 -0
- package/dist/ai/MoveOrdering.d.ts.map +1 -0
- package/dist/ai/MoveOrdering.js +173 -0
- package/dist/ai/MoveOrdering.js.map +1 -0
- package/dist/ai/Search.d.ts +62 -0
- package/dist/ai/Search.d.ts.map +1 -0
- package/dist/ai/Search.js +340 -0
- package/dist/ai/Search.js.map +1 -0
- package/dist/ai/TranspositionTable.d.ts +100 -0
- package/dist/ai/TranspositionTable.d.ts.map +1 -0
- package/dist/ai/TranspositionTable.js +176 -0
- package/dist/ai/TranspositionTable.js.map +1 -0
- package/dist/core/AttackDetector.d.ts +52 -0
- package/dist/core/AttackDetector.d.ts.map +1 -0
- package/dist/core/AttackDetector.js +397 -0
- package/dist/core/AttackDetector.js.map +1 -0
- package/dist/core/Board.d.ts +109 -0
- package/dist/core/Board.d.ts.map +1 -0
- package/dist/core/Board.js +410 -0
- package/dist/core/Board.js.map +1 -0
- package/dist/core/MoveGenerator.d.ts +48 -0
- package/dist/core/MoveGenerator.d.ts.map +1 -0
- package/dist/core/MoveGenerator.js +678 -0
- package/dist/core/MoveGenerator.js.map +1 -0
- package/dist/core/Position.d.ts +135 -0
- package/dist/core/Position.d.ts.map +1 -0
- package/dist/core/Position.js +351 -0
- package/dist/core/Position.js.map +1 -0
- package/dist/core/index.d.ts +9 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +25 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/zobrist.d.ts +93 -0
- package/dist/core/zobrist.d.ts.map +1 -0
- package/dist/core/zobrist.js +273 -0
- package/dist/core/zobrist.js.map +1 -0
- package/dist/index.d.ts +154 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +353 -0
- package/dist/index.js.map +1 -0
- package/dist/types/ai.types.d.ts +97 -0
- package/dist/types/ai.types.d.ts.map +1 -0
- package/dist/types/ai.types.js +17 -0
- package/dist/types/ai.types.js.map +1 -0
- package/dist/types/board.types.d.ts +140 -0
- package/dist/types/board.types.d.ts.map +1 -0
- package/dist/types/board.types.js +34 -0
- package/dist/types/board.types.js.map +1 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +26 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/move.types.d.ts +70 -0
- package/dist/types/move.types.d.ts.map +1 -0
- package/dist/types/move.types.js +53 -0
- package/dist/types/move.types.js.map +1 -0
- package/dist/utils/constants.d.ts +123 -0
- package/dist/utils/constants.d.ts.map +1 -0
- package/dist/utils/constants.js +259 -0
- package/dist/utils/constants.js.map +1 -0
- package/dist/utils/conversion.d.ts +152 -0
- package/dist/utils/conversion.d.ts.map +1 -0
- package/dist/utils/conversion.js +288 -0
- package/dist/utils/conversion.js.map +1 -0
- package/dist/utils/environment.d.ts +33 -0
- package/dist/utils/environment.d.ts.map +1 -0
- package/dist/utils/environment.js +71 -0
- package/dist/utils/environment.js.map +1 -0
- package/dist/utils/fen.d.ts +28 -0
- package/dist/utils/fen.d.ts.map +1 -0
- package/dist/utils/fen.js +203 -0
- package/dist/utils/fen.js.map +1 -0
- package/package.json +31 -29
- package/.eslintrc.json +0 -16
- package/.github/workflows/main.yml +0 -20
- package/CHANGELOG.md +0 -587
- package/dist/js-chess-engine.js +0 -1
- package/example/aiMatch.mjs +0 -21
- package/example/console.mjs +0 -37
- package/example/server.mjs +0 -27
- package/lib/Board.mjs +0 -943
- package/lib/const/board.mjs +0 -838
- package/lib/js-chess-engine.mjs +0 -99
- package/lib/utils.mjs +0 -154
- package/test/.eslintrc.json +0 -11
- package/test/ai.test.mjs +0 -132
- package/test/badge.svg +0 -1
- package/test/importExport.mjs +0 -108
- package/test/moves.test.mjs +0 -773
- package/webpack.config.js +0 -12
|
@@ -0,0 +1,678 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Move generation for all piece types
|
|
4
|
+
*
|
|
5
|
+
* This module generates all legal moves for a given position using
|
|
6
|
+
* bitboard-based algorithms for performance.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.generateLegalMoves = generateLegalMoves;
|
|
10
|
+
exports.generatePseudoLegalMoves = generatePseudoLegalMoves;
|
|
11
|
+
exports.getMovesForPiece = getMovesForPiece;
|
|
12
|
+
exports.isMoveLegal = isMoveLegal;
|
|
13
|
+
exports.applyMoveComplete = applyMoveComplete;
|
|
14
|
+
const types_1 = require("../types");
|
|
15
|
+
const Position_1 = require("./Position");
|
|
16
|
+
const AttackDetector_1 = require("./AttackDetector");
|
|
17
|
+
const conversion_1 = require("../utils/conversion");
|
|
18
|
+
const Board_1 = require("./Board");
|
|
19
|
+
const constants_1 = require("../utils/constants");
|
|
20
|
+
/**
|
|
21
|
+
* Generate all legal moves for the current position
|
|
22
|
+
*
|
|
23
|
+
* @param board - Board state
|
|
24
|
+
* @returns Array of legal moves
|
|
25
|
+
*/
|
|
26
|
+
function generateLegalMoves(board) {
|
|
27
|
+
const pseudoLegalMoves = generatePseudoLegalMoves(board);
|
|
28
|
+
const currentColor = board.turn;
|
|
29
|
+
// Check if the current player has a king
|
|
30
|
+
const ourKingBitboard = currentColor === types_1.InternalColor.WHITE ? board.whiteKing : board.blackKing;
|
|
31
|
+
if (ourKingBitboard === 0n) {
|
|
32
|
+
// No king - return all pseudo-legal moves (for test scenarios)
|
|
33
|
+
return pseudoLegalMoves;
|
|
34
|
+
}
|
|
35
|
+
// Filter to only legal moves
|
|
36
|
+
return pseudoLegalMoves.filter(move => {
|
|
37
|
+
// Special handling for castling - already checked in generation
|
|
38
|
+
if (move.flags & types_1.MoveFlag.CASTLING) {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
// Make the move on a temporary board copy to check if it's legal
|
|
42
|
+
const testBoard = (0, Board_1.copyBoard)(board);
|
|
43
|
+
const originalTurn = testBoard.turn;
|
|
44
|
+
makeMove(testBoard, move);
|
|
45
|
+
// After making the move, check if OUR king (the one that just moved) is in check
|
|
46
|
+
// makeMove switches the turn, so we need to check the OPPOSITE color
|
|
47
|
+
const kingBitboardAfter = originalTurn === types_1.InternalColor.WHITE ? testBoard.whiteKing : testBoard.blackKing;
|
|
48
|
+
if (kingBitboardAfter === 0n) {
|
|
49
|
+
return true; // King was captured (shouldn't happen in legal game)
|
|
50
|
+
}
|
|
51
|
+
const kingSquare = (0, conversion_1.getLowestSetBit)(kingBitboardAfter);
|
|
52
|
+
const opponentColor = originalTurn === types_1.InternalColor.WHITE ? types_1.InternalColor.BLACK : types_1.InternalColor.WHITE;
|
|
53
|
+
// The move is legal if our king is NOT being attacked after the move
|
|
54
|
+
return !(0, AttackDetector_1.isSquareAttacked)(testBoard, kingSquare, opponentColor);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Apply a move to a board (mutates the board)
|
|
59
|
+
* Used internally for legal move checking
|
|
60
|
+
*
|
|
61
|
+
* @param board - Board to modify
|
|
62
|
+
* @param move - Move to apply
|
|
63
|
+
*/
|
|
64
|
+
function makeMove(board, move) {
|
|
65
|
+
// Handle castling specially
|
|
66
|
+
if (move.flags & types_1.MoveFlag.CASTLING) {
|
|
67
|
+
// Move the king
|
|
68
|
+
(0, Board_1.removePiece)(board, move.from);
|
|
69
|
+
(0, Board_1.setPiece)(board, move.to, move.piece);
|
|
70
|
+
// Move the rook
|
|
71
|
+
const color = board.turn;
|
|
72
|
+
if (color === types_1.InternalColor.WHITE) {
|
|
73
|
+
if (move.to === constants_1.CASTLING.WHITE_SHORT.kingTo) {
|
|
74
|
+
// White short castling
|
|
75
|
+
(0, Board_1.removePiece)(board, constants_1.CASTLING.WHITE_SHORT.rookFrom);
|
|
76
|
+
(0, Board_1.setPiece)(board, constants_1.CASTLING.WHITE_SHORT.rookTo, types_1.Piece.WHITE_ROOK);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
// White long castling
|
|
80
|
+
(0, Board_1.removePiece)(board, constants_1.CASTLING.WHITE_LONG.rookFrom);
|
|
81
|
+
(0, Board_1.setPiece)(board, constants_1.CASTLING.WHITE_LONG.rookTo, types_1.Piece.WHITE_ROOK);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
if (move.to === constants_1.CASTLING.BLACK_SHORT.kingTo) {
|
|
86
|
+
// Black short castling
|
|
87
|
+
(0, Board_1.removePiece)(board, constants_1.CASTLING.BLACK_SHORT.rookFrom);
|
|
88
|
+
(0, Board_1.setPiece)(board, constants_1.CASTLING.BLACK_SHORT.rookTo, types_1.Piece.BLACK_ROOK);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
// Black long castling
|
|
92
|
+
(0, Board_1.removePiece)(board, constants_1.CASTLING.BLACK_LONG.rookFrom);
|
|
93
|
+
(0, Board_1.setPiece)(board, constants_1.CASTLING.BLACK_LONG.rookTo, types_1.Piece.BLACK_ROOK);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
else if (move.flags & types_1.MoveFlag.EN_PASSANT) {
|
|
98
|
+
// En passant capture
|
|
99
|
+
(0, Board_1.removePiece)(board, move.from);
|
|
100
|
+
(0, Board_1.setPiece)(board, move.to, move.piece);
|
|
101
|
+
// Remove the captured pawn (on different square than move.to)
|
|
102
|
+
const capturedPawnSquare = board.turn === types_1.InternalColor.WHITE
|
|
103
|
+
? (move.to - 8) // Captured pawn is one rank below
|
|
104
|
+
: (move.to + 8); // Captured pawn is one rank above
|
|
105
|
+
(0, Board_1.removePiece)(board, capturedPawnSquare);
|
|
106
|
+
}
|
|
107
|
+
else if (move.flags & types_1.MoveFlag.PROMOTION) {
|
|
108
|
+
// Promotion
|
|
109
|
+
(0, Board_1.removePiece)(board, move.from);
|
|
110
|
+
if (move.capturedPiece !== types_1.Piece.EMPTY) {
|
|
111
|
+
(0, Board_1.removePiece)(board, move.to);
|
|
112
|
+
}
|
|
113
|
+
(0, Board_1.setPiece)(board, move.to, move.promotionPiece);
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
// Normal move or capture
|
|
117
|
+
(0, Board_1.removePiece)(board, move.from);
|
|
118
|
+
if (move.capturedPiece !== types_1.Piece.EMPTY) {
|
|
119
|
+
(0, Board_1.removePiece)(board, move.to);
|
|
120
|
+
}
|
|
121
|
+
(0, Board_1.setPiece)(board, move.to, move.piece);
|
|
122
|
+
}
|
|
123
|
+
// Update en passant square
|
|
124
|
+
if (move.flags & types_1.MoveFlag.PAWN_DOUBLE_PUSH) {
|
|
125
|
+
const epSquare = board.turn === types_1.InternalColor.WHITE
|
|
126
|
+
? (move.from + 8)
|
|
127
|
+
: (move.from - 8);
|
|
128
|
+
board.enPassantSquare = epSquare;
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
board.enPassantSquare = null;
|
|
132
|
+
}
|
|
133
|
+
// Switch turn (needed for isKingInCheck to check the right king)
|
|
134
|
+
board.turn = board.turn === types_1.InternalColor.WHITE ? types_1.InternalColor.BLACK : types_1.InternalColor.WHITE;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Generate all pseudo-legal moves (may leave king in check)
|
|
138
|
+
*
|
|
139
|
+
* @param board - Board state
|
|
140
|
+
* @returns Array of pseudo-legal moves
|
|
141
|
+
*/
|
|
142
|
+
function generatePseudoLegalMoves(board) {
|
|
143
|
+
const moves = [];
|
|
144
|
+
const color = board.turn;
|
|
145
|
+
const friendlyPieces = color === types_1.InternalColor.WHITE ? board.whitePieces : board.blackPieces;
|
|
146
|
+
const enemyPieces = color === types_1.InternalColor.WHITE ? board.blackPieces : board.whitePieces;
|
|
147
|
+
// Generate moves for each piece type
|
|
148
|
+
generatePawnMoves(board, moves, color, friendlyPieces, enemyPieces);
|
|
149
|
+
generateKnightMoves(board, moves, color, friendlyPieces);
|
|
150
|
+
generateBishopMoves(board, moves, color, friendlyPieces);
|
|
151
|
+
generateRookMoves(board, moves, color, friendlyPieces);
|
|
152
|
+
generateQueenMoves(board, moves, color, friendlyPieces);
|
|
153
|
+
generateKingMoves(board, moves, color, friendlyPieces);
|
|
154
|
+
generateCastlingMoves(board, moves, color);
|
|
155
|
+
return moves;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Generate pawn moves (including promotions and en passant)
|
|
159
|
+
*/
|
|
160
|
+
function generatePawnMoves(board, moves, color, _friendlyPieces, enemyPieces) {
|
|
161
|
+
const pawns = color === types_1.InternalColor.WHITE ? board.whitePawns : board.blackPawns;
|
|
162
|
+
const pawnPiece = color === types_1.InternalColor.WHITE ? types_1.Piece.WHITE_PAWN : types_1.Piece.BLACK_PAWN;
|
|
163
|
+
const promotionRank = color === types_1.InternalColor.WHITE ? 7 : 0;
|
|
164
|
+
const empty = ~board.allPieces;
|
|
165
|
+
if (color === types_1.InternalColor.WHITE) {
|
|
166
|
+
// Single push
|
|
167
|
+
const singlePush = (0, Position_1.shiftNorth)(pawns) & empty;
|
|
168
|
+
const singlePushIndices = (0, conversion_1.bitboardToIndices)(singlePush);
|
|
169
|
+
for (const to of singlePushIndices) {
|
|
170
|
+
const from = (to - 8);
|
|
171
|
+
const toRank = (0, conversion_1.getRankIndex)(to);
|
|
172
|
+
// Check for promotion
|
|
173
|
+
if (toRank === promotionRank) {
|
|
174
|
+
// Add all promotion moves
|
|
175
|
+
moves.push(createMove(from, to, pawnPiece, types_1.Piece.EMPTY, types_1.MoveFlag.PROMOTION, types_1.Piece.WHITE_QUEEN));
|
|
176
|
+
moves.push(createMove(from, to, pawnPiece, types_1.Piece.EMPTY, types_1.MoveFlag.PROMOTION, types_1.Piece.WHITE_ROOK));
|
|
177
|
+
moves.push(createMove(from, to, pawnPiece, types_1.Piece.EMPTY, types_1.MoveFlag.PROMOTION, types_1.Piece.WHITE_BISHOP));
|
|
178
|
+
moves.push(createMove(from, to, pawnPiece, types_1.Piece.EMPTY, types_1.MoveFlag.PROMOTION, types_1.Piece.WHITE_KNIGHT));
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
moves.push(createMove(from, to, pawnPiece, types_1.Piece.EMPTY, types_1.MoveFlag.NONE));
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
// Double push
|
|
185
|
+
const doublePushSource = pawns & 0x000000000000ff00n; // Rank 2
|
|
186
|
+
const doublePush = (0, Position_1.shiftNorth)((0, Position_1.shiftNorth)(doublePushSource) & empty) & empty;
|
|
187
|
+
const doublePushIndices = (0, conversion_1.bitboardToIndices)(doublePush);
|
|
188
|
+
for (const to of doublePushIndices) {
|
|
189
|
+
const from = (to - 16);
|
|
190
|
+
moves.push(createMove(from, to, pawnPiece, types_1.Piece.EMPTY, types_1.MoveFlag.PAWN_DOUBLE_PUSH));
|
|
191
|
+
}
|
|
192
|
+
// Captures north-east
|
|
193
|
+
const capturesNE = (0, Position_1.shiftNorthEast)(pawns) & enemyPieces;
|
|
194
|
+
const capturesNEIndices = (0, conversion_1.bitboardToIndices)(capturesNE);
|
|
195
|
+
for (const to of capturesNEIndices) {
|
|
196
|
+
const from = (to - 9);
|
|
197
|
+
const capturedPiece = (0, Board_1.getPiece)(board, to);
|
|
198
|
+
const toRank = (0, conversion_1.getRankIndex)(to);
|
|
199
|
+
if (toRank === promotionRank) {
|
|
200
|
+
// Promotion capture
|
|
201
|
+
moves.push(createMove(from, to, pawnPiece, capturedPiece, types_1.MoveFlag.PROMOTION | types_1.MoveFlag.CAPTURE, types_1.Piece.WHITE_QUEEN));
|
|
202
|
+
moves.push(createMove(from, to, pawnPiece, capturedPiece, types_1.MoveFlag.PROMOTION | types_1.MoveFlag.CAPTURE, types_1.Piece.WHITE_ROOK));
|
|
203
|
+
moves.push(createMove(from, to, pawnPiece, capturedPiece, types_1.MoveFlag.PROMOTION | types_1.MoveFlag.CAPTURE, types_1.Piece.WHITE_BISHOP));
|
|
204
|
+
moves.push(createMove(from, to, pawnPiece, capturedPiece, types_1.MoveFlag.PROMOTION | types_1.MoveFlag.CAPTURE, types_1.Piece.WHITE_KNIGHT));
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
moves.push(createMove(from, to, pawnPiece, capturedPiece, types_1.MoveFlag.CAPTURE));
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
// Captures north-west
|
|
211
|
+
const capturesNW = (0, Position_1.shiftNorthWest)(pawns) & enemyPieces;
|
|
212
|
+
const capturesNWIndices = (0, conversion_1.bitboardToIndices)(capturesNW);
|
|
213
|
+
for (const to of capturesNWIndices) {
|
|
214
|
+
const from = (to - 7);
|
|
215
|
+
const capturedPiece = (0, Board_1.getPiece)(board, to);
|
|
216
|
+
const toRank = (0, conversion_1.getRankIndex)(to);
|
|
217
|
+
if (toRank === promotionRank) {
|
|
218
|
+
// Promotion capture
|
|
219
|
+
moves.push(createMove(from, to, pawnPiece, capturedPiece, types_1.MoveFlag.PROMOTION | types_1.MoveFlag.CAPTURE, types_1.Piece.WHITE_QUEEN));
|
|
220
|
+
moves.push(createMove(from, to, pawnPiece, capturedPiece, types_1.MoveFlag.PROMOTION | types_1.MoveFlag.CAPTURE, types_1.Piece.WHITE_ROOK));
|
|
221
|
+
moves.push(createMove(from, to, pawnPiece, capturedPiece, types_1.MoveFlag.PROMOTION | types_1.MoveFlag.CAPTURE, types_1.Piece.WHITE_BISHOP));
|
|
222
|
+
moves.push(createMove(from, to, pawnPiece, capturedPiece, types_1.MoveFlag.PROMOTION | types_1.MoveFlag.CAPTURE, types_1.Piece.WHITE_KNIGHT));
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
moves.push(createMove(from, to, pawnPiece, capturedPiece, types_1.MoveFlag.CAPTURE));
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
// En passant
|
|
229
|
+
if (board.enPassantSquare !== null) {
|
|
230
|
+
const epSquare = board.enPassantSquare;
|
|
231
|
+
const epTarget = 1n << BigInt(epSquare);
|
|
232
|
+
// Check if any pawn can capture en passant
|
|
233
|
+
const canCaptureEP = ((0, Position_1.shiftSouthWest)(epTarget) | (0, Position_1.shiftSouthEast)(epTarget)) & pawns;
|
|
234
|
+
if (canCaptureEP !== 0n) {
|
|
235
|
+
const attackerIndices = (0, conversion_1.bitboardToIndices)(canCaptureEP);
|
|
236
|
+
for (const from of attackerIndices) {
|
|
237
|
+
const capturedPiece = types_1.Piece.BLACK_PAWN;
|
|
238
|
+
moves.push(createMove(from, epSquare, pawnPiece, capturedPiece, types_1.MoveFlag.EN_PASSANT | types_1.MoveFlag.CAPTURE));
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
// Black pawns (move south)
|
|
245
|
+
// Single push
|
|
246
|
+
const singlePush = (0, Position_1.shiftSouth)(pawns) & empty;
|
|
247
|
+
const singlePushIndices = (0, conversion_1.bitboardToIndices)(singlePush);
|
|
248
|
+
for (const to of singlePushIndices) {
|
|
249
|
+
const from = (to + 8);
|
|
250
|
+
const toRank = (0, conversion_1.getRankIndex)(to);
|
|
251
|
+
// Check for promotion
|
|
252
|
+
if (toRank === promotionRank) {
|
|
253
|
+
// Add all promotion moves
|
|
254
|
+
moves.push(createMove(from, to, pawnPiece, types_1.Piece.EMPTY, types_1.MoveFlag.PROMOTION, types_1.Piece.BLACK_QUEEN));
|
|
255
|
+
moves.push(createMove(from, to, pawnPiece, types_1.Piece.EMPTY, types_1.MoveFlag.PROMOTION, types_1.Piece.BLACK_ROOK));
|
|
256
|
+
moves.push(createMove(from, to, pawnPiece, types_1.Piece.EMPTY, types_1.MoveFlag.PROMOTION, types_1.Piece.BLACK_BISHOP));
|
|
257
|
+
moves.push(createMove(from, to, pawnPiece, types_1.Piece.EMPTY, types_1.MoveFlag.PROMOTION, types_1.Piece.BLACK_KNIGHT));
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
moves.push(createMove(from, to, pawnPiece, types_1.Piece.EMPTY, types_1.MoveFlag.NONE));
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
// Double push
|
|
264
|
+
const doublePushSource = pawns & 0x00ff000000000000n; // Rank 7
|
|
265
|
+
const doublePush = (0, Position_1.shiftSouth)((0, Position_1.shiftSouth)(doublePushSource) & empty) & empty;
|
|
266
|
+
const doublePushIndices = (0, conversion_1.bitboardToIndices)(doublePush);
|
|
267
|
+
for (const to of doublePushIndices) {
|
|
268
|
+
const from = (to + 16);
|
|
269
|
+
moves.push(createMove(from, to, pawnPiece, types_1.Piece.EMPTY, types_1.MoveFlag.PAWN_DOUBLE_PUSH));
|
|
270
|
+
}
|
|
271
|
+
// Captures south-east
|
|
272
|
+
const capturesSE = (0, Position_1.shiftSouthEast)(pawns) & enemyPieces;
|
|
273
|
+
const capturesSEIndices = (0, conversion_1.bitboardToIndices)(capturesSE);
|
|
274
|
+
for (const to of capturesSEIndices) {
|
|
275
|
+
const from = (to + 7);
|
|
276
|
+
const capturedPiece = (0, Board_1.getPiece)(board, to);
|
|
277
|
+
const toRank = (0, conversion_1.getRankIndex)(to);
|
|
278
|
+
if (toRank === promotionRank) {
|
|
279
|
+
// Promotion capture
|
|
280
|
+
moves.push(createMove(from, to, pawnPiece, capturedPiece, types_1.MoveFlag.PROMOTION | types_1.MoveFlag.CAPTURE, types_1.Piece.BLACK_QUEEN));
|
|
281
|
+
moves.push(createMove(from, to, pawnPiece, capturedPiece, types_1.MoveFlag.PROMOTION | types_1.MoveFlag.CAPTURE, types_1.Piece.BLACK_ROOK));
|
|
282
|
+
moves.push(createMove(from, to, pawnPiece, capturedPiece, types_1.MoveFlag.PROMOTION | types_1.MoveFlag.CAPTURE, types_1.Piece.BLACK_BISHOP));
|
|
283
|
+
moves.push(createMove(from, to, pawnPiece, capturedPiece, types_1.MoveFlag.PROMOTION | types_1.MoveFlag.CAPTURE, types_1.Piece.BLACK_KNIGHT));
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
moves.push(createMove(from, to, pawnPiece, capturedPiece, types_1.MoveFlag.CAPTURE));
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
// Captures south-west
|
|
290
|
+
const capturesSW = (0, Position_1.shiftSouthWest)(pawns) & enemyPieces;
|
|
291
|
+
const capturesSWIndices = (0, conversion_1.bitboardToIndices)(capturesSW);
|
|
292
|
+
for (const to of capturesSWIndices) {
|
|
293
|
+
const from = (to + 9);
|
|
294
|
+
const capturedPiece = (0, Board_1.getPiece)(board, to);
|
|
295
|
+
const toRank = (0, conversion_1.getRankIndex)(to);
|
|
296
|
+
if (toRank === promotionRank) {
|
|
297
|
+
// Promotion capture
|
|
298
|
+
moves.push(createMove(from, to, pawnPiece, capturedPiece, types_1.MoveFlag.PROMOTION | types_1.MoveFlag.CAPTURE, types_1.Piece.BLACK_QUEEN));
|
|
299
|
+
moves.push(createMove(from, to, pawnPiece, capturedPiece, types_1.MoveFlag.PROMOTION | types_1.MoveFlag.CAPTURE, types_1.Piece.BLACK_ROOK));
|
|
300
|
+
moves.push(createMove(from, to, pawnPiece, capturedPiece, types_1.MoveFlag.PROMOTION | types_1.MoveFlag.CAPTURE, types_1.Piece.BLACK_BISHOP));
|
|
301
|
+
moves.push(createMove(from, to, pawnPiece, capturedPiece, types_1.MoveFlag.PROMOTION | types_1.MoveFlag.CAPTURE, types_1.Piece.BLACK_KNIGHT));
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
moves.push(createMove(from, to, pawnPiece, capturedPiece, types_1.MoveFlag.CAPTURE));
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
// En passant
|
|
308
|
+
if (board.enPassantSquare !== null) {
|
|
309
|
+
const epSquare = board.enPassantSquare;
|
|
310
|
+
const epTarget = 1n << BigInt(epSquare);
|
|
311
|
+
// Check if any pawn can capture en passant
|
|
312
|
+
const canCaptureEP = ((0, Position_1.shiftNorthWest)(epTarget) | (0, Position_1.shiftNorthEast)(epTarget)) & pawns;
|
|
313
|
+
if (canCaptureEP !== 0n) {
|
|
314
|
+
const attackerIndices = (0, conversion_1.bitboardToIndices)(canCaptureEP);
|
|
315
|
+
for (const from of attackerIndices) {
|
|
316
|
+
const capturedPiece = types_1.Piece.WHITE_PAWN;
|
|
317
|
+
moves.push(createMove(from, epSquare, pawnPiece, capturedPiece, types_1.MoveFlag.EN_PASSANT | types_1.MoveFlag.CAPTURE));
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Generate knight moves
|
|
325
|
+
*/
|
|
326
|
+
function generateKnightMoves(board, moves, color, friendlyPieces) {
|
|
327
|
+
const knights = color === types_1.InternalColor.WHITE ? board.whiteKnights : board.blackKnights;
|
|
328
|
+
const knightPiece = color === types_1.InternalColor.WHITE ? types_1.Piece.WHITE_KNIGHT : types_1.Piece.BLACK_KNIGHT;
|
|
329
|
+
let knightsBB = knights;
|
|
330
|
+
while (knightsBB !== 0n) {
|
|
331
|
+
const from = (0, conversion_1.getLowestSetBit)(knightsBB);
|
|
332
|
+
const attacks = (0, Position_1.getKnightAttacks)(from) & ~friendlyPieces;
|
|
333
|
+
const attackIndices = (0, conversion_1.bitboardToIndices)(attacks);
|
|
334
|
+
for (const to of attackIndices) {
|
|
335
|
+
const capturedPiece = (0, Board_1.getPiece)(board, to);
|
|
336
|
+
const flags = capturedPiece !== types_1.Piece.EMPTY ? types_1.MoveFlag.CAPTURE : types_1.MoveFlag.NONE;
|
|
337
|
+
moves.push(createMove(from, to, knightPiece, capturedPiece, flags));
|
|
338
|
+
}
|
|
339
|
+
knightsBB &= knightsBB - 1n; // Clear lowest bit
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Generate bishop moves
|
|
344
|
+
*/
|
|
345
|
+
function generateBishopMoves(board, moves, color, friendlyPieces) {
|
|
346
|
+
const bishops = color === types_1.InternalColor.WHITE ? board.whiteBishops : board.blackBishops;
|
|
347
|
+
const bishopPiece = color === types_1.InternalColor.WHITE ? types_1.Piece.WHITE_BISHOP : types_1.Piece.BLACK_BISHOP;
|
|
348
|
+
let bishopsBB = bishops;
|
|
349
|
+
while (bishopsBB !== 0n) {
|
|
350
|
+
const from = (0, conversion_1.getLowestSetBit)(bishopsBB);
|
|
351
|
+
const attacks = (0, Position_1.getBishopAttacks)(from, board.allPieces) & ~friendlyPieces;
|
|
352
|
+
const attackIndices = (0, conversion_1.bitboardToIndices)(attacks);
|
|
353
|
+
for (const to of attackIndices) {
|
|
354
|
+
const capturedPiece = (0, Board_1.getPiece)(board, to);
|
|
355
|
+
const flags = capturedPiece !== types_1.Piece.EMPTY ? types_1.MoveFlag.CAPTURE : types_1.MoveFlag.NONE;
|
|
356
|
+
moves.push(createMove(from, to, bishopPiece, capturedPiece, flags));
|
|
357
|
+
}
|
|
358
|
+
bishopsBB &= bishopsBB - 1n;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Generate rook moves
|
|
363
|
+
*/
|
|
364
|
+
function generateRookMoves(board, moves, color, friendlyPieces) {
|
|
365
|
+
const rooks = color === types_1.InternalColor.WHITE ? board.whiteRooks : board.blackRooks;
|
|
366
|
+
const rookPiece = color === types_1.InternalColor.WHITE ? types_1.Piece.WHITE_ROOK : types_1.Piece.BLACK_ROOK;
|
|
367
|
+
let rooksBB = rooks;
|
|
368
|
+
while (rooksBB !== 0n) {
|
|
369
|
+
const from = (0, conversion_1.getLowestSetBit)(rooksBB);
|
|
370
|
+
const attacks = (0, Position_1.getRookAttacks)(from, board.allPieces) & ~friendlyPieces;
|
|
371
|
+
const attackIndices = (0, conversion_1.bitboardToIndices)(attacks);
|
|
372
|
+
for (const to of attackIndices) {
|
|
373
|
+
const capturedPiece = (0, Board_1.getPiece)(board, to);
|
|
374
|
+
const flags = capturedPiece !== types_1.Piece.EMPTY ? types_1.MoveFlag.CAPTURE : types_1.MoveFlag.NONE;
|
|
375
|
+
moves.push(createMove(from, to, rookPiece, capturedPiece, flags));
|
|
376
|
+
}
|
|
377
|
+
rooksBB &= rooksBB - 1n;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Generate queen moves
|
|
382
|
+
*/
|
|
383
|
+
function generateQueenMoves(board, moves, color, friendlyPieces) {
|
|
384
|
+
const queens = color === types_1.InternalColor.WHITE ? board.whiteQueens : board.blackQueens;
|
|
385
|
+
const queenPiece = color === types_1.InternalColor.WHITE ? types_1.Piece.WHITE_QUEEN : types_1.Piece.BLACK_QUEEN;
|
|
386
|
+
let queensBB = queens;
|
|
387
|
+
while (queensBB !== 0n) {
|
|
388
|
+
const from = (0, conversion_1.getLowestSetBit)(queensBB);
|
|
389
|
+
const attacks = (0, Position_1.getQueenAttacks)(from, board.allPieces) & ~friendlyPieces;
|
|
390
|
+
const attackIndices = (0, conversion_1.bitboardToIndices)(attacks);
|
|
391
|
+
for (const to of attackIndices) {
|
|
392
|
+
const capturedPiece = (0, Board_1.getPiece)(board, to);
|
|
393
|
+
const flags = capturedPiece !== types_1.Piece.EMPTY ? types_1.MoveFlag.CAPTURE : types_1.MoveFlag.NONE;
|
|
394
|
+
moves.push(createMove(from, to, queenPiece, capturedPiece, flags));
|
|
395
|
+
}
|
|
396
|
+
queensBB &= queensBB - 1n;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Generate king moves (excluding castling)
|
|
401
|
+
*/
|
|
402
|
+
function generateKingMoves(board, moves, color, friendlyPieces) {
|
|
403
|
+
const king = color === types_1.InternalColor.WHITE ? board.whiteKing : board.blackKing;
|
|
404
|
+
const kingPiece = color === types_1.InternalColor.WHITE ? types_1.Piece.WHITE_KING : types_1.Piece.BLACK_KING;
|
|
405
|
+
if (king === 0n)
|
|
406
|
+
return;
|
|
407
|
+
const from = (0, conversion_1.getLowestSetBit)(king);
|
|
408
|
+
const attacks = (0, Position_1.getKingAttacks)(from) & ~friendlyPieces;
|
|
409
|
+
const attackIndices = (0, conversion_1.bitboardToIndices)(attacks);
|
|
410
|
+
for (const to of attackIndices) {
|
|
411
|
+
const capturedPiece = (0, Board_1.getPiece)(board, to);
|
|
412
|
+
const flags = capturedPiece !== types_1.Piece.EMPTY ? types_1.MoveFlag.CAPTURE : types_1.MoveFlag.NONE;
|
|
413
|
+
moves.push(createMove(from, to, kingPiece, capturedPiece, flags));
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Generate castling moves
|
|
418
|
+
*/
|
|
419
|
+
function generateCastlingMoves(board, moves, color) {
|
|
420
|
+
const opponentColor = color === types_1.InternalColor.WHITE ? types_1.InternalColor.BLACK : types_1.InternalColor.WHITE;
|
|
421
|
+
if (color === types_1.InternalColor.WHITE) {
|
|
422
|
+
// White short castling (O-O)
|
|
423
|
+
if (board.castlingRights.whiteShort &&
|
|
424
|
+
(0, Board_1.getPiece)(board, constants_1.CASTLING.WHITE_SHORT.kingFrom) === types_1.Piece.WHITE_KING &&
|
|
425
|
+
(0, Board_1.getPiece)(board, constants_1.CASTLING.WHITE_SHORT.rookFrom) === types_1.Piece.WHITE_ROOK &&
|
|
426
|
+
(0, Board_1.isSquareEmpty)(board, 5) && // F1
|
|
427
|
+
(0, Board_1.isSquareEmpty)(board, 6) && // G1
|
|
428
|
+
!(0, AttackDetector_1.isSquareAttacked)(board, 4, opponentColor) && // E1 not in check
|
|
429
|
+
!(0, AttackDetector_1.isSquareAttacked)(board, 5, opponentColor) && // F1 not attacked
|
|
430
|
+
!(0, AttackDetector_1.isSquareAttacked)(board, 6, opponentColor) // G1 not attacked
|
|
431
|
+
) {
|
|
432
|
+
moves.push(createMove(constants_1.CASTLING.WHITE_SHORT.kingFrom, constants_1.CASTLING.WHITE_SHORT.kingTo, types_1.Piece.WHITE_KING, types_1.Piece.EMPTY, types_1.MoveFlag.CASTLING));
|
|
433
|
+
}
|
|
434
|
+
// White long castling (O-O-O)
|
|
435
|
+
if (board.castlingRights.whiteLong &&
|
|
436
|
+
(0, Board_1.getPiece)(board, constants_1.CASTLING.WHITE_LONG.kingFrom) === types_1.Piece.WHITE_KING &&
|
|
437
|
+
(0, Board_1.getPiece)(board, constants_1.CASTLING.WHITE_LONG.rookFrom) === types_1.Piece.WHITE_ROOK &&
|
|
438
|
+
(0, Board_1.isSquareEmpty)(board, 1) && // B1
|
|
439
|
+
(0, Board_1.isSquareEmpty)(board, 2) && // C1
|
|
440
|
+
(0, Board_1.isSquareEmpty)(board, 3) && // D1
|
|
441
|
+
!(0, AttackDetector_1.isSquareAttacked)(board, 4, opponentColor) && // E1 not in check
|
|
442
|
+
!(0, AttackDetector_1.isSquareAttacked)(board, 3, opponentColor) && // D1 not attacked
|
|
443
|
+
!(0, AttackDetector_1.isSquareAttacked)(board, 2, opponentColor) // C1 not attacked
|
|
444
|
+
) {
|
|
445
|
+
moves.push(createMove(constants_1.CASTLING.WHITE_LONG.kingFrom, constants_1.CASTLING.WHITE_LONG.kingTo, types_1.Piece.WHITE_KING, types_1.Piece.EMPTY, types_1.MoveFlag.CASTLING));
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
else {
|
|
449
|
+
// Black short castling (O-O)
|
|
450
|
+
if (board.castlingRights.blackShort &&
|
|
451
|
+
(0, Board_1.getPiece)(board, constants_1.CASTLING.BLACK_SHORT.kingFrom) === types_1.Piece.BLACK_KING &&
|
|
452
|
+
(0, Board_1.getPiece)(board, constants_1.CASTLING.BLACK_SHORT.rookFrom) === types_1.Piece.BLACK_ROOK &&
|
|
453
|
+
(0, Board_1.isSquareEmpty)(board, 61) && // F8
|
|
454
|
+
(0, Board_1.isSquareEmpty)(board, 62) && // G8
|
|
455
|
+
!(0, AttackDetector_1.isSquareAttacked)(board, 60, opponentColor) && // E8 not in check
|
|
456
|
+
!(0, AttackDetector_1.isSquareAttacked)(board, 61, opponentColor) && // F8 not attacked
|
|
457
|
+
!(0, AttackDetector_1.isSquareAttacked)(board, 62, opponentColor) // G8 not attacked
|
|
458
|
+
) {
|
|
459
|
+
moves.push(createMove(constants_1.CASTLING.BLACK_SHORT.kingFrom, constants_1.CASTLING.BLACK_SHORT.kingTo, types_1.Piece.BLACK_KING, types_1.Piece.EMPTY, types_1.MoveFlag.CASTLING));
|
|
460
|
+
}
|
|
461
|
+
// Black long castling (O-O-O)
|
|
462
|
+
if (board.castlingRights.blackLong &&
|
|
463
|
+
(0, Board_1.getPiece)(board, constants_1.CASTLING.BLACK_LONG.kingFrom) === types_1.Piece.BLACK_KING &&
|
|
464
|
+
(0, Board_1.getPiece)(board, constants_1.CASTLING.BLACK_LONG.rookFrom) === types_1.Piece.BLACK_ROOK &&
|
|
465
|
+
(0, Board_1.isSquareEmpty)(board, 57) && // B8
|
|
466
|
+
(0, Board_1.isSquareEmpty)(board, 58) && // C8
|
|
467
|
+
(0, Board_1.isSquareEmpty)(board, 59) && // D8
|
|
468
|
+
!(0, AttackDetector_1.isSquareAttacked)(board, 60, opponentColor) && // E8 not in check
|
|
469
|
+
!(0, AttackDetector_1.isSquareAttacked)(board, 59, opponentColor) && // D8 not attacked
|
|
470
|
+
!(0, AttackDetector_1.isSquareAttacked)(board, 58, opponentColor) // C8 not attacked
|
|
471
|
+
) {
|
|
472
|
+
moves.push(createMove(constants_1.CASTLING.BLACK_LONG.kingFrom, constants_1.CASTLING.BLACK_LONG.kingTo, types_1.Piece.BLACK_KING, types_1.Piece.EMPTY, types_1.MoveFlag.CASTLING));
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Helper to create a move object
|
|
478
|
+
*/
|
|
479
|
+
function createMove(from, to, piece, capturedPiece, flags, promotionPiece) {
|
|
480
|
+
return {
|
|
481
|
+
from,
|
|
482
|
+
to,
|
|
483
|
+
piece,
|
|
484
|
+
capturedPiece,
|
|
485
|
+
flags,
|
|
486
|
+
promotionPiece,
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Get all legal moves for a specific piece
|
|
491
|
+
*
|
|
492
|
+
* @param board - Board state
|
|
493
|
+
* @param square - Square of the piece
|
|
494
|
+
* @returns Array of legal moves for that piece
|
|
495
|
+
*/
|
|
496
|
+
function getMovesForPiece(board, square) {
|
|
497
|
+
const allMoves = generateLegalMoves(board);
|
|
498
|
+
return allMoves.filter(move => move.from === square);
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Check if a move is legal
|
|
502
|
+
*
|
|
503
|
+
* @param board - Board state
|
|
504
|
+
* @param from - From square
|
|
505
|
+
* @param to - To square
|
|
506
|
+
* @returns true if move is legal
|
|
507
|
+
*/
|
|
508
|
+
function isMoveLegal(board, from, to) {
|
|
509
|
+
const legalMoves = generateLegalMoves(board);
|
|
510
|
+
return legalMoves.some(move => move.from === from && move.to === to);
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Apply a move to the board with full state updates (mutates the board)
|
|
514
|
+
* Updates turn, castling rights, en passant, move counters, and game status
|
|
515
|
+
*
|
|
516
|
+
* @param board - Board state to modify
|
|
517
|
+
* @param move - Move to apply
|
|
518
|
+
* @returns The applied move
|
|
519
|
+
*/
|
|
520
|
+
function applyMoveComplete(board, move) {
|
|
521
|
+
const { from, to, piece, capturedPiece, flags, promotionPiece } = move;
|
|
522
|
+
// Reset en passant square (will be set if this is a double pawn push)
|
|
523
|
+
board.enPassantSquare = null;
|
|
524
|
+
// Handle captures
|
|
525
|
+
if (capturedPiece !== types_1.Piece.EMPTY) {
|
|
526
|
+
(0, Board_1.removePiece)(board, to);
|
|
527
|
+
board.halfMoveClock = 0;
|
|
528
|
+
}
|
|
529
|
+
else {
|
|
530
|
+
board.halfMoveClock++;
|
|
531
|
+
}
|
|
532
|
+
// Handle en passant capture
|
|
533
|
+
if (flags & types_1.MoveFlag.EN_PASSANT) {
|
|
534
|
+
const captureSquare = board.turn === types_1.InternalColor.WHITE ? to - 8 : to + 8;
|
|
535
|
+
(0, Board_1.removePiece)(board, captureSquare);
|
|
536
|
+
board.halfMoveClock = 0;
|
|
537
|
+
}
|
|
538
|
+
// Handle castling
|
|
539
|
+
if (flags & types_1.MoveFlag.CASTLING) {
|
|
540
|
+
// Move the rook
|
|
541
|
+
if (to === constants_1.CASTLING.WHITE_SHORT.kingTo) {
|
|
542
|
+
// White kingside
|
|
543
|
+
(0, Board_1.removePiece)(board, constants_1.CASTLING.WHITE_SHORT.rookFrom);
|
|
544
|
+
(0, Board_1.setPiece)(board, constants_1.CASTLING.WHITE_SHORT.rookTo, types_1.Piece.WHITE_ROOK);
|
|
545
|
+
}
|
|
546
|
+
else if (to === constants_1.CASTLING.WHITE_LONG.kingTo) {
|
|
547
|
+
// White queenside
|
|
548
|
+
(0, Board_1.removePiece)(board, constants_1.CASTLING.WHITE_LONG.rookFrom);
|
|
549
|
+
(0, Board_1.setPiece)(board, constants_1.CASTLING.WHITE_LONG.rookTo, types_1.Piece.WHITE_ROOK);
|
|
550
|
+
}
|
|
551
|
+
else if (to === constants_1.CASTLING.BLACK_SHORT.kingTo) {
|
|
552
|
+
// Black kingside
|
|
553
|
+
(0, Board_1.removePiece)(board, constants_1.CASTLING.BLACK_SHORT.rookFrom);
|
|
554
|
+
(0, Board_1.setPiece)(board, constants_1.CASTLING.BLACK_SHORT.rookTo, types_1.Piece.BLACK_ROOK);
|
|
555
|
+
}
|
|
556
|
+
else if (to === constants_1.CASTLING.BLACK_LONG.kingTo) {
|
|
557
|
+
// Black queenside
|
|
558
|
+
(0, Board_1.removePiece)(board, constants_1.CASTLING.BLACK_LONG.rookFrom);
|
|
559
|
+
(0, Board_1.setPiece)(board, constants_1.CASTLING.BLACK_LONG.rookTo, types_1.Piece.BLACK_ROOK);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
// Move the piece
|
|
563
|
+
(0, Board_1.removePiece)(board, from);
|
|
564
|
+
// Handle promotion
|
|
565
|
+
if (flags & types_1.MoveFlag.PROMOTION && promotionPiece) {
|
|
566
|
+
(0, Board_1.setPiece)(board, to, promotionPiece);
|
|
567
|
+
}
|
|
568
|
+
else {
|
|
569
|
+
(0, Board_1.setPiece)(board, to, piece);
|
|
570
|
+
}
|
|
571
|
+
// Reset half-move clock on pawn moves
|
|
572
|
+
if (piece === types_1.Piece.WHITE_PAWN || piece === types_1.Piece.BLACK_PAWN) {
|
|
573
|
+
board.halfMoveClock = 0;
|
|
574
|
+
}
|
|
575
|
+
// Handle double pawn push (set en passant square)
|
|
576
|
+
if (flags & types_1.MoveFlag.PAWN_DOUBLE_PUSH) {
|
|
577
|
+
const enPassantSquare = board.turn === types_1.InternalColor.WHITE ? from + 8 : from - 8;
|
|
578
|
+
board.enPassantSquare = enPassantSquare;
|
|
579
|
+
}
|
|
580
|
+
// Update castling rights
|
|
581
|
+
updateCastlingRights(board, from, to, piece);
|
|
582
|
+
// Switch turn
|
|
583
|
+
board.turn = board.turn === types_1.InternalColor.WHITE ? types_1.InternalColor.BLACK : types_1.InternalColor.WHITE;
|
|
584
|
+
// Increment full move number after black's move
|
|
585
|
+
if (board.turn === types_1.InternalColor.WHITE) {
|
|
586
|
+
board.fullMoveNumber++;
|
|
587
|
+
}
|
|
588
|
+
// Update game status (check, checkmate, stalemate)
|
|
589
|
+
updateGameStatus(board);
|
|
590
|
+
return move;
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* Update castling rights after a move
|
|
594
|
+
*
|
|
595
|
+
* @param board - Board state
|
|
596
|
+
* @param from - From square
|
|
597
|
+
* @param to - To square
|
|
598
|
+
* @param piece - Piece that moved
|
|
599
|
+
*/
|
|
600
|
+
function updateCastlingRights(board, from, to, piece) {
|
|
601
|
+
// If king moves, lose all castling rights for that color
|
|
602
|
+
if (piece === types_1.Piece.WHITE_KING) {
|
|
603
|
+
board.castlingRights.whiteShort = false;
|
|
604
|
+
board.castlingRights.whiteLong = false;
|
|
605
|
+
}
|
|
606
|
+
else if (piece === types_1.Piece.BLACK_KING) {
|
|
607
|
+
board.castlingRights.blackShort = false;
|
|
608
|
+
board.castlingRights.blackLong = false;
|
|
609
|
+
}
|
|
610
|
+
// If rook moves from starting square, lose castling right for that side
|
|
611
|
+
if (piece === types_1.Piece.WHITE_ROOK) {
|
|
612
|
+
if (from === constants_1.CASTLING.WHITE_SHORT.rookFrom) {
|
|
613
|
+
board.castlingRights.whiteShort = false;
|
|
614
|
+
}
|
|
615
|
+
else if (from === constants_1.CASTLING.WHITE_LONG.rookFrom) {
|
|
616
|
+
board.castlingRights.whiteLong = false;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
else if (piece === types_1.Piece.BLACK_ROOK) {
|
|
620
|
+
if (from === constants_1.CASTLING.BLACK_SHORT.rookFrom) {
|
|
621
|
+
board.castlingRights.blackShort = false;
|
|
622
|
+
}
|
|
623
|
+
else if (from === constants_1.CASTLING.BLACK_LONG.rookFrom) {
|
|
624
|
+
board.castlingRights.blackLong = false;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
// If rook is captured, lose castling right for that side
|
|
628
|
+
if (to === constants_1.CASTLING.WHITE_SHORT.rookFrom) {
|
|
629
|
+
board.castlingRights.whiteShort = false;
|
|
630
|
+
}
|
|
631
|
+
else if (to === constants_1.CASTLING.WHITE_LONG.rookFrom) {
|
|
632
|
+
board.castlingRights.whiteLong = false;
|
|
633
|
+
}
|
|
634
|
+
else if (to === constants_1.CASTLING.BLACK_SHORT.rookFrom) {
|
|
635
|
+
board.castlingRights.blackShort = false;
|
|
636
|
+
}
|
|
637
|
+
else if (to === constants_1.CASTLING.BLACK_LONG.rookFrom) {
|
|
638
|
+
board.castlingRights.blackLong = false;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* Update game status (check, checkmate, stalemate)
|
|
643
|
+
*
|
|
644
|
+
* @param board - Board state
|
|
645
|
+
*/
|
|
646
|
+
function updateGameStatus(board) {
|
|
647
|
+
const currentColor = board.turn;
|
|
648
|
+
const kingBitboard = currentColor === types_1.InternalColor.WHITE ? board.whiteKing : board.blackKing;
|
|
649
|
+
if (kingBitboard === 0n) {
|
|
650
|
+
board.isCheck = false;
|
|
651
|
+
board.isCheckmate = false;
|
|
652
|
+
board.isStalemate = false;
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
const kingSquare = (0, conversion_1.getLowestSetBit)(kingBitboard);
|
|
656
|
+
// Check if the current player's king is attacked by the OPPONENT
|
|
657
|
+
const opponentColor = currentColor === types_1.InternalColor.WHITE ? types_1.InternalColor.BLACK : types_1.InternalColor.WHITE;
|
|
658
|
+
const inCheck = (0, AttackDetector_1.isSquareAttacked)(board, kingSquare, opponentColor);
|
|
659
|
+
board.isCheck = inCheck;
|
|
660
|
+
// Check if there are any legal moves
|
|
661
|
+
const legalMoves = generateLegalMoves(board);
|
|
662
|
+
const hasLegalMoves = legalMoves.length > 0;
|
|
663
|
+
if (!hasLegalMoves) {
|
|
664
|
+
if (inCheck) {
|
|
665
|
+
board.isCheckmate = true;
|
|
666
|
+
board.isStalemate = false;
|
|
667
|
+
}
|
|
668
|
+
else {
|
|
669
|
+
board.isCheckmate = false;
|
|
670
|
+
board.isStalemate = true;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
else {
|
|
674
|
+
board.isCheckmate = false;
|
|
675
|
+
board.isStalemate = false;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
//# sourceMappingURL=MoveGenerator.js.map
|