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
);
64 let parts
= super.getPartFen(o
);
65 parts
["cmove"] = this.getCmoveFen();
66 parts
["stage"] = this.getStageFen();
74 C
.CoordsToSquare(this.cmove
.start
) + C
.CoordsToSquare(this.cmove
.end
)
79 return (this.stage
+ this.sideCheckered
);
83 let fen
= super.getFlagsFen();
85 for (let c
of ["w", "b"])
86 for (let i
= 0; i
< 8; i
++)
87 fen
+= (this.pawnFlags
[c
][i
] ? "1" : "0");
92 return super.getPawnShift(color
== 'c' ? this.turn : color
);
96 let baseRes
= super.pieces(color
, x
, y
);
98 this.getPiece(x
, y
) == 'p' &&
100 this.getColor(x
, y
) == 'c'
102 // Checkered pawns on stage 2 are bidirectional
103 const initRank
= ((color
== 'w' && x
>= 6) || (color
== 'b' && x
<= 1));
108 steps: [[1, 0], [-1, 0]],
109 range: (initRank
? 2 : 1)
114 steps: [[1, 1], [1, -1], [-1, 1], [-1, -1]],
121 's': {"class": "checkered-pawn", moveas: 'p'},
122 'u': {"class": "checkered-rook", moveas: 'r'},
123 'o': {"class": "checkered-knight", moveas: 'n'},
124 'c': {"class": "checkered-bishop", moveas: 'b'},
125 't': {"class": "checkered-queen", moveas: 'q'}
127 return Object
.assign(baseRes
, checkered
);
130 setOtherVariables(fenParsed
) {
131 super.setOtherVariables(fenParsed
);
132 // Non-capturing last checkered move (if any)
133 const cmove
= fenParsed
.cmove
;
134 if (cmove
== "-") this.cmove
= null;
137 start: C
.SquareToCoords(cmove
.substr(0, 2)),
138 end: C
.SquareToCoords(cmove
.substr(2))
141 // Stage 1: as Checkered2. Stage 2: checkered pieces are autonomous
142 const stageInfo
= fenParsed
.stage
;
143 this.stage
= parseInt(stageInfo
[0], 10);
144 this.sideCheckered
= (this.stage
== 2 ? stageInfo
[1] : "");
148 super.setFlags(fenflags
); //castleFlags
150 w: [...Array(8)], //pawns can move 2 squares?
153 const flags
= fenflags
.substr(4); //skip first 4 letters, for castle
154 for (let c
of ["w", "b"]) {
155 for (let i
= 0; i
< 8; i
++)
156 this.pawnFlags
[c
][i
] = flags
.charAt((c
== "w" ? 0 : 8) + i
) == "1";
161 return [this.castleFlags
, this.pawnFlags
];
164 disaggregateFlags(flags
) {
165 this.castleFlags
= flags
[0];
166 this.pawnFlags
= flags
[1];
169 getEpSquare(moveOrSquare
) {
170 // At stage 2, all pawns can be captured en-passant
173 typeof moveOrSquare
!== "object" ||
174 (moveOrSquare
.appear
.length
> 0 && moveOrSquare
.appear
[0].c
!= 'c')
176 return super.getEpSquare(moveOrSquare
);
177 // Checkered or switch move: no en-passant
182 // No checkered move to undo at stage 2:
183 if (this.stage
== 1 && move.vanish
.length
== 1 && move.appear
[0].c
== "c")
184 return {start: move.start
, end: move.end
};
188 canTake([x1
, y1
], [x2
, y2
]) {
189 const color1
= this.getColor(x1
, y1
);
190 const color2
= this.getColor(x2
, y2
);
191 if (this.stage
== 2) {
192 // Black & White <-- takes --> Checkered
193 const color1
= this.getColor(x1
, y1
);
194 const color2
= this.getColor(x2
, y2
);
195 return color1
!= color2
&& [color1
, color2
].includes('c');
199 color2
!= "c" && //checkered aren't captured
200 (color1
!= "c" || color2
!= this.turn
)
204 postProcessPotentialMoves(moves
) {
205 if (this.stage
== 2 || moves
.length
== 0)
207 const color
= this.turn
;
208 // Apply "checkerization" of standard moves
209 const lastRank
= (color
== "w" ? 0 : 7);
210 const [x
, y
] = [moves
[0].start
.x
, moves
[0].start
.y
];
212 const piece
= this.getPiece(x
, y
);
213 // King is treated differently: it never turn checkered
215 // If at least one checkered piece, allow switching:
217 this.options
["withswitch"] &&
218 this.board
.some(b
=> b
.some(cell
=> cell
[0] == 'c'))
220 const oppCol
= C
.GetOppCol(color
);
221 const oppKingPos
= this.searchKingPos(oppCol
)[0];
224 start: { x: x
, y: y
},
225 end: { x: oppKingPos
[0], y: oppKingPos
[1] },
234 // Filter out forbidden pawn moves
235 moves
= moves
.filter(m
=> {
236 if (m
.vanish
[0].p
== 'p') {
238 Math
.abs(m
.end
.x
- m
.start
.x
) == 2 &&
239 !this.pawnFlags
[this.turn
][m
.start
.y
]
241 return false; //forbidden 2-squares jumps
244 this.board
[m
.end
.x
][m
.end
.y
] == V
.EMPTY
&&
245 m
.vanish
.length
== 2 &&
246 this.getColor(m
.start
.x
, m
.start
.y
) == "c"
248 return false; //checkered pawns cannot take en-passant
256 if (m
.vanish
.length
== 2 && m
.appear
.length
== 1)
260 m
.appear
[0].p
!= m
.vanish
[1].p
&&
261 // No choice if promotion:
262 (m
.vanish
[0].p
!= 'p' || m
.end
.x
!= lastRank
)
264 // Add transformation into captured piece
265 let m2
= JSON
.parse(JSON
.stringify(m
));
266 m2
.appear
[0].p
= m
.vanish
[1].p
;
271 return moves
.concat(extraMoves
);
274 canIplay(side
, [x
, y
]) {
275 if (this.stage
== 2) {
276 const color
= this.getColor(x
, y
);
278 this.turn
== this.sideCheckered
280 : ['w', 'b'].includes(color
)
283 return side
== this.turn
&& [side
, "c"].includes(this.getColor(x
, y
));
286 // Does m2 un-do m1 ? (to disallow undoing checkered moves)
287 oppositeMoves(m1
, m2
) {
290 m2
.appear
.length
== 1 &&
291 m2
.vanish
.length
== 1 &&
292 m2
.appear
[0].c
== "c" &&
293 m1
.start
.x
== m2
.end
.x
&&
294 m1
.end
.x
== m2
.start
.x
&&
295 m1
.start
.y
== m2
.end
.y
&&
296 m1
.end
.y
== m2
.start
.y
301 const color
= this.turn
;
302 if (stage
== 2 && this.sideCheckered
== color
)
303 // Checkered cannot be under check (no king)
305 let kingPos
= super.searchKingPos(color
);
306 const oppCol
= C
.GetOppCol(color
);
308 // Must consider both kings (attacked by checkered side)
309 kingPos
= [kingPos
, super.searchKingPos(oppCol
)];
310 return moves
.filter(m
=> {
314 res
= !this.oppositeMoves(this.cmove
, m
);
315 if (res
&& m
.appear
.length
> 0)
316 // NOTE: oppCol might be inaccurate; fixed in underCheck()
317 res
= !this.underCheck(kingPos
, oppCol
);
323 atLeastOneMove(color
) {
324 const oppCol
= C
.GetOppCol(color
);
325 for (let i
= 0; i
< this.size
.x
; i
++) {
326 for (let j
= 0; j
< this.size
.y
; j
++) {
327 const colIJ
= this.getColor(i
, j
);
329 this.board
[i
][j
] != "" &&
331 (this.stage
== 1 && colIJ
!= oppCol
) ||
334 (this.sideCheckered
== color
&& colIJ
== 'c') ||
335 (this.sideCheckered
!= color
&& ['w', 'b'].includes(colIJ
))
340 const moves
= this.getPotentialMovesFrom([i
, j
]);
341 if (moves
.some(m
=> this.filterValid([m
]).length
>= 1))
349 underCheck(square_s
, oppCol
) {
351 return super.underAttack(square_s
, [oppCol
, 'c']);
352 if (oppCol
!= this.sideCheckered
)
353 return false; //checkered pieces is me, I'm not under check
354 return super.underAttack(square_s
, 'c');
358 if (move.appear
.length
> 0 && move.vanish
.length
> 0) {
361 [1, 6].includes(move.start
.x
) &&
362 move.vanish
[0].p
== V
.PAWN
&&
363 Math
.abs(move.end
.x
- move.start
.x
) == 2
365 // This move turns off a 2-squares pawn flag
366 this.pawnFlags
[move.start
.x
== 6 ? "w" : "b"][move.start
.y
] = false;
372 if (move.appear
.length
== 0 && move.vanish
.length
== 0) {
374 this.sideCheckered
= this.turn
;
377 super.postPlay(move);
378 this.cmove
= this.getCmove(move);
381 tryChangeTurn(move) {
382 if (move.appear
.length
> 0 && move.vanish
.length
> 0)
383 super.tryChangeTurn(move);
387 const color
= this.turn
;
388 if (this.stage
== 1) {
389 if (this.atLeastOneMove(color
))
391 // Artifically change turn, for checkered pawns
392 const oppCol
= C
.GetOppCol(color
);
394 const kingPos
= super.searchKingPos(color
)[0];
396 if (super.underAttack(kingPos
, [oppCol
, 'c']))
397 res
= (color
== "w" ? "0-1" : "1-0");
402 if (this.sideCheckered
== color
) {
403 // Check if remaining checkered pieces: if none, I lost
404 if (this.board
.some(b
=> b
.some(cell
=> cell
[0] == 'c'))) {
405 if (!this.atLeastOneMove(color
))
409 return (color
== 'w' ? "0-1" : "1-0");
411 if (this.atLeastOneMove(color
))
413 let res
= super.underAttack(super.searchKingPos(color
)[0], 'c');
415 res
= super.underAttack(super.searchKingPos(oppCol
)[0], 'c');
417 return (color
== 'w' ? "0-1" : "1-0");