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
{
13 variable: "randomness",
16 { label: "Deterministic", value: 0 },
17 { label: "Symmetric random", value: 1 },
18 { label: "Asymmetric random", value: 2 }
25 static get PawnSpecs() {
26 return SuicideRules
.PawnSpecs
;
29 static get HasCastle() {
33 static get HasEnpassant() {
37 static get CorrConfirm() {
38 // Because of bonus effects
42 static get CanAnalyze() {
46 static get SomeHiddenMoves() {
50 static get IMMOBILIZE_CODE() {
61 static get IMMOBILIZE_DECODE() {
72 static get INVISIBLE_QUEEN() {
76 // Fictive color 'a', bomb banana mushroom egg
81 return 'd'; //"Donkey"
86 static get MUSHROOM() {
93 ? "w" + f
.toLowerCase()
94 : (['w', 'd', 'e', 'm'].includes(f
) ? "a" : "b") + f
100 ChessRules
.PIECES
.concat(
101 Object
.keys(V
.IMMOBILIZE_DECODE
)).concat(
102 [V
.BANANA
, V
.BOMB
, V
.EGG
, V
.MUSHROOM
, V
.INVISIBLE_QUEEN
])
110 b
[1] == V
.INVISIBLE_QUEEN
||
111 Object
.keys(V
.IMMOBILIZE_DECODE
).includes(b
[1])
119 if (!!m
.promoteInto
) return m
.promoteInto
;
120 if (m
.appear
.length
== 0 && m
.vanish
.length
== 1)
121 // King 'remote shell capture', on an adjacent square:
122 return this.getPpath(m
.vanish
[0].c
+ m
.vanish
[0].p
);
123 let piece
= m
.appear
[0].p
;
124 if (Object
.keys(V
.IMMOBILIZE_DECODE
).includes(piece
))
125 // Promotion by capture into immobilized piece: do not reveal!
126 piece
= V
.IMMOBILIZE_DECODE
[piece
];
127 return this.getPpath(m
.appear
[0].c
+ piece
);
130 static ParseFen(fen
) {
131 const fenParts
= fen
.split(" ");
132 return Object
.assign(
133 ChessRules
.ParseFen(fen
),
134 { captured: fenParts
[4] }
138 static IsGoodFen(fen
) {
139 if (!ChessRules
.IsGoodFen(fen
)) return false;
140 const captured
= V
.ParseFen(fen
).captured
;
141 if (!captured
|| !captured
.match(/^[0-9]{12,12}$/)) return false;
145 // King can be l or L (immobilized) --> similar to Alice variant
146 static IsGoodPosition(position
) {
147 if (position
.length
== 0) return false;
148 const rows
= position
.split("/");
149 if (rows
.length
!= V
.size
.x
) return false;
150 let kings
= { "k": 0, "K": 0, 'l': 0, 'L': 0 };
151 for (let row
of rows
) {
153 for (let i
= 0; i
< row
.length
; i
++) {
154 if (['K', 'k', 'L', 'l'].includes(row
[i
])) kings
[row
[i
]]++;
155 if (V
.PIECES
.includes(row
[i
].toLowerCase())) sumElts
++;
157 const num
= parseInt(row
[i
], 10);
158 if (isNaN(num
)) return false;
162 if (sumElts
!= V
.size
.y
) return false;
164 if (kings
['k'] + kings
['l'] == 0 || kings
['K'] + kings
['L'] == 0)
169 static IsGoodFlags(flags
) {
170 // 4 for Peach + Mario w, b
171 return !!flags
.match(/^[01]{4,4}$/);
175 // King can send shell? Queen can be invisible?
177 w: { 'k': false, 'q': false },
178 b: { 'k': false, 'q': false }
180 for (let c
of ["w", "b"]) {
181 for (let p
of ['k', 'q']) {
182 this.powerFlags
[c
][p
] =
183 fenflags
.charAt((c
== "w" ? 0 : 2) + (p
== 'k' ? 0 : 1)) == "1";
189 return this.powerFlags
;
192 disaggregateFlags(flags
) {
193 this.powerFlags
= flags
;
197 return super.getFen() + " " + this.getCapturedFen();
201 return super.getFenForRepeat() + "_" + this.getCapturedFen();
205 let counts
= [...Array(12).fill(0)];
207 for (let p
of V
.RESERVE_PIECES
) {
208 counts
[i
] = this.captured
["w"][p
];
209 counts
[6 + i
] = this.captured
["b"][p
];
212 return counts
.join("");
217 setOtherVariables(fen
) {
218 super.setOtherVariables(fen
);
219 // Initialize captured pieces' counts from FEN
221 V
.ParseFen(fen
).captured
.split("").map(x
=> parseInt(x
, 10));
224 [V
.PAWN
]: captured
[0],
225 [V
.ROOK
]: captured
[1],
226 [V
.KNIGHT
]: captured
[2],
227 [V
.BISHOP
]: captured
[3],
228 [V
.QUEEN
]: captured
[4],
229 [V
.KING
]: captured
[5]
232 [V
.PAWN
]: captured
[6],
233 [V
.ROOK
]: captured
[7],
234 [V
.KNIGHT
]: captured
[8],
235 [V
.BISHOP
]: captured
[9],
236 [V
.QUEEN
]: captured
[10],
237 [V
.KING
]: captured
[11]
247 for (let c
of ["w", "b"])
248 for (let p
of ['k', 'q']) fen
+= (this.powerFlags
[c
][p
] ? "1" : "0");
253 if (i
>= V
.size
.x
) return i
== V
.size
.x
? "w" : "b";
254 return this.board
[i
][j
].charAt(0);
258 if (i
>= V
.size
.x
) return V
.RESERVE_PIECES
[j
];
259 return this.board
[i
][j
].charAt(1);
262 getReservePpath(index
, color
) {
263 return color
+ V
.RESERVE_PIECES
[index
];
266 static get RESERVE_PIECES() {
267 return [V
.PAWN
, V
.ROOK
, V
.KNIGHT
, V
.BISHOP
, V
.QUEEN
, V
.KING
];
270 getReserveMoves([x
, y
]) {
271 const color
= this.turn
;
272 const p
= V
.RESERVE_PIECES
[y
];
273 if (this.reserve
[color
][p
] == 0) return [];
275 const start
= (color
== 'w' && p
== V
.PAWN
? 1 : 0);
276 const end
= (color
== 'b' && p
== V
.PAWN
? 7 : 8);
277 for (let i
= start
; i
< end
; i
++) {
278 for (let j
= 0; j
< V
.size
.y
; j
++) {
280 this.board
[i
][j
] == V
.EMPTY
||
281 this.getColor(i
, j
) == 'a' ||
282 this.getPiece(i
, j
) == V
.INVISIBLE_QUEEN
284 let m
= this.getBasicMove({ p: p
, x: i
, y: j
});
285 m
.start
= { x: x
, y: y
};
293 getPotentialMovesFrom([x
, y
]) {
295 if (this.subTurn
== 1) {
296 moves
= super.getPotentialMovesFrom([x
, y
]);
297 const finalPieces
= V
.PawnSpecs
.promotions
;
298 const color
= this.turn
;
299 const lastRank
= (color
== "w" ? 0 : 7);
303 m
.appear
.length
> 0 &&
304 ['p', 's'].includes(m
.appear
[0].p
) &&
305 m
.appear
[0].x
== lastRank
307 for (let i
= 1; i
< finalPieces
.length
; i
++) {
308 const piece
= finalPieces
[i
];
309 let otherM
= JSON
.parse(JSON
.stringify(m
));
311 m
.appear
[0].p
== V
.PAWN
313 : V
.IMMOBILIZE_CODE
[finalPieces
[i
]];
316 // Finally alter m itself:
318 m
.appear
[0].p
== V
.PAWN
320 : V
.IMMOBILIZE_CODE
[finalPieces
[0]];
323 Array
.prototype.push
.apply(moves
, pMoves
);
327 const L
= this.effects
.length
;
328 switch (this.effects
[L
-1]) {
330 // Exchange position with any visible piece,
331 // except pawns if arriving on last rank.
332 const lastRank
= { 'w': 0, 'b': 7 };
333 const color
= this.turn
;
334 const allowLastRank
= (this.getPiece(x
, y
) != V
.PAWN
);
335 for (let i
=0; i
<8; i
++) {
336 for (let j
=0; j
<8; j
++) {
337 const colIJ
= this.getColor(i
, j
);
338 const pieceIJ
= this.getPiece(i
, j
);
340 (i
!= x
|| j
!= y
) &&
341 this.board
[i
][j
] != V
.EMPTY
&&
342 pieceIJ
!= V
.INVISIBLE_QUEEN
&&
346 (pieceIJ
!= V
.PAWN
|| x
!= lastRank
[colIJ
]) &&
347 (allowLastRank
|| i
!= lastRank
[color
])
349 const movedUnit
= new PiPo({
353 p: this.getPiece(i
, j
)
355 let mMove
= this.getBasicMove({ x: x
, y: y
}, [i
, j
]);
356 mMove
.appear
.push(movedUnit
);
364 // Resurrect a captured piece
365 if (x
>= V
.size
.x
) moves
= this.getReserveMoves([x
, y
]);
368 // Play again with any piece
369 moves
= super.getPotentialMovesFrom([x
, y
]);
376 // Helper for getBasicMove(): banana/bomb effect
377 getRandomSquare([x
, y
], steps
) {
378 const validSteps
= steps
.filter(s
=> V
.OnBoard(x
+ s
[0], y
+ s
[1]));
379 const step
= validSteps
[randInt(validSteps
.length
)];
380 return [x
+ step
[0], y
+ step
[1]];
383 // Apply mushroom, bomb or banana effect (hidden to the player).
384 // Determine egg effect, too, and apply its first part if possible.
385 getBasicMove_aux(psq1
, sq2
, tr
, initMove
) {
386 const [x1
, y1
] = [psq1
.x
, psq1
.y
];
387 const color1
= this.turn
;
388 const piece1
= (!!tr
? tr
.p : (psq1
.p
|| this.getPiece(x1
, y1
)));
389 const oppCol
= V
.GetOppCol(color1
);
395 // banana or bomb defines next square, or the move ends there
404 if (this.board
[x1
][y1
] != V
.EMPTY
) {
405 const initP1
= this.getPiece(x1
, y1
);
410 c: this.getColor(x1
, y1
),
414 if ([V
.BANANA
, V
.BOMB
].includes(initP1
)) {
415 const steps
= V
.steps
[initP1
== V
.BANANA
? V
.ROOK : V
.BISHOP
];
416 move.next
= this.getRandomSquare([x1
, y1
], steps
);
419 move.end
= { x: x1
, y: y1
};
422 const [x2
, y2
] = [sq2
[0], sq2
[1]];
423 // The move starts normally, on board:
424 let move = super.getBasicMove([x1
, y1
], [x2
, y2
], tr
);
425 if (!!tr
) move.promoteInto
= tr
.c
+ tr
.p
; //in case of (chomped...)
426 const L
= this.effects
.length
;
428 [V
.PAWN
, V
.KNIGHT
].includes(piece1
) &&
430 (this.subTurn
== 1 || this.effects
[L
-1] == "daisy")
434 const twoSquaresMove
= (Math
.abs(x2
- x1
) == 2);
435 const mushroomX
= x1
+ (twoSquaresMove
? (x2
- x1
) / 2 : 0);
444 if (this.getColor(mushroomX
, y1
) == 'a') {
450 p: this.getPiece(mushroomX
, y1
)
457 const deltaX
= Math
.abs(x2
- x1
);
458 const deltaY
= Math
.abs(y2
- y1
);
460 x1
+ (deltaX
== 2 ? (x2
- x1
) / 2 : 0),
461 y1
+ (deltaY
== 2 ? (y2
- y1
) / 2 : 0)
464 this.board
[eggSquare
[0]][eggSquare
[1]] != V
.EMPTY
&&
465 this.getColor(eggSquare
[0], eggSquare
[1]) != 'a'
478 if (this.getColor(eggSquare
[0], eggSquare
[1]) == 'a') {
484 p: this.getPiece(eggSquare
[0], eggSquare
[1])
492 // For (wa)luigi effect:
493 const changePieceColor
= (color
) => {
495 const oppLastRank
= (color
== 'w' ? 7 : 0);
496 for (let i
=0; i
<8; i
++) {
497 for (let j
=0; j
<8; j
++) {
498 const piece
= this.getPiece(i
, j
);
500 (i
!= move.vanish
[0].x
|| j
!= move.vanish
[0].y
) &&
501 this.board
[i
][j
] != V
.EMPTY
&&
502 piece
!= V
.INVISIBLE_QUEEN
&&
503 this.getColor(i
, j
) == color
505 if (piece
!= V
.KING
&& (piece
!= V
.PAWN
|| i
!= oppLastRank
))
506 pieces
.push({ x: i
, y: j
, p: piece
});
510 // Special case of the current piece (still at its initial position)
512 pieces
.push({ x: move.appear
[0].x
, y: move.appear
[0].y
, p: piece1
});
513 const cp
= pieces
[randInt(pieces
.length
)];
514 if (move.appear
[0].x
!= cp
.x
|| move.appear
[0].y
!= cp
.y
) {
524 else move.appear
.shift();
529 c: V
.GetOppCol(color
),
534 const applyEggEffect
= () => {
535 if (this.subTurn
== 2)
536 // No egg effects at subTurn 2
538 // 1) Determine the effect (some may be impossible)
539 let effects
= ["kingboo", "koopa", "chomp", "bowser", "daisy"];
540 if (Object
.values(this.captured
[color1
]).some(c
=> c
>= 1))
541 effects
.push("toadette");
542 const lastRank
= { 'w': 0, 'b': 7 };
544 this.board
.some((b
,i
) =>
549 (cell
[1] != V
.PAWN
|| i
!= lastRank
[color1
])
554 effects
.push("luigi");
559 (piece1
!= V
.PAWN
|| move.appear
[0].x
!= lastRank
[oppCol
])
561 this.board
.some((b
,i
) =>
566 (cell
[1] != V
.PAWN
|| i
!= lastRank
[oppCol
])
571 effects
.push("waluigi");
573 const effect
= effects
[randInt(effects
.length
)];
574 move.end
.effect
= effect
;
575 // 2) Apply it if possible
576 if (!(["kingboo", "toadette", "daisy"].includes(effect
))) {
580 // Maybe egg effect was applied after others,
581 // so just shift vanish array:
588 move.appear
[0].p
= V
.IMMOBILIZE_CODE
[piece1
];
591 changePieceColor(oppCol
);
594 changePieceColor(color1
);
599 const applyMushroomEffect
= () => {
600 if ([V
.PAWN
, V
.KING
, V
.KNIGHT
].includes(piece1
)) {
601 // Just make another similar step, if possible (non-capturing)
603 move.appear
[0].x
+ (x2
- x1
),
604 move.appear
[0].y
+ (y2
- y1
)
609 this.board
[i
][j
] == V
.EMPTY
||
610 this.getPiece(i
, j
) == V
.INVISIBLE_QUEEN
||
611 this.getColor(i
, j
) == 'a'
614 move.appear
[0].x
= i
;
615 move.appear
[0].y
= j
;
616 if (this.board
[i
][j
] != V
.EMPTY
) {
617 const object
= this.getPiece(i
, j
);
618 const color
= this.getColor(i
, j
);
630 const steps
= V
.steps
[object
== V
.BANANA
? V
.ROOK : V
.BISHOP
];
631 move.next
= this.getRandomSquare([i
, j
], steps
);
637 applyMushroomEffect();
644 // Queen, bishop or rook:
646 (x2
- x1
) / Math
.abs(x2
- x1
) || 0,
647 (y2
- y1
) / Math
.abs(y2
- y1
) || 0
649 const next
= [move.appear
[0].x
+ step
[0], move.appear
[0].y
+ step
[1]];
651 V
.OnBoard(next
[0], next
[1]) &&
652 this.board
[next
[0]][next
[1]] != V
.EMPTY
&&
653 this.getPiece(next
[0], next
[1]) != V
.INVISIBLE_QUEEN
&&
654 this.getColor(next
[0], next
[1]) != 'a'
656 const afterNext
= [next
[0] + step
[0], next
[1] + step
[1]];
657 if (V
.OnBoard(afterNext
[0], afterNext
[1])) {
658 const afterColor
= this.getColor(afterNext
[0], afterNext
[1]);
660 this.board
[afterNext
[0]][afterNext
[1]] == V
.EMPTY
||
663 move.appear
[0].x
= afterNext
[0];
664 move.appear
[0].y
= afterNext
[1];
665 if (this.board
[afterNext
[0]][afterNext
[1]] != V
.EMPTY
) {
666 // object = banana, bomb, mushroom or egg
667 const object
= this.getPiece(afterNext
[0], afterNext
[1]);
680 V
.steps
[object
== V
.BANANA
? V
.ROOK : V
.BISHOP
];
681 move.next
= this.getRandomSquare(
682 [afterNext
[0], afterNext
[1]], steps
);
688 applyMushroomEffect();
697 const color2
= this.getColor(x2
, y2
);
698 const piece2
= this.getPiece(x2
, y2
);
703 const steps
= V
.steps
[piece2
== V
.BANANA
? V
.ROOK : V
.BISHOP
];
704 move.next
= this.getRandomSquare([x2
, y2
], steps
);
707 applyMushroomEffect();
710 if (this.subTurn
== 1)
711 // No egg effect at subTurn 2
719 move.appear
.length
> 0 &&
720 [V
.ROOK
, V
.BISHOP
].includes(piece1
)
722 const finalSquare
= [move.appear
[0].x
, move.appear
[0].y
];
725 this.getColor(finalSquare
[0], finalSquare
[1]) != 'a' ||
726 this.getPiece(finalSquare
[0], finalSquare
[1]) != V
.EGG
729 V
.steps
[piece1
== V
.ROOK
? V
.BISHOP : V
.ROOK
].filter(s
=> {
730 const [i
, j
] = [finalSquare
[0] + s
[0], finalSquare
[1] + s
[1]];
733 // NOTE: do not place a bomb or banana on the invisible queen!
734 (this.board
[i
][j
] == V
.EMPTY
|| this.getColor(i
, j
) == 'a')
737 if (validSteps
.length
>= 1) {
738 const randIdx
= randInt(validSteps
.length
);
740 finalSquare
[0] + validSteps
[randIdx
][0],
741 finalSquare
[1] + validSteps
[randIdx
][1]
748 p: (piece1
== V
.ROOK
? V
.BANANA : V
.BOMB
)
751 if (this.board
[x
][y
] != V
.EMPTY
) {
753 new PiPo({ x: x
, y: y
, c: 'a', p: this.getPiece(x
, y
) }));
761 getBasicMove(psq1
, sq2
, tr
) {
763 if (Array
.isArray(psq1
)) psq1
= { x: psq1
[0], y: psq1
[1] };
764 let m
= this.getBasicMove_aux(psq1
, sq2
, tr
, "initMove");
766 // Last move ended on bomb or banana, direction change
767 V
.PlayOnBoard(this.board
, m
);
769 m
= this.getBasicMove_aux(
770 { x: m
.appear
[0].x
, y: m
.appear
[0].y
}, m
.next
);
772 for (let i
=moves
.length
-1; i
>=0; i
--) V
.UndoOnBoard(this.board
, moves
[i
]);
774 // Now merge moves into one
776 // start is wrong for Toadette moves --> it's fixed later
777 move.start
= { x: psq1
.x
, y: psq1
.y
};
778 move.end
= !!sq2
? { x: sq2
[0], y: sq2
[1] } : { x: psq1
.x
, y: psq1
.y
};
779 if (!!tr
) move.promoteInto
= moves
[0].promoteInto
;
780 let lm
= moves
[moves
.length
-1];
781 if (this.subTurn
== 1 && !!lm
.end
.effect
)
782 move.end
.effect
= lm
.end
.effect
;
783 if (moves
.length
== 1) {
784 move.appear
= moves
[0].appear
;
785 move.vanish
= moves
[0].vanish
;
788 // Keep first vanish and last appear (if any)
789 move.appear
= lm
.appear
;
790 move.vanish
= moves
[0].vanish
;
792 move.vanish
.length
>= 1 &&
793 move.appear
.length
>= 1 &&
794 move.vanish
[0].x
== move.appear
[0].x
&&
795 move.vanish
[0].y
== move.appear
[0].y
797 // Loopback on initial square:
801 for (let i
=1; i
< moves
.length
- 1; i
++) {
802 for (let v
of moves
[i
].vanish
) {
803 // Only vanishing objects, not appearing at init move
807 moves
[0].appear
.length
== 1 ||
808 moves
[0].appear
[1].x
!= v
.x
||
809 moves
[0].appear
[1].y
!= v
.y
816 // Final vanish is our piece, but others might be relevant
817 // (for some egg bonuses at least).
818 for (let i
=1; i
< lm
.vanish
.length
; i
++) {
820 lm
.vanish
[i
].c
!= 'a' ||
821 moves
[0].appear
.length
== 1 ||
822 moves
[0].appear
[1].x
!= lm
.vanish
[i
].x
||
823 moves
[0].appear
[1].y
!= lm
.vanish
[i
].y
825 move.vanish
.push(lm
.vanish
[i
]);
832 getPotentialPawnMoves([x
, y
]) {
833 const color
= this.turn
;
834 const oppCol
= V
.GetOppCol(color
);
835 const [sizeX
, sizeY
] = [V
.size
.x
, V
.size
.y
];
836 const shiftX
= V
.PawnSpecs
.directions
[color
];
837 const firstRank
= (color
== "w" ? sizeX
- 1 : 0);
840 this.board
[x
+ shiftX
][y
] == V
.EMPTY
||
841 this.getColor(x
+ shiftX
, y
) == 'a' ||
842 this.getPiece(x
+ shiftX
, y
) == V
.INVISIBLE_QUEEN
844 this.addPawnMoves([x
, y
], [x
+ shiftX
, y
], moves
);
846 [firstRank
, firstRank
+ shiftX
].includes(x
) &&
848 this.board
[x
+ 2 * shiftX
][y
] == V
.EMPTY
||
849 this.getColor(x
+ 2 * shiftX
, y
) == 'a' ||
850 this.getPiece(x
+ 2 * shiftX
, y
) == V
.INVISIBLE_QUEEN
853 moves
.push(this.getBasicMove({ x: x
, y: y
}, [x
+ 2 * shiftX
, y
]));
856 for (let shiftY
of [-1, 1]) {
859 y
+ shiftY
< sizeY
&&
860 this.board
[x
+ shiftX
][y
+ shiftY
] != V
.EMPTY
&&
861 // Pawns cannot capture invisible queen this way!
862 this.getPiece(x
+ shiftX
, y
+ shiftY
) != V
.INVISIBLE_QUEEN
&&
863 ['a', oppCol
].includes(this.getColor(x
+ shiftX
, y
+ shiftY
))
865 this.addPawnMoves([x
, y
], [x
+ shiftX
, y
+ shiftY
], moves
);
871 getPotentialQueenMoves(sq
) {
872 const normalMoves
= super.getPotentialQueenMoves(sq
);
873 // If flag allows it, add 'invisible movements'
874 let invisibleMoves
= [];
875 if (this.powerFlags
[this.turn
][V
.QUEEN
]) {
876 normalMoves
.forEach(m
=> {
878 m
.appear
.length
== 1 &&
879 m
.vanish
.length
== 1 &&
880 // Only simple non-capturing moves:
883 let im
= JSON
.parse(JSON
.stringify(m
));
884 im
.appear
[0].p
= V
.INVISIBLE_QUEEN
;
885 im
.end
.noHighlight
= true;
886 invisibleMoves
.push(im
);
890 return normalMoves
.concat(invisibleMoves
);
893 getPotentialKingMoves([x
, y
]) {
894 let moves
= super.getPotentialKingMoves([x
, y
]);
895 const color
= this.turn
;
896 // If flag allows it, add 'remote shell captures'
897 if (this.powerFlags
[this.turn
][V
.KING
]) {
898 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]).forEach(step
=> {
899 let [i
, j
] = [x
+ step
[0], y
+ step
[1]];
903 this.board
[i
][j
] == V
.EMPTY
||
904 this.getPiece(i
, j
) == V
.INVISIBLE_QUEEN
||
906 this.getColor(i
, j
) == 'a' &&
907 [V
.EGG
, V
.MUSHROOM
].includes(this.getPiece(i
, j
))
914 if (V
.OnBoard(i
, j
)) {
915 const colIJ
= this.getColor(i
, j
);
916 if (colIJ
!= color
) {
917 // May just destroy a bomb or banana:
920 start: { x: x
, y: y
},
925 x: i
, y: j
, c: colIJ
, p: this.getPiece(i
, j
)
937 getSlideNJumpMoves([x
, y
], steps
, oneStep
) {
939 outerLoop: for (let step
of steps
) {
945 this.board
[i
][j
] == V
.EMPTY
||
946 this.getPiece(i
, j
) == V
.INVISIBLE_QUEEN
||
948 this.getColor(i
, j
) == 'a' &&
949 [V
.EGG
, V
.MUSHROOM
].includes(this.getPiece(i
, j
))
953 moves
.push(this.getBasicMove({ x: x
, y: y
}, [i
, j
]));
954 if (oneStep
) continue outerLoop
;
958 if (V
.OnBoard(i
, j
) && this.canTake([x
, y
], [i
, j
]))
959 moves
.push(this.getBasicMove({ x: x
, y: y
}, [i
, j
]));
964 getAllPotentialMoves() {
965 if (this.subTurn
== 1) return super.getAllPotentialMoves();
967 const color
= this.turn
;
968 const L
= this.effects
.length
;
969 switch (this.effects
[L
-1]) {
972 for (let i
=0; i
<8; i
++) {
973 for (let j
=0; j
<8; j
++) {
974 const colIJ
= this.getColor(i
, j
);
975 const pieceIJ
= this.getPiece(i
, j
);
978 this.board
[i
][j
] != V
.EMPTY
&&
980 pieceIJ
!= V
.INVISIBLE_QUEEN
982 allPieces
.push({ x: i
, y: j
, c: colIJ
, p: pieceIJ
});
986 for (let x
=0; x
<8; x
++) {
987 for (let y
=0; y
<8; y
++) {
988 if (this.getColor(i
, j
) == color
) {
989 // Add exchange with something
990 allPieces
.forEach(pp
=> {
991 if (pp
.x
!= i
|| pp
.y
!= j
) {
992 const movedUnit
= new PiPo({
998 let mMove
= this.getBasicMove({ x: x
, y: y
}, [pp
.x
, pp
.y
]);
999 mMove
.appear
.push(movedUnit
);
1009 const x
= V
.size
.x
+ (this.turn
== 'w' ? 0 : 1);
1010 for (let y
= 0; y
< 8; y
++)
1011 Array
.prototype.push
.apply(moves
, this.getReserveMoves([x
, y
]));
1015 moves
= super.getAllPotentialMoves();
1022 // if (!this.states) this.states = [];
1023 // const stateFen = this.getFen();
1024 // this.states.push(stateFen);
1026 move.flags
= JSON
.stringify(this.aggregateFlags());
1027 V
.PlayOnBoard(this.board
, move);
1028 move.turn
= [this.turn
, this.subTurn
];
1029 if (["kingboo", "toadette", "daisy"].includes(move.end
.effect
)) {
1030 this.effects
.push(move.end
.effect
);
1034 this.turn
= V
.GetOppCol(this.turn
);
1038 this.postPlay(move);
1042 if (move.end
.effect
== "toadette") this.reserve
= this.captured
;
1043 else this.reserve
= undefined;
1044 const color
= move.turn
[0];
1046 move.vanish
.length
== 2 &&
1047 move.vanish
[1].c
!= 'a' &&
1048 move.appear
.length
== 1 //avoid king Boo!
1050 // Capture: update this.captured
1051 let capturedPiece
= move.vanish
[1].p
;
1052 if (capturedPiece
== V
.INVISIBLE_QUEEN
) capturedPiece
= V
.QUEEN
;
1053 else if (Object
.keys(V
.IMMOBILIZE_DECODE
).includes(capturedPiece
))
1054 capturedPiece
= V
.IMMOBILIZE_DECODE
[capturedPiece
];
1055 this.captured
[move.vanish
[1].c
][capturedPiece
]++;
1057 else if (move.vanish
.length
== 0) {
1058 if (move.appear
.length
== 0 || move.appear
[0].c
== 'a') return;
1059 // A piece is back on board
1060 this.captured
[move.appear
[0].c
][move.appear
[0].p
]--;
1062 if (move.appear
.length
== 0) {
1063 // Three cases: king "shell capture", Chomp or Koopa
1064 if (this.getPiece(move.start
.x
, move.start
.y
) == V
.KING
)
1065 // King remote capture:
1066 this.powerFlags
[color
][V
.KING
] = false;
1067 else if (move.end
.effect
== "chomp")
1068 this.captured
[color
][move.vanish
[0].p
]++;
1070 else if (move.appear
[0].p
== V
.INVISIBLE_QUEEN
)
1071 this.powerFlags
[move.appear
[0].c
][V
.QUEEN
] = false;
1072 if (this.subTurn
== 2) return;
1074 move.turn
[1] == 1 &&
1075 move.appear
.length
== 0 ||
1076 !(Object
.keys(V
.IMMOBILIZE_DECODE
).includes(move.appear
[0].p
))
1078 // Look for an immobilized piece of my color: it can now move
1079 for (let i
=0; i
<8; i
++) {
1080 for (let j
=0; j
<8; j
++) {
1081 if (this.board
[i
][j
] != V
.EMPTY
) {
1082 const piece
= this.getPiece(i
, j
);
1084 this.getColor(i
, j
) == color
&&
1085 Object
.keys(V
.IMMOBILIZE_DECODE
).includes(piece
)
1087 this.board
[i
][j
] = color
+ V
.IMMOBILIZE_DECODE
[piece
];
1088 move.wasImmobilized
= [i
, j
];
1094 // Also make opponent invisible queen visible again, if any
1095 const oppCol
= V
.GetOppCol(color
);
1096 for (let i
=0; i
<8; i
++) {
1097 for (let j
=0; j
<8; j
++) {
1099 this.board
[i
][j
] != V
.EMPTY
&&
1100 this.getColor(i
, j
) == oppCol
&&
1101 this.getPiece(i
, j
) == V
.INVISIBLE_QUEEN
1103 this.board
[i
][j
] = oppCol
+ V
.QUEEN
;
1104 move.wasInvisible
= [i
, j
];
1111 this.disaggregateFlags(JSON
.parse(move.flags
));
1112 V
.UndoOnBoard(this.board
, move);
1113 if (["kingboo", "toadette", "daisy"].includes(move.end
.effect
))
1115 else this.movesCount
--;
1116 this.turn
= move.turn
[0];
1117 this.subTurn
= move.turn
[1];
1118 this.postUndo(move);
1120 // const stateFen = this.getFen();
1121 // if (stateFen != this.states[this.states.length-1]) debugger;
1122 // this.states.pop();
1126 if (!!move.wasImmobilized
) {
1127 const [i
, j
] = move.wasImmobilized
;
1129 this.getColor(i
, j
) + V
.IMMOBILIZE_CODE
[this.getPiece(i
, j
)];
1131 if (!!move.wasInvisible
) {
1132 const [i
, j
] = move.wasInvisible
;
1133 this.board
[i
][j
] = this.getColor(i
, j
) + V
.INVISIBLE_QUEEN
;
1135 if (move.vanish
.length
== 2 && move.vanish
[1].c
!= 'a') {
1136 let capturedPiece
= move.vanish
[1].p
;
1137 if (capturedPiece
== V
.INVISIBLE_QUEEN
) capturedPiece
= V
.QUEEN
;
1138 else if (Object
.keys(V
.IMMOBILIZE_DECODE
).includes(capturedPiece
))
1139 capturedPiece
= V
.IMMOBILIZE_DECODE
[capturedPiece
];
1140 this.captured
[move.vanish
[1].c
][capturedPiece
]--;
1142 else if (move.vanish
.length
== 0) {
1143 if (move.appear
.length
== 0 || move.appear
[0].c
== 'a') return;
1144 // A piece was back on board
1145 this.captured
[move.appear
[0].c
][move.appear
[0].p
]++;
1147 else if (move.appear
.length
== 0 && move.end
.effect
== "chomp")
1148 this.captured
[move.vanish
[0].c
][move.vanish
[0].p
]--;
1149 if (move.vanish
.length
== 0) this.reserve
= this.captured
;
1150 else this.reserve
= undefined;
1158 // Find kings (not tracked in this variant)
1159 let kingThere
= { w: false, b: false };
1160 for (let i
=0; i
<8; i
++) {
1161 for (let j
=0; j
<8; j
++) {
1163 this.board
[i
][j
] != V
.EMPTY
&&
1164 ['k', 'l'].includes(this.getPiece(i
, j
))
1166 kingThere
[this.getColor(i
, j
)] = true;
1170 if (!kingThere
['w']) return "0-1";
1171 if (!kingThere
['b']) return "1-0";
1172 if (!this.atLeastOneMove()) return (this.turn
== 'w' ? "0-1" : "1-0");
1176 static GenRandInitFen(options
) {
1178 SuicideRules
.GenRandInitFen(options
).slice(0, -1) +
1179 // Add Peach + Mario flags + capture counts
1184 filterValid(moves
) {
1188 static get VALUES() {
1189 return Object
.assign(
1207 static get SEARCH_DEPTH() {
1212 const moves
= this.getAllValidMoves();
1213 // Split into "normal" and "random" moves:
1214 // (Next splitting condition is OK because cannot take self object
1215 // without a banana or bomb on the way).
1216 const deterministicMoves
= moves
.filter(m
=> {
1217 return m
.vanish
.every(a
=> a
.c
!= 'a' || a
.p
== V
.MUSHROOM
);
1219 const randomMoves
= moves
.filter(m
=> {
1220 return m
.vanish
.some(a
=> a
.c
== 'a' && a
.p
!= V
.MUSHROOM
);
1222 if (Math
.random() < deterministicMoves
.length
/ randomMoves
.length
)
1223 // Play a deterministic one: capture king or material if possible
1224 return super.getComputerMove(deterministicMoves
);
1225 // Play a random effect move, at random:
1226 let move1
= randomMoves
[randInt(randomMoves
.length
)];
1228 let move2
= undefined;
1229 if (this.subTurn
== 2) {
1230 const moves2
= this.getAllValidMoves();
1231 move2
= moves2
[randInt(moves2
.length
)];
1234 if (!move2
) return move1
;
1235 return [move1
, move2
];
1239 if (move.vanish
.length
== 0 && move.appear
.length
== 0) return "-";
1242 move.appear
.length
> 0 &&
1243 move.appear
[0].p
== V
.INVISIBLE_QUEEN
1247 const finalSquare
= V
.CoordsToSquare(move.end
);
1248 // Next condition also includes Toadette placements:
1249 if (move.appear
.length
> 0 && move.vanish
.every(a
=> a
.c
== 'a')) {
1251 move.appear
[0].p
!= V
.PAWN
? move.appear
[0].p
.toUpperCase() : "";
1252 return piece
+ "@" + finalSquare
;
1254 else if (move.appear
.length
== 0) {
1255 const piece
= this.getPiece(move.start
.x
, move.start
.y
);
1256 if (piece
== V
.KING
&& !move.end
.effect
)
1257 // King remote capture
1258 return "Kx" + finalSquare
;
1259 // Koopa or Chomp, or loopback after bananas, bombs & mushrooms:
1261 piece
.toUpperCase() + "x" + finalSquare
+
1264 ? "*" + (move.end
.effect
== "koopa" ? "K" : "C")
1269 if (move.appear
.length
== 1 && move.vanish
.length
== 1) {
1270 const moveStart
= move.appear
[0].p
.toUpperCase() + "@";
1271 if (move.appear
[0].c
== 'a' && move.vanish
[0].c
== 'a')
1272 // Bonus replacement:
1273 return moveStart
+ finalSquare
;
1275 move.vanish
[0].p
== V
.INVISIBLE_QUEEN
&&
1276 move.appear
[0].x
== move.vanish
[0].x
&&
1277 move.appear
[0].y
== move.vanish
[0].y
1279 // Toadette takes invisible queen
1280 return moveStart
+ "Q" + finalSquare
;
1284 move.appear
.length
== 2 &&
1285 move.vanish
.length
== 2 &&
1286 move.appear
.every(a
=> a
.c
!= 'a') &&
1287 move.vanish
.every(v
=> v
.c
!= 'a')
1289 // King Boo exchange
1290 return V
.CoordsToSquare(move.start
) + finalSquare
;
1292 const piece
= move.vanish
[0].p
;
1293 let notation
= undefined;
1294 if (piece
== V
.PAWN
) {
1296 if (this.board
[move.end
.x
][move.end
.y
] != V
.EMPTY
) {
1298 const startColumn
= V
.CoordToColumn(move.start
.y
);
1299 notation
= startColumn
+ "x" + finalSquare
;
1301 else notation
= finalSquare
;
1302 if (move.appear
[0].p
!= V
.PAWN
)
1304 notation
+= "=" + move.appear
[0].p
.toUpperCase();
1308 piece
.toUpperCase() +
1309 (this.board
[move.end
.x
][move.end
.y
] != V
.EMPTY
? "x" : "") +
1312 if (!!move.end
.effect
) {
1313 switch (move.end
.effect
) {
1328 const lastAppear
= move.appear
[move.appear
.length
- 1];
1330 V
.CoordsToSquare({ x: lastAppear
.x
, y : lastAppear
.y
});
1331 notation
+= "*" + move.end
.effect
[0].toUpperCase() + effectOn
;