1 import { ChessRules
, Move
, PiPo
} from "@/base_rules";
2 import { SuicideRules
} from "@/variants/Suicide";
4 export class ChakartRules
extends ChessRules
{
5 static get PawnSpecs() {
6 return SuicideRules
.PawnSpecs
;
9 static get HasCastle() {
13 static get CorrConfirm() {
14 // Because of bonus effects
18 static get CanAnalyze() {
22 hoverHighlight(x
, y
) {
23 if (this.subTurn
== 1) return false;
24 const L
= this.firstMove
.length
;
25 const fm
= this.firstMove
[L
-1];
26 if (fm
.end
.effect
!= 0) return false;
27 const deltaX
= Math
.abs(fm
.end
.x
- x
);
28 const deltaY
= Math
.abs(fm
.end
.y
- y
);
30 (deltaX
== 0 && deltaY
== 0) ||
32 this.board
[x
][y
] == V
.EMPTY
&&
34 (fm
.vanish
[0].p
== V
.ROOK
&& deltaX
== 1 && deltaY
== 1) ||
35 (fm
.vanish
[0].p
== V
.BISHOP
&& deltaX
+ deltaY
== 1)
41 static get IMMOBILIZE_CODE() {
52 static get IMMOBILIZE_DECODE() {
63 static get INVISIBLE_QUEEN() {
67 // Fictive color 'a', bomb banana mushroom egg
69 // Doesn't collide with bishop because color 'a'
78 static get MUSHROOM() {
84 ChessRules
.PIECES
.concat(
85 Object
.keys(V
.IMMOBILIZE_DECODE
)).concat(
86 [V
.BANANA
, V
.BOMB
, V
.EGG
, V
.MUSHROOM
, V
.INVISIBLE_QUEEN
])
94 b
[1] == V
.INVISIBLE_QUEEN
||
95 Object
.keys(V
.IMMOBILIZE_DECODE
).includes(b
[1])
102 static ParseFen(fen
) {
103 const fenParts
= fen
.split(" ");
104 return Object
.assign(
105 ChessRules
.ParseFen(fen
),
106 { captured: fenParts
[5] }
110 // King can be l or L (immobilized) --> similar to Alice variant
111 static IsGoodPosition(position
) {
112 if (position
.length
== 0) return false;
113 const rows
= position
.split("/");
114 if (rows
.length
!= V
.size
.x
) return false;
115 let kings
= { "k": 0, "K": 0, 'l': 0, 'L': 0 };
116 for (let row
of rows
) {
118 for (let i
= 0; i
< row
.length
; i
++) {
119 if (['K','k','L','l'].includes(row
[i
])) kings
[row
[i
]]++;
120 if (V
.PIECES
.includes(row
[i
].toLowerCase())) sumElts
++;
122 const num
= parseInt(row
[i
]);
123 if (isNaN(num
)) return false;
127 if (sumElts
!= V
.size
.y
) return false;
129 if (kings
['k'] + kings
['l'] != 1 || kings
['K'] + kings
['L'] != 1)
134 static IsGoodFlags(flags
) {
135 // 4 for Peach + Mario w, b
136 return !!flags
.match(/^[01]{4,4}$/);
140 // King can send shell? Queen can be invisible?
142 w: [{ 'k': false, 'q': false }],
143 b: [{ 'k': false, 'q': false }]
145 for (let c
of ["w", "b"]) {
146 for (let p
of ['k', 'q']) {
147 this.powerFlags
[c
][p
] =
148 fenFlags
.charAt((c
== "w" ? 0 : 2) + (p
== 'k' ? 0 : 1)) == "1";
154 return this.powerFlags
;
157 disaggregateFlags(flags
) {
158 this.powerFlags
= flags
;
162 return super.getFen() + " " + this.getCapturedFen();
166 return super.getFenForRepeat() + "_" + this.getCapturedFen();
170 let counts
= [...Array(10).fill(0)];
172 for (let p
of [V
.ROOK
, V
.KNIGHT
, V
.BISHOP
, V
.QUEEN
, V
.PAWN
]) {
173 counts
[i
] = this.captured
["w"][p
];
174 counts
[5 + i
] = this.captured
["b"][p
];
177 return counts
.join("");
180 setOtherVariables(fen
) {
181 const fenParsed
= V
.ParseFen(fen
);
182 // Initialize captured pieces' counts from FEN
185 [V
.ROOK
]: parseInt(fenParsed
.captured
[0]),
186 [V
.KNIGHT
]: parseInt(fenParsed
.captured
[1]),
187 [V
.BISHOP
]: parseInt(fenParsed
.captured
[2]),
188 [V
.QUEEN
]: parseInt(fenParsed
.captured
[3]),
189 [V
.PAWN
]: parseInt(fenParsed
.captured
[4]),
192 [V
.ROOK
]: parseInt(fenParsed
.captured
[5]),
193 [V
.KNIGHT
]: parseInt(fenParsed
.captured
[6]),
194 [V
.BISHOP
]: parseInt(fenParsed
.captured
[7]),
195 [V
.QUEEN
]: parseInt(fenParsed
.captured
[8]),
196 [V
.PAWN
]: parseInt(fenParsed
.captured
[9]),
206 for (let c
of ["w", "b"])
207 for (let p
of ['k', 'q']) fen
+= (this.powerFlags
[c
][p
] ? "1" : "0");
211 static get RESERVE_PIECES() {
212 return [V
.PAWN
, V
.ROOK
, V
.KNIGHT
, V
.BISHOP
, V
.QUEEN
];
215 getReserveMoves([x
, y
]) {
216 const color
= this.turn
;
217 const p
= V
.RESERVE_PIECES
[y
];
218 if (this.reserve
[color
][p
] == 0) return [];
220 const start
= (color
== 'w' && p
== V
.PAWN
? 1 : 0);
221 const end
= (color
== 'b' && p
== V
.PAWN
? 7 : 8);
222 for (let i
= start
; i
< end
; i
++) {
223 for (let j
= 0; j
< V
.size
.y
; j
++) {
224 if (this.board
[i
][j
] == V
.EMPTY
) {
235 start: { x: x
, y: y
}, //a bit artificial...
245 getPotentialMovesFrom([x
, y
]) {
246 if (this.subTurn
== 1) return super.getPotentialMovesFrom([x
, y
]);
247 if (this.subTurn
== 2) {
249 const L
= this.firstMove
.length
;
250 const fm
= this.firstMove
[L
-1];
251 switch (fm
.end
.effect
) {
252 // case 0: a click is required (banana or bomb)
254 // Exchange position with any piece
255 for (let i
=0; i
<8; i
++) {
256 for (let j
=0; j
<8; j
++) {
257 const colIJ
= this.getColor(i
, j
);
261 this.board
[i
][j
] != V
.EMPTY
&&
264 const movedUnit
= new PiPo({
268 p: this.getPiece(i
, j
)
270 let mMove
= this.getBasicMove([x
, y
], [i
, j
]);
271 mMove
.appear
.push(movedUnit
);
278 // Resurrect a captured piece
279 if (x
>= V
.size
.x
) moves
= this.getReserveMoves([x
, y
]);
282 // Play again with the same piece
283 if (fm
.end
.x
== x
&& fm
.end
.y
== y
)
284 moves
= super.getPotentialMovesFrom([x
, y
]);
291 getBasicMove([x1
, y1
], [x2
, y2
], tr
) {
292 // TODO: if this.subTurn == 2 :: no mushroom effect
293 // (first, transformation. then:)
294 // Apply mushroom, bomb or banana effect (hidden to the player).
295 // Determine egg effect, too, and apply its first part if possible.
296 // add egg + add mushroom for pawns.
297 let move = super.getBasicMove([x1
, y1
], [x2
, y2
]);
300 // Infer move type based on its effects (used to decide subTurn 1 --> 2)
301 // --> impossible étant donné juste first part (egg --> effect?)
302 // => stocker l'effet (i, ii ou iii) dans le coup directement,
303 // Pas terrible, mais y'aura pas 36 variantes comme ça. Disons end.effect == 0, 1, 2 ou 3
304 // 0 => tour ou fou, pose potentielle.
305 // If queen can be invisible, add move same start + end but final type changes
306 // set move.end.effect (if subTurn --> 2)
309 getEnpassantCaptures([x
, y
], shiftX
) {
310 const Lep
= this.epSquares
.length
;
311 const epSquare
= this.epSquares
[Lep
- 1]; //always at least one element
312 let enpassantMove
= null;
315 epSquare
.x
== x
+ shiftX
&&
316 Math
.abs(epSquare
.y
- y
) == 1
318 // Not using this.getBasicMove() because the mushroom has no effect
319 enpassantMove
= super.getBasicMove([x
, y
], [epSquare
.x
, epSquare
.y
]);
320 enpassantMove
.vanish
.push({
324 c: this.getColor(x
, epSquare
.y
)
327 return !!enpassantMove
? [enpassantMove
] : [];
330 getPotentialQueenMoves(sq
) {
331 const normalMoves
= super.getPotentialQueenMoves(sq
);
332 // If flag allows it, add 'invisible movements'
333 let invisibleMoves
= [];
334 if (this.powerFlags
[this.turn
][V
.QUEEN
]) {
335 normalMoves
.forEach(m
=> {
336 if (m
.vanish
.length
== 1) {
337 let im
= JSON
.parse(JSON
.stringify(m
));
338 m
.appear
[0].p
= V
.INVISIBLE_QUEEN
;
339 invisibleMoves
.push(im
);
343 return normalMoves
.concat(invisibleMoves
);
346 getPotentialKingMoves([x
, y
]) {
347 let moves
= super.getPotentialKingMoves([x
, y
]);
348 const color
= this.turn
;
349 // If flag allows it, add 'remote shell captures'
350 if (this.powerFlags
[this.turn
][V
.KING
]) {
351 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]).forEach(step
=> {
352 let [i
, j
] = [x
+ 2 * step
[0], y
+ 2 * step
[1]];
356 this.board
[i
][j
] == V
.EMPTY
||
358 this.getColor(i
, j
) == 'a' &&
359 [V
.EGG
, V
.MUSHROOM
].includes(this.getPiece(i
, j
))
366 if (V
.OnBoard(i
, j
) && this.getColor(i
, j
) != color
)
367 // May just destroy a bomb or banana:
368 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
374 getSlideNJumpMoves([x
, y
], steps
, oneStep
) {
376 outerLoop: for (let step
of steps
) {
382 this.board
[i
][j
] == V
.EMPTY
||
384 this.getColor(i
, j
) == 'a' &&
385 [V
.EGG
, V
.MUSHROOM
].includes(this.getPiece(i
, j
))
389 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
390 if (oneStep
) continue outerLoop
;
394 if (V
.OnBoard(i
, j
) && this.canTake([x
, y
], [i
, j
]))
395 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
400 getAllPotentialMoves() {
401 if (this.subTurn
== 1) return super.getAllPotentialMoves();
403 const L
= this.firstMove
.length
;
404 const fm
= this.firstMove
[L
-1];
405 //switch (fm.end.effect) {
410 if (isNaN(square
[0])) return null;
411 if (this.subTurn
== 1) return null;
412 const L
= this.firstMove
.length
;
413 const fm
= this.firstMove
[L
-1];
414 if (fm
.end
.effect
!= 0) return null;
415 const [x
, y
] = [square
[0], square
[1]];
416 const deltaX
= Math
.abs(fm
.end
.x
- x
);
417 const deltaY
= Math
.abs(fm
.end
.y
- y
);
418 if (deltaX
== 0 && deltaY
== 0) {
421 start: { x: -1, y: -1 },
422 end: { x: -1, y: -1 },
428 this.board
[x
][y
] == V
.EMPTY
&&
430 (fm
.vanish
[0].p
== V
.ROOK
&& deltaX
== 1 && deltaY
== 1) ||
431 (fm
.vanish
[0].p
== V
.BISHOP
&& deltaX
+ deltaY
== 1)
435 start: { x: -1, y: -1 },
442 p: (fm
.vanish
[0].p
== V
.ROOK
? V
.BANANA : V
.BOMB
)
452 move.flags
= JSON
.stringify(this.aggregateFlags());
453 this.epSquares
.push(this.getEpSquare(move));
454 V
.PlayOnBoard(this.board
, move);
455 if (move.end
.effect
!== undefined) {
456 this.firstMove
.push(move);
458 if (move.end
.effect
== 2) this.reserve
= this.captured
;
461 this.turn
= V
.GetOppCol(this.turn
);
468 if (move.vanish
[0].p
== V
.KING
) { }
469 //si roi et delta >= 2 ou dame et appear invisible queen : turn flag off
470 if (move.vanish
.length
== 2 && move.vanish
[1].c
!= 'a')
471 // Capture: update this.captured
472 this.captured
[move.vanish
[1].c
][move.vanish
[1].p
]++;
473 else if (move.vanish
.length
== 0) {
474 // A piece is back on board
475 this.captured
[move.vanish
[1].c
][move.vanish
[1].p
]++;
478 // si pièce immobilisée de ma couleur : elle redevient utilisable (changer status fin de play)
479 // TODO: un-immobilize my formerly immobilized piece, if any.
480 // Make invisible queen visible again, if any opponent invisible queen.
484 // TODO: should be easy once end.effect is set in getBasicMove()
485 if (move.end
.effect
!== undefined)
486 this.firstMove
.pop();
490 if (move.vanish
.length
== 2 && move.vanish
[1].c
!= 'a')
491 this.captured
[move.vanish
[1].c
][move.vanish
[1].p
]--;
499 // Find kings (not tracked in this variant)
500 let kingThere
= { w: false, b: false };
501 for (let i
=0; i
<8; i
++) {
502 for (let j
=0; j
<8; j
++) {
503 if (this.board
[i
][j
] != V
.EMPTY
&& this.getPiece(i
, j
) == V
.KING
)
504 kingThere
[this.getColor(i
, j
)] = true;
507 if (!kingThere
['w']) return "0-1";
508 if (!kingThere
['b']) return "1-0";
512 static GenRandInitFen(randomness
) {
514 SuicideRules
.GenRandInitFen(randomness
).slice(0, -1) +
515 // Add Peach + Mario flags, re-add en-passant + capture counts
526 const moves
= this.getAllValidMoves();
527 let move1
= moves
[randInt(movs
.length
)];
529 let move2
= undefined;
530 if (this.subTurn
== 2) {
531 const moves2
= this.getAllValidMoves();
532 move2
= moves2
[randInt(moves2
.length
)];
535 if (!move2
) return move1
;
536 return [move1
, move2
];
540 // TODO: invisibility used => move notation Q??
541 // Also, bonus should be clearly indicated + bomb/bananas locations
542 return super.getNotation(move);