45230a14f4f354fa92aeae022d954531b318fa8b
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 HasEnpassant() {
19 static get CorrConfirm() {
20 // Because of bonus effects
24 static get CanAnalyze() {
28 static get SomeHiddenMoves() {
32 hoverHighlight(x
, y
) {
33 if (this.subTurn
== 1) return false;
34 const L
= this.firstMove
.length
;
35 const fm
= this.firstMove
[L
-1];
36 if (fm
.end
.effect
!= 0) return false;
37 const deltaX
= Math
.abs(fm
.appear
[0].x
- x
);
38 const deltaY
= Math
.abs(fm
.appear
[0].y
- y
);
40 (this.board
[x
][y
] == V
.EMPTY
|| this.getColor(x
, y
) == 'a') &&
42 (fm
.vanish
[0].p
== V
.ROOK
&& deltaX
== 1 && deltaY
== 1) ||
43 (fm
.vanish
[0].p
== V
.BISHOP
&& deltaX
+ deltaY
== 1)
48 static get IMMOBILIZE_CODE() {
59 static get IMMOBILIZE_DECODE() {
70 static get INVISIBLE_QUEEN() {
74 // Fictive color 'a', bomb banana mushroom egg
79 return 'd'; //"Donkey"
84 static get MUSHROOM() {
91 ? "w" + f
.toLowerCase()
92 : (['w', 'd', 'e', 'm'].includes(f
) ? "a" : "b") + f
98 ChessRules
.PIECES
.concat(
99 Object
.keys(V
.IMMOBILIZE_DECODE
)).concat(
100 [V
.BANANA
, V
.BOMB
, V
.EGG
, V
.MUSHROOM
, V
.INVISIBLE_QUEEN
])
108 b
[1] == V
.INVISIBLE_QUEEN
||
109 Object
.keys(V
.IMMOBILIZE_DECODE
).includes(b
[1])
117 let piece
= m
.appear
[0].p
;
118 if (Object
.keys(V
.IMMOBILIZE_DECODE
).includes(piece
))
119 piece
= V
.IMMOBILIZE_DECODE
[piece
];
120 return this.getPpath(m
.appear
[0].c
+ piece
);
123 static ParseFen(fen
) {
124 const fenParts
= fen
.split(" ");
125 return Object
.assign(
126 ChessRules
.ParseFen(fen
),
127 { captured: fenParts
[4] }
131 static IsGoodFen(fen
) {
132 if (!ChessRules
.IsGoodFen(fen
)) return false;
133 const captured
= V
.ParseFen(fen
).captured
;
134 if (!captured
|| !captured
.match(/^[0-9]{12,12}$/)) return false;
138 // King can be l or L (immobilized) --> similar to Alice variant
139 static IsGoodPosition(position
) {
140 if (position
.length
== 0) return false;
141 const rows
= position
.split("/");
142 if (rows
.length
!= V
.size
.x
) return false;
143 let kings
= { "k": 0, "K": 0, 'l': 0, 'L': 0 };
144 for (let row
of rows
) {
146 for (let i
= 0; i
< row
.length
; i
++) {
147 if (['K', 'k', 'L', 'l'].includes(row
[i
])) kings
[row
[i
]]++;
148 if (V
.PIECES
.includes(row
[i
].toLowerCase())) sumElts
++;
150 const num
= parseInt(row
[i
]);
151 if (isNaN(num
)) return false;
155 if (sumElts
!= V
.size
.y
) return false;
157 if (kings
['k'] + kings
['l'] != 1 || kings
['K'] + kings
['L'] != 1)
162 static IsGoodFlags(flags
) {
163 // 4 for Peach + Mario w, b
164 return !!flags
.match(/^[01]{4,4}$/);
168 // King can send shell? Queen can be invisible?
170 w: { 'k': false, 'q': false },
171 b: { 'k': false, 'q': false }
173 for (let c
of ["w", "b"]) {
174 for (let p
of ['k', 'q']) {
175 this.powerFlags
[c
][p
] =
176 fenflags
.charAt((c
== "w" ? 0 : 2) + (p
== 'k' ? 0 : 1)) == "1";
182 return this.powerFlags
;
185 disaggregateFlags(flags
) {
186 this.powerFlags
= flags
;
190 return super.getFen() + " " + this.getCapturedFen();
194 return super.getFenForRepeat() + "_" + this.getCapturedFen();
198 let counts
= [...Array(12).fill(0)];
200 for (let p
of V
.RESERVE_PIECES
) {
201 counts
[i
] = this.captured
["w"][p
];
202 counts
[6 + i
] = this.captured
["b"][p
];
205 return counts
.join("");
210 setOtherVariables(fen
) {
211 super.setOtherVariables(fen
);
212 const fenParsed
= V
.ParseFen(fen
);
213 // Initialize captured pieces' counts from FEN
216 [V
.ROOK
]: parseInt(fenParsed
.captured
[0]),
217 [V
.KNIGHT
]: parseInt(fenParsed
.captured
[1]),
218 [V
.BISHOP
]: parseInt(fenParsed
.captured
[2]),
219 [V
.QUEEN
]: parseInt(fenParsed
.captured
[3]),
220 [V
.KING
]: parseInt(fenParsed
.captured
[4]),
221 [V
.PAWN
]: parseInt(fenParsed
.captured
[5]),
224 [V
.ROOK
]: parseInt(fenParsed
.captured
[6]),
225 [V
.KNIGHT
]: parseInt(fenParsed
.captured
[7]),
226 [V
.BISHOP
]: parseInt(fenParsed
.captured
[8]),
227 [V
.QUEEN
]: parseInt(fenParsed
.captured
[9]),
228 [V
.KING
]: parseInt(fenParsed
.captured
[10]),
229 [V
.PAWN
]: parseInt(fenParsed
.captured
[11]),
239 for (let c
of ["w", "b"])
240 for (let p
of ['k', 'q']) fen
+= (this.powerFlags
[c
][p
] ? "1" : "0");
245 if (i
>= V
.size
.x
) return i
== V
.size
.x
? "w" : "b";
246 return this.board
[i
][j
].charAt(0);
250 if (i
>= V
.size
.x
) return V
.RESERVE_PIECES
[j
];
251 return this.board
[i
][j
].charAt(1);
254 getReservePpath(index
, color
) {
255 return color
+ V
.RESERVE_PIECES
[index
];
258 static get RESERVE_PIECES() {
259 return [V
.PAWN
, V
.ROOK
, V
.KNIGHT
, V
.BISHOP
, V
.QUEEN
, V
.KING
];
262 getReserveMoves([x
, y
]) {
263 const color
= this.turn
;
264 const p
= V
.RESERVE_PIECES
[y
];
265 if (this.reserve
[color
][p
] == 0) return [];
267 const start
= (color
== 'w' && p
== V
.PAWN
? 1 : 0);
268 const end
= (color
== 'b' && p
== V
.PAWN
? 7 : 8);
269 for (let i
= start
; i
< end
; i
++) {
270 for (let j
= 0; j
< V
.size
.y
; j
++) {
271 const colIJ
= this.getColor(i
, j
);
272 if (this.board
[i
][j
] == V
.EMPTY
|| colIJ
== 'a') {
283 start: { x: x
, y: y
}, //a bit artificial...
287 const pieceIJ
= this.getPiece(i
, j
);
296 if ([V
.BANANA
, V
.BOMB
].includes(pieceIJ
)) {
297 // Apply first effect, remove bonus from board, and then
298 // relay to getBasicMove. Finally merge mv.appear/vanish and
299 // put back the bonus on the board:
300 const bSteps
= V
.steps
[pieceIJ
== V
.BANANA
? V
.ROOK : V
.BISHOP
];
301 const sqEffect
= this.getRandomSquare([i
, j
], bSteps
);
302 if (sqEffect
[0] != i
|| sqEffect
[1] != j
) {
303 this.board
[i
][j
] = color
+ p
;
305 this.getBasicMove([i
, j
], [sqEffect
[0], sqEffect
[1]]);
306 this.board
[i
][j
] = 'a' + pieceIJ
;
307 mv
.appear
[0].x
= bMove
.appear
[0].x
;
308 mv
.appear
[0].y
= bMove
.appear
[0].y
;
309 for (let k
= 1; k
< bMove
.vanish
.length
; k
++)
310 mv
.vanish
.push(bMove
.vanish
[k
]);
321 getPotentialMovesFrom([x
, y
]) {
323 if (this.subTurn
== 1) {
324 moves
= super.getPotentialMovesFrom([x
, y
]);
325 const finalPieces
= V
.PawnSpecs
.promotions
;
326 const color
= this.turn
;
327 const lastRank
= (color
== "w" ? 0 : 7);
331 m
.appear
.length
> 0 &&
332 ['p', 's'].includes(m
.appear
[0].p
) &&
333 m
.appear
[0].x
== lastRank
335 for (let i
= 1; i
< finalPieces
.length
; i
++) {
336 const piece
= finalPieces
[i
];
337 let otherM
= JSON
.parse(JSON
.stringify(m
));
339 m
.appear
[0].p
== V
.PAWN
341 : V
.IMMOBILIZE_CODE
[finalPieces
[i
]];
344 // Finally alter m itself:
346 m
.appear
[0].p
== V
.PAWN
348 : V
.IMMOBILIZE_CODE
[finalPieces
[0]];
351 Array
.prototype.push
.apply(moves
, pMoves
);
355 const L
= this.firstMove
.length
;
356 const fm
= this.firstMove
[L
-1];
357 switch (fm
.end
.effect
) {
358 // case 0: a click is required (banana or bomb)
360 // Exchange position with any piece,
361 // except pawns if arriving on last rank.
362 const lastRank
= { 'w': 0, 'b': 7 };
363 const color
= this.turn
;
364 const allowLastRank
= (this.getPiece(x
, y
) != V
.PAWN
);
365 for (let i
=0; i
<8; i
++) {
366 for (let j
=0; j
<8; j
++) {
367 const colIJ
= this.getColor(i
, j
);
369 (i
!= x
|| j
!= y
) &&
370 this.board
[i
][j
] != V
.EMPTY
&&
373 const pieceIJ
= this.getPiece(i
, j
);
375 (pieceIJ
!= V
.PAWN
|| x
!= lastRank
[colIJ
]) &&
376 (allowLastRank
|| i
!= lastRank
[color
])
378 const movedUnit
= new PiPo({
382 p: this.getPiece(i
, j
)
384 let mMove
= this.getBasicMove([x
, y
], [i
, j
]);
385 mMove
.appear
.push(movedUnit
);
393 // Resurrect a captured piece
394 if (x
>= V
.size
.x
) moves
= this.getReserveMoves([x
, y
]);
397 // Play again with the same piece
398 if (fm
.appear
[0].x
== x
&& fm
.appear
[0].y
== y
)
399 moves
= super.getPotentialMovesFrom([x
, y
]);
406 // Helper for getBasicMove()
407 getRandomSquare([x
, y
], steps
) {
408 const validSteps
= steps
.filter(s
=> {
409 const [i
, j
] = [x
+ s
[0], y
+ s
[1]];
412 (this.board
[i
][j
] == V
.EMPTY
|| this.getColor(i
, j
) == 'a')
415 if (validSteps
.length
== 0)
416 // Can happen after mushroom jump
418 const step
= validSteps
[randInt(validSteps
.length
)];
419 return [x
+ step
[0], y
+ step
[1]];
422 canMove([x
, y
], piece
) {
423 const color
= this.getColor(x
, y
);
424 const oppCol
= V
.GetOppCol(color
);
425 piece
= piece
|| this.getPiece(x
, y
);
426 if (piece
== V
.PAWN
) {
427 const forward
= (color
== 'w' ? -1 : 1);
429 this.board
[x
+ forward
][y
] != oppCol
||
431 V
.OnBoard(x
+ forward
, y
+ 1) &&
432 this.board
[x
+ forward
][y
+ 1] != V
.EMPTY
&&
433 this.getColor
[x
+ forward
, y
+ 1] == oppCol
436 V
.OnBoard(x
+ forward
, y
- 1) &&
437 this.board
[x
+ forward
][y
- 1] != V
.EMPTY
&&
438 this.getColor
[x
+ forward
, y
- 1] == oppCol
442 // Checking one step is enough:
444 [V
.KING
, V
.QUEEN
].includes(piece
)
445 ? V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
])
447 if (!Array
.isArray(steps
)) debugger;
448 for (let step
of steps
) {
449 const [i
, j
] = [x
+ step
[0], y
+ step
[1]];
452 (this.board
[i
][j
] == V
.EMPTY
|| this.getColor(i
, j
) != color
)
460 getBasicMove([x1
, y1
], [x2
, y2
], tr
) {
461 // Apply mushroom, bomb or banana effect (hidden to the player).
462 // Determine egg effect, too, and apply its first part if possible.
463 let move = super.getBasicMove([x1
, y1
], [x2
, y2
], tr
);
464 const color1
= this.getColor(x1
, y1
);
465 const piece1
= this.getPiece(x1
, y1
);
466 const oppCol
= V
.GetOppCol(color1
);
467 if ([V
.PAWN
, V
.KNIGHT
].includes(piece1
)) {
470 const twoSquaresMove
= (Math
.abs(x2
- x1
) == 2);
471 const mushroomX
= x1
+ (twoSquaresMove
? (x2
- x1
) / 2 : 0);
480 if (this.getColor(mushroomX
, y1
) == 'a') {
486 p: this.getPiece(mushroomX
, y1
)
493 const deltaX
= Math
.abs(x2
- x1
);
494 const deltaY
= Math
.abs(y2
- y1
);
496 x1
+ (deltaX
== 2 ? (x2
- x1
) / 2 : 0),
497 y1
+ (deltaY
== 2 ? (y2
- y1
) / 2 : 0)
500 this.board
[eggSquare
[0]][eggSquare
[1]] != V
.EMPTY
&&
501 this.getColor(eggSquare
[0], eggSquare
[1]) != 'a'
514 if (this.getColor(eggSquare
[0], eggSquare
[1]) == 'a') {
520 p: this.getPiece(eggSquare
[0], eggSquare
[1])
528 // Avoid looping back on effect already applied:
529 let usedEffect
= ArrayFun
.init(8, 8, false);
530 const applyBeffect
= (steps
) => {
531 const [x
, y
] = [move.appear
[0].x
, move.appear
[0].y
];
532 if (usedEffect
[x
][y
]) return;
533 usedEffect
[x
][y
] = true;
534 const moveTo
= this.getRandomSquare([x
, y
], steps
);
535 move.appear
[0].x
= moveTo
[0];
536 move.appear
[0].y
= moveTo
[1];
538 this.board
[moveTo
[0]][moveTo
[1]] != V
.EMPTY
&&
539 this.getColor(moveTo
[0], moveTo
[1]) == 'a'
546 p: this.getPiece(moveTo
[0], moveTo
[1])
549 // Artificially change direction, before applying new effects:
554 switch (this.getPiece(moveTo
[0], moveTo
[1])) {
556 applyBeffect(V
.steps
[V
.ROOK
]);
559 applyBeffect(V
.steps
[V
.BISHOP
]);
562 applyMushroomEffect();
570 // For (wa)luigi effect:
571 const changePieceColor
= (color
) => {
573 const oppLastRank
= (color
== 'w' ? 7 : 0);
574 for (let i
=0; i
<8; i
++) {
575 for (let j
=0; j
<8; j
++) {
576 if (this.board
[i
][j
] != V
.EMPTY
&& this.getColor(i
, j
) == color
) {
577 const piece
= this.getPiece(i
, j
);
578 if (piece
!= V
.KING
&& (piece
!= V
.PAWN
|| i
!= oppLastRank
))
579 pieces
.push({ x: i
, y: j
, p: piece
});
583 // Special case of the current piece (temporarily off board):
585 pieces
.push({ x: move.appear
[0].x
, y: move.appear
[0].y
, p: piece1
});
586 const cp
= pieces
[randInt(pieces
.length
)];
587 if (move.appear
[0].x
!= cp
.x
|| move.appear
[0].y
!= cp
.y
) {
597 else move.appear
.pop();
602 c: V
.GetOppCol(color
),
607 const applyEggEffect
= () => {
608 if (this.subTurn
== 2)
609 // No egg effects at subTurn 2
611 // 1) Determine the effect (some may be impossible)
612 let effects
= ["kingboo", "koopa", "chomp", "bowser"];
613 if (Object
.values(this.captured
[color1
]).some(c
=> c
>= 1))
614 effects
.push("toadette");
615 const lastRank
= { 'w': 0, 'b': 7 };
616 let canPlayAgain
= undefined;
618 move.appear
[0].p
== V
.PAWN
&&
619 move.appear
[0].x
== lastRank
[color1
]
621 // Always possible: promote into a queen rook or king
625 move.end
.effect
= "daisy";
626 this.board
[move.start
.x
][move.start
.y
] = saveCurrent
;
627 V
.PlayOnBoard(this.board
, move);
628 const square
= [move.appear
[0].x
, move.appear
[0].y
];
629 canPlayAgain
= this.canMove(square
, piece1
);
630 V
.UndoOnBoard(this.board
, move);
631 this.board
[move.start
.x
][move.start
.y
] = V
.EMPTY
;
632 delete move.end
["effect"];
634 if (canPlayAgain
) effects
.push("daisy");
636 this.board
.some((b
,i
) =>
641 (cell
[1] != V
.PAWN
|| i
!= lastRank
[color1
])
646 effects
.push("luigi");
651 (piece1
!= V
.PAWN
|| move.appear
[0].x
!= lastRank
[oppCol
])
653 this.board
.some((b
,i
) =>
658 (cell
[1] != V
.PAWN
|| i
!= lastRank
[oppCol
])
663 effects
.push("waluigi");
665 const effect
= effects
[randInt(effects
.length
)];
666 move.end
.effect
= effect
;
667 // 2) Apply it if possible
668 if (!(["kingboo", "toadette", "daisy"].includes(effect
))) {
672 // Maybe egg effect was applied after others,
673 // so just shift vanish array:
680 move.appear
[0].p
= V
.IMMOBILIZE_CODE
[piece1
];
683 changePieceColor(oppCol
);
686 changePieceColor(color1
);
691 const applyMushroomEffect
= () => {
692 if (usedEffect
[move.appear
[0].x
][move.appear
[0].y
]) return;
693 usedEffect
[move.appear
[0].x
][move.appear
[0].y
] = true;
694 if ([V
.PAWN
, V
.KING
, V
.KNIGHT
].includes(piece1
)) {
695 // Just make another similar step, if possible (non-capturing)
697 move.appear
[0].x
+ (x2
- x1
),
698 move.appear
[0].y
+ (y2
- y1
)
702 (this.board
[i
][j
] == V
.EMPTY
|| this.getColor(i
, j
) == 'a')
704 move.appear
[0].x
= i
;
705 move.appear
[0].y
= j
;
706 if (this.board
[i
][j
] != V
.EMPTY
) {
707 const object
= this.getPiece(i
, j
);
718 applyBeffect(V
.steps
[V
.ROOK
]);
721 applyBeffect(V
.steps
[V
.BISHOP
]);
727 applyMushroomEffect();
734 // Queen, bishop or rook:
736 (x2
- x1
) / Math
.abs(x2
- x1
) || 0,
737 (y2
- y1
) / Math
.abs(y2
- y1
) || 0
739 const next
= [move.appear
[0].x
+ step
[0], move.appear
[0].y
+ step
[1]];
741 V
.OnBoard(next
[0], next
[1]) &&
742 this.board
[next
[0]][next
[1]] != V
.EMPTY
&&
743 this.getColor(next
[0], next
[1]) != 'a'
745 const afterNext
= [next
[0] + step
[0], next
[1] + step
[1]];
746 if (V
.OnBoard(afterNext
[0], afterNext
[1])) {
747 const afterColor
= this.getColor(afterNext
[0], afterNext
[1])
749 this.board
[afterNext
[0]][afterNext
[1]] == V
.EMPTY
||
752 move.appear
[0].x
= afterNext
[0];
753 move.appear
[0].y
= afterNext
[1];
754 if (this.board
[afterNext
[0]][afterNext
[1]] != V
.EMPTY
) {
755 // The "object" could also be an opponent's piece
756 const object
= this.getPiece(afterNext
[0], afterNext
[1]);
767 applyBeffect(V
.steps
[V
.ROOK
]);
770 applyBeffect(V
.steps
[V
.BISHOP
]);
776 applyMushroomEffect();
785 const color2
= this.getColor(x2
, y2
);
786 const piece2
= this.getPiece(x2
, y2
);
787 // In case of (bonus effects might go through initial square):
788 // TODO: push the idea further, objects left initially should alter the
789 // trajectory or move as well (mushroom or egg).
790 const saveCurrent
= this.board
[move.start
.x
][move.start
.y
];
791 this.board
[move.start
.x
][move.start
.y
] = V
.EMPTY
;
795 applyBeffect(V
.steps
[V
.ROOK
]);
798 applyBeffect(V
.steps
[V
.BISHOP
]);
801 applyMushroomEffect();
804 if (this.subTurn
== 1)
805 // No egg effect at subTurn 2
812 move.appear
.length
> 0 &&
813 [V
.ROOK
, V
.BISHOP
].includes(piece1
)
815 const finalSquare
= [move.appear
[0].x
, move.appear
[0].y
];
818 this.getColor(finalSquare
[0], finalSquare
[1]) != 'a' ||
819 this.getPiece(finalSquare
[0], finalSquare
[1]) != V
.EGG
822 V
.steps
[piece1
== V
.ROOK
? V
.BISHOP : V
.ROOK
].filter(s
=> {
823 const [i
, j
] = [finalSquare
[0] + s
[0], finalSquare
[1] + s
[1]];
826 (this.board
[i
][j
] == V
.EMPTY
|| this.getColor(i
, j
) == 'a')
829 if (validSteps
.length
>= 2) move.end
.effect
= 0;
830 else if (validSteps
.length
== 1) {
832 finalSquare
[0] + validSteps
[0][0],
833 finalSquare
[1] + validSteps
[0][1]
840 p: (piece1
== V
.ROOK
? V
.BANANA : V
.BOMB
)
843 if (this.board
[x
][y
] != V
.EMPTY
) {
845 new PiPo({ x: x
, y: y
, c: 'a', p: this.getPiece(x
, y
) }));
850 this.board
[move.start
.x
][move.start
.y
] = saveCurrent
;
852 move.appear
.length
== 2 &&
853 move.appear
[0].x
== move.appear
[1].x
&&
854 move.appear
[0].y
== move.appear
[1].y
856 // Arrive on bonus left initially:
860 // TODO: if loopback to initial square, simplify move.
863 getPotentialPawnMoves([x
, y
]) {
864 const color
= this.turn
;
865 const oppCol
= V
.GetOppCol(color
);
866 const [sizeX
, sizeY
] = [V
.size
.x
, V
.size
.y
];
867 const shiftX
= V
.PawnSpecs
.directions
[color
];
868 const firstRank
= (color
== "w" ? sizeX
- 1 : 0);
871 this.board
[x
+ shiftX
][y
] == V
.EMPTY
||
872 this.getColor(x
+ shiftX
, y
) == 'a'
874 this.addPawnMoves([x
, y
], [x
+ shiftX
, y
], moves
);
876 [firstRank
, firstRank
+ shiftX
].includes(x
) &&
878 this.board
[x
+ 2 * shiftX
][y
] == V
.EMPTY
||
879 this.getColor(x
+ 2 * shiftX
, y
) == 'a'
882 moves
.push(this.getBasicMove([x
, y
], [x
+ 2 * shiftX
, y
]));
885 for (let shiftY
of [-1, 1]) {
888 y
+ shiftY
< sizeY
&&
889 this.board
[x
+ shiftX
][y
+ shiftY
] != V
.EMPTY
&&
890 ['a', oppCol
].includes(this.getColor(x
+ shiftX
, y
+ shiftY
))
892 this.addPawnMoves([x
, y
], [x
+ shiftX
, y
+ shiftY
], moves
);
898 getPotentialQueenMoves(sq
) {
899 const normalMoves
= super.getPotentialQueenMoves(sq
);
900 // If flag allows it, add 'invisible movements'
901 let invisibleMoves
= [];
902 if (this.powerFlags
[this.turn
][V
.QUEEN
]) {
903 normalMoves
.forEach(m
=> {
905 m
.appear
.length
== 1 &&
906 m
.vanish
.length
== 1 &&
907 // Only simple non-capturing moves:
910 let im
= JSON
.parse(JSON
.stringify(m
));
911 im
.appear
[0].p
= V
.INVISIBLE_QUEEN
;
912 im
.end
.noHighlight
= true;
913 invisibleMoves
.push(im
);
917 return normalMoves
.concat(invisibleMoves
);
920 getPotentialKingMoves([x
, y
]) {
921 let moves
= super.getPotentialKingMoves([x
, y
]);
922 const color
= this.turn
;
923 // If flag allows it, add 'remote shell captures'
924 if (this.powerFlags
[this.turn
][V
.KING
]) {
925 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]).forEach(step
=> {
926 const [nextX
, nextY
] = [x
+ step
[0], y
+ step
[1]];
928 V
.OnBoard(nextX
, nextY
) &&
930 this.board
[nextX
][nextY
] == V
.EMPTY
||
932 this.getColor(nextX
, nextY
) == 'a' &&
933 [V
.EGG
, V
.MUSHROOM
].includes(this.getPiece(nextX
, nextY
))
937 let [i
, j
] = [x
+ 2 * step
[0], y
+ 2 * step
[1]];
941 this.board
[i
][j
] == V
.EMPTY
||
943 this.getColor(i
, j
) == 'a' &&
944 [V
.EGG
, V
.MUSHROOM
].includes(this.getPiece(i
, j
))
951 if (V
.OnBoard(i
, j
)) {
952 const colIJ
= this.getColor(i
, j
);
953 if (colIJ
!= color
) {
954 // May just destroy a bomb or banana:
957 start: { x: x
, y: y
},
962 x: i
, y: j
, c: colIJ
, p: this.getPiece(i
, j
)
975 getSlideNJumpMoves([x
, y
], steps
, oneStep
) {
977 outerLoop: for (let step
of steps
) {
983 this.board
[i
][j
] == V
.EMPTY
||
985 this.getColor(i
, j
) == 'a' &&
986 [V
.EGG
, V
.MUSHROOM
].includes(this.getPiece(i
, j
))
990 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
991 if (oneStep
) continue outerLoop
;
995 if (V
.OnBoard(i
, j
) && this.canTake([x
, y
], [i
, j
]))
996 moves
.push(this.getBasicMove([x
, y
], [i
, j
]));
1001 getAllPotentialMoves() {
1002 if (this.subTurn
== 1) return super.getAllPotentialMoves();
1004 const L
= this.firstMove
.length
;
1005 const fm
= this.firstMove
[L
-1];
1006 switch (fm
.end
.effect
) {
1009 start: { x: -1, y: -1 },
1010 end: { x: -1, y: -1 },
1016 (fm
.vanish
[0].p
== V
.ROOK
? V
.steps
[V
.BISHOP
] : V
.steps
[V
.ROOK
])
1018 const [i
, j
] = [fm
.appear
[0].x
+ step
[0], fm
.appear
[0].y
+ step
[1]];
1021 (this.board
[i
][j
] == V
.EMPTY
|| this.getColor(i
, j
) == 'a')
1024 start: { x: -1, y: -1 },
1025 end: { x: i
, y: j
},
1031 p: (fm
.vanish
[0].p
== V
.ROOK
? V
.BANANA : V
.BOMB
)
1036 if (this.board
[i
][j
] != V
.EMPTY
) {
1038 new PiPo({ x: i
, y: j
, c: 'a', p: this.getPiece(i
, j
) }));
1045 const [x
, y
] = [fm
.appear
[0].x
, fm
.appear
[0].y
];
1046 for (let i
=0; i
<8; i
++) {
1047 for (let j
=0; j
<8; j
++) {
1048 const colIJ
= this.getColor(i
, j
);
1052 this.board
[i
][j
] != V
.EMPTY
&&
1055 const movedUnit
= new PiPo({
1059 p: this.getPiece(i
, j
)
1061 let mMove
= this.getBasicMove([x
, y
], [i
, j
]);
1062 mMove
.appear
.push(movedUnit
);
1070 const x
= V
.size
.x
+ (this.turn
== 'w' ? 0 : 1);
1071 for (let y
= 0; y
< 8; y
++)
1072 Array
.prototype.push
.apply(moves
, this.getReserveMoves([x
, y
]));
1076 moves
= super.getPotentialMovesFrom([fm
.appear
[0].x
, fm
.appear
[0].y
]);
1083 const L
= this.firstMove
.length
;
1084 const fm
= (L
> 0 ? this.firstMove
[L
-1] : null);
1087 this.subTurn
== 1 ||
1088 !([0, "daisy"].includes(fm
.end
.effect
))
1092 const [x
, y
] = [square
[0], square
[1]];
1093 const deltaX
= Math
.abs(fm
.appear
[0].x
- x
);
1094 const deltaY
= Math
.abs(fm
.appear
[0].y
- y
);
1096 fm
.end
.effect
== 0 &&
1097 (this.board
[x
][y
] == V
.EMPTY
|| this.getColor(x
, y
) == 'a') &&
1099 (fm
.vanish
[0].p
== V
.ROOK
&& deltaX
== 1 && deltaY
== 1) ||
1100 (fm
.vanish
[0].p
== V
.BISHOP
&& deltaX
+ deltaY
== 1)
1104 start: { x: -1, y: -1 },
1105 end: { x: x
, y: y
},
1111 p: (fm
.vanish
[0].p
== V
.ROOK
? V
.BANANA : V
.BOMB
)
1116 if (this.board
[x
][y
] != V
.EMPTY
) {
1118 new PiPo({ x: x
, y: y
, c: 'a', p: this.getPiece(x
, y
) }));
1123 fm
.end
.effect
== "daisy" &&
1124 deltaX
== 0 && deltaY
== 0 &&
1125 !this.canMove([x
, y
])
1127 // No possible move: return empty move
1129 start: { x: -1, y: -1 },
1130 end: { x: -1, y: -1 },
1139 // if (!this.states) this.states = [];
1140 // const stateFen = this.getFen();
1141 // this.states.push(stateFen);
1143 move.flags
= JSON
.stringify(this.aggregateFlags());
1144 V
.PlayOnBoard(this.board
, move);
1145 move.turn
= [this.turn
, this.subTurn
];
1146 if ([0, "kingboo", "toadette", "daisy"].includes(move.end
.effect
)) {
1147 this.firstMove
.push(move);
1151 this.turn
= V
.GetOppCol(this.turn
);
1155 this.postPlay(move);
1159 if (move.end
.effect
== "toadette") this.reserve
= this.captured
;
1160 else this.reserve
= undefined;
1161 const color
= move.turn
[0];
1162 if (move.vanish
.length
== 2 && move.vanish
[1].c
!= 'a')
1163 // Capture: update this.captured
1164 this.captured
[move.vanish
[1].c
][move.vanish
[1].p
]++;
1165 else if (move.vanish
.length
== 0) {
1166 if (move.appear
.length
== 0 || move.appear
[0].c
== 'a') return;
1167 // A piece is back on board
1168 this.captured
[move.appear
[0].c
][move.appear
[0].p
]--;
1170 if (move.appear
.length
== 0) {
1171 // Three cases: king "shell capture", Chomp or Koopa
1172 if (this.getPiece(move.start
.x
, move.start
.y
) == V
.KING
)
1173 // King remote capture:
1174 this.powerFlags
[color
][V
.KING
] = false;
1175 else if (move.end
.effect
== "chomp")
1176 this.captured
[color
][move.vanish
[0].p
]++;
1178 else if (move.appear
[0].p
== V
.INVISIBLE_QUEEN
)
1179 this.powerFlags
[move.appear
[0].c
][V
.QUEEN
] = false;
1180 if (move.turn
[1] == 2) return;
1182 move.appear
.length
== 0 ||
1183 !(Object
.keys(V
.IMMOBILIZE_DECODE
).includes(move.appear
[0].p
))
1185 // Look for an immobilized piece of my color: it can now move
1186 // Also make opponent invisible queen visible again, if any
1187 const oppCol
= V
.GetOppCol(color
);
1188 for (let i
=0; i
<8; i
++) {
1189 for (let j
=0; j
<8; j
++) {
1190 if (this.board
[i
][j
] != V
.EMPTY
) {
1191 const colIJ
= this.getColor(i
, j
);
1192 const piece
= this.getPiece(i
, j
);
1195 Object
.keys(V
.IMMOBILIZE_DECODE
).includes(piece
)
1197 this.board
[i
][j
] = color
+ V
.IMMOBILIZE_DECODE
[piece
];
1198 move.wasImmobilized
= [i
, j
];
1202 piece
== V
.INVISIBLE_QUEEN
1204 this.board
[i
][j
] = oppCol
+ V
.QUEEN
;
1205 move.wasInvisible
= [i
, j
];
1214 this.disaggregateFlags(JSON
.parse(move.flags
));
1215 V
.UndoOnBoard(this.board
, move);
1216 if ([0, "kingboo", "toadette", "daisy"].includes(move.end
.effect
))
1217 this.firstMove
.pop();
1218 else this.movesCount
--;
1219 this.turn
= move.turn
[0];
1220 this.subTurn
= move.turn
[1];
1221 this.postUndo(move);
1223 // const stateFen = this.getFen();
1224 // if (stateFen != this.states[this.states.length-1]) debugger;
1225 // this.states.pop();
1229 if (!!move.wasImmobilized
) {
1230 const [i
, j
] = move.wasImmobilized
;
1232 this.getColor(i
, j
) + V
.IMMOBILIZE_CODE
[this.getPiece(i
, j
)];
1234 if (!!move.wasInvisible
) {
1235 const [i
, j
] = move.wasInvisible
;
1236 this.board
[i
][j
] = this.getColor(i
, j
) + V
.INVISIBLE_QUEEN
;
1238 if (move.vanish
.length
== 2 && move.vanish
[1].c
!= 'a')
1239 this.captured
[move.vanish
[1].c
][move.vanish
[1].p
]--;
1240 else if (move.vanish
.length
== 0) {
1241 if (move.appear
.length
== 0 || move.appear
[0].c
== 'a') return;
1242 // A piece was back on board
1243 this.captured
[move.appear
[0].c
][move.appear
[0].p
]++;
1245 else if (move.appear
.length
== 0 && move.end
.effect
== "chomp")
1246 this.captured
[move.vanish
[0].c
][move.vanish
[0].p
]--;
1247 if (move.vanish
.length
== 0) this.reserve
= this.captured
;
1248 else this.reserve
= undefined;
1256 // Find kings (not tracked in this variant)
1257 let kingThere
= { w: false, b: false };
1258 for (let i
=0; i
<8; i
++) {
1259 for (let j
=0; j
<8; j
++) {
1261 this.board
[i
][j
] != V
.EMPTY
&&
1262 ['k', 'l'].includes(this.getPiece(i
, j
))
1264 kingThere
[this.getColor(i
, j
)] = true;
1268 if (!kingThere
['w']) return "0-1";
1269 if (!kingThere
['b']) return "1-0";
1270 if (!this.atLeastOneMove()) return (this.turn
== 'w' ? "0-1" : "1-0");
1274 static GenRandInitFen(randomness
) {
1276 SuicideRules
.GenRandInitFen(randomness
).slice(0, -1) +
1277 // Add Peach + Mario flags + capture counts
1282 filterValid(moves
) {
1288 const moves
= this.getAllValidMoves();
1289 let move1
= moves
[randInt(moves
.length
)];
1291 let move2
= undefined;
1292 if (this.subTurn
== 2) {
1293 const moves2
= this.getAllValidMoves();
1294 move2
= moves2
[randInt(moves2
.length
)];
1297 if (!move2
) return move1
;
1298 return [move1
, move2
];
1301 // TODO: king chomped, king koopa: notation is incomplete.
1302 // Also, king boo effect should be better written like "e4Sg1".
1303 // Toadette placements on bonus square are badly written as well.
1305 if (move.vanish
.length
== 0) {
1306 if (move.appear
.length
== 0) return "-";
1308 move.appear
[0].p
!= V
.PAWN
? move.appear
[0].p
.toUpperCase() : "";
1309 return piece
+ "@" + V
.CoordsToSquare(move.end
);
1313 move.appear
.length
> 0 &&
1314 move.appear
[0].p
== V
.INVISIBLE_QUEEN
1318 const finalSquare
= V
.CoordsToSquare(move.end
);
1319 let piece
= undefined;
1320 if (move.appear
.length
== 0) {
1321 piece
= this.getPiece(move.start
.x
, move.start
.y
);
1322 if (piece
== V
.KING
) return "Kx" + finalSquare
;
1325 piece
.toUpperCase() + "x" + finalSquare
+
1326 "*" + (move.end
.effect
== "koopa" ? "K" : "C")
1330 move.appear
.length
== 1 &&
1331 move.vanish
.length
== 1 &&
1332 move.appear
[0].c
== 'a' &&
1333 move.vanish
[0].c
== 'a'
1335 // Bonus replacement:
1336 piece
= move.appear
[0].p
.toUpperCase();
1337 return piece
+ "@" + finalSquare
;
1339 piece
= move.vanish
[0].p
;
1340 let notation
= undefined;
1341 if (piece
== V
.PAWN
) {
1343 if (move.vanish
.length
>= 2) {
1345 const startColumn
= V
.CoordToColumn(move.start
.y
);
1346 notation
= startColumn
+ "x" + finalSquare
;
1348 else notation
= finalSquare
;
1349 if (move.appear
[0].p
!= V
.PAWN
)
1351 notation
+= "=" + move.appear
[0].p
.toUpperCase();
1355 piece
.toUpperCase() +
1356 (move.vanish
.length
>= 2 ? "x" : "") +
1359 if (!!move.end
.effect
) {
1360 switch (move.end
.effect
) {