1 import { ChessRules
, Move
, PiPo
} from "@/base_rules";
3 export class Checkered1Rules
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))
55 // Stage 1: as Checkered2. Stage 2: checkered pieces are autonomous
56 const stageInfo
= V
.ParseFen(fen
).stage
;
57 this.stage
= parseInt(stageInfo
[0], 10);
58 this.sideCheckered
= (this.stage
== 2 ? stageInfo
[1] : undefined);
61 static IsGoodFen(fen
) {
62 if (!ChessRules
.IsGoodFen(fen
)) return false;
63 const fenParts
= fen
.split(" ");
64 if (fenParts
.length
!= 7) return false;
65 if (fenParts
[5] != "-" && !fenParts
[5].match(/^([a-h][1-8]){2}$/))
67 if (!fenParts
[6].match(/^[12][wb]?$/)) return false;
71 static IsGoodFlags(flags
) {
72 // 4 for castle + 16 for pawns
73 return !!flags
.match(/^[a-z]{4,4}[01]{16,16}$/);
77 super.setFlags(fenflags
); //castleFlags
79 w: [...Array(8)], //pawns can move 2 squares?
82 const flags
= fenflags
.substr(4); //skip first 4 letters, for castle
83 for (let c
of ["w", "b"]) {
84 for (let i
= 0; i
< 8; i
++)
85 this.pawnFlags
[c
][i
] = flags
.charAt((c
== "w" ? 0 : 8) + i
) == "1";
90 return [this.castleFlags
, this.pawnFlags
];
93 disaggregateFlags(flags
) {
94 this.castleFlags
= flags
[0];
95 this.pawnFlags
= flags
[1];
98 getEpSquare(moveOrSquare
) {
99 // At stage 2, all pawns can be captured en-passant
102 typeof moveOrSquare
!== "object" ||
103 (moveOrSquare
.appear
.length
> 0 && moveOrSquare
.appear
[0].c
!= 'c')
105 return super.getEpSquare(moveOrSquare
);
106 // Checkered or switch move: no en-passant
111 // No checkered move to undo at stage 2:
112 if (this.stage
== 1 && move.vanish
.length
== 1 && move.appear
[0].c
== "c")
113 return { start: move.start
, end: move.end
};
117 canTake([x1
, y1
], [x2
, y2
]) {
118 const color1
= this.getColor(x1
, y1
);
119 const color2
= this.getColor(x2
, y2
);
120 if (this.stage
== 2) {
121 // Black & White <-- takes --> Checkered
122 const color1
= this.getColor(x1
, y1
);
123 const color2
= this.getColor(x2
, y2
);
124 return color1
!= color2
&& [color1
, color2
].includes('c');
126 // Checkered aren't captured
130 (color1
!= "c" || color2
!= this.turn
)
134 getPotentialMovesFrom([x
, y
]) {
135 let standardMoves
= super.getPotentialMovesFrom([x
, y
]);
136 if (this.stage
== 1) {
137 const color
= this.turn
;
138 // Post-processing: apply "checkerization" of standard moves
139 const lastRank
= (color
== "w" ? 0 : 7);
141 // King is treated differently: it never turn checkered
142 if (this.getPiece(x
, y
) == V
.KING
) {
143 // If at least one checkered piece, allow switching:
144 if (this.board
.some(b
=> b
.some(cell
=> cell
[0] == 'c'))) {
145 const oppCol
= V
.GetOppCol(color
);
148 start: { x: x
, y: y
},
149 end: { x: this.kingPos
[oppCol
][0], y: this.kingPos
[oppCol
][1] },
155 return standardMoves
.concat(moves
);
157 standardMoves
.forEach(m
=> {
158 if (m
.vanish
[0].p
== V
.PAWN
) {
160 Math
.abs(m
.end
.x
- m
.start
.x
) == 2 &&
161 !this.pawnFlags
[this.turn
][m
.start
.y
]
163 return; //skip forbidden 2-squares jumps
166 this.board
[m
.end
.x
][m
.end
.y
] == V
.EMPTY
&&
167 m
.vanish
.length
== 2 &&
168 this.getColor(m
.start
.x
, m
.start
.y
) == "c"
170 return; //checkered pawns cannot take en-passant
173 if (m
.vanish
.length
== 1)
177 // A capture occured (m.vanish.length == 2)
181 // Avoid promotions (already treated):
182 m
.appear
[0].p
!= m
.vanish
[1].p
&&
183 (m
.vanish
[0].p
!= V
.PAWN
|| m
.end
.x
!= lastRank
)
185 // Add transformation into captured piece
186 let m2
= JSON
.parse(JSON
.stringify(m
));
187 m2
.appear
[0].p
= m
.vanish
[1].p
;
194 return standardMoves
;
197 getPotentialPawnMoves([x
, y
]) {
198 const color
= this.getColor(x
, y
);
199 if (this.stage
== 2) {
200 const saveTurn
= this.turn
;
201 if (this.sideCheckered
== this.turn
) {
202 // Cannot change PawnSpecs.bidirectional, so cheat a little:
204 const wMoves
= super.getPotentialPawnMoves([x
, y
]);
206 const bMoves
= super.getPotentialPawnMoves([x
, y
]);
207 this.turn
= saveTurn
;
208 return wMoves
.concat(bMoves
);
210 // Playing with both colors:
212 const moves
= super.getPotentialPawnMoves([x
, y
]);
213 this.turn
= saveTurn
;
216 let moves
= super.getPotentialPawnMoves([x
, y
]);
217 // Post-process: set right color for checkered moves
220 m
.appear
[0].c
= 'c'; //may be done twice if capture
227 canIplay(side
, [x
, y
]) {
228 if (this.stage
== 2) {
229 const color
= this.getColor(x
, y
);
231 this.turn
== this.sideCheckered
233 : ['w', 'b'].includes(color
)
236 return side
== this.turn
&& [side
, "c"].includes(this.getColor(x
, y
));
239 // Does m2 un-do m1 ? (to disallow undoing checkered moves)
240 oppositeMoves(m1
, m2
) {
243 m2
.appear
[0].c
== "c" &&
244 m2
.appear
.length
== 1 &&
245 m2
.vanish
.length
== 1 &&
246 m1
.start
.x
== m2
.end
.x
&&
247 m1
.end
.x
== m2
.start
.x
&&
248 m1
.start
.y
== m2
.end
.y
&&
249 m1
.end
.y
== m2
.start
.y
254 if (moves
.length
== 0) return [];
255 const color
= this.turn
;
256 const oppCol
= V
.GetOppCol(color
);
257 const L
= this.cmoves
.length
; //at least 1: init from FEN
258 const stage
= this.stage
; //may change if switch
259 return moves
.filter(m
=> {
260 // Checkered cannot be under check (no king)
261 if (stage
== 2 && this.sideCheckered
== color
) return true;
265 if (m
.appear
.length
== 0 && m
.vanish
.length
== 0) {
266 // Special "switch" move: kings must not be attacked by checkered.
267 // Not checking for oppositeMoves here: checkered are autonomous
269 !this.isAttacked(this.kingPos
['w'], ['c']) &&
270 !this.isAttacked(this.kingPos
['b'], ['c']) &&
271 this.getAllPotentialMoves().length
> 0
274 else res
= !this.oppositeMoves(this.cmoves
[L
- 1], m
);
276 if (res
&& m
.appear
.length
> 0) res
= !this.underCheck(color
);
277 // At stage 2, side with B & W can be undercheck with both kings:
278 if (res
&& stage
== 2) res
= !this.underCheck(oppCol
);
284 getAllPotentialMoves() {
285 const color
= this.turn
;
286 const oppCol
= V
.GetOppCol(color
);
287 let potentialMoves
= [];
288 for (let i
= 0; i
< V
.size
.x
; i
++) {
289 for (let j
= 0; j
< V
.size
.y
; j
++) {
290 const colIJ
= this.getColor(i
, j
);
292 this.board
[i
][j
] != V
.EMPTY
&&
294 (this.stage
== 1 && colIJ
!= oppCol
) ||
297 (this.sideCheckered
== color
&& colIJ
== 'c') ||
298 (this.sideCheckered
!= color
&& ['w', 'b'].includes(colIJ
))
303 Array
.prototype.push
.apply(
305 this.getPotentialMovesFrom([i
, j
])
310 return potentialMoves
;
314 const color
= this.turn
;
315 const oppCol
= V
.GetOppCol(color
);
316 for (let i
= 0; i
< V
.size
.x
; i
++) {
317 for (let j
= 0; j
< V
.size
.y
; j
++) {
318 const colIJ
= this.getColor(i
, j
);
320 this.board
[i
][j
] != V
.EMPTY
&&
322 (this.stage
== 1 && colIJ
!= oppCol
) ||
325 (this.sideCheckered
== color
&& colIJ
== 'c') ||
326 (this.sideCheckered
!= color
&& ['w', 'b'].includes(colIJ
))
331 const moves
= this.getPotentialMovesFrom([i
, j
]);
332 if (moves
.length
> 0) {
333 for (let k
= 0; k
< moves
.length
; k
++)
334 if (this.filterValid([moves
[k
]]).length
> 0) return true;
342 // colors: array, 'w' and 'c' or 'b' and 'c' at stage 1,
343 // just 'c' (or unused) at stage 2
344 isAttacked(sq
, colors
) {
345 if (!Array
.isArray(colors
)) colors
= [colors
];
347 this.isAttackedByPawn(sq
, colors
) ||
348 this.isAttackedByRook(sq
, colors
) ||
349 this.isAttackedByKnight(sq
, colors
) ||
350 this.isAttackedByBishop(sq
, colors
) ||
351 this.isAttackedByQueen(sq
, colors
) ||
352 this.isAttackedByKing(sq
, colors
)
356 isAttackedByPawn([x
, y
], colors
) {
357 for (let c
of colors
) {
359 if (this.stage
== 1) {
360 const color
= (c
== "c" ? this.turn : c
);
361 shifts
= [color
== "w" ? 1 : -1];
364 // Stage 2: checkered pawns are bidirectional
365 if (c
== 'c') shifts
= [-1, 1];
366 else shifts
= [c
== "w" ? 1 : -1];
368 for (let pawnShift
of shifts
) {
369 if (x
+ pawnShift
>= 0 && x
+ pawnShift
< 8) {
370 for (let i
of [-1, 1]) {
374 this.getPiece(x
+ pawnShift
, y
+ i
) == V
.PAWN
&&
375 this.getColor(x
+ pawnShift
, y
+ i
) == c
386 isAttackedBySlideNJump([x
, y
], colors
, piece
, steps
, oneStep
) {
387 for (let step
of steps
) {
388 let rx
= x
+ step
[0],
390 while (V
.OnBoard(rx
, ry
) && this.board
[rx
][ry
] == V
.EMPTY
&& !oneStep
) {
396 this.getPiece(rx
, ry
) === piece
&&
397 colors
.includes(this.getColor(rx
, ry
))
405 isAttackedByRook(sq
, colors
) {
406 return this.isAttackedBySlideNJump(sq
, colors
, V
.ROOK
, V
.steps
[V
.ROOK
]);
409 isAttackedByKnight(sq
, colors
) {
410 return this.isAttackedBySlideNJump(
419 isAttackedByBishop(sq
, colors
) {
420 return this.isAttackedBySlideNJump(
421 sq
, colors
, V
.BISHOP
, V
.steps
[V
.BISHOP
]);
424 isAttackedByQueen(sq
, colors
) {
425 return this.isAttackedBySlideNJump(
429 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
])
433 isAttackedByKing(sq
, colors
) {
434 return this.isAttackedBySlideNJump(
438 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]),
445 return this.isAttacked(this.kingPos
[color
], [V
.GetOppCol(color
), "c"]);
446 if (color
== this.sideCheckered
) return false;
448 this.isAttacked(this.kingPos
['w'], ["c"]) ||
449 this.isAttacked(this.kingPos
['b'], ["c"])
454 const color
= this.turn
;
455 if (this.stage
== 1) {
456 // Artifically change turn, for checkered pawns
457 this.turn
= V
.GetOppCol(color
);
461 [V
.GetOppCol(color
), "c"]
463 let res
= kingAttacked
464 ? [JSON
.parse(JSON
.stringify(this.kingPos
[color
]))]
469 if (this.sideCheckered
== color
) return [];
471 for (let c
of ['w', 'b']) {
472 if (this.isAttacked(this.kingPos
[c
], ['c']))
473 res
.push(JSON
.parse(JSON
.stringify(this.kingPos
[c
])));
479 move.flags
= JSON
.stringify(this.aggregateFlags());
480 this.epSquares
.push(this.getEpSquare(move));
481 V
.PlayOnBoard(this.board
, move);
482 if (move.appear
.length
> 0 || move.vanish
.length
> 0)
484 this.turn
= V
.GetOppCol(this.turn
);
490 updateCastleFlags(move, piece
) {
491 const c
= V
.GetOppCol(this.turn
);
492 const firstRank
= (c
== "w" ? V
.size
.x
- 1 : 0);
493 // Update castling flags if rooks are moved
494 const oppCol
= this.turn
;
495 const oppFirstRank
= V
.size
.x
- 1 - firstRank
;
496 if (piece
== V
.KING
&& move.appear
.length
> 0)
497 this.castleFlags
[c
] = [V
.size
.y
, V
.size
.y
];
499 move.start
.x
== firstRank
&& //our rook moves?
500 this.castleFlags
[c
].includes(move.start
.y
)
502 const flagIdx
= (move.start
.y
== this.castleFlags
[c
][0] ? 0 : 1);
503 this.castleFlags
[c
][flagIdx
] = V
.size
.y
;
505 // NOTE: not "else if" because a rook could take an opposing rook
507 move.end
.x
== oppFirstRank
&& //we took opponent rook?
508 this.castleFlags
[oppCol
].includes(move.end
.y
)
510 const flagIdx
= (move.end
.y
== this.castleFlags
[oppCol
][0] ? 0 : 1);
511 this.castleFlags
[oppCol
][flagIdx
] = V
.size
.y
;
516 if (move.appear
.length
== 0 && move.vanish
.length
== 0) {
518 this.sideCheckered
= this.turn
;
521 const c
= move.vanish
[0].c
;
522 const piece
= move.vanish
[0].p
;
523 if (piece
== V
.KING
) {
524 this.kingPos
[c
][0] = move.appear
[0].x
;
525 this.kingPos
[c
][1] = move.appear
[0].y
;
527 this.updateCastleFlags(move, piece
);
528 // Does this move turn off a 2-squares pawn flag?
529 if ([1, 6].includes(move.start
.x
) && move.vanish
[0].p
== V
.PAWN
)
530 this.pawnFlags
[move.start
.x
== 6 ? "w" : "b"][move.start
.y
] = false;
532 this.cmoves
.push(this.getCmove(move));
536 this.epSquares
.pop();
537 this.disaggregateFlags(JSON
.parse(move.flags
));
538 V
.UndoOnBoard(this.board
, move);
539 if (move.appear
.length
> 0 || move.vanish
.length
> 0)
541 this.turn
= V
.GetOppCol(this.turn
);
548 if (move.appear
.length
== 0 && move.vanish
.length
== 0) this.stage
= 1;
549 else super.postUndo(move);
554 const color
= this.turn
;
555 if (this.stage
== 1) {
556 if (this.atLeastOneMove()) return "*";
557 // Artifically change turn, for checkered pawns
558 this.turn
= V
.GetOppCol(this.turn
);
560 this.isAttacked(this.kingPos
[color
], [V
.GetOppCol(color
), "c"])
565 this.turn
= V
.GetOppCol(this.turn
);
569 if (this.sideCheckered
== this.turn
) {
570 // Check if remaining checkered pieces: if none, I lost
571 if (this.board
.some(b
=> b
.some(cell
=> cell
[0] == 'c'))) {
572 if (!this.atLeastOneMove()) return "1/2";
575 return color
== 'w' ? "0-1" : "1-0";
577 if (this.atLeastOneMove()) return "*";
578 let res
= this.isAttacked(this.kingPos
['w'], ["c"]);
579 if (!res
) res
= this.isAttacked(this.kingPos
['b'], ["c"]);
580 if (res
) return color
== 'w' ? "0-1" : "1-0";
586 // Just count material for now, considering checkered neutral at stage 1.
587 const baseSign
= (this.turn
== 'w' ? 1 : -1);
588 for (let i
= 0; i
< V
.size
.x
; i
++) {
589 for (let j
= 0; j
< V
.size
.y
; j
++) {
590 if (this.board
[i
][j
] != V
.EMPTY
) {
591 const sqColor
= this.getColor(i
, j
);
592 if (this.stage
== 1) {
593 if (["w", "b"].includes(sqColor
)) {
594 const sign
= sqColor
== "w" ? 1 : -1;
595 evaluation
+= sign
* V
.VALUES
[this.getPiece(i
, j
)];
600 this.sideCheckered
== this.turn
601 ? (sqColor
== 'c' ? 1 : -1) * baseSign
602 : (sqColor
== 'c' ? -1 : 1) * baseSign
;
603 evaluation
+= sign
* V
.VALUES
[this.getPiece(i
, j
)];
611 static GenRandInitFen(randomness
) {
612 // Add 16 pawns flags + empty cmovei + stage == 1:
613 return ChessRules
.GenRandInitFen(randomness
)
614 .slice(0, -2) + "1111111111111111 - - 1";
617 static ParseFen(fen
) {
618 const fenParts
= fen
.split(" ");
619 return Object
.assign(
620 ChessRules
.ParseFen(fen
),
629 const L
= this.cmoves
.length
;
633 : ChessRules
.CoordsToSquare(this.cmoves
[L
- 1].start
) +
634 ChessRules
.CoordsToSquare(this.cmoves
[L
- 1].end
)
639 return (this.stage
== 1 ? "1" : "2" + this.sideCheckered
);
644 super.getFen() + " " + this.getCmoveFen() + " " + this.getStageFen()
650 super.getFenForRepeat() + "_" +
651 this.getCmoveFen() + "_" + this.getStageFen()
656 let fen
= super.getFlagsFen();
658 for (let c
of ["w", "b"])
659 for (let i
= 0; i
< 8; i
++) fen
+= (this.pawnFlags
[c
][i
] ? "1" : "0");
663 static get SEARCH_DEPTH() {
668 // To simplify, prevent the bot from switching (TODO...)
670 super.getComputerMove(
671 this.getAllValidMoves().filter(m
=> m
.appear
.length
> 0)
677 if (move.appear
.length
== 0 && move.vanish
.length
== 0) return "S";
678 if (move.appear
.length
== 2) {
680 if (move.end
.y
< move.start
.y
) return "0-0-0";
684 const finalSquare
= V
.CoordsToSquare(move.end
);
685 const piece
= this.getPiece(move.start
.x
, move.start
.y
);
687 if (piece
== V
.PAWN
) {
688 if (move.vanish
.length
> 1) {
689 const startColumn
= V
.CoordToColumn(move.start
.y
);
690 notation
= startColumn
+ "x" + finalSquare
;
691 } else notation
= finalSquare
;
695 piece
.toUpperCase() +
696 (move.vanish
.length
> 1 ? "x" : "") +
699 if (move.appear
[0].p
!= move.vanish
[0].p
)
700 notation
+= "=" + move.appear
[0].p
.toUpperCase();