6ef3353b57bdd2ca49a6f42d3bfe76b4f754291a
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",
18 // Game modifiers (using "elementary variants"). Default: false
33 const checkered_codes
= {
40 if (b
[0] == "c") 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
);
65 setOtherVariables(fenParsed
) {
66 super.setOtherVariables(fenParsed
);
67 // Non-capturing last checkered move (if any)
68 const cmove
= fenParsed
.cmove
;
69 if (cmove
== "-") this.cmove
= null;
72 start: C
.SquareToCoords(cmove
.substr(0, 2)),
73 end: C
.SquareToCoords(cmove
.substr(2))
76 // Stage 1: as Checkered2. Stage 2: checkered pieces are autonomous
77 const stageInfo
= fenParsed
.stage
;
78 this.stage
= parseInt(stageInfo
[0], 10);
79 this.canSwitch
= (this.stage
== 1 && stageInfo
[1] != '-');
80 this.sideCheckered
= (this.stage
== 2 ? stageInfo
[1] : undefined);
84 super.setFlags(fenflags
); //castleFlags
86 w: [...Array(8)], //pawns can move 2 squares?
89 const flags
= fenflags
.substr(4); //skip first 4 letters, for castle
90 for (let c
of ["w", "b"]) {
91 for (let i
= 0; i
< 8; i
++)
92 this.pawnFlags
[c
][i
] = flags
.charAt((c
== "w" ? 0 : 8) + i
) == "1";
97 return [this.castleFlags
, this.pawnFlags
];
100 disaggregateFlags(flags
) {
101 this.castleFlags
= flags
[0];
102 this.pawnFlags
= flags
[1];
105 getEpSquare(moveOrSquare
) {
106 // At stage 2, all pawns can be captured en-passant
109 typeof moveOrSquare
!== "object" ||
110 (moveOrSquare
.appear
.length
> 0 && moveOrSquare
.appear
[0].c
!= 'c')
112 return super.getEpSquare(moveOrSquare
);
113 // Checkered or switch move: no en-passant
118 // No checkered move to undo at stage 2:
119 if (this.stage
== 1 && move.vanish
.length
== 1 && move.appear
[0].c
== "c")
120 return { start: move.start
, end: move.end
};
124 canTake([x1
, y1
], [x2
, y2
]) {
125 const color1
= this.getColor(x1
, y1
);
126 const color2
= this.getColor(x2
, y2
);
127 if (this.stage
== 2) {
128 // Black & White <-- takes --> Checkered
129 const color1
= this.getColor(x1
, y1
);
130 const color2
= this.getColor(x2
, y2
);
131 return color1
!= color2
&& [color1
, color2
].includes('c');
133 // Checkered aren't captured
137 (color1
!= "c" || color2
!= this.turn
)
142 getPotentialMovesFrom([x
, y
], noswitch
) {
143 let standardMoves
= super.getPotentialMovesFrom([x
, y
]);
144 if (this.stage
== 1) {
145 const color
= this.turn
;
146 // Post-processing: apply "checkerization" of standard moves
147 const lastRank
= (color
== "w" ? 0 : 7);
149 // King is treated differently: it never turn checkered
150 if (this.getPiece(x
, y
) == V
.KING
) {
151 // If at least one checkered piece, allow switching:
153 this.canSwitch
&& !noswitch
&&
154 this.board
.some(b
=> b
.some(cell
=> cell
[0] == 'c'))
156 const oppCol
= V
.GetOppCol(color
);
159 start: { x: x
, y: y
},
160 end: { x: this.kingPos
[oppCol
][0], y: this.kingPos
[oppCol
][1] },
166 return standardMoves
.concat(moves
);
168 standardMoves
.forEach(m
=> {
169 if (m
.vanish
[0].p
== V
.PAWN
) {
171 Math
.abs(m
.end
.x
- m
.start
.x
) == 2 &&
172 !this.pawnFlags
[this.turn
][m
.start
.y
]
174 return; //skip forbidden 2-squares jumps
177 this.board
[m
.end
.x
][m
.end
.y
] == V
.EMPTY
&&
178 m
.vanish
.length
== 2 &&
179 this.getColor(m
.start
.x
, m
.start
.y
) == "c"
181 return; //checkered pawns cannot take en-passant
184 if (m
.vanish
.length
== 1)
188 // A capture occured (m.vanish.length == 2)
192 // Avoid promotions (already treated):
193 m
.appear
[0].p
!= m
.vanish
[1].p
&&
194 (m
.vanish
[0].p
!= V
.PAWN
|| m
.end
.x
!= lastRank
)
196 // Add transformation into captured piece
197 let m2
= JSON
.parse(JSON
.stringify(m
));
198 m2
.appear
[0].p
= m
.vanish
[1].p
;
205 return standardMoves
;
208 // TODO: merge this into pieces() method
209 getPotentialPawnMoves([x
, y
]) {
210 const color
= this.getColor(x
, y
);
211 if (this.stage
== 2) {
212 const saveTurn
= this.turn
;
213 if (this.sideCheckered
== this.turn
) {
214 // Cannot change PawnSpecs.bidirectional, so cheat a little:
216 const wMoves
= super.getPotentialPawnMoves([x
, y
]);
218 const bMoves
= super.getPotentialPawnMoves([x
, y
]);
219 this.turn
= saveTurn
;
220 return wMoves
.concat(bMoves
);
222 // Playing with both colors:
224 const moves
= super.getPotentialPawnMoves([x
, y
]);
225 this.turn
= saveTurn
;
228 let moves
= super.getPotentialPawnMoves([x
, y
]);
229 // Post-process: set right color for checkered moves
232 m
.appear
[0].c
= 'c'; //may be done twice if capture
239 canIplay(side
, [x
, y
]) {
240 if (this.stage
== 2) {
241 const color
= this.getColor(x
, y
);
243 this.turn
== this.sideCheckered
245 : ['w', 'b'].includes(color
)
248 return side
== this.turn
&& [side
, "c"].includes(this.getColor(x
, y
));
251 // Does m2 un-do m1 ? (to disallow undoing checkered moves)
252 oppositeMoves(m1
, m2
) {
255 m2
.appear
[0].c
== "c" &&
256 m2
.appear
.length
== 1 &&
257 m2
.vanish
.length
== 1 &&
258 m1
.start
.x
== m2
.end
.x
&&
259 m1
.end
.x
== m2
.start
.x
&&
260 m1
.start
.y
== m2
.end
.y
&&
261 m1
.end
.y
== m2
.start
.y
265 // TODO: adapt, merge
267 if (moves
.length
== 0) return [];
268 const color
= this.turn
;
269 const oppCol
= V
.GetOppCol(color
);
270 const L
= this.cmoves
.length
; //at least 1: init from FEN
271 const stage
= this.stage
; //may change if switch
272 return moves
.filter(m
=> {
273 // Checkered cannot be under check (no king)
274 if (stage
== 2 && this.sideCheckered
== color
) return true;
278 if (m
.appear
.length
== 0 && m
.vanish
.length
== 0) {
279 // Special "switch" move: kings must not be attacked by checkered.
280 // Not checking for oppositeMoves here: checkered are autonomous
282 !this.isAttacked(this.kingPos
['w'], ['c']) &&
283 !this.isAttacked(this.kingPos
['b'], ['c']) &&
284 this.getAllPotentialMoves().length
> 0
287 else res
= !this.oppositeMoves(this.cmoves
[L
- 1], m
);
289 if (res
&& m
.appear
.length
> 0) res
= !this.underCheck(color
);
290 // At stage 2, side with B & W can be undercheck with both kings:
291 if (res
&& stage
== 2) res
= !this.underCheck(oppCol
);
298 const color
= this.turn
;
299 const oppCol
= V
.GetOppCol(color
);
300 for (let i
= 0; i
< V
.size
.x
; i
++) {
301 for (let j
= 0; j
< V
.size
.y
; j
++) {
302 const colIJ
= this.getColor(i
, j
);
304 this.board
[i
][j
] != V
.EMPTY
&&
306 (this.stage
== 1 && colIJ
!= oppCol
) ||
309 (this.sideCheckered
== color
&& colIJ
== 'c') ||
310 (this.sideCheckered
!= color
&& ['w', 'b'].includes(colIJ
))
315 const moves
= this.getPotentialMovesFrom([i
, j
], "noswitch");
316 if (moves
.length
> 0) {
317 for (let k
= 0; k
< moves
.length
; k
++)
318 if (this.filterValid([moves
[k
]]).length
> 0) return true;
329 return this.isAttacked(this.kingPos
[color
], [V
.GetOppCol(color
), "c"]);
330 if (color
== this.sideCheckered
) return false;
332 this.isAttacked(this.kingPos
['w'], ["c"]) ||
333 this.isAttacked(this.kingPos
['b'], ["c"])
338 move.flags
= JSON
.stringify(this.aggregateFlags());
339 this.epSquares
.push(this.getEpSquare(move));
340 V
.PlayOnBoard(this.board
, move);
341 if (move.appear
.length
> 0 || move.vanish
.length
> 0)
343 this.turn
= V
.GetOppCol(this.turn
);
350 if (move.appear
.length
== 0 && move.vanish
.length
== 0) {
352 this.sideCheckered
= this.turn
;
355 const c
= move.vanish
[0].c
;
356 const piece
= move.vanish
[0].p
;
357 if (piece
== V
.KING
) {
358 this.kingPos
[c
][0] = move.appear
[0].x
;
359 this.kingPos
[c
][1] = move.appear
[0].y
;
361 super.updateCastleFlags(move, piece
);
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;
371 this.cmove
= this.getCmove(move);
375 const color
= this.turn
;
376 if (this.stage
== 1) {
377 if (this.atLeastOneMove()) return "*";
378 // Artifically change turn, for checkered pawns
379 this.turn
= V
.GetOppCol(this.turn
);
381 this.isAttacked(this.kingPos
[color
], [V
.GetOppCol(color
), "c"])
386 this.turn
= V
.GetOppCol(this.turn
);
390 if (this.sideCheckered
== this.turn
) {
391 // Check if remaining checkered pieces: if none, I lost
392 if (this.board
.some(b
=> b
.some(cell
=> cell
[0] == 'c'))) {
393 if (!this.atLeastOneMove()) return "1/2";
396 return color
== 'w' ? "0-1" : "1-0";
398 if (this.atLeastOneMove()) return "*";
399 let res
= this.isAttacked(this.kingPos
['w'], ["c"]);
400 if (!res
) res
= this.isAttacked(this.kingPos
['b'], ["c"]);
401 if (res
) return color
== 'w' ? "0-1" : "1-0";
406 static GenRandInitFen(options
) {
407 const baseFen
= ChessRules
.GenRandInitFen(options
);
409 // Add 16 pawns flags + empty cmove + stage == 1:
410 baseFen
.slice(0, -2) + "1111111111111111 - - 1" +
411 (!options
["switch"] ? '-' : "")
420 const L
= this.cmoves
.length
;
424 : ChessRules
.CoordsToSquare(this.cmoves
[L
- 1].start
) +
425 ChessRules
.CoordsToSquare(this.cmoves
[L
- 1].end
)
430 if (this.stage
== 1) return "1" + (!this.canSwitch
? '-' : "");
432 return "2" + this.sideCheckered
;
437 super.getFen() + " " + this.getCmoveFen() + " " + this.getStageFen()
442 let fen
= super.getFlagsFen();
444 for (let c
of ["w", "b"])
445 for (let i
= 0; i
< 8; i
++) fen
+= (this.pawnFlags
[c
][i
] ? "1" : "0");