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
);
122 if (this.getPiece(x
, y
) == 'p' && color
== 'c') {
123 const pawnShift
= this.getPawnShift(this.turn
); //cannot trust color
125 (this.stage
== 2 && [1, 6].includes(x
)) ||
127 ((x
== 1 && this.turn
== 'b') || (x
== 6 && this.turn
== 'w'))
130 // Checkered pawns on stage 2 are bidirectional
131 let moveSteps
= [[pawnShift
, 0]],
132 attackSteps
= [[pawnShift
, 1], [pawnShift
, -1]];
133 if (this.stage
== 2) {
134 moveSteps
.push([-pawnShift
, 0]);
135 Array
.prototype.push
.apply(attackSteps
,
136 [[-pawnShift
, 1], [-pawnShift
, -1]]);
143 range: (initRank
? 2 : 1)
155 's': {"class": "checkered-pawn", moveas: 'p'},
156 'u': {"class": "checkered-rook", moveas: 'r'},
157 'o': {"class": "checkered-knight", moveas: 'n'},
158 'c': {"class": "checkered-bishop", moveas: 'b'},
159 't': {"class": "checkered-queen", moveas: 'q'}
161 return Object
.assign(baseRes
, checkered
);
164 setOtherVariables(fenParsed
) {
165 super.setOtherVariables(fenParsed
);
166 // Non-capturing last checkered move (if any)
167 const cmove
= fenParsed
.cmove
;
168 if (cmove
== "-") this.cmove
= null;
171 start: C
.SquareToCoords(cmove
.substr(0, 2)),
172 end: C
.SquareToCoords(cmove
.substr(2))
175 // Stage 1: as Checkered2. Stage 2: checkered pieces are autonomous
176 const stageInfo
= fenParsed
.stage
;
177 this.stage
= parseInt(stageInfo
[0], 10);
178 this.sideCheckered
= (this.stage
== 2 ? stageInfo
[1] : "");
182 super.setFlags(fenflags
); //castleFlags
184 w: [...Array(8)], //pawns can move 2 squares?
187 const flags
= fenflags
.substr(4); //skip first 4 letters, for castle
188 for (let c
of ["w", "b"]) {
189 for (let i
= 0; i
< 8; i
++)
190 this.pawnFlags
[c
][i
] = flags
.charAt((c
== "w" ? 0 : 8) + i
) == "1";
194 getEpSquare(moveOrSquare
) {
195 // At stage 2, all pawns can be captured en-passant
198 typeof moveOrSquare
!== "object" ||
199 (moveOrSquare
.appear
.length
> 0 && moveOrSquare
.appear
[0].c
!= 'c')
201 return super.getEpSquare(moveOrSquare
);
202 // Checkered or switch move: no en-passant
207 // No checkered move to undo at stage 2:
208 if (this.stage
== 1 && move.vanish
.length
== 1 && move.appear
[0].c
== "c")
209 return {start: move.start
, end: move.end
};
213 canTake([x1
, y1
], [x2
, y2
]) {
214 const color1
= this.getColor(x1
, y1
);
215 const color2
= this.getColor(x2
, y2
);
216 if (this.stage
== 2) {
217 // Black & White <-- takes --> Checkered
218 const color1
= this.getColor(x1
, y1
);
219 const color2
= this.getColor(x2
, y2
);
220 return color1
!= color2
&& [color1
, color2
].includes('c');
224 color2
!= "c" && //checkered aren't captured
225 (color1
!= "c" || color2
!= this.turn
)
229 postProcessPotentialMoves(moves
) {
230 moves
= super.postProcessPotentialMoves(moves
);
231 if (this.stage
== 2 || moves
.length
== 0)
233 const color
= this.turn
;
234 // Apply "checkerization" of standard moves
235 const lastRank
= (color
== "w" ? 0 : 7);
236 const [x
, y
] = [moves
[0].start
.x
, moves
[0].start
.y
];
237 const piece
= this.getPiece(x
, y
);
238 // King is treated differently: it never turn checkered
239 if (piece
== 'k' && this.stage
== 1) {
240 // If at least one checkered piece, allow switching:
242 this.options
["withswitch"] &&
243 this.board
.some(b
=> b
.some(cell
=> cell
[0] == 'c'))
245 const oppKingPos
= this.searchKingPos(C
.GetOppTurn(this.turn
))[0];
248 start: { x: x
, y: y
},
249 end: {x: oppKingPos
[0], y: oppKingPos
[1]},
258 // Filter out forbidden pawn moves
259 moves
= moves
.filter(m
=> {
260 if (m
.vanish
.length
> 0 && m
.vanish
[0].p
== 'p') {
262 Math
.abs(m
.end
.x
- m
.start
.x
) == 2 &&
263 !this.pawnFlags
[this.turn
][m
.start
.y
]
265 return false; //forbidden 2-squares jumps
268 this.board
[m
.end
.x
][m
.end
.y
] == "" &&
269 m
.vanish
.length
== 2 &&
270 this.getColor(m
.start
.x
, m
.start
.y
) == "c"
272 return false; //checkered pawns cannot take en-passant
280 if (m
.vanish
.length
== 2 && m
.appear
.length
== 1) {
284 m
.appear
[0].p
!= m
.vanish
[1].p
&&
285 // No choice if promotion:
286 (m
.vanish
[0].p
!= 'p' || m
.end
.x
!= lastRank
)
288 // Add transformation into captured piece
289 let m2
= JSON
.parse(JSON
.stringify(m
));
290 m2
.appear
[0].p
= m
.vanish
[1].p
;
295 return moves
.concat(extraMoves
);
299 if (this.stage
== 2) {
300 const color
= this.getColor(x
, y
);
302 this.turn
== this.sideCheckered
304 : ['w', 'b'].includes(color
)
308 this.playerColor
== this.turn
&&
309 [this.turn
, "c"].includes(this.getColor(x
, y
))
313 // Does m2 un-do m1 ? (to disallow undoing checkered moves)
314 oppositeMoves(m1
, m2
) {
317 m2
.appear
.length
== 1 &&
318 m2
.vanish
.length
== 1 &&
319 m2
.appear
[0].c
== "c" &&
320 m1
.start
.x
== m2
.end
.x
&&
321 m1
.end
.x
== m2
.start
.x
&&
322 m1
.start
.y
== m2
.end
.y
&&
323 m1
.end
.y
== m2
.start
.y
328 const color
= this.turn
;
329 if (this.stage
== 2 && this.sideCheckered
== color
)
330 // Checkered cannot be under check (no king)
332 let kingPos
= super.searchKingPos(color
);
334 // Must consider both kings (attacked by checkered side)
335 kingPos
.push(super.searchKingPos(C
.GetOppTurn(this.turn
))[0]);
336 const oppCols
= this.getOppCols(color
);
337 const filteredMoves
= moves
.filter(m
=> {
338 if (m
.vanish
.length
== 0 && m
.appear
.length
== 0)
339 return true; //switch move
341 if (m
.vanish
[0].p
== 'k')
342 kingPos
[0] = [m
.appear
[0].x
, m
.appear
[0].y
];
345 res
= !this.oppositeMoves(this.cmove
, m
);
346 if (res
&& m
.appear
.length
> 0)
347 res
= !this.underCheck(kingPos
, oppCols
);
348 if (m
.vanish
[0].p
== 'k')
349 kingPos
[0] = [m
.vanish
[0].x
, m
.vanish
[0].y
];
353 return filteredMoves
;
356 atLeastOneMove(color
) {
357 const myCols
= [color
, 'c'];
358 for (let i
= 0; i
< this.size
.x
; i
++) {
359 for (let j
= 0; j
< this.size
.y
; j
++) {
360 const colIJ
= this.getColor(i
, j
);
362 this.board
[i
][j
] != "" &&
364 (this.stage
== 1 && myCols
.includes(colIJ
)) ||
367 (this.sideCheckered
== color
&& colIJ
== 'c') ||
368 (this.sideCheckered
!= color
&& ['w', 'b'].includes(colIJ
))
373 const moves
= this.getPotentialMovesFrom([i
, j
]);
374 if (moves
.some(m
=> this.filterValid([m
]).length
>= 1))
382 underCheck(square_s
, oppCols
) {
383 if (this.stage
== 2 && this.turn
== this.sideCheckered
)
384 return false; //checkered pieces is me, I'm not under check
385 // Artificial turn change required, because canTake uses turn info.
386 // canTake is called from underCheck --> ... --> findDestSquares
387 this.turn
= C
.GetOppTurn(this.turn
);
388 const res
= square_s
.some(sq
=> super.underAttack(sq
, oppCols
));
389 this.turn
= C
.GetOppTurn(this.turn
);
394 if (move.appear
.length
> 0 && move.vanish
.length
> 0) {
397 [1, 6].includes(move.start
.x
) &&
398 move.vanish
[0].p
== 'p' &&
399 Math
.abs(move.end
.x
- move.start
.x
) == 2
401 // This move turns off a 2-squares pawn flag
402 this.pawnFlags
[move.start
.x
== 6 ? "w" : "b"][move.start
.y
] = false;
408 if (move.appear
.length
== 0 && move.vanish
.length
== 0) {
410 this.sideCheckered
= this.turn
;
411 if (this.playerColor
!= this.turn
)
412 super.displayMessage(null, "Autonomous checkered!", "info-text", 2000);
415 super.postPlay(move);
416 this.cmove
= this.getCmove(move);
419 tryChangeTurn(move) {
420 if (move.appear
.length
> 0 && move.vanish
.length
> 0)
421 super.tryChangeTurn(move);
425 const color
= this.turn
;
426 if (this.stage
== 1) {
427 if (this.atLeastOneMove(color
))
429 // Artifically change turn, for checkered pawns
430 const oppTurn
= C
.GetOppTurn(color
);
432 const kingPos
= super.searchKingPos(color
)[0];
434 if (super.underAttack(kingPos
, [oppTurn
, 'c']))
435 res
= (color
== "w" ? "0-1" : "1-0");
440 if (this.sideCheckered
== color
) {
441 // Check if remaining checkered pieces: if none, I lost
442 if (this.board
.some(b
=> b
.some(cell
=> cell
[0] == 'c'))) {
443 if (!this.atLeastOneMove(color
))
447 return (color
== 'w' ? "0-1" : "1-0");
449 if (this.atLeastOneMove(color
))
451 let res
= super.underAttack(super.searchKingPos(color
)[0], ['c']);
453 res
= super.underAttack(super.searchKingPos(oppCol
)[0], ['c']);
455 return (color
== 'w' ? "0-1" : "1-0");