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",
32 const checkered_codes
= {
40 return checkered_codes
[b
[1]];
41 return super.board2fen(b
);
45 // Tolerate upper-case versions of checkered pieces (why not?)
46 const checkered_pieces
= {
58 if (Object
.keys(checkered_pieces
).includes(f
))
59 return "c" + checkered_pieces
[f
];
60 return super.fen2board(f
);
66 "cmove": o
.init
? "-" : this.getCmoveFen(),
67 "stage": o
.init
? "1" : this.getStageFen()
77 C
.CoordsToSquare(this.cmove
.start
) + C
.CoordsToSquare(this.cmove
.end
)
82 return (this.stage
+ this.sideCheckered
);
86 let fen
= super.getFlagsFen();
88 for (let c
of ["w", "b"]) {
89 for (let i
= 0; i
< 8; i
++)
90 fen
+= (this.pawnFlags
[c
][i
] ? "1" : "0");
96 return super.getPawnShift(color
== 'c' ? this.turn : color
);
101 return super.getOppCols(color
).concat(['c']);
102 // Stage 2: depends if color is w+b or checkered
103 if (color
== this.sideCheckered
)
108 pieces(color
, x
, y
) {
109 let baseRes
= super.pieces(color
, x
, y
);
111 this.getPiece(x
, y
) == 'p' &&
113 this.getColor(x
, y
) == 'c'
115 // Checkered pawns on stage 2 are bidirectional
116 const initRank
= ((color
== 'w' && x
>= 6) || (color
== 'b' && x
<= 1));
121 steps: [[1, 0], [-1, 0]],
122 range: (initRank
? 2 : 1)
127 steps: [[1, 1], [1, -1], [-1, 1], [-1, -1]],
134 's': {"class": "checkered-pawn", moveas: 'p'},
135 'u': {"class": "checkered-rook", moveas: 'r'},
136 'o': {"class": "checkered-knight", moveas: 'n'},
137 'c': {"class": "checkered-bishop", moveas: 'b'},
138 't': {"class": "checkered-queen", moveas: 'q'}
140 return Object
.assign(baseRes
, checkered
);
143 setOtherVariables(fenParsed
) {
144 super.setOtherVariables(fenParsed
);
145 // Non-capturing last checkered move (if any)
146 const cmove
= fenParsed
.cmove
;
147 if (cmove
== "-") this.cmove
= null;
150 start: C
.SquareToCoords(cmove
.substr(0, 2)),
151 end: C
.SquareToCoords(cmove
.substr(2))
154 // Stage 1: as Checkered2. Stage 2: checkered pieces are autonomous
155 const stageInfo
= fenParsed
.stage
;
156 this.stage
= parseInt(stageInfo
[0], 10);
157 this.sideCheckered
= (this.stage
== 2 ? stageInfo
[1] : "");
161 super.setFlags(fenflags
); //castleFlags
163 w: [...Array(8)], //pawns can move 2 squares?
166 const flags
= fenflags
.substr(4); //skip first 4 letters, for castle
167 for (let c
of ["w", "b"]) {
168 for (let i
= 0; i
< 8; i
++)
169 this.pawnFlags
[c
][i
] = flags
.charAt((c
== "w" ? 0 : 8) + i
) == "1";
173 getEpSquare(moveOrSquare
) {
174 // At stage 2, all pawns can be captured en-passant
177 typeof moveOrSquare
!== "object" ||
178 (moveOrSquare
.appear
.length
> 0 && moveOrSquare
.appear
[0].c
!= 'c')
180 return super.getEpSquare(moveOrSquare
);
181 // Checkered or switch move: no en-passant
186 // No checkered move to undo at stage 2:
187 if (this.stage
== 1 && move.vanish
.length
== 1 && move.appear
[0].c
== "c")
188 return {start: move.start
, end: move.end
};
192 canTake([x1
, y1
], [x2
, y2
]) {
193 const color1
= this.getColor(x1
, y1
);
194 const color2
= this.getColor(x2
, y2
);
195 if (this.stage
== 2) {
196 // Black & White <-- takes --> Checkered
197 const color1
= this.getColor(x1
, y1
);
198 const color2
= this.getColor(x2
, y2
);
199 return color1
!= color2
&& [color1
, color2
].includes('c');
203 color2
!= "c" && //checkered aren't captured
204 (color1
!= "c" || color2
!= this.turn
)
208 postProcessPotentialMoves(moves
) {
209 if (this.stage
== 2 || moves
.length
== 0)
211 const color
= this.turn
;
212 // Apply "checkerization" of standard moves
213 const lastRank
= (color
== "w" ? 0 : 7);
214 const [x
, y
] = [moves
[0].start
.x
, moves
[0].start
.y
];
215 const piece
= this.getPiece(x
, y
);
216 // King is treated differently: it never turn checkered
217 if (piece
== 'k' && this.stage
== 1) {
218 // If at least one checkered piece, allow switching:
220 this.options
["withswitch"] &&
221 this.board
.some(b
=> b
.some(cell
=> cell
[0] == 'c'))
223 const oppKingPos
= this.searchKingPos(C
.GetOppTurn(this.turn
))[0];
226 start: { x: x
, y: y
},
227 end: {x: oppKingPos
[0], y: oppKingPos
[1]},
236 // Filter out forbidden pawn moves
237 moves
= moves
.filter(m
=> {
238 if (m
.vanish
.length
> 0 && m
.vanish
[0].p
== 'p') {
240 Math
.abs(m
.end
.x
- m
.start
.x
) == 2 &&
241 !this.pawnFlags
[this.turn
][m
.start
.y
]
243 return false; //forbidden 2-squares jumps
246 this.board
[m
.end
.x
][m
.end
.y
] == V
.EMPTY
&&
247 m
.vanish
.length
== 2 &&
248 this.getColor(m
.start
.x
, m
.start
.y
) == "c"
250 return false; //checkered pawns cannot take en-passant
258 if (m
.vanish
.length
== 2 && m
.appear
.length
== 1) {
262 m
.appear
[0].p
!= m
.vanish
[1].p
&&
263 // No choice if promotion:
264 (m
.vanish
[0].p
!= 'p' || m
.end
.x
!= lastRank
)
266 // Add transformation into captured piece
267 let m2
= JSON
.parse(JSON
.stringify(m
));
268 m2
.appear
[0].p
= m
.vanish
[1].p
;
273 return moves
.concat(extraMoves
);
277 if (this.stage
== 2) {
278 const color
= this.getColor(x
, y
);
280 this.turn
== this.sideCheckered
282 : ['w', 'b'].includes(color
)
286 this.playerColor
== this.turn
&&
287 [this.turn
, "c"].includes(this.getColor(x
, y
))
291 // Does m2 un-do m1 ? (to disallow undoing checkered moves)
292 oppositeMoves(m1
, m2
) {
295 m2
.appear
.length
== 1 &&
296 m2
.vanish
.length
== 1 &&
297 m2
.appear
[0].c
== "c" &&
298 m1
.start
.x
== m2
.end
.x
&&
299 m1
.end
.x
== m2
.start
.x
&&
300 m1
.start
.y
== m2
.end
.y
&&
301 m1
.end
.y
== m2
.start
.y
306 const color
= this.turn
;
307 if (stage
== 2 && this.sideCheckered
== color
)
308 // Checkered cannot be under check (no king)
310 let kingPos
= super.searchKingPos(color
);
312 // Must consider both kings (attacked by checkered side)
313 kingPos
= [kingPos
, super.searchKingPos(C
.GetOppTurn(this.turn
))];
314 const oppCols
= this.getOppCols(color
);
315 return moves
.filter(m
=> {
319 res
= !this.oppositeMoves(this.cmove
, m
);
320 if (res
&& m
.appear
.length
> 0)
321 res
= !this.underCheck(kingPos
, oppCols
);
327 atLeastOneMove(color
) {
328 const myCols
= [color
, 'c'];
329 for (let i
= 0; i
< this.size
.x
; i
++) {
330 for (let j
= 0; j
< this.size
.y
; j
++) {
331 const colIJ
= this.getColor(i
, j
);
333 this.board
[i
][j
] != "" &&
335 (this.stage
== 1 && myCols
.includes(colIJ
)) ||
338 (this.sideCheckered
== color
&& colIJ
== 'c') ||
339 (this.sideCheckered
!= color
&& ['w', 'b'].includes(colIJ
))
344 const moves
= this.getPotentialMovesFrom([i
, j
]);
345 if (moves
.some(m
=> this.filterValid([m
]).length
>= 1))
353 underCheck(square_s
, oppCols
) {
354 if (this.stage
== 2 && oppCol
!= this.sideCheckered
)
355 return false; //checkered pieces is me, I'm not under check
356 return super.underAttack(square_s
, oppCols
);
360 if (move.appear
.length
> 0 && move.vanish
.length
> 0) {
363 [1, 6].includes(move.start
.x
) &&
364 move.vanish
[0].p
== V
.PAWN
&&
365 Math
.abs(move.end
.x
- move.start
.x
) == 2
367 // This move turns off a 2-squares pawn flag
368 this.pawnFlags
[move.start
.x
== 6 ? "w" : "b"][move.start
.y
] = false;
374 if (move.appear
.length
== 0 && move.vanish
.length
== 0) {
376 this.sideCheckered
= this.turn
;
379 super.postPlay(move);
380 this.cmove
= this.getCmove(move);
383 tryChangeTurn(move) {
384 if (move.appear
.length
> 0 && move.vanish
.length
> 0)
385 super.tryChangeTurn(move);
389 const color
= this.turn
;
390 if (this.stage
== 1) {
391 if (this.atLeastOneMove(color
))
393 // Artifically change turn, for checkered pawns
394 const oppTurn
= C
.GetOppTurn(color
);
396 const kingPos
= super.searchKingPos(color
)[0];
398 if (super.underAttack(kingPos
, [oppTurn
, 'c']))
399 res
= (color
== "w" ? "0-1" : "1-0");
404 if (this.sideCheckered
== color
) {
405 // Check if remaining checkered pieces: if none, I lost
406 if (this.board
.some(b
=> b
.some(cell
=> cell
[0] == 'c'))) {
407 if (!this.atLeastOneMove(color
))
411 return (color
== 'w' ? "0-1" : "1-0");
413 if (this.atLeastOneMove(color
))
415 let res
= super.underAttack(super.searchKingPos(color
)[0], ['c']);
417 res
= super.underAttack(super.searchKingPos(oppCol
)[0], ['c']);
419 return (color
== 'w' ? "0-1" : "1-0");