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
], noswitch
) {
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:
146 this.board
.some(b
=> b
.some(cell
=> cell
[0] == 'c'))
148 const oppCol
= V
.GetOppCol(color
);
151 start: { x: x
, y: y
},
152 end: { x: this.kingPos
[oppCol
][0], y: this.kingPos
[oppCol
][1] },
158 return standardMoves
.concat(moves
);
160 standardMoves
.forEach(m
=> {
161 if (m
.vanish
[0].p
== V
.PAWN
) {
163 Math
.abs(m
.end
.x
- m
.start
.x
) == 2 &&
164 !this.pawnFlags
[this.turn
][m
.start
.y
]
166 return; //skip forbidden 2-squares jumps
169 this.board
[m
.end
.x
][m
.end
.y
] == V
.EMPTY
&&
170 m
.vanish
.length
== 2 &&
171 this.getColor(m
.start
.x
, m
.start
.y
) == "c"
173 return; //checkered pawns cannot take en-passant
176 if (m
.vanish
.length
== 1)
180 // A capture occured (m.vanish.length == 2)
184 // Avoid promotions (already treated):
185 m
.appear
[0].p
!= m
.vanish
[1].p
&&
186 (m
.vanish
[0].p
!= V
.PAWN
|| m
.end
.x
!= lastRank
)
188 // Add transformation into captured piece
189 let m2
= JSON
.parse(JSON
.stringify(m
));
190 m2
.appear
[0].p
= m
.vanish
[1].p
;
197 return standardMoves
;
200 getPotentialPawnMoves([x
, y
]) {
201 const color
= this.getColor(x
, y
);
202 if (this.stage
== 2) {
203 const saveTurn
= this.turn
;
204 if (this.sideCheckered
== this.turn
) {
205 // Cannot change PawnSpecs.bidirectional, so cheat a little:
207 const wMoves
= super.getPotentialPawnMoves([x
, y
]);
209 const bMoves
= super.getPotentialPawnMoves([x
, y
]);
210 this.turn
= saveTurn
;
211 return wMoves
.concat(bMoves
);
213 // Playing with both colors:
215 const moves
= super.getPotentialPawnMoves([x
, y
]);
216 this.turn
= saveTurn
;
219 let moves
= super.getPotentialPawnMoves([x
, y
]);
220 // Post-process: set right color for checkered moves
223 m
.appear
[0].c
= 'c'; //may be done twice if capture
230 canIplay(side
, [x
, y
]) {
231 if (this.stage
== 2) {
232 const color
= this.getColor(x
, y
);
234 this.turn
== this.sideCheckered
236 : ['w', 'b'].includes(color
)
239 return side
== this.turn
&& [side
, "c"].includes(this.getColor(x
, y
));
242 // Does m2 un-do m1 ? (to disallow undoing checkered moves)
243 oppositeMoves(m1
, m2
) {
246 m2
.appear
[0].c
== "c" &&
247 m2
.appear
.length
== 1 &&
248 m2
.vanish
.length
== 1 &&
249 m1
.start
.x
== m2
.end
.x
&&
250 m1
.end
.x
== m2
.start
.x
&&
251 m1
.start
.y
== m2
.end
.y
&&
252 m1
.end
.y
== m2
.start
.y
257 if (moves
.length
== 0) return [];
258 const color
= this.turn
;
259 const oppCol
= V
.GetOppCol(color
);
260 const L
= this.cmoves
.length
; //at least 1: init from FEN
261 const stage
= this.stage
; //may change if switch
262 return moves
.filter(m
=> {
263 // Checkered cannot be under check (no king)
264 if (stage
== 2 && this.sideCheckered
== color
) return true;
268 if (m
.appear
.length
== 0 && m
.vanish
.length
== 0) {
269 // Special "switch" move: kings must not be attacked by checkered.
270 // Not checking for oppositeMoves here: checkered are autonomous
272 !this.isAttacked(this.kingPos
['w'], ['c']) &&
273 !this.isAttacked(this.kingPos
['b'], ['c']) &&
274 this.getAllPotentialMoves().length
> 0
277 else res
= !this.oppositeMoves(this.cmoves
[L
- 1], m
);
279 if (res
&& m
.appear
.length
> 0) res
= !this.underCheck(color
);
280 // At stage 2, side with B & W can be undercheck with both kings:
281 if (res
&& stage
== 2) res
= !this.underCheck(oppCol
);
287 getAllPotentialMoves() {
288 const color
= this.turn
;
289 const oppCol
= V
.GetOppCol(color
);
290 let potentialMoves
= [];
291 for (let i
= 0; i
< V
.size
.x
; i
++) {
292 for (let j
= 0; j
< V
.size
.y
; j
++) {
293 const colIJ
= this.getColor(i
, j
);
295 this.board
[i
][j
] != V
.EMPTY
&&
297 (this.stage
== 1 && colIJ
!= oppCol
) ||
300 (this.sideCheckered
== color
&& colIJ
== 'c') ||
301 (this.sideCheckered
!= color
&& ['w', 'b'].includes(colIJ
))
306 Array
.prototype.push
.apply(
308 this.getPotentialMovesFrom([i
, j
])
313 return potentialMoves
;
317 const color
= this.turn
;
318 const oppCol
= V
.GetOppCol(color
);
319 for (let i
= 0; i
< V
.size
.x
; i
++) {
320 for (let j
= 0; j
< V
.size
.y
; j
++) {
321 const colIJ
= this.getColor(i
, j
);
323 this.board
[i
][j
] != V
.EMPTY
&&
325 (this.stage
== 1 && colIJ
!= oppCol
) ||
328 (this.sideCheckered
== color
&& colIJ
== 'c') ||
329 (this.sideCheckered
!= color
&& ['w', 'b'].includes(colIJ
))
334 const moves
= this.getPotentialMovesFrom([i
, j
], "noswitch");
335 if (moves
.length
> 0) {
336 for (let k
= 0; k
< moves
.length
; k
++)
337 if (this.filterValid([moves
[k
]]).length
> 0) return true;
345 // colors: array, 'w' and 'c' or 'b' and 'c' at stage 1,
346 // just 'c' (or unused) at stage 2
347 isAttacked(sq
, colors
) {
348 if (!Array
.isArray(colors
)) colors
= [colors
];
350 this.isAttackedByPawn(sq
, colors
) ||
351 this.isAttackedByRook(sq
, colors
) ||
352 this.isAttackedByKnight(sq
, colors
) ||
353 this.isAttackedByBishop(sq
, colors
) ||
354 this.isAttackedByQueen(sq
, colors
) ||
355 this.isAttackedByKing(sq
, colors
)
359 isAttackedByPawn([x
, y
], colors
) {
360 for (let c
of colors
) {
362 if (this.stage
== 1) {
363 const color
= (c
== "c" ? this.turn : c
);
364 shifts
= [color
== "w" ? 1 : -1];
367 // Stage 2: checkered pawns are bidirectional
368 if (c
== 'c') shifts
= [-1, 1];
369 else shifts
= [c
== "w" ? 1 : -1];
371 for (let pawnShift
of shifts
) {
372 if (x
+ pawnShift
>= 0 && x
+ pawnShift
< 8) {
373 for (let i
of [-1, 1]) {
377 this.getPiece(x
+ pawnShift
, y
+ i
) == V
.PAWN
&&
378 this.getColor(x
+ pawnShift
, y
+ i
) == c
389 isAttackedBySlideNJump([x
, y
], colors
, piece
, steps
, oneStep
) {
390 for (let step
of steps
) {
391 let rx
= x
+ step
[0],
393 while (V
.OnBoard(rx
, ry
) && this.board
[rx
][ry
] == V
.EMPTY
&& !oneStep
) {
399 this.getPiece(rx
, ry
) === piece
&&
400 colors
.includes(this.getColor(rx
, ry
))
408 isAttackedByRook(sq
, colors
) {
409 return this.isAttackedBySlideNJump(sq
, colors
, V
.ROOK
, V
.steps
[V
.ROOK
]);
412 isAttackedByKnight(sq
, colors
) {
413 return this.isAttackedBySlideNJump(
422 isAttackedByBishop(sq
, colors
) {
423 return this.isAttackedBySlideNJump(
424 sq
, colors
, V
.BISHOP
, V
.steps
[V
.BISHOP
]);
427 isAttackedByQueen(sq
, colors
) {
428 return this.isAttackedBySlideNJump(
432 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
])
436 isAttackedByKing(sq
, colors
) {
437 return this.isAttackedBySlideNJump(
441 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]),
448 return this.isAttacked(this.kingPos
[color
], [V
.GetOppCol(color
), "c"]);
449 if (color
== this.sideCheckered
) return false;
451 this.isAttacked(this.kingPos
['w'], ["c"]) ||
452 this.isAttacked(this.kingPos
['b'], ["c"])
457 const color
= this.turn
;
458 if (this.stage
== 1) {
459 // Artifically change turn, for checkered pawns
460 this.turn
= V
.GetOppCol(color
);
464 [V
.GetOppCol(color
), "c"]
466 let res
= kingAttacked
467 ? [JSON
.parse(JSON
.stringify(this.kingPos
[color
]))]
472 if (this.sideCheckered
== color
) return [];
474 for (let c
of ['w', 'b']) {
475 if (this.isAttacked(this.kingPos
[c
], ['c']))
476 res
.push(JSON
.parse(JSON
.stringify(this.kingPos
[c
])));
482 move.flags
= JSON
.stringify(this.aggregateFlags());
483 this.epSquares
.push(this.getEpSquare(move));
484 V
.PlayOnBoard(this.board
, move);
485 if (move.appear
.length
> 0 || move.vanish
.length
> 0)
487 this.turn
= V
.GetOppCol(this.turn
);
494 if (move.appear
.length
== 0 && move.vanish
.length
== 0) {
496 this.sideCheckered
= this.turn
;
499 const c
= move.vanish
[0].c
;
500 const piece
= move.vanish
[0].p
;
501 if (piece
== V
.KING
) {
502 this.kingPos
[c
][0] = move.appear
[0].x
;
503 this.kingPos
[c
][1] = move.appear
[0].y
;
505 super.updateCastleFlags(move, piece
);
506 // Does this move turn off a 2-squares pawn flag?
507 if ([1, 6].includes(move.start
.x
) && move.vanish
[0].p
== V
.PAWN
)
508 this.pawnFlags
[move.start
.x
== 6 ? "w" : "b"][move.start
.y
] = false;
510 this.cmoves
.push(this.getCmove(move));
514 this.epSquares
.pop();
515 this.disaggregateFlags(JSON
.parse(move.flags
));
516 V
.UndoOnBoard(this.board
, move);
517 if (move.appear
.length
> 0 || move.vanish
.length
> 0)
519 this.turn
= V
.GetOppCol(this.turn
);
526 if (move.appear
.length
== 0 && move.vanish
.length
== 0) this.stage
= 1;
527 else super.postUndo(move);
532 const color
= this.turn
;
533 if (this.stage
== 1) {
534 if (this.atLeastOneMove()) return "*";
535 // Artifically change turn, for checkered pawns
536 this.turn
= V
.GetOppCol(this.turn
);
538 this.isAttacked(this.kingPos
[color
], [V
.GetOppCol(color
), "c"])
543 this.turn
= V
.GetOppCol(this.turn
);
547 if (this.sideCheckered
== this.turn
) {
548 // Check if remaining checkered pieces: if none, I lost
549 if (this.board
.some(b
=> b
.some(cell
=> cell
[0] == 'c'))) {
550 if (!this.atLeastOneMove()) return "1/2";
553 return color
== 'w' ? "0-1" : "1-0";
555 if (this.atLeastOneMove()) return "*";
556 let res
= this.isAttacked(this.kingPos
['w'], ["c"]);
557 if (!res
) res
= this.isAttacked(this.kingPos
['b'], ["c"]);
558 if (res
) return color
== 'w' ? "0-1" : "1-0";
564 // Just count material for now, considering checkered neutral at stage 1.
565 const baseSign
= (this.turn
== 'w' ? 1 : -1);
566 for (let i
= 0; i
< V
.size
.x
; i
++) {
567 for (let j
= 0; j
< V
.size
.y
; j
++) {
568 if (this.board
[i
][j
] != V
.EMPTY
) {
569 const sqColor
= this.getColor(i
, j
);
570 if (this.stage
== 1) {
571 if (["w", "b"].includes(sqColor
)) {
572 const sign
= sqColor
== "w" ? 1 : -1;
573 evaluation
+= sign
* V
.VALUES
[this.getPiece(i
, j
)];
578 this.sideCheckered
== this.turn
579 ? (sqColor
== 'c' ? 1 : -1) * baseSign
580 : (sqColor
== 'c' ? -1 : 1) * baseSign
;
581 evaluation
+= sign
* V
.VALUES
[this.getPiece(i
, j
)];
589 static GenRandInitFen(randomness
) {
590 // Add 16 pawns flags + empty cmove + stage == 1:
591 return ChessRules
.GenRandInitFen(randomness
)
592 .slice(0, -2) + "1111111111111111 - - 1";
595 static ParseFen(fen
) {
596 const fenParts
= fen
.split(" ");
597 return Object
.assign(
598 ChessRules
.ParseFen(fen
),
607 const L
= this.cmoves
.length
;
611 : ChessRules
.CoordsToSquare(this.cmoves
[L
- 1].start
) +
612 ChessRules
.CoordsToSquare(this.cmoves
[L
- 1].end
)
617 return (this.stage
== 1 ? "1" : "2" + this.sideCheckered
);
622 super.getFen() + " " + this.getCmoveFen() + " " + this.getStageFen()
628 super.getFenForRepeat() + "_" +
629 this.getCmoveFen() + "_" + this.getStageFen()
634 let fen
= super.getFlagsFen();
636 for (let c
of ["w", "b"])
637 for (let i
= 0; i
< 8; i
++) fen
+= (this.pawnFlags
[c
][i
] ? "1" : "0");
641 static get SEARCH_DEPTH() {
646 // To simplify, prevent the bot from switching (TODO...)
648 super.getComputerMove(
649 this.getAllValidMoves().filter(m
=> m
.appear
.length
> 0)
655 if (move.appear
.length
== 0 && move.vanish
.length
== 0) return "S";
656 if (move.appear
.length
== 2) {
658 if (move.end
.y
< move.start
.y
) return "0-0-0";
662 const finalSquare
= V
.CoordsToSquare(move.end
);
663 const piece
= this.getPiece(move.start
.x
, move.start
.y
);
665 if (piece
== V
.PAWN
) {
666 if (move.vanish
.length
> 1) {
667 const startColumn
= V
.CoordToColumn(move.start
.y
);
668 notation
= startColumn
+ "x" + finalSquare
;
669 } else notation
= finalSquare
;
673 piece
.toUpperCase() +
674 (move.vanish
.length
> 1 ? "x" : "") +
677 if (move.appear
[0].p
!= move.vanish
[0].p
)
678 notation
+= "=" + move.appear
[0].p
.toUpperCase();