1 import { ChessRules
, Move
, PiPo
} from "@/base_rules";
3 export class CheckeredRules
extends ChessRules
{
21 static AbbreviateOptions(opts
) {
22 return (!opts
["switch"] ? "NS" : "");
26 const checkered_codes
= {
33 if (b
[0] == "c") return checkered_codes
[b
[1]];
34 return ChessRules
.board2fen(b
);
38 // Tolerate upper-case versions of checkered pieces (why not?)
39 const checkered_pieces
= {
51 if (Object
.keys(checkered_pieces
).includes(f
))
52 return "c" + checkered_pieces
[f
];
53 return ChessRules
.fen2board(f
);
57 return ChessRules
.PIECES
.concat(["s", "t", "u", "c", "o"]);
61 return (b
[0] == "c" ? "Checkered/" : "") + b
;
64 setOtherVariables(fen
) {
65 super.setOtherVariables(fen
);
66 // Local stack of non-capturing checkered moves:
68 const cmove
= V
.ParseFen(fen
).cmove
;
69 if (cmove
== "-") this.cmoves
.push(null);
72 start: ChessRules
.SquareToCoords(cmove
.substr(0, 2)),
73 end: ChessRules
.SquareToCoords(cmove
.substr(2))
76 // Stage 1: as Checkered2. Stage 2: checkered pieces are autonomous
77 const stageInfo
= V
.ParseFen(fen
).stage
;
78 this.stage
= parseInt(stageInfo
[0], 10);
79 this.canSwitch
= (this.stage
== 1 && stageInfo
[1] != '-');
80 this.sideCheckered
= (this.stage
== 2 ? stageInfo
[1] : undefined);
83 static IsGoodFen(fen
) {
84 if (!ChessRules
.IsGoodFen(fen
)) return false;
85 const fenParts
= fen
.split(" ");
86 if (fenParts
.length
!= 7) return false;
87 if (fenParts
[5] != "-" && !fenParts
[5].match(/^([a-h][1-8]){2}$/))
89 if (!fenParts
[6].match(/^[12][wb-]?$/)) return false;
93 static IsGoodFlags(flags
) {
94 // 4 for castle + 16 for pawns
95 return !!flags
.match(/^[a-z]{4,4}[01]{16,16}$/);
99 super.setFlags(fenflags
); //castleFlags
101 w: [...Array(8)], //pawns can move 2 squares?
104 const flags
= fenflags
.substr(4); //skip first 4 letters, for castle
105 for (let c
of ["w", "b"]) {
106 for (let i
= 0; i
< 8; i
++)
107 this.pawnFlags
[c
][i
] = flags
.charAt((c
== "w" ? 0 : 8) + i
) == "1";
112 return [this.castleFlags
, this.pawnFlags
];
115 disaggregateFlags(flags
) {
116 this.castleFlags
= flags
[0];
117 this.pawnFlags
= flags
[1];
120 getEpSquare(moveOrSquare
) {
121 // At stage 2, all pawns can be captured en-passant
124 typeof moveOrSquare
!== "object" ||
125 (moveOrSquare
.appear
.length
> 0 && moveOrSquare
.appear
[0].c
!= 'c')
127 return super.getEpSquare(moveOrSquare
);
128 // Checkered or switch move: no en-passant
133 // No checkered move to undo at stage 2:
134 if (this.stage
== 1 && move.vanish
.length
== 1 && move.appear
[0].c
== "c")
135 return { start: move.start
, end: move.end
};
139 canTake([x1
, y1
], [x2
, y2
]) {
140 const color1
= this.getColor(x1
, y1
);
141 const color2
= this.getColor(x2
, y2
);
142 if (this.stage
== 2) {
143 // Black & White <-- takes --> Checkered
144 const color1
= this.getColor(x1
, y1
);
145 const color2
= this.getColor(x2
, y2
);
146 return color1
!= color2
&& [color1
, color2
].includes('c');
148 // Checkered aren't captured
152 (color1
!= "c" || color2
!= this.turn
)
156 getPotentialMovesFrom([x
, y
], noswitch
) {
157 let standardMoves
= super.getPotentialMovesFrom([x
, y
]);
158 if (this.stage
== 1) {
159 const color
= this.turn
;
160 // Post-processing: apply "checkerization" of standard moves
161 const lastRank
= (color
== "w" ? 0 : 7);
163 // King is treated differently: it never turn checkered
164 if (this.getPiece(x
, y
) == V
.KING
) {
165 // If at least one checkered piece, allow switching:
167 this.canSwitch
&& !noswitch
&&
168 this.board
.some(b
=> b
.some(cell
=> cell
[0] == 'c'))
170 const oppCol
= V
.GetOppCol(color
);
173 start: { x: x
, y: y
},
174 end: { x: this.kingPos
[oppCol
][0], y: this.kingPos
[oppCol
][1] },
180 return standardMoves
.concat(moves
);
182 standardMoves
.forEach(m
=> {
183 if (m
.vanish
[0].p
== V
.PAWN
) {
185 Math
.abs(m
.end
.x
- m
.start
.x
) == 2 &&
186 !this.pawnFlags
[this.turn
][m
.start
.y
]
188 return; //skip forbidden 2-squares jumps
191 this.board
[m
.end
.x
][m
.end
.y
] == V
.EMPTY
&&
192 m
.vanish
.length
== 2 &&
193 this.getColor(m
.start
.x
, m
.start
.y
) == "c"
195 return; //checkered pawns cannot take en-passant
198 if (m
.vanish
.length
== 1)
202 // A capture occured (m.vanish.length == 2)
206 // Avoid promotions (already treated):
207 m
.appear
[0].p
!= m
.vanish
[1].p
&&
208 (m
.vanish
[0].p
!= V
.PAWN
|| m
.end
.x
!= lastRank
)
210 // Add transformation into captured piece
211 let m2
= JSON
.parse(JSON
.stringify(m
));
212 m2
.appear
[0].p
= m
.vanish
[1].p
;
219 return standardMoves
;
222 getPotentialPawnMoves([x
, y
]) {
223 const color
= this.getColor(x
, y
);
224 if (this.stage
== 2) {
225 const saveTurn
= this.turn
;
226 if (this.sideCheckered
== this.turn
) {
227 // Cannot change PawnSpecs.bidirectional, so cheat a little:
229 const wMoves
= super.getPotentialPawnMoves([x
, y
]);
231 const bMoves
= super.getPotentialPawnMoves([x
, y
]);
232 this.turn
= saveTurn
;
233 return wMoves
.concat(bMoves
);
235 // Playing with both colors:
237 const moves
= super.getPotentialPawnMoves([x
, y
]);
238 this.turn
= saveTurn
;
241 let moves
= super.getPotentialPawnMoves([x
, y
]);
242 // Post-process: set right color for checkered moves
245 m
.appear
[0].c
= 'c'; //may be done twice if capture
252 canIplay(side
, [x
, y
]) {
253 if (this.stage
== 2) {
254 const color
= this.getColor(x
, y
);
256 this.turn
== this.sideCheckered
258 : ['w', 'b'].includes(color
)
261 return side
== this.turn
&& [side
, "c"].includes(this.getColor(x
, y
));
264 // Does m2 un-do m1 ? (to disallow undoing checkered moves)
265 oppositeMoves(m1
, m2
) {
268 m2
.appear
[0].c
== "c" &&
269 m2
.appear
.length
== 1 &&
270 m2
.vanish
.length
== 1 &&
271 m1
.start
.x
== m2
.end
.x
&&
272 m1
.end
.x
== m2
.start
.x
&&
273 m1
.start
.y
== m2
.end
.y
&&
274 m1
.end
.y
== m2
.start
.y
279 if (moves
.length
== 0) return [];
280 const color
= this.turn
;
281 const oppCol
= V
.GetOppCol(color
);
282 const L
= this.cmoves
.length
; //at least 1: init from FEN
283 const stage
= this.stage
; //may change if switch
284 return moves
.filter(m
=> {
285 // Checkered cannot be under check (no king)
286 if (stage
== 2 && this.sideCheckered
== color
) return true;
290 if (m
.appear
.length
== 0 && m
.vanish
.length
== 0) {
291 // Special "switch" move: kings must not be attacked by checkered.
292 // Not checking for oppositeMoves here: checkered are autonomous
294 !this.isAttacked(this.kingPos
['w'], ['c']) &&
295 !this.isAttacked(this.kingPos
['b'], ['c']) &&
296 this.getAllPotentialMoves().length
> 0
299 else res
= !this.oppositeMoves(this.cmoves
[L
- 1], m
);
301 if (res
&& m
.appear
.length
> 0) res
= !this.underCheck(color
);
302 // At stage 2, side with B & W can be undercheck with both kings:
303 if (res
&& stage
== 2) res
= !this.underCheck(oppCol
);
309 getAllPotentialMoves() {
310 const color
= this.turn
;
311 const oppCol
= V
.GetOppCol(color
);
312 let potentialMoves
= [];
313 for (let i
= 0; i
< V
.size
.x
; i
++) {
314 for (let j
= 0; j
< V
.size
.y
; j
++) {
315 const colIJ
= this.getColor(i
, j
);
317 this.board
[i
][j
] != V
.EMPTY
&&
319 (this.stage
== 1 && colIJ
!= oppCol
) ||
322 (this.sideCheckered
== color
&& colIJ
== 'c') ||
323 (this.sideCheckered
!= color
&& ['w', 'b'].includes(colIJ
))
328 Array
.prototype.push
.apply(
330 this.getPotentialMovesFrom([i
, j
])
335 return potentialMoves
;
339 const color
= this.turn
;
340 const oppCol
= V
.GetOppCol(color
);
341 for (let i
= 0; i
< V
.size
.x
; i
++) {
342 for (let j
= 0; j
< V
.size
.y
; j
++) {
343 const colIJ
= this.getColor(i
, j
);
345 this.board
[i
][j
] != V
.EMPTY
&&
347 (this.stage
== 1 && colIJ
!= oppCol
) ||
350 (this.sideCheckered
== color
&& colIJ
== 'c') ||
351 (this.sideCheckered
!= color
&& ['w', 'b'].includes(colIJ
))
356 const moves
= this.getPotentialMovesFrom([i
, j
], "noswitch");
357 if (moves
.length
> 0) {
358 for (let k
= 0; k
< moves
.length
; k
++)
359 if (this.filterValid([moves
[k
]]).length
> 0) return true;
367 // colors: array, 'w' and 'c' or 'b' and 'c' at stage 1,
368 // just 'c' (or unused) at stage 2
369 isAttacked(sq
, colors
) {
370 if (!Array
.isArray(colors
)) colors
= [colors
];
372 this.isAttackedByPawn(sq
, colors
) ||
373 this.isAttackedByRook(sq
, colors
) ||
374 this.isAttackedByKnight(sq
, colors
) ||
375 this.isAttackedByBishop(sq
, colors
) ||
376 this.isAttackedByQueen(sq
, colors
) ||
377 this.isAttackedByKing(sq
, colors
)
381 isAttackedByPawn([x
, y
], colors
) {
382 for (let c
of colors
) {
384 if (this.stage
== 1) {
385 const color
= (c
== "c" ? this.turn : c
);
386 shifts
= [color
== "w" ? 1 : -1];
389 // Stage 2: checkered pawns are bidirectional
390 if (c
== 'c') shifts
= [-1, 1];
391 else shifts
= [c
== "w" ? 1 : -1];
393 for (let pawnShift
of shifts
) {
394 if (x
+ pawnShift
>= 0 && x
+ pawnShift
< 8) {
395 for (let i
of [-1, 1]) {
399 this.getPiece(x
+ pawnShift
, y
+ i
) == V
.PAWN
&&
400 this.getColor(x
+ pawnShift
, y
+ i
) == c
411 isAttackedBySlideNJump([x
, y
], colors
, piece
, steps
, oneStep
) {
412 for (let step
of steps
) {
413 let rx
= x
+ step
[0],
415 while (V
.OnBoard(rx
, ry
) && this.board
[rx
][ry
] == V
.EMPTY
&& !oneStep
) {
421 this.getPiece(rx
, ry
) === piece
&&
422 colors
.includes(this.getColor(rx
, ry
))
430 isAttackedByRook(sq
, colors
) {
431 return this.isAttackedBySlideNJump(sq
, colors
, V
.ROOK
, V
.steps
[V
.ROOK
]);
434 isAttackedByKnight(sq
, colors
) {
435 return this.isAttackedBySlideNJump(
436 sq
, colors
, V
.KNIGHT
, V
.steps
[V
.KNIGHT
], "oneStep");
439 isAttackedByBishop(sq
, colors
) {
440 return this.isAttackedBySlideNJump(
441 sq
, colors
, V
.BISHOP
, V
.steps
[V
.BISHOP
]);
444 isAttackedByQueen(sq
, colors
) {
445 return this.isAttackedBySlideNJump(
446 sq
, colors
, V
.QUEEN
, V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]));
449 isAttackedByKing(sq
, colors
) {
450 return this.isAttackedBySlideNJump(
452 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]), "oneStep"
458 return this.isAttacked(this.kingPos
[color
], [V
.GetOppCol(color
), "c"]);
459 if (color
== this.sideCheckered
) return false;
461 this.isAttacked(this.kingPos
['w'], ["c"]) ||
462 this.isAttacked(this.kingPos
['b'], ["c"])
467 const color
= this.turn
;
468 if (this.stage
== 1) {
469 // Artifically change turn, for checkered pawns
470 this.turn
= V
.GetOppCol(color
);
474 [V
.GetOppCol(color
), "c"]
476 let res
= kingAttacked
477 ? [JSON
.parse(JSON
.stringify(this.kingPos
[color
]))]
482 if (this.sideCheckered
== color
) return [];
484 for (let c
of ['w', 'b']) {
485 if (this.isAttacked(this.kingPos
[c
], ['c']))
486 res
.push(JSON
.parse(JSON
.stringify(this.kingPos
[c
])));
492 move.flags
= JSON
.stringify(this.aggregateFlags());
493 this.epSquares
.push(this.getEpSquare(move));
494 V
.PlayOnBoard(this.board
, move);
495 if (move.appear
.length
> 0 || move.vanish
.length
> 0)
497 this.turn
= V
.GetOppCol(this.turn
);
504 if (move.appear
.length
== 0 && move.vanish
.length
== 0) {
506 this.sideCheckered
= this.turn
;
509 const c
= move.vanish
[0].c
;
510 const piece
= move.vanish
[0].p
;
511 if (piece
== V
.KING
) {
512 this.kingPos
[c
][0] = move.appear
[0].x
;
513 this.kingPos
[c
][1] = move.appear
[0].y
;
515 super.updateCastleFlags(move, piece
);
517 [1, 6].includes(move.start
.x
) &&
518 move.vanish
[0].p
== V
.PAWN
&&
519 Math
.abs(move.end
.x
- move.start
.x
) == 2
521 // This move turns off a 2-squares pawn flag
522 this.pawnFlags
[move.start
.x
== 6 ? "w" : "b"][move.start
.y
] = false;
525 this.cmoves
.push(this.getCmove(move));
529 this.epSquares
.pop();
530 this.disaggregateFlags(JSON
.parse(move.flags
));
531 V
.UndoOnBoard(this.board
, move);
532 if (move.appear
.length
> 0 || move.vanish
.length
> 0)
534 this.turn
= V
.GetOppCol(this.turn
);
541 if (move.appear
.length
== 0 && move.vanish
.length
== 0) this.stage
= 1;
542 else super.postUndo(move);
547 const color
= this.turn
;
548 if (this.stage
== 1) {
549 if (this.atLeastOneMove()) return "*";
550 // Artifically change turn, for checkered pawns
551 this.turn
= V
.GetOppCol(this.turn
);
553 this.isAttacked(this.kingPos
[color
], [V
.GetOppCol(color
), "c"])
558 this.turn
= V
.GetOppCol(this.turn
);
562 if (this.sideCheckered
== this.turn
) {
563 // Check if remaining checkered pieces: if none, I lost
564 if (this.board
.some(b
=> b
.some(cell
=> cell
[0] == 'c'))) {
565 if (!this.atLeastOneMove()) return "1/2";
568 return color
== 'w' ? "0-1" : "1-0";
570 if (this.atLeastOneMove()) return "*";
571 let res
= this.isAttacked(this.kingPos
['w'], ["c"]);
572 if (!res
) res
= this.isAttacked(this.kingPos
['b'], ["c"]);
573 if (res
) return color
== 'w' ? "0-1" : "1-0";
579 // Just count material for now, considering checkered neutral at stage 1.
580 const baseSign
= (this.turn
== 'w' ? 1 : -1);
581 for (let i
= 0; i
< V
.size
.x
; i
++) {
582 for (let j
= 0; j
< V
.size
.y
; j
++) {
583 if (this.board
[i
][j
] != V
.EMPTY
) {
584 const sqColor
= this.getColor(i
, j
);
585 if (this.stage
== 1) {
586 if (["w", "b"].includes(sqColor
)) {
587 const sign
= sqColor
== "w" ? 1 : -1;
588 evaluation
+= sign
* V
.VALUES
[this.getPiece(i
, j
)];
593 this.sideCheckered
== this.turn
594 ? (sqColor
== 'c' ? 1 : -1) * baseSign
595 : (sqColor
== 'c' ? -1 : 1) * baseSign
;
596 evaluation
+= sign
* V
.VALUES
[this.getPiece(i
, j
)];
604 static GenRandInitFen(options
) {
605 const baseFen
= ChessRules
.GenRandInitFen(options
);
607 // Add 16 pawns flags + empty cmove + stage == 1:
608 baseFen
.slice(0, -2) + "1111111111111111 - - 1" +
609 (!options
["switch"] ? '-' : "")
613 static ParseFen(fen
) {
614 const fenParts
= fen
.split(" ");
615 return Object
.assign(
616 ChessRules
.ParseFen(fen
),
625 const L
= this.cmoves
.length
;
629 : ChessRules
.CoordsToSquare(this.cmoves
[L
- 1].start
) +
630 ChessRules
.CoordsToSquare(this.cmoves
[L
- 1].end
)
635 if (this.stage
== 1) return "1" + (!this.canSwitch
? '-' : "");
637 return "2" + this.sideCheckered
;
642 super.getFen() + " " + this.getCmoveFen() + " " + this.getStageFen()
648 super.getFenForRepeat() + "_" +
649 this.getCmoveFen() + "_" + this.getStageFen()
654 let fen
= super.getFlagsFen();
656 for (let c
of ["w", "b"])
657 for (let i
= 0; i
< 8; i
++) fen
+= (this.pawnFlags
[c
][i
] ? "1" : "0");
661 static get SEARCH_DEPTH() {
666 // To simplify, prevent the bot from switching (TODO...)
668 super.getComputerMove(
669 this.getAllValidMoves().filter(m
=> m
.appear
.length
> 0)
675 if (move.appear
.length
== 0 && move.vanish
.length
== 0) return "S";
676 if (move.appear
.length
== 2) {
678 if (move.end
.y
< move.start
.y
) return "0-0-0";
682 const finalSquare
= V
.CoordsToSquare(move.end
);
683 const piece
= this.getPiece(move.start
.x
, move.start
.y
);
685 if (piece
== V
.PAWN
) {
686 if (move.vanish
.length
> 1) {
687 const startColumn
= V
.CoordToColumn(move.start
.y
);
688 notation
= startColumn
+ "x" + finalSquare
;
689 } else notation
= finalSquare
;
693 piece
.toUpperCase() +
694 (move.vanish
.length
> 1 ? "x" : "") +
697 if (move.appear
[0].p
!= move.vanish
[0].p
)
698 notation
+= "=" + move.appear
[0].p
.toUpperCase();