+ getCastleMoves([x, y]) {
+ const c = this.getColor(x, y);
+ const oppCol = V.GetOppCol(c);
+ let moves = [];
+ const finalSquares = [ [2, 3], [6, 5] ];
+ castlingCheck: for (let castleSide = 0; castleSide < 2; castleSide++) {
+ if (this.castleFlags[c][castleSide] >= 8) continue;
+ const rookPos = this.castleFlags[c][castleSide];
+ const castlingColor = this.board[x][rookPos].charAt(0);
+ const castlingPiece = this.board[x][rookPos].charAt(1);
+
+ // Nothing on the path of the king ?
+ const finDist = finalSquares[castleSide][0] - y;
+ let step = finDist / Math.max(1, Math.abs(finDist));
+ let i = y;
+ let kingSquares = [y];
+ do {
+ if (
+ (
+ this.board[x][i] != V.EMPTY &&
+ (this.getColor(x, i) != c || ![y, rookPos].includes(i))
+ )
+ ) {
+ continue castlingCheck;
+ }
+ i += step;
+ kingSquares.push(i);
+ } while (i != finalSquares[castleSide][0]);
+ // No checks on the path of the king ?
+ if (this.isAttacked(kingSquares, oppCol)) continue castlingCheck;
+
+ // Nothing on the path to the rook?
+ step = castleSide == 0 ? -1 : 1;
+ for (i = y + step; i != rookPos; i += step) {
+ if (this.board[x][i] != V.EMPTY) continue castlingCheck;
+ }
+
+ // Nothing on final squares, except maybe king and castling rook?
+ for (i = 0; i < 2; i++) {
+ if (
+ finalSquares[castleSide][i] != rookPos &&
+ this.board[x][finalSquares[castleSide][i]] != V.EMPTY &&
+ (
+ finalSquares[castleSide][i] != y ||
+ this.getColor(x, finalSquares[castleSide][i]) != c
+ )
+ ) {
+ continue castlingCheck;
+ }
+ }
+
+ moves.push(
+ new Move({
+ appear: [
+ new PiPo({
+ x: x,
+ y: finalSquares[castleSide][0],
+ p: V.KING,
+ c: c
+ }),
+ new PiPo({
+ x: x,
+ y: finalSquares[castleSide][1],
+ p: castlingPiece,
+ c: castlingColor
+ })
+ ],
+ vanish: [
+ // King might be initially disguised (Titan...)
+ new PiPo({ x: x, y: y, p: V.KING, c: c }),
+ new PiPo({ x: x, y: rookPos, p: castlingPiece, c: castlingColor })
+ ],
+ end:
+ Math.abs(y - rookPos) <= 2
+ ? { x: x, y: rookPos }
+ : { x: x, y: y + 2 * (castleSide == 0 ? -1 : 1) }
+ })
+ );
+ }
+
+ return moves;
+ }
+
+ getEnpassantCaptures(sq, shiftX) {
+ // HACK: when artificially change turn, do not consider en-passant
+ const mcMod2 = this.movesCount % 2;
+ const c = this.turn;
+ if ((c == 'w' && mcMod2 == 1) || (c == 'b' && mcMod2 == 0)) return [];
+ return super.getEnpassantCaptures(sq, shiftX);
+ }
+
+ isAttacked_aux(files, color, positions, fromSquare, released) {
+ // "positions" = array of FENs to detect infinite loops. Example:
+ // r1q1k2r/p1Pb1ppp/5n2/1f1p4/AV5P/P1eDP3/3B1PP1/R3K1NR,
+ // Bxd2 Bxc3 Bxb4 Bxc3 Bxb4 etc.
+ const newPos = {
+ fen: super.getBaseFen(),
+ piece: released,
+ from: fromSquare
+ };
+ if (
+ positions.some(p => {
+ return (
+ p.piece == newPos.piece &&
+ p.fen == newPos.fen &&
+ p.from == newPos.from
+ );
+ })
+ ) {
+ // Start of an infinite loop: exit
+ return false;
+ }
+ positions.push(newPos);
+ const rank = (color == 'w' ? 0 : 7);
+ const moves = this.getPotentialMovesFrom(fromSquare);
+ if (moves.some(m => m.end.x == rank && files.includes(m.end.y)))
+ // Found an attack!
+ return true;
+ for (let m of moves) {
+ if (!!m.end.released) {
+ // Turn won't change since !!m.released
+ this.play(m);
+ const res = this.isAttacked_aux(
+ files, color, positions, [m.end.x, m.end.y], m.end.released);
+ this.undo(m);
+ if (res) return true;
+ }
+ }
+ return false;
+ }
+
+ isAttacked(files, color) {
+ const rank = (color == 'w' ? 0 : 7);
+ // Since it's too difficult (impossible?) to search from the square itself,
+ // let's adopt a suboptimal but working strategy: find all attacks.
+ const c = this.turn;
+ // Artificial turn change is required:
+ this.turn = color;
+ let res = false;
+ outerLoop: for (let i=0; i<8; i++) {
+ for (let j=0; j<8; j++) {
+ // Attacks must start from a normal piece, not an union.
+ // Therefore, the following test is correct.
+ if (
+ this.board[i][j] != V.EMPTY &&
+ // Do not start with king (irrelevant, and lead to infinite calls)
+ [V.PAWN, V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN].includes(
+ this.board[i][j].charAt(1)) &&
+ this.board[i][j].charAt(0) == color
+ ) {
+ // Try from here.
+ const moves = this.getPotentialMovesFrom([i, j]);
+ if (moves.some(m => m.end.x == rank && files.includes(m.end.y))) {
+ res = true;
+ break outerLoop;
+ }
+ for (let m of moves) {
+ if (!!m.end.released) {
+ // Turn won't change since !!m.released
+ this.play(m);
+ let positions = [];
+ res = this.isAttacked_aux(
+ files, color, positions, [m.end.x, m.end.y], m.end.released);
+ this.undo(m);
+ if (res) break outerLoop;
+ }
+ }
+ }
+ }
+ }
+ this.turn = c;
+ return res;
+ }
+
+ isAttackedBySlideNJump([x, y], color, piece, steps, oneStep) {
+ for (let step of steps) {
+ let rx = x + step[0],
+ ry = y + step[1];
+ while (V.OnBoard(rx, ry) && this.board[rx][ry] == V.EMPTY && !oneStep) {
+ rx += step[0];
+ ry += step[1];
+ }
+ if (
+ V.OnBoard(rx, ry) &&
+ this.board[rx][ry] != V.EMPTY &&
+ this.getPiece(rx, ry) == piece &&
+ this.getColor(rx, ry) == color &&
+ this.canTake([rx, ry], [x, y]) //TODO: necessary line?
+ //If not, generic method is OK
+ ) {
+ return true;
+ }
+ }