1 import ChessRules
from "/base_rules.js";
2 import PiPo
from "/utils/PiPo.js";
3 import Move
from "/utils/Move.js";
5 export default class CheckeredRules
extends ChessRules
{
9 select: C
.Options
.select
,
12 label: "Allow switching",
13 variable: "withswitch",
31 static GetColorClass(c
) {
34 return C
.GetColorClass(c
);
38 const checkered_codes
= {
46 return checkered_codes
[b
[1]];
47 return super.board2fen(b
);
51 // Tolerate upper-case versions of checkered pieces (why not?)
52 const checkered_pieces
= {
64 if (Object
.keys(checkered_pieces
).includes(f
))
65 return "c" + checkered_pieces
[f
];
66 return super.fen2board(f
);
69 genRandInitBaseFen() {
70 let res
= super.genRandInitBaseFen();
71 res
.o
.flags
+= "1".repeat(16); //pawns flags
78 "cmove": o
.init
? "-" : this.getCmoveFen(),
79 "stage": o
.init
? "1" : this.getStageFen()
89 C
.CoordsToSquare(this.cmove
.start
) + C
.CoordsToSquare(this.cmove
.end
)
94 return (this.stage
+ this.sideCheckered
);
98 let fen
= super.getFlagsFen();
100 for (let c
of ["w", "b"]) {
101 for (let i
= 0; i
< 8; i
++)
102 fen
+= (this.pawnFlags
[c
][i
] ? "1" : "0");
107 getPawnShift(color
) {
108 return super.getPawnShift(color
== 'c' ? this.turn : color
);
113 return super.getOppCols(color
).concat(['c']);
114 // Stage 2: depends if color is w+b or checkered
115 if (color
== this.sideCheckered
)
120 pieces(color
, x
, y
) {
121 let baseRes
= super.pieces(color
, x
, y
);
123 this.getPiece(x
, y
) == 'p' &&
125 this.getColor(x
, y
) == 'c'
127 // Checkered pawns on stage 2 are bidirectional
128 const initRank
= ((color
== 'w' && x
>= 6) || (color
== 'b' && x
<= 1));
133 steps: [[1, 0], [-1, 0]],
134 range: (initRank
? 2 : 1)
139 steps: [[1, 1], [1, -1], [-1, 1], [-1, -1]],
146 's': {"class": "checkered-pawn", moveas: 'p'},
147 'u': {"class": "checkered-rook", moveas: 'r'},
148 'o': {"class": "checkered-knight", moveas: 'n'},
149 'c': {"class": "checkered-bishop", moveas: 'b'},
150 't': {"class": "checkered-queen", moveas: 'q'}
152 return Object
.assign(baseRes
, checkered
);
155 setOtherVariables(fenParsed
) {
156 super.setOtherVariables(fenParsed
);
157 // Non-capturing last checkered move (if any)
158 const cmove
= fenParsed
.cmove
;
159 if (cmove
== "-") this.cmove
= null;
162 start: C
.SquareToCoords(cmove
.substr(0, 2)),
163 end: C
.SquareToCoords(cmove
.substr(2))
166 // Stage 1: as Checkered2. Stage 2: checkered pieces are autonomous
167 const stageInfo
= fenParsed
.stage
;
168 this.stage
= parseInt(stageInfo
[0], 10);
169 this.sideCheckered
= (this.stage
== 2 ? stageInfo
[1] : "");
173 super.setFlags(fenflags
); //castleFlags
175 w: [...Array(8)], //pawns can move 2 squares?
178 const flags
= fenflags
.substr(4); //skip first 4 letters, for castle
179 for (let c
of ["w", "b"]) {
180 for (let i
= 0; i
< 8; i
++)
181 this.pawnFlags
[c
][i
] = flags
.charAt((c
== "w" ? 0 : 8) + i
) == "1";
185 getEpSquare(moveOrSquare
) {
186 // At stage 2, all pawns can be captured en-passant
189 typeof moveOrSquare
!== "object" ||
190 (moveOrSquare
.appear
.length
> 0 && moveOrSquare
.appear
[0].c
!= 'c')
192 return super.getEpSquare(moveOrSquare
);
193 // Checkered or switch move: no en-passant
198 // No checkered move to undo at stage 2:
199 if (this.stage
== 1 && move.vanish
.length
== 1 && move.appear
[0].c
== "c")
200 return {start: move.start
, end: move.end
};
204 canTake([x1
, y1
], [x2
, y2
]) {
205 const color1
= this.getColor(x1
, y1
);
206 const color2
= this.getColor(x2
, y2
);
207 if (this.stage
== 2) {
208 // Black & White <-- takes --> Checkered
209 const color1
= this.getColor(x1
, y1
);
210 const color2
= this.getColor(x2
, y2
);
211 return color1
!= color2
&& [color1
, color2
].includes('c');
215 color2
!= "c" && //checkered aren't captured
216 (color1
!= "c" || color2
!= this.turn
)
220 postProcessPotentialMoves(moves
) {
221 if (this.stage
== 2 || moves
.length
== 0)
223 const color
= this.turn
;
224 // Apply "checkerization" of standard moves
225 const lastRank
= (color
== "w" ? 0 : 7);
226 const [x
, y
] = [moves
[0].start
.x
, moves
[0].start
.y
];
227 const piece
= this.getPiece(x
, y
);
228 // King is treated differently: it never turn checkered
229 if (piece
== 'k' && this.stage
== 1) {
230 // If at least one checkered piece, allow switching:
232 this.options
["withswitch"] &&
233 this.board
.some(b
=> b
.some(cell
=> cell
[0] == 'c'))
235 const oppKingPos
= this.searchKingPos(C
.GetOppTurn(this.turn
))[0];
238 start: { x: x
, y: y
},
239 end: {x: oppKingPos
[0], y: oppKingPos
[1]},
248 // Filter out forbidden pawn moves
249 moves
= moves
.filter(m
=> {
250 if (m
.vanish
.length
> 0 && m
.vanish
[0].p
== 'p') {
252 Math
.abs(m
.end
.x
- m
.start
.x
) == 2 &&
253 !this.pawnFlags
[this.turn
][m
.start
.y
]
255 return false; //forbidden 2-squares jumps
258 this.board
[m
.end
.x
][m
.end
.y
] == "" &&
259 m
.vanish
.length
== 2 &&
260 this.getColor(m
.start
.x
, m
.start
.y
) == "c"
262 return false; //checkered pawns cannot take en-passant
270 if (m
.vanish
.length
== 2 && m
.appear
.length
== 1) {
274 m
.appear
[0].p
!= m
.vanish
[1].p
&&
275 // No choice if promotion:
276 (m
.vanish
[0].p
!= 'p' || m
.end
.x
!= lastRank
)
278 // Add transformation into captured piece
279 let m2
= JSON
.parse(JSON
.stringify(m
));
280 m2
.appear
[0].p
= m
.vanish
[1].p
;
285 return moves
.concat(extraMoves
);
289 if (this.stage
== 2) {
290 const color
= this.getColor(x
, y
);
292 this.turn
== this.sideCheckered
294 : ['w', 'b'].includes(color
)
298 this.playerColor
== this.turn
&&
299 [this.turn
, "c"].includes(this.getColor(x
, y
))
303 // Does m2 un-do m1 ? (to disallow undoing checkered moves)
304 oppositeMoves(m1
, m2
) {
307 m2
.appear
.length
== 1 &&
308 m2
.vanish
.length
== 1 &&
309 m2
.appear
[0].c
== "c" &&
310 m1
.start
.x
== m2
.end
.x
&&
311 m1
.end
.x
== m2
.start
.x
&&
312 m1
.start
.y
== m2
.end
.y
&&
313 m1
.end
.y
== m2
.start
.y
318 const color
= this.turn
;
319 if (this.stage
== 2 && this.sideCheckered
== color
)
320 // Checkered cannot be under check (no king)
322 let kingPos
= super.searchKingPos(color
);
324 // Must consider both kings (attacked by checkered side)
325 kingPos
= [kingPos
, super.searchKingPos(C
.GetOppTurn(this.turn
))];
326 const oppCols
= this.getOppCols(color
);
327 return moves
.filter(m
=> {
331 res
= !this.oppositeMoves(this.cmove
, m
);
332 if (res
&& m
.appear
.length
> 0)
333 res
= !this.underCheck(kingPos
, oppCols
);
339 atLeastOneMove(color
) {
340 const myCols
= [color
, 'c'];
341 for (let i
= 0; i
< this.size
.x
; i
++) {
342 for (let j
= 0; j
< this.size
.y
; j
++) {
343 const colIJ
= this.getColor(i
, j
);
345 this.board
[i
][j
] != "" &&
347 (this.stage
== 1 && myCols
.includes(colIJ
)) ||
350 (this.sideCheckered
== color
&& colIJ
== 'c') ||
351 (this.sideCheckered
!= color
&& ['w', 'b'].includes(colIJ
))
356 const moves
= this.getPotentialMovesFrom([i
, j
]);
357 if (moves
.some(m
=> this.filterValid([m
]).length
>= 1))
365 underCheck(square_s
, oppCols
) {
366 if (this.stage
== 2 && oppCol
!= this.sideCheckered
)
367 return false; //checkered pieces is me, I'm not under check
368 return square_s
.some(sq
=> super.underAttack(sq
, oppCols
));
372 if (move.appear
.length
> 0 && move.vanish
.length
> 0) {
375 [1, 6].includes(move.start
.x
) &&
376 move.vanish
[0].p
== 'p' &&
377 Math
.abs(move.end
.x
- move.start
.x
) == 2
379 // This move turns off a 2-squares pawn flag
380 this.pawnFlags
[move.start
.x
== 6 ? "w" : "b"][move.start
.y
] = false;
386 if (move.appear
.length
== 0 && move.vanish
.length
== 0) {
388 this.sideCheckered
= this.turn
;
391 super.postPlay(move);
392 this.cmove
= this.getCmove(move);
395 tryChangeTurn(move) {
396 if (move.appear
.length
> 0 && move.vanish
.length
> 0)
397 super.tryChangeTurn(move);
401 const color
= this.turn
;
402 if (this.stage
== 1) {
403 if (this.atLeastOneMove(color
))
405 // Artifically change turn, for checkered pawns
406 const oppTurn
= C
.GetOppTurn(color
);
408 const kingPos
= super.searchKingPos(color
)[0];
410 if (super.underAttack(kingPos
, [oppTurn
, 'c']))
411 res
= (color
== "w" ? "0-1" : "1-0");
416 if (this.sideCheckered
== color
) {
417 // Check if remaining checkered pieces: if none, I lost
418 if (this.board
.some(b
=> b
.some(cell
=> cell
[0] == 'c'))) {
419 if (!this.atLeastOneMove(color
))
423 return (color
== 'w' ? "0-1" : "1-0");
425 if (this.atLeastOneMove(color
))
427 let res
= super.underAttack(super.searchKingPos(color
)[0], ['c']);
429 res
= super.underAttack(super.searchKingPos(oppCol
)[0], ['c']);
431 return (color
== 'w' ? "0-1" : "1-0");