1 import { ChessRules
, Move
, PiPo
} from "@/base_rules";
2 import { SuicideRules
} from "@/variants/Suicide";
3 import { ArrayFun
} from "@/utils/array";
4 import { randInt
} from "@/utils/alea";
6 export class ChakartRules
extends ChessRules
{
7 static get PawnSpecs() {
8 return SuicideRules
.PawnSpecs
;
11 static get HasCastle() {
15 static get CorrConfirm() {
16 // Because of bonus effects
20 static get CanAnalyze() {
24 static get SomeHiddenMoves() {
28 hoverHighlight(x
, y
) {
29 if (this.subTurn
== 1) return false;
30 const L
= this.firstMove
.length
;
31 const fm
= this.firstMove
[L
-1];
32 if (fm
.end
.effect
!= 0) return false;
33 const deltaX
= Math
.abs(fm
.appear
[0].x
- x
);
34 const deltaY
= Math
.abs(fm
.appear
[0].y
- y
);
36 (deltaX
== 0 && deltaY
== 0) ||
38 this.board
[x
][y
] == V
.EMPTY
&&
40 (fm
.vanish
[0].p
== V
.ROOK
&& deltaX
== 1 && deltaY
== 1) ||
41 (fm
.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
78 return 'd'; //"Donkey"
83 static get MUSHROOM() {
90 ? "w" + f
.toLowerCase()
91 : (['w', 'd', 'e', 'm'].includes(f
) ? "a" : "b") + f
97 ChessRules
.PIECES
.concat(
98 Object
.keys(V
.IMMOBILIZE_DECODE
)).concat(
99 [V
.BANANA
, V
.BOMB
, V
.EGG
, V
.MUSHROOM
, V
.INVISIBLE_QUEEN
])
107 b
[1] == V
.INVISIBLE_QUEEN
||
108 Object
.keys(V
.IMMOBILIZE_DECODE
).includes(b
[1])
116 let piece
= m
.appear
[0].p
;
117 if (Object
.keys(V
.IMMOBILIZE_DECODE
).includes(piece
))
118 piece
= V
.IMMOBILIZE_DECODE
[piece
];
119 return this.getPpath(m
.appear
[0].c
+ piece
);
122 static ParseFen(fen
) {
123 const fenParts
= fen
.split(" ");
124 return Object
.assign(
125 ChessRules
.ParseFen(fen
),
126 { captured: fenParts
[5] }
130 // King can be l or L (immobilized) --> similar to Alice variant
131 static IsGoodPosition(position
) {
132 if (position
.length
== 0) return false;
133 const rows
= position
.split("/");
134 if (rows
.length
!= V
.size
.x
) return false;
135 let kings
= { "k": 0, "K": 0, 'l': 0, 'L': 0 };
136 for (let row
of rows
) {
138 for (let i
= 0; i
< row
.length
; i
++) {
139 if (['K', 'k', 'L', 'l'].includes(row
[i
])) kings
[row
[i
]]++;
140 if (V
.PIECES
.includes(row
[i
].toLowerCase())) sumElts
++;
142 const num
= parseInt(row
[i
]);
143 if (isNaN(num
)) return false;
147 if (sumElts
!= V
.size
.y
) return false;
149 if (kings
['k'] + kings
['l'] != 1 || kings
['K'] + kings
['L'] != 1)
154 static IsGoodFlags(flags
) {
155 // 4 for Peach + Mario w, b
156 return !!flags
.match(/^[01]{4,4}$/);
160 // King can send shell? Queen can be invisible?
162 w: { 'k': false, 'q': false },
163 b: { 'k': false, 'q': false }
165 for (let c
of ["w", "b"]) {
166 for (let p
of ['k', 'q']) {
167 this.powerFlags
[c
][p
] =
168 fenflags
.charAt((c
== "w" ? 0 : 2) + (p
== 'k' ? 0 : 1)) == "1";
174 return this.powerFlags
;
177 disaggregateFlags(flags
) {
178 this.powerFlags
= flags
;
182 return super.getFen() + " " + this.getCapturedFen();
186 return super.getFenForRepeat() + "_" + this.getCapturedFen();
190 let counts
= [...Array(10).fill(0)];
192 for (let p
of [V
.ROOK
, V
.KNIGHT
, V
.BISHOP
, V
.QUEEN
, V
.PAWN
]) {
193 counts
[i
] = this.captured
["w"][p
];
194 counts
[5 + i
] = this.captured
["b"][p
];
197 return counts
.join("");
202 setOtherVariables(fen
) {
203 super.setOtherVariables(fen
);
204 const fenParsed
= V
.ParseFen(fen
);
205 // Initialize captured pieces' counts from FEN
208 [V
.ROOK
]: parseInt(fenParsed
.captured
[0]),
209 [V
.KNIGHT
]: parseInt(fenParsed
.captured
[1]),
210 [V
.BISHOP
]: parseInt(fenParsed
.captured
[2]),
211 [V
.QUEEN
]: parseInt(fenParsed
.captured
[3]),
212 [V
.PAWN
]: parseInt(fenParsed
.captured
[4]),
215 [V
.ROOK
]: parseInt(fenParsed
.captured
[5]),
216 [V
.KNIGHT
]: parseInt(fenParsed
.captured
[6]),
217 [V
.BISHOP
]: parseInt(fenParsed
.captured
[7]),
218 [V
.QUEEN
]: parseInt(fenParsed
.captured
[8]),
219 [V
.PAWN
]: parseInt(fenParsed
.captured
[9]),
229 for (let c
of ["w", "b"])
230 for (let p
of ['k', 'q']) fen
+= (this.powerFlags
[c
][p
] ? "1" : "0");
234 getReservePpath(index
, color
) {
235 return color
+ V
.RESERVE_PIECES
[index
];
238 static get RESERVE_PIECES() {
239 return [V
.PAWN
, V
.ROOK
, V
.KNIGHT
, V
.BISHOP
, V
.QUEEN
];
242 getReserveMoves([x
, y
]) {
243 const color
= this.turn
;
244 const p
= V
.RESERVE_PIECES
[y
];
245 if (this.reserve
[color
][p
] == 0) return [];
247 const start
= (color
== 'w' && p
== V
.PAWN
? 1 : 0);
248 const end
= (color
== 'b' && p
== V
.PAWN
? 7 : 8);
249 for (let i
= start
; i
< end
; i
++) {
250 for (let j
= 0; j
< V
.size
.y
; j
++) {
251 // TODO: allow also to drop on bonus square?
252 // No effect if mushroom, but normal (recursive) effect otherwise.
256 //Egg: no effect on subTurn == 2 => OK
257 //Juste bootstrap banan or bomb effect => getBasicMove otherwise
260 if (this.board
[i
][j
] == V
.EMPTY
) {
271 start: { x: x
, y: y
}, //a bit artificial...
281 getPotentialMovesFrom([x
, y
]) {
283 if (this.subTurn
== 1) {
284 moves
= super.getPotentialMovesFrom([x
, y
]);
285 const finalPieces
= V
.PawnSpecs
.promotions
;
286 const color
= this.turn
;
287 const lastRank
= (color
== "w" ? 0 : 7);
291 m
.appear
.length
> 0 &&
292 ['p', 's'].includes(m
.appear
[0].p
) &&
293 m
.appear
[0].x
== lastRank
295 for (let i
= 1; i
< finalPieces
.length
; i
++) {
296 const piece
= finalPieces
[i
];
297 let otherM
= JSON
.parse(JSON
.stringify(m
));
299 m
.appear
[0].p
== V
.PAWN
301 : V
.IMMOBILIZE_CODE
[finalPieces
[i
]];
304 // Finally alter m itself:
306 m
.appear
[0].p
== V
.PAWN
308 : V
.IMMOBILIZE_CODE
[finalPieces
[0]];
311 Array
.prototype.push
.apply(moves
, pMoves
);
315 const L
= this.firstMove
.length
;
316 const fm
= this.firstMove
[L
-1];
317 switch (fm
.end
.effect
) {
318 // case 0: a click is required (banana or bomb)
320 // Exchange position with any piece,
321 // except pawns if arriving on last rank.
322 const lastRank
= { 'w': 0, 'b': 7 };
323 const color
= this.turn
;
324 const allowLastRank
= (this.getPiece(x
, y
) != V
.PAWN
);
325 for (let i
=0; i
<8; i
++) {
326 for (let j
=0; j
<8; j
++) {
327 const colIJ
= this.getColor(i
, j
);
329 (i
!= x
|| j
!= y
) &&
330 this.board
[i
][j
] != V
.EMPTY
&&
333 const pieceIJ
= this.getPiece(i
, j
);
335 (pieceIJ
!= V
.PAWN
|| x
!= lastRank
[colIJ
]) &&
336 (allowLastRank
|| i
!= lastRank
[color
])
338 const movedUnit
= new PiPo({
342 p: this.getPiece(i
, j
)
344 let mMove
= this.getBasicMove([x
, y
], [i
, j
]);
345 mMove
.appear
.push(movedUnit
);
353 // Resurrect a captured piece
354 if (x
>= V
.size
.x
) moves
= this.getReserveMoves([x
, y
]);
357 // Play again with the same piece
358 if (fm
.appear
[0].x
== x
&& fm
.appear
[0].y
== y
)
359 moves
= super.getPotentialMovesFrom([x
, y
]);
366 // Helper for getBasicMove()
367 getRandomSquare([x
, y
], steps
) {
368 const validSteps
= steps
.filter(s
=> {
369 const [i
, j
] = [x
+ s
[0], y
+ s
[1]];
372 (this.board
[i
][j
] == V
.EMPTY
|| this.getColor(i
, j
) == 'a')
375 if (validSteps
.length
== 0)
376 // Can happen after mushroom jump
378 const step
= validSteps
[randInt(validSteps
.length
)];
379 return [x
+ step
[0], y
+ step
[1]];
382 getBasicMove([x1
, y1
], [x2
, y2
], tr
) {
383 // Apply mushroom, bomb or banana effect (hidden to the player).
384 // Determine egg effect, too, and apply its first part if possible.
385 let move = super.getBasicMove([x1
, y1
], [x2
, y2
], tr
);
386 const color1
= this.getColor(x1
, y1
);
387 const color2
= this.getColor(x2
, y2
);
388 const piece1
= this.getPiece(x1
, y1
);
389 const piece2
= this.getPiece(x2
, y2
);
390 const oppCol
= V
.GetOppCol(color1
);
391 if ([V
.PAWN
, V
.KNIGHT
].includes(piece1
)) {
392 //&& (color2 != 'a' || !([V.BANANA, V.BOMB].includes(piece2)))
395 const twoSquaresMove
= (Math
.abs(x2
- x1
) == 2);
396 const mushroomX
= x1
+ (twoSquaresMove
? (x2
- x1
) / 2 : 0);
405 if (this.getColor(mushroomX
, y1
) == 'a') {
411 p: this.getPiece(mushroomX
, y1
)
418 const deltaX
= Math
.abs(x2
- x1
);
419 const deltaY
= Math
.abs(y2
- y1
);
421 x1
+ (deltaX
== 2 ? (x2
- x1
) / 2 : 0),
422 y1
+ (deltaY
== 2 ? (y2
- y1
) / 2 : 0)
425 this.board
[eggSquare
[0]][eggSquare
[1]] != V
.EMPTY
&&
426 this.getColor(eggSquare
[0], eggSquare
[1]) != 'a'
439 if (this.getColor(eggSquare
[0], eggSquare
[1]) == 'a') {
445 p: this.getPiece(eggSquare
[0], eggSquare
[1])
453 // Avoid looping back on effect already applied:
454 let usedEffect
= ArrayFun
.init(8, 8, false);
455 const applyBeffect
= (steps
) => {
456 const [x
, y
] = [move.appear
[0].x
, move.appear
[0].y
];
457 if (usedEffect
[x
][y
]) return;
458 usedEffect
[x
][y
] = true;
459 const moveTo
= this.getRandomSquare([x
, y
], steps
);
460 move.appear
[0].x
= moveTo
[0];
461 move.appear
[0].y
= moveTo
[1];
463 this.board
[moveTo
[0]][moveTo
[1]] != V
.EMPTY
&&
464 this.getColor(moveTo
[0], moveTo
[1]) == 'a'
466 // Artificially change direction, before applying new effects:
471 switch (this.getPiece(moveTo
[0], moveTo
[1])) {
473 applyBeffect(V
.steps
[V
.ROOK
]);
476 applyBeffect(V
.steps
[V
.BISHOP
]);
479 applyMushroomEffect();
487 // For (wa)luigi effect:
488 const changePieceColor
= (color
) => {
490 const oppLastRank
= (color
== 'w' ? 7 : 0);
491 for (let i
=0; i
<8; i
++) {
492 for (let j
=0; j
<8; j
++) {
494 this.board
[i
][j
] != V
.EMPTY
&&
495 this.getColor(i
, j
) == color
497 const piece
= this.getPiece(i
, j
);
498 if (piece
!= V
.KING
&& (piece
!= V
.PAWN
|| i
!= oppLastRank
))
499 pieces
.push({ x: i
, y: j
, p: piece
});
503 const cp
= pieces
[randInt(pieces
.length
)];
516 c: V
.GetOppCol(color
),
521 const applyEggEffect
= () => {
522 if (this.subTurn
== 2)
523 // No egg effects at subTurn 2
525 // 1) Determine the effect (some may be impossible)
526 let effects
= ["kingboo", "koopa", "chomp", "bowser"];
527 if (Object
.values(this.captured
[color1
]).some(c
=> c
>= 1))
528 effects
.push("toadette");
529 const lastRank
= { 'w': 0, 'b': 7 };
530 let canPlayAgain
= undefined;
532 move.appear
[0].p
== V
.PAWN
&&
533 move.appear
[0].x
== lastRank
[color1
]
535 // Always possible: promote into a queen rook or king
539 move.end
.effect
= "toadette";
541 canPlayAgain
= this.getPotentialMovesFrom([x2
, y2
]).length
> 0;
543 delete move.end
["effect"];
545 if (canPlayAgain
) effects
.push("daisy");
547 this.board
.some((b
,i
) =>
552 (cell
[1] != V
.PAWN
|| i
!= lastRank
[oppCol
])
557 effects
.push("luigi");
560 this.board
.some((b
,i
) =>
565 (cell
[1] != V
.PAWN
|| i
!= lastRank
[color1
])
570 effects
.push("waluigi");
572 const effect
= effects
[randInt(effects
.length
)];
573 move.end
.effect
= effect
;
574 // 2) Apply it if possible
575 if (!(["kingboo", "toadette", "daisy"].includes(effect
))) {
579 // Maybe egg effect was applied after others,
580 // so just shift vanish array:
587 move.appear
[0].p
= V
.IMMOBILIZE_CODE
[piece1
];
590 changePieceColor(oppCol
);
593 changePieceColor(color1
);
598 const applyMushroomEffect
= () => {
599 if (usedEffect
[move.appear
[0].x
][move.appear
[0].y
]) return;
600 usedEffect
[move.appear
[0].x
][move.appear
[0].y
] = true;
601 if ([V
.PAWN
, V
.KING
, V
.KNIGHT
].includes(piece1
)) {
602 // Just make another similar step, if possible (non-capturing)
604 move.appear
[0].x
+ (x2
- x1
),
605 move.appear
[0].y
+ (y2
- y1
)
609 (this.board
[i
][j
] == V
.EMPTY
|| this.getColor(i
, j
) == 'a')
611 move.appear
[0].x
= i
;
612 move.appear
[0].y
= j
;
613 if (this.board
[i
][j
] != V
.EMPTY
) {
614 const object
= this.getPiece(i
, j
);
625 applyBeffect(V
.steps
[V
.ROOK
]);
628 applyBeffect(V
.steps
[V
.BISHOP
]);
634 applyMushroomEffect();
641 // Queen, bishop or rook:
643 (x2
- x1
) / Math
.abs(x2
- x1
) || 0,
644 (y2
- y1
) / Math
.abs(y2
- y1
) || 0
646 const next
= [move.appear
[0].x
+ step
[0], move.appear
[0].y
+ step
[1]];
648 V
.OnBoard(next
[0], next
[1]) &&
649 this.board
[next
[0]][next
[1]] != V
.EMPTY
&&
650 this.getColor(next
[0], next
[1]) != 'a'
652 const afterNext
= [next
[0] + step
[0], next
[1] + step
[1]];
654 V
.OnBoard(afterNext
[0], afterNext
[1]) &&
656 this.board
[afterNext
[0]][afterNext
[1]] == V
.EMPTY
||
657 this.getColor(afterNext
[0], afterNext
[1]) == 'a'
660 move.appear
[0].x
= afterNext
[0];
661 move.appear
[0].y
= afterNext
[1];
662 if (this.board
[afterNext
[0]][afterNext
[1]] != V
.EMPTY
) {
663 const object
= this.getPiece(afterNext
[0], afterNext
[1]);
674 applyBeffect(V
.steps
[V
.ROOK
]);
677 applyBeffect(V
.steps
[V
.BISHOP
]);
683 applyMushroomEffect();
694 applyBeffect(V
.steps
[V
.ROOK
]);
697 applyBeffect(V
.steps
[V
.BISHOP
]);
700 applyMushroomEffect();
703 if (this.subTurn
== 1) {
704 // No egg effect at subTurn 2
705 if ([V
.ROOK
, V
.BISHOP
].includes(piece1
)) {
706 // Drop a bomb or banana at random, because even if bonus is
707 // "play again" nothing can be done after next move.
708 const steps
= V
.steps
[piece1
== V
.ROOK
? V
.BISHOP : V
.ROOK
];
709 const object
= (piece1
== V
.ROOK
? V
.BANANA : V
.BOMB
);
710 const dropOn
= this.getRandomSquare([x2
, y2
], steps
);
727 (color2
!= 'a' || piece2
!= V
.EGG
) &&
728 [V
.ROOK
, V
.BISHOP
].includes(piece1
)
735 getEnpassantCaptures([x
, y
], shiftX
) {
736 const Lep
= this.epSquares
.length
;
737 const epSquare
= this.epSquares
[Lep
- 1]; //always at least one element
738 let enpassantMove
= null;
741 epSquare
.x
== x
+ shiftX
&&
742 Math
.abs(epSquare
.y
- y
) == 1
744 // Not using this.getBasicMove() because the mushroom has no effect
745 enpassantMove
= super.getBasicMove([x
, y
], [epSquare
.x
, epSquare
.y
]);
746 enpassantMove
.vanish
.push({
750 c: this.getColor(x
, epSquare
.y
)
753 return !!enpassantMove
? [enpassantMove
] : [];
756 getPotentialPawnMoves([x
, y
]) {
757 const color
= this.turn
;
758 const oppCol
= V
.GetOppCol(color
);
759 const [sizeX
, sizeY
] = [V
.size
.x
, V
.size
.y
];
760 const shiftX
= V
.PawnSpecs
.directions
[color
];
761 const firstRank
= (color
== "w" ? sizeX
- 1 : 0);
764 this.board
[x
+ shiftX
][y
] == V
.EMPTY
||
765 this.getColor(x
+ shiftX
, y
) == 'a'
767 this.addPawnMoves([x
, y
], [x
+ shiftX
, y
], moves
);
769 [firstRank
, firstRank
+ shiftX
].includes(x
) &&
771 this.board
[x
+ 2 * shiftX
][y
] == V
.EMPTY
||
772 this.getColor(x
+ 2 * shiftX
, y
) == 'a'
775 moves
.push(this.getBasicMove([x
, y
], [x
+ 2 * shiftX
, y
]));
778 for (let shiftY
of [-1, 1]) {
781 y
+ shiftY
< sizeY
&&
782 this.board
[x
+ shiftX
][y
+ shiftY
] != V
.EMPTY
&&
783 this.getColor(x
+ shiftX
, y
+ shiftY
) == oppCol
785 this.addPawnMoves([x
, y
], [x
+ shiftX
, y
+ shiftY
], moves
);
788 Array
.prototype.push
.apply(
790 this.getEnpassantCaptures([x
, y
], shiftX
)
795 getPotentialQueenMoves(sq
) {
796 const normalMoves
= super.getPotentialQueenMoves(sq
);
797 // If flag allows it, add 'invisible movements'
798 let invisibleMoves
= [];
799 if (this.powerFlags
[this.turn
][V
.QUEEN
]) {
800 normalMoves
.forEach(m
=> {
801 if (m
.vanish
.length
== 1) {
802 let im
= JSON
.parse(JSON
.stringify(m
));
803 im
.appear
[0].p
= V
.INVISIBLE_QUEEN
;
804 im
.end
.noHighlight
= true;
805 invisibleMoves
.push(im
);
809 return normalMoves
.concat(invisibleMoves
);
812 getPotentialKingMoves([x
, y
]) {
813 let moves
= super.getPotentialKingMoves([x
, y
]);
814 const color
= this.turn
;
815 // If flag allows it, add 'remote shell captures'
816 if (this.powerFlags
[this.turn
][V
.KING
]) {
817 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]).forEach(step
=> {
819 V
.OnBoard(x
+ step
[0], y
+ step
[1]) &&
820 this.board
[x
+ step
[0]][y
+ step
[1]] == V
.EMPTY
822 let [i
, j
] = [x
+ 2 * step
[0], y
+ 2 * step
[1]];
826 this.board
[i
][j
] == V
.EMPTY
||
828 this.getColor(i
, j
) == 'a' &&
829 [V
.EGG
, V
.MUSHROOM
].includes(this.getPiece(i
, j
))
836 if (V
.OnBoard(i
, j
)) {
837 const colIJ
= this.getColor(i
, j
);
838 if (colIJ
!= color
) {
839 // May just destroy a bomb or banana:
842 start: { x: x
, y: y
},
847 x: i
, y: j
, c: colIJ
, p: this.getPiece(i
, j
)
860 getSlideNJumpMoves([x
, y
], steps
, oneStep
) {
862 outerLoop: for (let step
of steps
) {
868 this.board
[i
][j
] == V
.EMPTY
||
870 this.getColor(i
, j
) == 'a' &&
871 [V
.EGG
, V
.MUSHROOM
].includes(this.getPiece(i
, j
))
875 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
876 if (oneStep
) continue outerLoop
;
880 if (V
.OnBoard(i
, j
) && this.canTake([x
, y
], [i
, j
]))
881 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
886 getAllPotentialMoves() {
887 if (this.subTurn
== 1) return super.getAllPotentialMoves();
889 const L
= this.firstMove
.length
;
890 const fm
= this.firstMove
[L
-1];
891 switch (fm
.end
.effect
) {
894 start: { x: -1, y: -1 },
895 end: { x: -1, y: -1 },
901 (fm
.vanish
[0].p
== V
.ROOK
? V
.steps
[V
.BISHOP
] : V
.steps
[V
.ROOK
])
903 const [i
, j
] = [fm
.appear
[0].x
+ step
[0], fm
.appear
[0].y
+ step
[1]];
906 (this.board
[i
][j
] == V
.EMPTY
|| this.getColor(i
, j
) == 'a')
909 start: { x: -1, y: -1 },
916 p: (fm
.vanish
[0].p
== V
.ROOK
? V
.BANANA : V
.BOMB
)
921 if (this.board
[i
][j
] != V
.EMPTY
) {
923 new PiPo({ x: i
, y: j
, c: 'a', p: this.getPiece(i
, j
) }));
930 const [x
, y
] = [fm
.appear
[0].x
, fm
.appear
[0].y
];
931 for (let i
=0; i
<8; i
++) {
932 for (let j
=0; j
<8; j
++) {
933 const colIJ
= this.getColor(i
, j
);
937 this.board
[i
][j
] != V
.EMPTY
&&
940 const movedUnit
= new PiPo({
944 p: this.getPiece(i
, j
)
946 let mMove
= this.getBasicMove([x
, y
], [i
, j
]);
947 mMove
.appear
.push(movedUnit
);
955 const x
= V
.size
.x
+ (this.turn
== 'w' ? 0 : 1);
956 for (let y
= 0; y
< 8; y
++)
957 Array
.prototype.push
.apply(moves
, this.getReserveMoves([x
, y
]));
961 moves
= super.getPotentialMovesFrom([fm
.appear
[0].x
, fm
.appear
[0].y
]);
968 if (isNaN(square
[0])) return null;
969 if (this.subTurn
== 1) return null;
970 const L
= this.firstMove
.length
;
971 const fm
= this.firstMove
[L
-1];
972 if (fm
.end
.effect
!= 0) return null;
973 const [x
, y
] = [square
[0], square
[1]];
974 const deltaX
= Math
.abs(fm
.appear
[0].x
- x
);
975 const deltaY
= Math
.abs(fm
.appear
[0].y
- y
);
976 if (deltaX
== 0 && deltaY
== 0) {
979 start: { x: -1, y: -1 },
980 end: { x: -1, y: -1 },
986 (this.board
[x
][y
] == V
.EMPTY
|| this.getColor(x
, y
) == 'a') &&
988 (fm
.vanish
[0].p
== V
.ROOK
&& deltaX
== 1 && deltaY
== 1) ||
989 (fm
.vanish
[0].p
== V
.BISHOP
&& deltaX
+ deltaY
== 1)
993 start: { x: -1, y: -1 },
1000 p: (fm
.vanish
[0].p
== V
.ROOK
? V
.BANANA : V
.BOMB
)
1005 if (this.board
[x
][y
] != V
.EMPTY
) {
1007 new PiPo({ x: x
, y: y
, c: 'a', p: this.getPiece(x
, y
) }));
1015 move.flags
= JSON
.stringify(this.aggregateFlags());
1016 this.epSquares
.push(this.getEpSquare(move));
1017 V
.PlayOnBoard(this.board
, move);
1018 move.turn
= [this.turn
, this.subTurn
];
1019 if ([0, "kingboo", "toadette", "daisy"].includes(move.end
.effect
)) {
1020 this.firstMove
.push(move);
1024 this.turn
= V
.GetOppCol(this.turn
);
1028 this.postPlay(move);
1032 if (move.end
.effect
== "toadette") this.reserve
= this.captured
;
1033 else this.reserve
= undefined;
1034 if (move.vanish
.length
== 2 && move.vanish
[1].c
!= 'a')
1035 // Capture: update this.captured
1036 this.captured
[move.vanish
[1].c
][move.vanish
[1].p
]++;
1037 else if (move.vanish
.length
== 0) {
1038 if (move.appear
.length
== 0 || move.appear
[0].c
== 'a') return;
1039 // A piece is back on board
1040 this.captured
[move.appear
[0].c
][move.appear
[0].p
]--;
1046 // TODO: simplify and fix this
1050 if (this.subTurn
== 1) {
1053 this.board
[move.start
.x
][move.start
.y
] != V
.EMPTY
&&
1054 this.getPiece(move.start
.x
, move.start
.y
) == V
.KING
&&
1056 Math
.abs(move.end
.x
- move.start
.x
) >= 2 ||
1057 Math
.abs(move.end
.y
- move.start
.y
) >= 2
1060 const myColor
= this.getColor(move.start
.x
, move.start
.y
);
1061 this.powerFlags
[myColor
][V
.KING
] = false;
1064 move.vanish
[0].p
== V
.QUEEN
&&
1065 this.getPiece(move.end
.x
, move.end
.y
) == V
.INVISIBLE_QUEEN
1067 this.powerFlags
[move.vanish
[0].c
][V
.QUEEN
] = false;
1069 const color
= move.vanish
[0].c
;
1070 const oppCol
= V
.GetOppCol(color
);
1071 if (!(Object
.keys(V
.IMMOBILIZE_DECODE
).includes(move.appear
[0].p
))) {
1072 // Look for an immobilized piece of my color: it can now move
1073 // Also make opponent invisible queen visible again, if any
1074 for (let i
=0; i
<8; i
++) {
1075 for (let j
=0; j
<8; j
++) {
1076 if (this.board
[i
][j
] != V
.EMPTY
) {
1077 const colIJ
= this.getColor(i
, j
);
1078 const piece
= this.getPiece(i
, j
);
1081 Object
.keys(V
.IMMOBILIZE_DECODE
).includes(piece
)
1083 this.board
[i
][j
] = color
+ V
.IMMOBILIZE_DECODE
[piece
];
1084 move.wasImmobilized
= [i
, j
];
1088 piece
== V
.INVISIBLE_QUEEN
1090 this.board
[i
][j
] = oppCol
+ V
.QUEEN
;
1091 move.wasInvisible
= [i
, j
];
1101 this.epSquares
.pop();
1102 this.disaggregateFlags(JSON
.parse(move.flags
));
1103 V
.UndoOnBoard(this.board
, move);
1104 if ([0, "kingboo", "toadette", "daisy"].includes(move.end
.effect
))
1105 this.firstMove
.pop();
1106 else this.movesCount
--;
1107 this.turn
= move.turn
[0];
1108 this.subTurn
= move.turn
[1];
1109 this.postUndo(move);
1113 if (!!move.wasImmobilized
) {
1114 const [i
, j
] = move.wasImmobilized
;
1116 this.getColor(i
, j
) + V
.IMMOBILIZE_CODE
[this.getPiece(i
, j
)];
1118 if (!!move.wasInvisible
) {
1119 const [i
, j
] = move.wasInvisible
;
1121 this.getColor(i
, j
) + V
.INVISIBLE_QUEEN
;
1123 if (move.vanish
.length
== 2 && move.vanish
[1].c
!= 'a')
1124 this.captured
[move.vanish
[1].c
][move.vanish
[1].p
]--;
1125 else if (move.vanish
.length
== 0) {
1126 if (move.appear
.length
== 0 || move.appear
[0].c
== 'a') return;
1127 // A piece was back on board
1128 this.captured
[move.appear
[0].c
][move.appear
[0].p
]++;
1130 if (move.vanish
.length
== 0) this.reserve
= this.captured
;
1131 else this.reserve
= undefined;
1139 // Find kings (not tracked in this variant)
1140 let kingThere
= { w: false, b: false };
1141 for (let i
=0; i
<8; i
++) {
1142 for (let j
=0; j
<8; j
++) {
1144 this.board
[i
][j
] != V
.EMPTY
&&
1145 ['k', 'l'].includes(this.getPiece(i
, j
))
1147 kingThere
[this.getColor(i
, j
)] = true;
1151 if (!kingThere
['w']) return "0-1";
1152 if (!kingThere
['b']) return "1-0";
1153 if (!this.atLeastOneMove()) return (this.turn
== 'w' ? "0-1" : "1-0");
1157 static GenRandInitFen(randomness
) {
1159 SuicideRules
.GenRandInitFen(randomness
).slice(0, -1) +
1160 // Add Peach + Mario flags, re-add en-passant + capture counts
1165 filterValid(moves
) {
1171 const moves
= this.getAllValidMoves();
1172 let move1
= moves
[randInt(moves
.length
)];
1174 let move2
= undefined;
1175 if (this.subTurn
== 2) {
1176 const moves2
= this.getAllValidMoves();
1177 move2
= moves2
[randInt(moves2
.length
)];
1182 if (!move2
) return move1
;
1183 return [move1
, move2
];
1187 if (move.vanish
.length
== 0) {
1188 if (move.appear
.length
== 0) return "-";
1190 move.appear
[0].p
!= V
.PAWN
? move.appear
[0].p
.toUpperCase() : "";
1191 return piece
+ "@" + V
.CoordsToSquare(move.end
);
1195 move.appear
.length
> 0 &&
1196 move.appear
[0].p
== V
.INVISIBLE_QUEEN
1200 const finalSquare
= V
.CoordsToSquare(move.end
);
1201 const piece
= move.vanish
[0].p
;
1202 if (move.appear
.length
== 0) {
1205 piece
.toUpperCase() + "x" + finalSquare
+
1206 "*" + (move.end
.effect
== "koopa" ? "K" : "C")
1209 let notation
= undefined;
1210 if (piece
== V
.PAWN
) {
1212 if (move.vanish
.length
>= 2) {
1214 const startColumn
= V
.CoordToColumn(move.start
.y
);
1215 notation
= startColumn
+ "x" + finalSquare
;
1217 else notation
= finalSquare
;
1218 if (move.appear
[0].p
!= V
.PAWN
)
1220 notation
+= "=" + move.appear
[0].p
.toUpperCase();
1224 piece
.toUpperCase() +
1225 (move.vanish
.length
>= 2 ? "x" : "") +
1228 if (!!move.end
.effect
) {
1229 switch (move.end
.effect
) {
1250 else if (move.vanish
.length
>= 2 && move.vanish
[1].c
== 'a') {
1252 switch (move.vanish
[1].p
) {
1263 notation
.replace('x', 'x' + symbol
);