1 import { ChessRules
, Move
, PiPo
} from "@/base_rules";
3 export class Checkered1Rules
extends ChessRules
{
6 const checkered_codes
= {
13 if (b
[0] == "c") return checkered_codes
[b
[1]];
14 return ChessRules
.board2fen(b
);
18 // Tolerate upper-case versions of checkered pieces (why not?)
19 const checkered_pieces
= {
31 if (Object
.keys(checkered_pieces
).includes(f
))
32 return "c" + checkered_pieces
[f
];
33 return ChessRules
.fen2board(f
);
37 return ChessRules
.PIECES
.concat(["s", "t", "u", "c", "o"]);
41 return (b
[0] == "c" ? "Checkered/" : "") + b
;
44 setOtherVariables(fen
) {
45 super.setOtherVariables(fen
);
46 // Local stack of non-capturing checkered moves:
48 const cmove
= V
.ParseFen(fen
).cmove
;
49 if (cmove
== "-") this.cmoves
.push(null);
52 start: ChessRules
.SquareToCoords(cmove
.substr(0, 2)),
53 end: ChessRules
.SquareToCoords(cmove
.substr(2))
56 // Stage 1: as Checkered2. Stage 2: checkered pieces are autonomous
57 const stageInfo
= V
.ParseFen(fen
).stage
;
58 this.stage
= parseInt(stageInfo
[0], 10);
59 this.sideCheckered
= (this.stage
== 2 ? stageInfo
[1] : undefined);
62 static IsGoodFen(fen
) {
63 if (!ChessRules
.IsGoodFen(fen
)) return false;
64 const fenParts
= fen
.split(" ");
65 if (fenParts
.length
!= 7) return false;
66 if (fenParts
[5] != "-" && !fenParts
[5].match(/^([a-h][1-8]){2}$/))
68 if (!fenParts
[6].match(/^[12][wb]?$/)) return false;
72 static IsGoodFlags(flags
) {
73 // 4 for castle + 16 for pawns
74 return !!flags
.match(/^[a-z]{4,4}[01]{16,16}$/);
78 super.setFlags(fenflags
); //castleFlags
80 w: [...Array(8)], //pawns can move 2 squares?
83 const flags
= fenflags
.substr(4); //skip first 4 letters, for castle
84 for (let c
of ["w", "b"]) {
85 for (let i
= 0; i
< 8; i
++)
86 this.pawnFlags
[c
][i
] = flags
.charAt((c
== "w" ? 0 : 8) + i
) == "1";
91 return [this.castleFlags
, this.pawnFlags
];
94 disaggregateFlags(flags
) {
95 this.castleFlags
= flags
[0];
96 this.pawnFlags
= flags
[1];
99 getEpSquare(moveOrSquare
) {
100 // At stage 2, all pawns can be captured en-passant
103 typeof moveOrSquare
!== "object" ||
104 (moveOrSquare
.appear
.length
> 0 && moveOrSquare
.appear
[0].c
!= 'c')
106 return super.getEpSquare(moveOrSquare
);
107 // Checkered or switch move: no en-passant
112 // No checkered move to undo at stage 2:
113 if (this.stage
== 1 && move.vanish
.length
== 1 && move.appear
[0].c
== "c")
114 return { start: move.start
, end: move.end
};
118 canTake([x1
, y1
], [x2
, y2
]) {
119 const color1
= this.getColor(x1
, y1
);
120 const color2
= this.getColor(x2
, y2
);
121 if (this.stage
== 2) {
122 // Black & White <-- takes --> Checkered
123 const color1
= this.getColor(x1
, y1
);
124 const color2
= this.getColor(x2
, y2
);
125 return color1
!= color2
&& [color1
, color2
].includes('c');
127 // Checkered aren't captured
131 (color1
!= "c" || color2
!= this.turn
)
135 getPotentialMovesFrom([x
, y
], noswitch
) {
136 let standardMoves
= super.getPotentialMovesFrom([x
, y
]);
137 if (this.stage
== 1) {
138 const color
= this.turn
;
139 // Post-processing: apply "checkerization" of standard moves
140 const lastRank
= (color
== "w" ? 0 : 7);
142 // King is treated differently: it never turn checkered
143 if (this.getPiece(x
, y
) == V
.KING
) {
144 // If at least one checkered piece, allow switching:
147 this.board
.some(b
=> b
.some(cell
=> cell
[0] == 'c'))
149 const oppCol
= V
.GetOppCol(color
);
152 start: { x: x
, y: y
},
153 end: { x: this.kingPos
[oppCol
][0], y: this.kingPos
[oppCol
][1] },
159 return standardMoves
.concat(moves
);
161 standardMoves
.forEach(m
=> {
162 if (m
.vanish
[0].p
== V
.PAWN
) {
164 Math
.abs(m
.end
.x
- m
.start
.x
) == 2 &&
165 !this.pawnFlags
[this.turn
][m
.start
.y
]
167 return; //skip forbidden 2-squares jumps
170 this.board
[m
.end
.x
][m
.end
.y
] == V
.EMPTY
&&
171 m
.vanish
.length
== 2 &&
172 this.getColor(m
.start
.x
, m
.start
.y
) == "c"
174 return; //checkered pawns cannot take en-passant
177 if (m
.vanish
.length
== 1)
181 // A capture occured (m.vanish.length == 2)
185 // Avoid promotions (already treated):
186 m
.appear
[0].p
!= m
.vanish
[1].p
&&
187 (m
.vanish
[0].p
!= V
.PAWN
|| m
.end
.x
!= lastRank
)
189 // Add transformation into captured piece
190 let m2
= JSON
.parse(JSON
.stringify(m
));
191 m2
.appear
[0].p
= m
.vanish
[1].p
;
198 return standardMoves
;
201 getPotentialPawnMoves([x
, y
]) {
202 const color
= this.getColor(x
, y
);
203 if (this.stage
== 2) {
204 const saveTurn
= this.turn
;
205 if (this.sideCheckered
== this.turn
) {
206 // Cannot change PawnSpecs.bidirectional, so cheat a little:
208 const wMoves
= super.getPotentialPawnMoves([x
, y
]);
210 const bMoves
= super.getPotentialPawnMoves([x
, y
]);
211 this.turn
= saveTurn
;
212 return wMoves
.concat(bMoves
);
214 // Playing with both colors:
216 const moves
= super.getPotentialPawnMoves([x
, y
]);
217 this.turn
= saveTurn
;
220 let moves
= super.getPotentialPawnMoves([x
, y
]);
221 // Post-process: set right color for checkered moves
224 m
.appear
[0].c
= 'c'; //may be done twice if capture
231 canIplay(side
, [x
, y
]) {
232 if (this.stage
== 2) {
233 const color
= this.getColor(x
, y
);
235 this.turn
== this.sideCheckered
237 : ['w', 'b'].includes(color
)
240 return side
== this.turn
&& [side
, "c"].includes(this.getColor(x
, y
));
243 // Does m2 un-do m1 ? (to disallow undoing checkered moves)
244 oppositeMoves(m1
, m2
) {
247 m2
.appear
[0].c
== "c" &&
248 m2
.appear
.length
== 1 &&
249 m2
.vanish
.length
== 1 &&
250 m1
.start
.x
== m2
.end
.x
&&
251 m1
.end
.x
== m2
.start
.x
&&
252 m1
.start
.y
== m2
.end
.y
&&
253 m1
.end
.y
== m2
.start
.y
258 if (moves
.length
== 0) return [];
259 const color
= this.turn
;
260 const oppCol
= V
.GetOppCol(color
);
261 const L
= this.cmoves
.length
; //at least 1: init from FEN
262 const stage
= this.stage
; //may change if switch
263 return moves
.filter(m
=> {
264 // Checkered cannot be under check (no king)
265 if (stage
== 2 && this.sideCheckered
== color
) return true;
269 if (m
.appear
.length
== 0 && m
.vanish
.length
== 0) {
270 // Special "switch" move: kings must not be attacked by checkered.
271 // Not checking for oppositeMoves here: checkered are autonomous
273 !this.isAttacked(this.kingPos
['w'], ['c']) &&
274 !this.isAttacked(this.kingPos
['b'], ['c']) &&
275 this.getAllPotentialMoves().length
> 0
278 else res
= !this.oppositeMoves(this.cmoves
[L
- 1], m
);
280 if (res
&& m
.appear
.length
> 0) res
= !this.underCheck(color
);
281 // At stage 2, side with B & W can be undercheck with both kings:
282 if (res
&& stage
== 2) res
= !this.underCheck(oppCol
);
288 getAllPotentialMoves() {
289 const color
= this.turn
;
290 const oppCol
= V
.GetOppCol(color
);
291 let potentialMoves
= [];
292 for (let i
= 0; i
< V
.size
.x
; i
++) {
293 for (let j
= 0; j
< V
.size
.y
; j
++) {
294 const colIJ
= this.getColor(i
, j
);
296 this.board
[i
][j
] != V
.EMPTY
&&
298 (this.stage
== 1 && colIJ
!= oppCol
) ||
301 (this.sideCheckered
== color
&& colIJ
== 'c') ||
302 (this.sideCheckered
!= color
&& ['w', 'b'].includes(colIJ
))
307 Array
.prototype.push
.apply(
309 this.getPotentialMovesFrom([i
, j
])
314 return potentialMoves
;
318 const color
= this.turn
;
319 const oppCol
= V
.GetOppCol(color
);
320 for (let i
= 0; i
< V
.size
.x
; i
++) {
321 for (let j
= 0; j
< V
.size
.y
; j
++) {
322 const colIJ
= this.getColor(i
, j
);
324 this.board
[i
][j
] != V
.EMPTY
&&
326 (this.stage
== 1 && colIJ
!= oppCol
) ||
329 (this.sideCheckered
== color
&& colIJ
== 'c') ||
330 (this.sideCheckered
!= color
&& ['w', 'b'].includes(colIJ
))
335 const moves
= this.getPotentialMovesFrom([i
, j
], "noswitch");
336 if (moves
.length
> 0) {
337 for (let k
= 0; k
< moves
.length
; k
++)
338 if (this.filterValid([moves
[k
]]).length
> 0) return true;
346 // colors: array, 'w' and 'c' or 'b' and 'c' at stage 1,
347 // just 'c' (or unused) at stage 2
348 isAttacked(sq
, colors
) {
349 if (!Array
.isArray(colors
)) colors
= [colors
];
351 this.isAttackedByPawn(sq
, colors
) ||
352 this.isAttackedByRook(sq
, colors
) ||
353 this.isAttackedByKnight(sq
, colors
) ||
354 this.isAttackedByBishop(sq
, colors
) ||
355 this.isAttackedByQueen(sq
, colors
) ||
356 this.isAttackedByKing(sq
, colors
)
360 isAttackedByPawn([x
, y
], colors
) {
361 for (let c
of colors
) {
363 if (this.stage
== 1) {
364 const color
= (c
== "c" ? this.turn : c
);
365 shifts
= [color
== "w" ? 1 : -1];
368 // Stage 2: checkered pawns are bidirectional
369 if (c
== 'c') shifts
= [-1, 1];
370 else shifts
= [c
== "w" ? 1 : -1];
372 for (let pawnShift
of shifts
) {
373 if (x
+ pawnShift
>= 0 && x
+ pawnShift
< 8) {
374 for (let i
of [-1, 1]) {
378 this.getPiece(x
+ pawnShift
, y
+ i
) == V
.PAWN
&&
379 this.getColor(x
+ pawnShift
, y
+ i
) == c
390 isAttackedBySlideNJump([x
, y
], colors
, piece
, steps
, oneStep
) {
391 for (let step
of steps
) {
392 let rx
= x
+ step
[0],
394 while (V
.OnBoard(rx
, ry
) && this.board
[rx
][ry
] == V
.EMPTY
&& !oneStep
) {
400 this.getPiece(rx
, ry
) === piece
&&
401 colors
.includes(this.getColor(rx
, ry
))
409 isAttackedByRook(sq
, colors
) {
410 return this.isAttackedBySlideNJump(sq
, colors
, V
.ROOK
, V
.steps
[V
.ROOK
]);
413 isAttackedByKnight(sq
, colors
) {
414 return this.isAttackedBySlideNJump(
423 isAttackedByBishop(sq
, colors
) {
424 return this.isAttackedBySlideNJump(
425 sq
, colors
, V
.BISHOP
, V
.steps
[V
.BISHOP
]);
428 isAttackedByQueen(sq
, colors
) {
429 return this.isAttackedBySlideNJump(
433 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
])
437 isAttackedByKing(sq
, colors
) {
438 return this.isAttackedBySlideNJump(
442 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]),
449 return this.isAttacked(this.kingPos
[color
], [V
.GetOppCol(color
), "c"]);
450 if (color
== this.sideCheckered
) return false;
452 this.isAttacked(this.kingPos
['w'], ["c"]) ||
453 this.isAttacked(this.kingPos
['b'], ["c"])
458 const color
= this.turn
;
459 if (this.stage
== 1) {
460 // Artifically change turn, for checkered pawns
461 this.turn
= V
.GetOppCol(color
);
465 [V
.GetOppCol(color
), "c"]
467 let res
= kingAttacked
468 ? [JSON
.parse(JSON
.stringify(this.kingPos
[color
]))]
473 if (this.sideCheckered
== color
) return [];
475 for (let c
of ['w', 'b']) {
476 if (this.isAttacked(this.kingPos
[c
], ['c']))
477 res
.push(JSON
.parse(JSON
.stringify(this.kingPos
[c
])));
483 move.flags
= JSON
.stringify(this.aggregateFlags());
484 this.epSquares
.push(this.getEpSquare(move));
485 V
.PlayOnBoard(this.board
, move);
486 if (move.appear
.length
> 0 || move.vanish
.length
> 0)
488 this.turn
= V
.GetOppCol(this.turn
);
495 if (move.appear
.length
== 0 && move.vanish
.length
== 0) {
497 this.sideCheckered
= this.turn
;
500 const c
= move.vanish
[0].c
;
501 const piece
= move.vanish
[0].p
;
502 if (piece
== V
.KING
) {
503 this.kingPos
[c
][0] = move.appear
[0].x
;
504 this.kingPos
[c
][1] = move.appear
[0].y
;
506 super.updateCastleFlags(move, piece
);
508 [1, 6].includes(move.start
.x
) &&
509 move.vanish
[0].p
== V
.PAWN
&&
510 Math
.abs(move.end
.x
- move.start
.x
) == 2
512 // This move turns off a 2-squares pawn flag
513 this.pawnFlags
[move.start
.x
== 6 ? "w" : "b"][move.start
.y
] = false;
516 this.cmoves
.push(this.getCmove(move));
520 this.epSquares
.pop();
521 this.disaggregateFlags(JSON
.parse(move.flags
));
522 V
.UndoOnBoard(this.board
, move);
523 if (move.appear
.length
> 0 || move.vanish
.length
> 0)
525 this.turn
= V
.GetOppCol(this.turn
);
532 if (move.appear
.length
== 0 && move.vanish
.length
== 0) this.stage
= 1;
533 else super.postUndo(move);
538 const color
= this.turn
;
539 if (this.stage
== 1) {
540 if (this.atLeastOneMove()) return "*";
541 // Artifically change turn, for checkered pawns
542 this.turn
= V
.GetOppCol(this.turn
);
544 this.isAttacked(this.kingPos
[color
], [V
.GetOppCol(color
), "c"])
549 this.turn
= V
.GetOppCol(this.turn
);
553 if (this.sideCheckered
== this.turn
) {
554 // Check if remaining checkered pieces: if none, I lost
555 if (this.board
.some(b
=> b
.some(cell
=> cell
[0] == 'c'))) {
556 if (!this.atLeastOneMove()) return "1/2";
559 return color
== 'w' ? "0-1" : "1-0";
561 if (this.atLeastOneMove()) return "*";
562 let res
= this.isAttacked(this.kingPos
['w'], ["c"]);
563 if (!res
) res
= this.isAttacked(this.kingPos
['b'], ["c"]);
564 if (res
) return color
== 'w' ? "0-1" : "1-0";
570 // Just count material for now, considering checkered neutral at stage 1.
571 const baseSign
= (this.turn
== 'w' ? 1 : -1);
572 for (let i
= 0; i
< V
.size
.x
; i
++) {
573 for (let j
= 0; j
< V
.size
.y
; j
++) {
574 if (this.board
[i
][j
] != V
.EMPTY
) {
575 const sqColor
= this.getColor(i
, j
);
576 if (this.stage
== 1) {
577 if (["w", "b"].includes(sqColor
)) {
578 const sign
= sqColor
== "w" ? 1 : -1;
579 evaluation
+= sign
* V
.VALUES
[this.getPiece(i
, j
)];
584 this.sideCheckered
== this.turn
585 ? (sqColor
== 'c' ? 1 : -1) * baseSign
586 : (sqColor
== 'c' ? -1 : 1) * baseSign
;
587 evaluation
+= sign
* V
.VALUES
[this.getPiece(i
, j
)];
595 static GenRandInitFen(randomness
) {
596 // Add 16 pawns flags + empty cmove + stage == 1:
597 return ChessRules
.GenRandInitFen(randomness
)
598 .slice(0, -2) + "1111111111111111 - - 1";
601 static ParseFen(fen
) {
602 const fenParts
= fen
.split(" ");
603 return Object
.assign(
604 ChessRules
.ParseFen(fen
),
613 const L
= this.cmoves
.length
;
617 : ChessRules
.CoordsToSquare(this.cmoves
[L
- 1].start
) +
618 ChessRules
.CoordsToSquare(this.cmoves
[L
- 1].end
)
623 return (this.stage
== 1 ? "1" : "2" + this.sideCheckered
);
628 super.getFen() + " " + this.getCmoveFen() + " " + this.getStageFen()
634 super.getFenForRepeat() + "_" +
635 this.getCmoveFen() + "_" + this.getStageFen()
640 let fen
= super.getFlagsFen();
642 for (let c
of ["w", "b"])
643 for (let i
= 0; i
< 8; i
++) fen
+= (this.pawnFlags
[c
][i
] ? "1" : "0");
647 static get SEARCH_DEPTH() {
652 // To simplify, prevent the bot from switching (TODO...)
654 super.getComputerMove(
655 this.getAllValidMoves().filter(m
=> m
.appear
.length
> 0)
661 if (move.appear
.length
== 0 && move.vanish
.length
== 0) return "S";
662 if (move.appear
.length
== 2) {
664 if (move.end
.y
< move.start
.y
) return "0-0-0";
668 const finalSquare
= V
.CoordsToSquare(move.end
);
669 const piece
= this.getPiece(move.start
.x
, move.start
.y
);
671 if (piece
== V
.PAWN
) {
672 if (move.vanish
.length
> 1) {
673 const startColumn
= V
.CoordToColumn(move.start
.y
);
674 notation
= startColumn
+ "x" + finalSquare
;
675 } else notation
= finalSquare
;
679 piece
.toUpperCase() +
680 (move.vanish
.length
> 1 ? "x" : "") +
683 if (move.appear
[0].p
!= move.vanish
[0].p
)
684 notation
+= "=" + move.appear
[0].p
.toUpperCase();