1 import { ChessRules
, Move
, PiPo
} from "@/base_rules";
3 export const VariantRules
= class CheckeredRules
extends ChessRules
{
5 const checkered_codes
= {
12 if (b
[0] == "c") return checkered_codes
[b
[1]];
13 return ChessRules
.board2fen(b
);
17 // Tolerate upper-case versions of checkered pieces (why not?)
18 const checkered_pieces
= {
30 if (Object
.keys(checkered_pieces
).includes(f
))
31 return "c" + checkered_pieces
[f
];
32 return ChessRules
.fen2board(f
);
36 return ChessRules
.PIECES
.concat(["s", "t", "u", "c", "o"]);
40 return (b
[0] == "c" ? "Checkered/" : "") + b
;
43 setOtherVariables(fen
) {
44 super.setOtherVariables(fen
);
45 // Local stack of non-capturing checkered moves:
47 const cmove
= V
.ParseFen(fen
).cmove
;
48 if (cmove
== "-") this.cmoves
.push(null);
51 start: ChessRules
.SquareToCoords(cmove
.substr(0, 2)),
52 end: ChessRules
.SquareToCoords(cmove
.substr(2))
57 static IsGoodFen(fen
) {
58 if (!ChessRules
.IsGoodFen(fen
)) return false;
59 const fenParts
= fen
.split(" ");
60 if (fenParts
.length
!= 6) return false;
61 if (fenParts
[5] != "-" && !fenParts
[5].match(/^([a-h][1-8]){2}$/))
66 static IsGoodFlags(flags
) {
67 // 4 for castle + 16 for pawns
68 return !!flags
.match(/^[a-z]{4,4}[01]{16,16}$/);
72 super.setFlags(fenflags
); //castleFlags
74 w: [...Array(8).fill(true)], //pawns can move 2 squares?
75 b: [...Array(8).fill(true)]
77 const flags
= fenflags
.substr(4); //skip first 4 letters, for castle
78 for (let c
of ["w", "b"]) {
79 for (let i
= 0; i
< 8; i
++)
80 this.pawnFlags
[c
][i
] = flags
.charAt((c
== "w" ? 0 : 8) + i
) == "1";
85 return [this.castleFlags
, this.pawnFlags
];
88 disaggregateFlags(flags
) {
89 this.castleFlags
= flags
[0];
90 this.pawnFlags
= flags
[1];
93 getEpSquare(moveOrSquare
) {
94 if (typeof moveOrSquare
!== "object" || moveOrSquare
.appear
[0].c
!= 'c')
95 return super.getEpSquare(moveOrSquare
);
96 // Checkered move: no en-passant
101 if (move.appear
[0].c
== "c" && move.vanish
.length
== 1)
102 return { start: move.start
, end: move.end
};
106 canTake([x1
, y1
], [x2
, y2
]) {
107 const color1
= this.getColor(x1
, y1
);
108 const color2
= this.getColor(x2
, y2
);
109 // Checkered aren't captured
113 (color1
!= "c" || color2
!= this.turn
)
117 // Post-processing: apply "checkerization" of standard moves
118 getPotentialMovesFrom([x
, y
]) {
119 let standardMoves
= super.getPotentialMovesFrom([x
, y
]);
120 const lastRank
= this.turn
== "w" ? 0 : 7;
121 // King has to be treated differently (for castles)
122 if (this.getPiece(x
, y
) == V
.KING
) return standardMoves
;
124 standardMoves
.forEach(m
=> {
125 if (m
.vanish
[0].p
== V
.PAWN
) {
127 Math
.abs(m
.end
.x
- m
.start
.x
) == 2 &&
128 !this.pawnFlags
[this.turn
][m
.start
.y
]
130 return; //skip forbidden 2-squares jumps
132 this.board
[m
.end
.x
][m
.end
.y
] == V
.EMPTY
&&
133 m
.vanish
.length
== 2 &&
134 this.getColor(m
.start
.x
, m
.start
.y
) == "c"
136 return; //checkered pawns cannot take en-passant
139 if (m
.vanish
.length
== 1) moves
.push(m
);
142 // A capture occured (m.vanish.length == 2)
146 m
.appear
[0].p
!= m
.vanish
[1].p
&& //avoid promotions (already treated):
147 (m
.vanish
[0].p
!= V
.PAWN
|| m
.end
.x
!= lastRank
)
149 // Add transformation into captured piece
150 let m2
= JSON
.parse(JSON
.stringify(m
));
151 m2
.appear
[0].p
= m
.vanish
[1].p
;
159 getPotentialPawnMoves([x
, y
]) {
160 const color
= this.turn
;
162 const [sizeX
, sizeY
] = [V
.size
.x
, V
.size
.y
];
163 const shiftX
= color
== "w" ? -1 : 1;
164 const startRank
= color
== "w" ? sizeX
- 2 : 1;
165 const lastRank
= color
== "w" ? 0 : sizeX
- 1;
166 const pawnColor
= this.getColor(x
, y
); //can be checkered
169 x
+ shiftX
== lastRank
170 ? [V
.ROOK
, V
.KNIGHT
, V
.BISHOP
, V
.QUEEN
]
172 if (this.board
[x
+ shiftX
][y
] == V
.EMPTY
) {
173 // One square forward
174 for (let piece
of finalPieces
) {
176 this.getBasicMove([x
, y
], [x
+ shiftX
, y
], {
184 this.board
[x
+ 2 * shiftX
][y
] == V
.EMPTY
187 moves
.push(this.getBasicMove([x
, y
], [x
+ 2 * shiftX
, y
]));
191 for (let shiftY
of [-1, 1]) {
194 y
+ shiftY
< sizeY
&&
195 this.board
[x
+ shiftX
][y
+ shiftY
] != V
.EMPTY
&&
196 this.canTake([x
, y
], [x
+ shiftX
, y
+ shiftY
])
198 for (let piece
of finalPieces
) {
200 this.getBasicMove([x
, y
], [x
+ shiftX
, y
+ shiftY
], {
210 const Lep
= this.epSquares
.length
;
211 const epSquare
= this.epSquares
[Lep
- 1]; //always at least one element
214 epSquare
.x
== x
+ shiftX
&&
215 Math
.abs(epSquare
.y
- y
) == 1
217 let enpassantMove
= this.getBasicMove([x
, y
], [epSquare
.x
, epSquare
.y
]);
218 enpassantMove
.vanish
.push({
222 c: this.getColor(x
, epSquare
.y
)
224 moves
.push(enpassantMove
);
230 // Same as in base_rules but with an array given to isAttacked:
231 getCastleMoves([x
, y
]) {
232 const c
= this.getColor(x
, y
);
233 if (x
!= (c
== "w" ? V
.size
.x
- 1 : 0) || y
!= this.INIT_COL_KING
[c
])
234 return []; //x isn't first rank, or king has moved (shortcut)
237 const oppCol
= V
.GetOppCol(c
);
241 const finalSquares
= [
243 [V
.size
.y
- 2, V
.size
.y
- 3]
248 castleSide
++ //large, then small
250 if (this.castleFlags
[c
][castleSide
] >= V
.size
.y
) continue;
251 // If this code is reached, rooks and king are on initial position
253 // Nothing on the path of the king ? (and no checks)
254 const finDist
= finalSquares
[castleSide
][0] - y
;
255 let step
= finDist
/ Math
.max(1, Math
.abs(finDist
));
259 this.isAttacked([x
, i
], [oppCol
]) ||
260 (this.board
[x
][i
] != V
.EMPTY
&&
261 // NOTE: next check is enough, because of chessboard constraints
262 (this.getColor(x
, i
) != c
||
263 ![V
.KING
, V
.ROOK
].includes(this.getPiece(x
, i
))))
265 continue castlingCheck
;
268 } while (i
!= finalSquares
[castleSide
][0]);
270 // Nothing on the path to the rook?
271 step
= castleSide
== 0 ? -1 : 1;
272 const rookPos
= this.castleFlags
[c
][castleSide
];
273 for (i
= y
+ step
; i
!= rookPos
; i
+= step
) {
274 if (this.board
[x
][i
] != V
.EMPTY
) continue castlingCheck
;
277 // Nothing on final squares, except maybe king and castling rook?
278 for (i
= 0; i
< 2; i
++) {
280 this.board
[x
][finalSquares
[castleSide
][i
]] != V
.EMPTY
&&
281 this.getPiece(x
, finalSquares
[castleSide
][i
]) != V
.KING
&&
282 finalSquares
[castleSide
][i
] != rookPos
284 continue castlingCheck
;
288 // If this code is reached, castle is valid
292 new PiPo({ x: x
, y: finalSquares
[castleSide
][0], p: V
.KING
, c: c
}),
293 new PiPo({ x: x
, y: finalSquares
[castleSide
][1], p: V
.ROOK
, c: c
})
296 new PiPo({ x: x
, y: y
, p: V
.KING
, c: c
}),
297 new PiPo({ x: x
, y: rookPos
, p: V
.ROOK
, c: c
})
300 Math
.abs(y
- rookPos
) <= 2
301 ? { x: x
, y: rookPos
}
302 : { x: x
, y: y
+ 2 * (castleSide
== 0 ? -1 : 1) }
310 canIplay(side
, [x
, y
]) {
311 return side
== this.turn
&& [side
, "c"].includes(this.getColor(x
, y
));
314 // Does m2 un-do m1 ? (to disallow undoing checkered moves)
315 oppositeMoves(m1
, m2
) {
318 m2
.appear
[0].c
== "c" &&
319 m2
.appear
.length
== 1 &&
320 m2
.vanish
.length
== 1 &&
321 m1
.start
.x
== m2
.end
.x
&&
322 m1
.end
.x
== m2
.start
.x
&&
323 m1
.start
.y
== m2
.end
.y
&&
324 m1
.end
.y
== m2
.start
.y
329 if (moves
.length
== 0) return [];
330 const color
= this.turn
;
331 const L
= this.cmoves
.length
; //at least 1: init from FEN
332 return moves
.filter(m
=> {
333 if (this.oppositeMoves(this.cmoves
[L
- 1], m
)) return false;
335 const res
= !this.underCheck(color
);
342 const oppCol
= V
.GetOppCol(this.turn
);
343 let potentialMoves
= [];
344 for (let i
= 0; i
< V
.size
.x
; i
++) {
345 for (let j
= 0; j
< V
.size
.y
; j
++) {
346 // NOTE: just testing == color isn't enough because of checkred pieces
347 if (this.board
[i
][j
] != V
.EMPTY
&& this.getColor(i
, j
) != oppCol
) {
348 Array
.prototype.push
.apply(
350 this.getPotentialMovesFrom([i
, j
])
355 return this.filterValid(potentialMoves
);
359 const oppCol
= V
.GetOppCol(this.turn
);
360 for (let i
= 0; i
< V
.size
.x
; i
++) {
361 for (let j
= 0; j
< V
.size
.y
; j
++) {
362 // NOTE: just testing == color isn't enough because of checkered pieces
363 if (this.board
[i
][j
] != V
.EMPTY
&& this.getColor(i
, j
) != oppCol
) {
364 const moves
= this.getPotentialMovesFrom([i
, j
]);
365 if (moves
.length
> 0) {
366 for (let k
= 0; k
< moves
.length
; k
++) {
367 if (this.filterValid([moves
[k
]]).length
> 0) return true;
376 // colors: array, generally 'w' and 'c' or 'b' and 'c'
377 isAttacked(sq
, colors
) {
379 this.isAttackedByPawn(sq
, colors
) ||
380 this.isAttackedByRook(sq
, colors
) ||
381 this.isAttackedByKnight(sq
, colors
) ||
382 this.isAttackedByBishop(sq
, colors
) ||
383 this.isAttackedByQueen(sq
, colors
) ||
384 this.isAttackedByKing(sq
, colors
)
388 isAttackedByPawn([x
, y
], colors
) {
389 for (let c
of colors
) {
390 const color
= (c
== "c" ? this.turn : c
);
391 let pawnShift
= color
== "w" ? 1 : -1;
392 if (x
+ pawnShift
>= 0 && x
+ pawnShift
< 8) {
393 for (let i
of [-1, 1]) {
397 this.getPiece(x
+ pawnShift
, y
+ i
) == V
.PAWN
&&
398 this.getColor(x
+ pawnShift
, y
+ i
) == c
408 isAttackedBySlideNJump([x
, y
], colors
, piece
, steps
, oneStep
) {
409 for (let step
of steps
) {
410 let rx
= x
+ step
[0],
412 while (V
.OnBoard(rx
, ry
) && this.board
[rx
][ry
] == V
.EMPTY
&& !oneStep
) {
418 this.getPiece(rx
, ry
) === piece
&&
419 colors
.includes(this.getColor(rx
, ry
))
427 isAttackedByRook(sq
, colors
) {
428 return this.isAttackedBySlideNJump(sq
, colors
, V
.ROOK
, V
.steps
[V
.ROOK
]);
431 isAttackedByKnight(sq
, colors
) {
432 return this.isAttackedBySlideNJump(
441 isAttackedByBishop(sq
, colors
) {
442 return this.isAttackedBySlideNJump(sq
, colors
, V
.BISHOP
, V
.steps
[V
.BISHOP
]);
445 isAttackedByQueen(sq
, colors
) {
446 return this.isAttackedBySlideNJump(
450 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
])
454 isAttackedByKing(sq
, colors
) {
455 return this.isAttackedBySlideNJump(
459 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]),
465 return this.isAttacked(this.kingPos
[color
], [V
.GetOppCol(color
), "c"]);
468 getCheckSquares(color
) {
469 // Artifically change turn, for checkered pawns
470 this.turn
= V
.GetOppCol(color
);
471 const kingAttacked
= this.isAttacked(this.kingPos
[color
], [
475 let res
= kingAttacked
476 ? [JSON
.parse(JSON
.stringify(this.kingPos
[color
]))] //need to duplicate!
483 super.postPlay(move);
484 // Does this move turn off a 2-squares pawn flag?
485 if ([1, 6].includes(move.start
.x
) && move.vanish
[0].p
== V
.PAWN
)
486 this.pawnFlags
[move.start
.x
== 6 ? "w" : "b"][move.start
.y
] = false;
487 this.cmoves
.push(this.getCmove(move));
491 super.postUndo(move);
496 if (this.atLeastOneMove())
500 const color
= this.turn
;
501 // Artifically change turn, for checkered pawns
502 this.turn
= V
.GetOppCol(this.turn
);
503 const res
= this.isAttacked(this.kingPos
[color
], [V
.GetOppCol(color
), "c"])
508 this.turn
= V
.GetOppCol(this.turn
);
514 // Just count material for now, considering checkered neutral (...)
515 for (let i
= 0; i
< V
.size
.x
; i
++) {
516 for (let j
= 0; j
< V
.size
.y
; j
++) {
517 if (this.board
[i
][j
] != V
.EMPTY
) {
518 const sqColor
= this.getColor(i
, j
);
519 if (["w","b"].includes(sqColor
)) {
520 const sign
= sqColor
== "w" ? 1 : -1;
521 evaluation
+= sign
* V
.VALUES
[this.getPiece(i
, j
)];
529 static GenRandInitFen(randomness
) {
530 // Add 16 pawns flags + empty cmove:
531 return ChessRules
.GenRandInitFen(randomness
)
532 .slice(0, -2) + "1111111111111111 - -";
535 static ParseFen(fen
) {
536 return Object
.assign({}, ChessRules
.ParseFen(fen
), {
537 cmove: fen
.split(" ")[5]
542 const L
= this.cmoves
.length
;
543 const cmoveFen
= !this.cmoves
[L
- 1]
545 : ChessRules
.CoordsToSquare(this.cmoves
[L
- 1].start
) +
546 ChessRules
.CoordsToSquare(this.cmoves
[L
- 1].end
);
547 return super.getFen() + " " + cmoveFen
;
551 let fen
= super.getFlagsFen();
553 for (let c
of ["w", "b"]) {
554 for (let i
= 0; i
< 8; i
++) fen
+= this.pawnFlags
[c
][i
] ? "1" : "0";
559 static get SEARCH_DEPTH() {
564 if (move.appear
.length
== 2) {
566 if (move.end
.y
< move.start
.y
) return "0-0-0";
570 // Translate final square
571 const finalSquare
= V
.CoordsToSquare(move.end
);
573 const piece
= this.getPiece(move.start
.x
, move.start
.y
);
574 if (piece
== V
.PAWN
) {
577 if (move.vanish
.length
> 1) {
579 const startColumn
= V
.CoordToColumn(move.start
.y
);
585 move.appear
[0].p
.toUpperCase();
588 notation
= finalSquare
;
589 if (move.appear
.length
> 0 && piece
!= move.appear
[0].p
)
591 notation
+= "=" + move.appear
[0].p
.toUpperCase();
597 piece
.toUpperCase() +
598 (move.vanish
.length
> 1 ? "x" : "") +
600 (move.vanish
.length
> 1 ? "=" + move.appear
[0].p
.toUpperCase() : "")