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
) {
24 this.firstMove
.appear
.length
== 0 ||
25 this.firstMove
.vanish
.length
== 0 ||
26 this.board
[x
][y
] != V
.EMPTY
30 const deltaX
= Math
.abs(this.firstMove
.end
.x
- x
);
31 const deltaY
= Math
.abs(this.firstMove
.end
.y
- y
);
34 // Condition: rook or bishop move, may capture, but no bonus move
35 [V
.ROOK
, V
.BISHOP
].includes(this.firstMove
.vanish
[0].p
) &&
37 this.firstMove
.vanish
.length
== 1 ||
38 ['w', 'b'].includes(this.firstMove
.vanish
[1].c
)
41 this.firstMove
.vanish
[0].p
== V
.ROOK
&& deltaX
== 1 && deltaY
== 1 ||
42 this.firstMove
.vanish
[0].p
== V
.BISHOP
&& deltaX
+ deltaY
== 1
47 static get IMMOBILIZE_CODE() {
58 static get IMMOBILIZE_DECODE() {
69 static get INVISIBLE_QUEEN() {
73 // Fictive color 'a', bomb banana mushroom egg
75 // Doesn't collide with bishop because color 'a'
84 static get MUSHROOM() {
90 ChessRules
.PIECES
.concat(
91 Object
.keys(V
.IMMOBILIZE_DECODE
)).concat(
92 [V
.BANANA
, V
.BOMB
, V
.EGG
, V
.MUSHROOM
, V
.INVISIBLE_QUEEN
])
100 b
[1] == V
.INVISIBLE_QUEEN
||
101 Object
.keys(V
.IMMOBILIZE_DECODE
).includes(b
[1])
108 static ParseFen(fen
) {
109 const fenParts
= fen
.split(" ");
110 return Object
.assign(
111 ChessRules
.ParseFen(fen
),
112 { captured: fenParts
[5] }
116 // King can be l or L (immobilized) --> similar to Alice variant
117 static IsGoodPosition(position
) {
118 if (position
.length
== 0) return false;
119 const rows
= position
.split("/");
120 if (rows
.length
!= V
.size
.x
) return false;
121 let kings
= { "k": 0, "K": 0, 'l': 0, 'L': 0 };
122 for (let row
of rows
) {
124 for (let i
= 0; i
< row
.length
; i
++) {
125 if (['K','k','L','l'].includes(row
[i
])) kings
[row
[i
]]++;
126 if (V
.PIECES
.includes(row
[i
].toLowerCase())) sumElts
++;
128 const num
= parseInt(row
[i
]);
129 if (isNaN(num
)) return false;
133 if (sumElts
!= V
.size
.y
) return false;
135 if (kings
['k'] + kings
['l'] != 1 || kings
['K'] + kings
['L'] != 1)
140 static IsGoodFlags(flags
) {
141 // 4 for Peach + Mario w, b
142 return !!flags
.match(/^[01]{4,4}$/);
146 // King can send shell? Queen can be invisible?
148 w: [{ 'k': false, 'q': false }],
149 b: [{ 'k': false, 'q': false }]
151 for (let c
of ["w", "b"]) {
152 for (let p
of ['k', 'q']) {
153 this.powerFlags
[c
][p
] =
154 fenFlags
.charAt((c
== "w" ? 0 : 2) + (p
== 'k' ? 0 : 1)) == "1";
160 return this.powerFlags
;
163 disaggregateFlags(flags
) {
164 this.powerFlags
= flags
;
168 return super.getFen() + " " + this.getCapturedFen();
172 return super.getFenForRepeat() + "_" + this.getCapturedFen();
176 let counts
= [...Array(10).fill(0)];
178 for (let p
of [V
.ROOK
, V
.KNIGHT
, V
.BISHOP
, V
.QUEEN
, V
.PAWN
]) {
179 counts
[i
] = this.captured
["w"][p
];
180 counts
[5 + i
] = this.captured
["b"][p
];
183 return counts
.join("");
186 setOtherVariables(fen
) {
187 const fenParsed
= V
.ParseFen(fen
);
188 // Initialize captured pieces' counts from FEN
191 [V
.ROOK
]: parseInt(fenParsed
.captured
[0]),
192 [V
.KNIGHT
]: parseInt(fenParsed
.captured
[1]),
193 [V
.BISHOP
]: parseInt(fenParsed
.captured
[2]),
194 [V
.QUEEN
]: parseInt(fenParsed
.captured
[3]),
195 [V
.PAWN
]: parseInt(fenParsed
.captured
[4]),
198 [V
.ROOK
]: parseInt(fenParsed
.captured
[5]),
199 [V
.KNIGHT
]: parseInt(fenParsed
.captured
[6]),
200 [V
.BISHOP
]: parseInt(fenParsed
.captured
[7]),
201 [V
.QUEEN
]: parseInt(fenParsed
.captured
[8]),
202 [V
.PAWN
]: parseInt(fenParsed
.captured
[9]),
212 for (let c
of ["w", "b"])
213 for (let p
of ['k', 'q']) fen
+= (this.powerFlags
[c
][p
] ? "1" : "0");
217 static get RESERVE_PIECES() {
218 return [V
.PAWN
, V
.ROOK
, V
.KNIGHT
, V
.BISHOP
, V
.QUEEN
];
221 getReserveMoves([x
, y
]) {
222 const color
= this.turn
;
223 const p
= V
.RESERVE_PIECES
[y
];
224 if (this.reserve
[color
][p
] == 0) return [];
226 const start
= (color
== 'w' && p
== V
.PAWN
? 1 : 0);
227 const end
= (color
== 'b' && p
== V
.PAWN
? 7 : 8);
228 for (let i
= start
; i
< end
; i
++) {
229 for (let j
= 0; j
< V
.size
.y
; j
++) {
230 if (this.board
[i
][j
] == V
.EMPTY
) {
241 start: { x: x
, y: y
}, //a bit artificial...
251 getPotentialMovesFrom([x
, y
]) {
252 if (this.subTurn
== 1) return super.getPotentialMovesFrom([x
, y
]);
253 if (this.subTurn
== 2) {
255 const L
= this.firstMove
.length
;
256 const fm
= this.firstMove
[L
-1];
257 switch (fm
.end
.effect
) {
258 // case 0: a click is required (banana or bomb)
260 // Exchange position with any piece
261 for (let i
=0; i
<8; i
++) {
262 for (let j
=0; j
<8; j
++) {
263 const colIJ
= this.getColor(i
, j
);
267 this.board
[i
][j
] != V
.EMPTY
&&
270 const movedUnit
= new PiPo({
274 p: this.getPiece(i
, j
)
276 let mMove
= this.getBasicMove([x
, y
], [i
, j
]);
277 mMove
.appear
.push(movedUnit
);
284 // Resurrect a captured piece
285 if (x
>= V
.size
.x
) moves
= this.getReserveMoves([x
, y
]);
288 // Play again with the same piece
289 if (fm
.end
.x
== x
&& fm
.end
.y
== y
)
290 moves
= super.getPotentialMovesFrom([x
, y
]);
297 getBasicMove([x1
, y1
], [x2
, y2
], tr
) {
298 // TODO: if this.subTurn == 2 :: no mushroom effect
299 // (first, transformation. then:)
300 // Apply mushroom, bomb or banana effect (hidden to the player).
301 // Determine egg effect, too, and apply its first part if possible.
302 // add egg + add mushroom for pawns.
303 let move = super.getBasicMove([x1
, y1
], [x2
, y2
]);
306 // Infer move type based on its effects (used to decide subTurn 1 --> 2)
307 // --> impossible étant donné juste first part (egg --> effect?)
308 // => stocker l'effet (i, ii ou iii) dans le coup directement,
309 // Pas terrible, mais y'aura pas 36 variantes comme ça. Disons end.effect == 0, 1, 2 ou 3
310 // 0 => tour ou fou, pose potentielle.
311 // If queen can be invisible, add move same start + end but final type changes
314 getEnpassantCaptures([x
, y
], shiftX
) {
315 const Lep
= this.epSquares
.length
;
316 const epSquare
= this.epSquares
[Lep
- 1]; //always at least one element
317 let enpassantMove
= null;
320 epSquare
.x
== x
+ shiftX
&&
321 Math
.abs(epSquare
.y
- y
) == 1
323 // Not using this.getBasicMove() because the mushroom has no effect
324 enpassantMove
= super.getBasicMove([x
, y
], [epSquare
.x
, epSquare
.y
]);
325 enpassantMove
.vanish
.push({
329 c: this.getColor(x
, epSquare
.y
)
332 return !!enpassantMove
? [enpassantMove
] : [];
335 getPotentialQueenMoves(sq
) {
336 const normalMoves
= super.getPotentialQueenMoves(sq
);
337 // If flag allows it, add 'invisible movements'
338 let invisibleMoves
= [];
339 if (this.powerFlags
[this.turn
][V
.QUEEN
]) {
340 normalMoves
.forEach(m
=> {
341 if (m
.vanish
.length
== 1) {
342 let im
= JSON
.parse(JSON
.stringify(m
));
343 m
.appear
[0].p
= V
.INVISIBLE_QUEEN
;
344 invisibleMoves
.push(im
);
348 return normalMoves
.concat(invisibleMoves
);
351 getPotentialKingMoves([x
, y
]) {
352 let moves
= super.getPotentialKingMoves([x
, y
]);
353 const color
= this.turn
;
354 // If flag allows it, add 'remote shell captures'
355 if (this.powerFlags
[this.turn
][V
.KING
]) {
356 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]).forEach(step
=> {
357 let [i
, j
] = [x
+ 2 * step
[0], y
+ 2 * step
[1]];
361 this.board
[i
][j
] == V
.EMPTY
||
363 this.getColor(i
, j
) == 'a' &&
364 [V
.EGG
, V
.MUSHROOM
].includes(this.getPiece(i
, j
))
371 if (V
.OnBoard(i
, j
) && this.getColor(i
, j
) != color
)
372 // May just destroy a bomb or banana:
373 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
379 getSlideNJumpMoves([x
, y
], steps
, oneStep
) {
381 outerLoop: for (let step
of steps
) {
387 this.board
[i
][j
] == V
.EMPTY
||
389 this.getColor(i
, j
) == 'a' &&
390 [V
.EGG
, V
.MUSHROOM
].includes(this.getPiece(i
, j
))
394 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
395 if (oneStep
) continue outerLoop
;
399 if (V
.OnBoard(i
, j
) && this.canTake([x
, y
], [i
, j
]))
400 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
405 getAllPotentialMoves() {
406 if (this.subTurn
== 1) return super.getAllPotentialMoves();
408 const L
= this.firstMove
.length
;
409 const fm
= this.firstMove
[L
-1];
410 //switch (fm.end.effect) {
415 // TODO: if click on x, y (piece), then return empty move same as Dynamo
416 if (isNaN(square
[0])) return null;
417 // TODO: If subTurn == 2:
418 // if square is empty && firstMove is compatible,
419 // complete the move (banana or bomb or piece exchange).
420 // if square not empty, just complete with empty move
421 const Lf
= this.firstMove
.length
;
422 if (this.subTurn
== 2) {
424 this.board
[square
[0]][square
[1]] == V
.EMPTY
&&
425 (La
== 0 || !this.oppositeMoves(this.amoves
[La
-1], this.firstMove
[Lf
-1]))
428 start: { x: -1, y: -1 },
429 end: { x: -1, y: -1 },
439 move.flags
= JSON
.stringify(this.aggregateFlags());
440 this.epSquares
.push(this.getEpSquare(move));
441 V
.PlayOnBoard(this.board
, move);
442 if (move.end
.effect
!== undefined) {
443 this.firstMove
.push(move);
445 if (move.end
.effect
== 2) this.reserve
= this.captured
;
448 this.turn
= V
.GetOppCol(this.turn
);
455 if (move.vanish
[0].p
== V
.KING
) { }
456 //si roi et delta >= 2 ou dame et appear invisible queen : turn flag off
457 if (move.vanish
.length
== 2 && move.vanish
[1].c
!= 'a')
458 // Capture: update this.captured
459 this.captured
[move.vanish
[1].c
][move.vanish
[1].p
]++;
460 else if (move.vanish
.length
== 0) {
461 // A piece is back on board
462 this.captured
[move.vanish
[1].c
][move.vanish
[1].p
]++;
465 // si pièce immobilisée de ma couleur : elle redevient utilisable (changer status fin de play)
466 // TODO: un-immobilize my formerly immobilized piece, if any.
467 // Make invisible queen visible again, if any opponent invisible queen.
471 // TODO: should be easy once end.effect is set in getBasicMove()
472 if (move.end
.effect
!== undefined)
473 this.firstMove
.pop();
477 if (move.vanish
.length
== 2 && move.vanish
[1].c
!= 'a')
478 this.captured
[move.vanish
[1].c
][move.vanish
[1].p
]--;
486 // Find kings (not tracked in this variant)
487 let kingThere
= { w: false, b: false };
488 for (let i
=0; i
<8; i
++) {
489 for (let j
=0; j
<8; j
++) {
490 if (this.board
[i
][j
] != V
.EMPTY
&& this.getPiece(i
, j
) == V
.KING
)
491 kingThere
[this.getColor(i
, j
)] = true;
494 if (!kingThere
['w']) return "0-1";
495 if (!kingThere
['b']) return "1-0";
499 static GenRandInitFen(randomness
) {
501 SuicideRules
.GenRandInitFen(randomness
).slice(0, -1) +
502 // Add Peach + Mario flags, re-add en-passant + capture counts
513 const moves
= this.getAllValidMoves();
514 let move1
= moves
[randInt(movs
.length
)];
516 let move2
= undefined;
517 if (this.subTurn
== 2) {
518 const moves2
= this.getAllValidMoves();
519 move2
= moves2
[randInt(moves2
.length
)];
522 if (!move2
) return move1
;
523 return [move1
, move2
];
527 // TODO: invisibility used => move notation Q??
528 // Also, bonus should be clearly indicated + bomb/bananas locations
529 return super.getNotation(move);