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 baseRes
= super.pieces(color
, x
, y
);
66 this.getPiece(x
, y
) == 'p' &&
68 this.getColor(x
, y
) == 'c'
70 // Checkered pawns on stage 2 are bidirectional
71 const initRank
= ((color
== 'w' && x
>= 6) || (color
== 'b' && x
<= 1));
76 steps: [[1, 0], [-1, 0]],
77 range: (initRank
? 2 : 1)
82 steps: [[1, 1], [1, -1], [-1, 1], [-1, -1]],
89 's': {"class": "checkered-pawn", moveas: 'p'},
90 'u': {"class": "checkered-rook", moveas: 'r'},
91 'o': {"class": "checkered-knight", moveas: 'n'},
92 'c': {"class": "checkered-bishop", moveas: 'b'},
93 't': {"class": "checkered-queen", moveas: 'q'}
95 return Object
.assign(baseRes
, checkered
);
98 setOtherVariables(fenParsed
) {
99 super.setOtherVariables(fenParsed
);
100 // Non-capturing last checkered move (if any)
101 const cmove
= fenParsed
.cmove
;
102 if (cmove
== "-") this.cmove
= null;
105 start: C
.SquareToCoords(cmove
.substr(0, 2)),
106 end: C
.SquareToCoords(cmove
.substr(2))
109 // Stage 1: as Checkered2. Stage 2: checkered pieces are autonomous
110 const stageInfo
= fenParsed
.stage
;
111 this.stage
= parseInt(stageInfo
[0], 10);
112 this.canSwitch
= (this.stage
== 1 && stageInfo
[1] != '-');
113 this.sideCheckered
= (this.stage
== 2 ? stageInfo
[1] : undefined);
117 super.setFlags(fenflags
); //castleFlags
119 w: [...Array(8)], //pawns can move 2 squares?
122 const flags
= fenflags
.substr(4); //skip first 4 letters, for castle
123 for (let c
of ["w", "b"]) {
124 for (let i
= 0; i
< 8; i
++)
125 this.pawnFlags
[c
][i
] = flags
.charAt((c
== "w" ? 0 : 8) + i
) == "1";
130 return [this.castleFlags
, this.pawnFlags
];
133 disaggregateFlags(flags
) {
134 this.castleFlags
= flags
[0];
135 this.pawnFlags
= flags
[1];
138 getEpSquare(moveOrSquare
) {
139 // At stage 2, all pawns can be captured en-passant
142 typeof moveOrSquare
!== "object" ||
143 (moveOrSquare
.appear
.length
> 0 && moveOrSquare
.appear
[0].c
!= 'c')
145 return super.getEpSquare(moveOrSquare
);
146 // Checkered or switch move: no en-passant
151 // No checkered move to undo at stage 2:
152 if (this.stage
== 1 && move.vanish
.length
== 1 && move.appear
[0].c
== "c")
153 return { start: move.start
, end: move.end
};
157 canTake([x1
, y1
], [x2
, y2
]) {
158 const color1
= this.getColor(x1
, y1
);
159 const color2
= this.getColor(x2
, y2
);
160 if (this.stage
== 2) {
161 // Black & White <-- takes --> Checkered
162 const color1
= this.getColor(x1
, y1
);
163 const color2
= this.getColor(x2
, y2
);
164 return color1
!= color2
&& [color1
, color2
].includes('c');
168 color2
!= "c" && //checkered aren't captured
169 (color1
!= "c" || color2
!= this.turn
)
173 postProcessPotentialMoves(moves
) {
174 if (this.stage
== 2 || moves
.length
== 0)
176 const color
= this.turn
;
177 // Apply "checkerization" of standard moves
178 const lastRank
= (color
== "w" ? 0 : 7);
179 const [x
, y
] = [moves
[0].start
.x
, moves
[0].start
.y
];
181 const piece
= this.getPiece(x
, y
);
182 // King is treated differently: it never turn checkered
184 // If at least one checkered piece, allow switching:
186 this.canSwitch
&& !noswitch
&&
187 this.board
.some(b
=> b
.some(cell
=> cell
[0] == 'c'))
189 const oppCol
= C
.GetOppCol(color
);
190 const oppKingPos
= this.searchKingPos(oppCol
)[0];
193 start: { x: x
, y: y
},
194 end: { x: oppKingPos
[0], y: oppKingPos
[1] },
203 // Filter out forbidden pawn moves
204 moves
= moves
.filter(m
=> {
205 if (m
.vanish
[0].p
== 'p') {
207 Math
.abs(m
.end
.x
- m
.start
.x
) == 2 &&
208 !this.pawnFlags
[this.turn
][m
.start
.y
]
210 return false; //forbidden 2-squares jumps
213 this.board
[m
.end
.x
][m
.end
.y
] == V
.EMPTY
&&
214 m
.vanish
.length
== 2 &&
215 this.getColor(m
.start
.x
, m
.start
.y
) == "c"
217 return false; //checkered pawns cannot take en-passant
225 if (m
.vanish
.length
== 2 && m
.appear
.length
== 1)
229 m
.appear
[0].p
!= m
.vanish
[1].p
&&
230 // No choice if promotion:
231 (m
.vanish
[0].p
!= 'p' || m
.end
.x
!= lastRank
)
233 // Add transformation into captured piece
234 let m2
= JSON
.parse(JSON
.stringify(m
));
235 m2
.appear
[0].p
= m
.vanish
[1].p
;
240 return moves
.concat(extraMoves
);
243 canIplay(side
, [x
, y
]) {
244 if (this.stage
== 2) {
245 const color
= this.getColor(x
, y
);
247 this.turn
== this.sideCheckered
249 : ['w', 'b'].includes(color
)
252 return side
== this.turn
&& [side
, "c"].includes(this.getColor(x
, y
));
255 // Does m2 un-do m1 ? (to disallow undoing checkered moves)
256 oppositeMoves(m1
, m2
) {
259 m2
.appear
[0].c
== "c" &&
260 m2
.appear
.length
== 1 &&
261 m2
.vanish
.length
== 1 &&
262 m1
.start
.x
== m2
.end
.x
&&
263 m1
.end
.x
== m2
.start
.x
&&
264 m1
.start
.y
== m2
.end
.y
&&
265 m1
.end
.y
== m2
.start
.y
269 // TODO: adapt, merge
271 if (moves
.length
== 0)
273 const color
= this.turn
;
274 const oppCol
= C
.GetOppCol(color
);
275 return moves
.filter(m
=> {
276 // Checkered cannot be under check (no king)
277 if (stage
== 2 && this.sideCheckered
== color
)
282 if (m
.appear
.length
== 0 && m
.vanish
.length
== 0) {
283 // Special "switch" move: kings must not be attacked by checkered.
284 // Not checking for oppositeMoves here: checkered are autonomous
286 !this.isAttacked(this.kingPos
['w'], ['c']) &&
287 !this.isAttacked(this.kingPos
['b'], ['c']) &&
288 this.getAllPotentialMoves().length
> 0
291 else res
= !this.oppositeMoves(this.cmove
, m
);
293 if (res
&& m
.appear
.length
> 0) res
= !this.underCheck(color
);
294 // At stage 2, side with B & W can be undercheck with both kings:
295 if (res
&& stage
== 2) res
= !this.underCheck(oppCol
);
302 const color
= this.turn
;
303 const oppCol
= V
.GetOppCol(color
);
304 for (let i
= 0; i
< V
.size
.x
; i
++) {
305 for (let j
= 0; j
< V
.size
.y
; j
++) {
306 const colIJ
= this.getColor(i
, j
);
308 this.board
[i
][j
] != V
.EMPTY
&&
310 (this.stage
== 1 && colIJ
!= oppCol
) ||
313 (this.sideCheckered
== color
&& colIJ
== 'c') ||
314 (this.sideCheckered
!= color
&& ['w', 'b'].includes(colIJ
))
319 const moves
= this.getPotentialMovesFrom([i
, j
]);
320 if (moves
.length
> 0) {
321 for (let k
= 0; k
< moves
.length
; k
++)
322 if (this.filterValid([moves
[k
]]).length
> 0) return true;
333 return this.isAttacked(this.kingPos
[color
], [V
.GetOppCol(color
), "c"]);
334 if (color
== this.sideCheckered
) return false;
336 this.isAttacked(this.kingPos
['w'], ["c"]) ||
337 this.isAttacked(this.kingPos
['b'], ["c"])
342 move.flags
= JSON
.stringify(this.aggregateFlags());
343 this.epSquares
.push(this.getEpSquare(move));
344 V
.PlayOnBoard(this.board
, move);
345 if (move.appear
.length
> 0 || move.vanish
.length
> 0)
347 this.turn
= V
.GetOppCol(this.turn
);
354 if (move.appear
.length
== 0 && move.vanish
.length
== 0) {
356 this.sideCheckered
= this.turn
;
359 const c
= move.vanish
[0].c
;
360 const piece
= move.vanish
[0].p
;
361 if (piece
== V
.KING
) {
362 this.kingPos
[c
][0] = move.appear
[0].x
;
363 this.kingPos
[c
][1] = move.appear
[0].y
;
365 super.updateCastleFlags(move, piece
);
367 [1, 6].includes(move.start
.x
) &&
368 move.vanish
[0].p
== V
.PAWN
&&
369 Math
.abs(move.end
.x
- move.start
.x
) == 2
371 // This move turns off a 2-squares pawn flag
372 this.pawnFlags
[move.start
.x
== 6 ? "w" : "b"][move.start
.y
] = false;
375 this.cmove
= this.getCmove(move);
379 const color
= this.turn
;
380 if (this.stage
== 1) {
381 if (this.atLeastOneMove()) return "*";
382 // Artifically change turn, for checkered pawns
383 this.turn
= V
.GetOppCol(this.turn
);
385 this.isAttacked(this.kingPos
[color
], [V
.GetOppCol(color
), "c"])
390 this.turn
= V
.GetOppCol(this.turn
);
394 if (this.sideCheckered
== this.turn
) {
395 // Check if remaining checkered pieces: if none, I lost
396 if (this.board
.some(b
=> b
.some(cell
=> cell
[0] == 'c'))) {
397 if (!this.atLeastOneMove()) return "1/2";
400 return color
== 'w' ? "0-1" : "1-0";
402 if (this.atLeastOneMove()) return "*";
403 let res
= this.isAttacked(this.kingPos
['w'], ["c"]);
404 if (!res
) res
= this.isAttacked(this.kingPos
['b'], ["c"]);
405 if (res
) return color
== 'w' ? "0-1" : "1-0";
410 static GenRandInitFen(options
) {
411 const baseFen
= ChessRules
.GenRandInitFen(options
);
413 // Add 16 pawns flags + empty cmove + stage == 1:
414 baseFen
.slice(0, -2) + "1111111111111111 - - 1" +
415 (!options
["switch"] ? '-' : "")
424 const L
= this.cmoves
.length
;
428 : ChessRules
.CoordsToSquare(this.cmoves
[L
- 1].start
) +
429 ChessRules
.CoordsToSquare(this.cmoves
[L
- 1].end
)
434 if (this.stage
== 1) return "1" + (!this.canSwitch
? '-' : "");
436 return "2" + this.sideCheckered
;
441 super.getFen() + " " + this.getCmoveFen() + " " + this.getStageFen()
446 let fen
= super.getFlagsFen();
448 for (let c
of ["w", "b"])
449 for (let i
= 0; i
< 8; i
++) fen
+= (this.pawnFlags
[c
][i
] ? "1" : "0");