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 if (!!m
.promoteInto
) return m
.promoteInto
;
118 let piece
= m
.appear
[0].p
;
119 if (Object
.keys(V
.IMMOBILIZE_DECODE
).includes(piece
))
120 piece
= V
.IMMOBILIZE_DECODE
[piece
];
121 return this.getPpath(m
.appear
[0].c
+ piece
);
124 static ParseFen(fen
) {
125 const fenParts
= fen
.split(" ");
126 return Object
.assign(
127 ChessRules
.ParseFen(fen
),
128 { captured: fenParts
[4] }
132 static IsGoodFen(fen
) {
133 if (!ChessRules
.IsGoodFen(fen
)) return false;
134 const captured
= V
.ParseFen(fen
).captured
;
135 if (!captured
|| !captured
.match(/^[0-9]{12,12}$/)) return false;
139 // King can be l or L (immobilized) --> similar to Alice variant
140 static IsGoodPosition(position
) {
141 if (position
.length
== 0) return false;
142 const rows
= position
.split("/");
143 if (rows
.length
!= V
.size
.x
) return false;
144 let kings
= { "k": 0, "K": 0, 'l': 0, 'L': 0 };
145 for (let row
of rows
) {
147 for (let i
= 0; i
< row
.length
; i
++) {
148 if (['K', 'k', 'L', 'l'].includes(row
[i
])) kings
[row
[i
]]++;
149 if (V
.PIECES
.includes(row
[i
].toLowerCase())) sumElts
++;
151 const num
= parseInt(row
[i
]);
152 if (isNaN(num
)) return false;
156 if (sumElts
!= V
.size
.y
) return false;
158 if (kings
['k'] + kings
['l'] != 1 || kings
['K'] + kings
['L'] != 1)
163 static IsGoodFlags(flags
) {
164 // 4 for Peach + Mario w, b
165 return !!flags
.match(/^[01]{4,4}$/);
169 // King can send shell? Queen can be invisible?
171 w: { 'k': false, 'q': false },
172 b: { 'k': false, 'q': false }
174 for (let c
of ["w", "b"]) {
175 for (let p
of ['k', 'q']) {
176 this.powerFlags
[c
][p
] =
177 fenflags
.charAt((c
== "w" ? 0 : 2) + (p
== 'k' ? 0 : 1)) == "1";
183 return this.powerFlags
;
186 disaggregateFlags(flags
) {
187 this.powerFlags
= flags
;
191 return super.getFen() + " " + this.getCapturedFen();
195 return super.getFenForRepeat() + "_" + this.getCapturedFen();
199 let counts
= [...Array(12).fill(0)];
201 for (let p
of V
.RESERVE_PIECES
) {
202 counts
[i
] = this.captured
["w"][p
];
203 counts
[6 + i
] = this.captured
["b"][p
];
206 return counts
.join("");
211 setOtherVariables(fen
) {
212 super.setOtherVariables(fen
);
213 const fenParsed
= V
.ParseFen(fen
);
214 // Initialize captured pieces' counts from FEN
217 [V
.ROOK
]: parseInt(fenParsed
.captured
[0]),
218 [V
.KNIGHT
]: parseInt(fenParsed
.captured
[1]),
219 [V
.BISHOP
]: parseInt(fenParsed
.captured
[2]),
220 [V
.QUEEN
]: parseInt(fenParsed
.captured
[3]),
221 [V
.KING
]: parseInt(fenParsed
.captured
[4]),
222 [V
.PAWN
]: parseInt(fenParsed
.captured
[5]),
225 [V
.ROOK
]: parseInt(fenParsed
.captured
[6]),
226 [V
.KNIGHT
]: parseInt(fenParsed
.captured
[7]),
227 [V
.BISHOP
]: parseInt(fenParsed
.captured
[8]),
228 [V
.QUEEN
]: parseInt(fenParsed
.captured
[9]),
229 [V
.KING
]: parseInt(fenParsed
.captured
[10]),
230 [V
.PAWN
]: parseInt(fenParsed
.captured
[11]),
240 for (let c
of ["w", "b"])
241 for (let p
of ['k', 'q']) fen
+= (this.powerFlags
[c
][p
] ? "1" : "0");
246 if (i
>= V
.size
.x
) return i
== V
.size
.x
? "w" : "b";
247 return this.board
[i
][j
].charAt(0);
251 if (i
>= V
.size
.x
) return V
.RESERVE_PIECES
[j
];
252 return this.board
[i
][j
].charAt(1);
255 getReservePpath(index
, color
) {
256 return color
+ V
.RESERVE_PIECES
[index
];
259 static get RESERVE_PIECES() {
260 return [V
.PAWN
, V
.ROOK
, V
.KNIGHT
, V
.BISHOP
, V
.QUEEN
, V
.KING
];
263 getReserveMoves([x
, y
]) {
264 const color
= this.turn
;
265 const p
= V
.RESERVE_PIECES
[y
];
266 if (this.reserve
[color
][p
] == 0) return [];
268 const start
= (color
== 'w' && p
== V
.PAWN
? 1 : 0);
269 const end
= (color
== 'b' && p
== V
.PAWN
? 7 : 8);
270 for (let i
= start
; i
< end
; i
++) {
271 for (let j
= 0; j
< V
.size
.y
; j
++) {
272 if (this.board
[i
][j
] == V
.EMPTY
|| this.getColor(i
, j
) == 'a') {
273 let m
= this.getBasicMove({ p: p
, x: i
, y: j
});
274 m
.start
= { x: x
, y: y
};
282 getPotentialMovesFrom([x
, y
]) {
284 if (this.subTurn
== 1) {
285 moves
= super.getPotentialMovesFrom([x
, y
]);
286 const finalPieces
= V
.PawnSpecs
.promotions
;
287 const color
= this.turn
;
288 const lastRank
= (color
== "w" ? 0 : 7);
292 m
.appear
.length
> 0 &&
293 ['p', 's'].includes(m
.appear
[0].p
) &&
294 m
.appear
[0].x
== lastRank
296 for (let i
= 1; i
< finalPieces
.length
; i
++) {
297 const piece
= finalPieces
[i
];
298 let otherM
= JSON
.parse(JSON
.stringify(m
));
300 m
.appear
[0].p
== V
.PAWN
302 : V
.IMMOBILIZE_CODE
[finalPieces
[i
]];
305 // Finally alter m itself:
307 m
.appear
[0].p
== V
.PAWN
309 : V
.IMMOBILIZE_CODE
[finalPieces
[0]];
312 Array
.prototype.push
.apply(moves
, pMoves
);
316 const L
= this.firstMove
.length
;
317 const fm
= this.firstMove
[L
-1];
318 switch (fm
.end
.effect
) {
319 // case 0: a click is required (banana or bomb)
321 // Exchange position with any piece,
322 // except pawns if arriving on last rank.
323 const lastRank
= { 'w': 0, 'b': 7 };
324 const color
= this.turn
;
325 const allowLastRank
= (this.getPiece(x
, y
) != V
.PAWN
);
326 for (let i
=0; i
<8; i
++) {
327 for (let j
=0; j
<8; j
++) {
328 const colIJ
= this.getColor(i
, j
);
330 (i
!= x
|| j
!= y
) &&
331 this.board
[i
][j
] != V
.EMPTY
&&
334 const pieceIJ
= this.getPiece(i
, j
);
336 (pieceIJ
!= V
.PAWN
|| x
!= lastRank
[colIJ
]) &&
337 (allowLastRank
|| i
!= lastRank
[color
])
339 const movedUnit
= new PiPo({
343 p: this.getPiece(i
, j
)
345 let mMove
= this.getBasicMove({ x: x
, y: y
}, [i
, j
]);
346 mMove
.appear
.push(movedUnit
);
354 // Resurrect a captured piece
355 if (x
>= V
.size
.x
) moves
= this.getReserveMoves([x
, y
]);
358 // Play again with the same piece
359 if (fm
.appear
[0].x
== x
&& fm
.appear
[0].y
== y
)
360 moves
= super.getPotentialMovesFrom([x
, y
]);
367 // Helper for getBasicMove()
368 getRandomSquare([x
, y
], steps
) {
369 const validSteps
= steps
.filter(s
=> {
370 const [i
, j
] = [x
+ s
[0], y
+ s
[1]];
373 (this.board
[i
][j
] == V
.EMPTY
|| this.getColor(i
, j
) == 'a')
376 if (validSteps
.length
== 0)
377 // Can happen after mushroom jump
379 const step
= validSteps
[randInt(validSteps
.length
)];
380 return [x
+ step
[0], y
+ step
[1]];
383 canMove([x
, y
], piece
) {
384 const color
= this.getColor(x
, y
);
385 const oppCol
= V
.GetOppCol(color
);
386 piece
= piece
|| this.getPiece(x
, y
);
387 if (piece
== V
.PAWN
) {
388 const forward
= (color
== 'w' ? -1 : 1);
390 V
.OnBoard(x
+ forward
, y
) &&
392 this.board
[x
+ forward
][y
] != oppCol
||
394 V
.OnBoard(x
+ forward
, y
+ 1) &&
395 this.board
[x
+ forward
][y
+ 1] != V
.EMPTY
&&
396 this.getColor
[x
+ forward
, y
+ 1] == oppCol
399 V
.OnBoard(x
+ forward
, y
- 1) &&
400 this.board
[x
+ forward
][y
- 1] != V
.EMPTY
&&
401 this.getColor
[x
+ forward
, y
- 1] == oppCol
406 // Checking one step is enough:
408 [V
.KING
, V
.QUEEN
].includes(piece
)
409 ? V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
])
411 for (let step
of steps
) {
412 const [i
, j
] = [x
+ step
[0], y
+ step
[1]];
415 (this.board
[i
][j
] == V
.EMPTY
|| this.getColor(i
, j
) != color
)
423 // Apply mushroom, bomb or banana effect (hidden to the player).
424 // Determine egg effect, too, and apply its first part if possible.
425 getBasicMove_aux(psq1
, sq2
, tr
, initMove
) {
426 const [x1
, y1
] = [psq1
.x
, psq1
.y
];
427 const color1
= this.turn
;
428 const piece1
= (!!tr
? tr
.p : (psq1
.p
|| this.getPiece(x1
, y1
)));
429 const oppCol
= V
.GetOppCol(color1
);
435 // banana or bomb defines next square, or the move ends there
444 if (this.board
[x1
][y1
] != V
.EMPTY
) {
445 const initP1
= this.getPiece(x1
, y1
);
450 c: this.getColor(x1
, y1
),
454 if ([V
.BANANA
, V
.BOMB
].includes(initP1
)) {
455 const steps
= V
.steps
[initP1
== V
.BANANA
? V
.ROOK : V
.BISHOP
];
456 move.next
= this.getRandomSquare([x1
, y1
], steps
);
459 move.end
= { x: x1
, y: y1
};
462 const [x2
, y2
] = [sq2
[0], sq2
[1]];
463 // The move starts normally, on board:
464 let move = super.getBasicMove([x1
, y1
], [x2
, y2
], tr
);
465 if (!!tr
) move.promoteInto
= tr
.c
+ tr
.p
; //in case of (chomped...)
466 const L
= this.firstMove
.length
;
468 [V
.PAWN
, V
.KNIGHT
].includes(piece1
) &&
470 (this.subTurn
== 1 || this.firstMove
[L
-1].end
.effect
== "daisy")
474 const twoSquaresMove
= (Math
.abs(x2
- x1
) == 2);
475 const mushroomX
= x1
+ (twoSquaresMove
? (x2
- x1
) / 2 : 0);
484 if (this.getColor(mushroomX
, y1
) == 'a') {
490 p: this.getPiece(mushroomX
, y1
)
497 const deltaX
= Math
.abs(x2
- x1
);
498 const deltaY
= Math
.abs(y2
- y1
);
500 x1
+ (deltaX
== 2 ? (x2
- x1
) / 2 : 0),
501 y1
+ (deltaY
== 2 ? (y2
- y1
) / 2 : 0)
504 this.board
[eggSquare
[0]][eggSquare
[1]] != V
.EMPTY
&&
505 this.getColor(eggSquare
[0], eggSquare
[1]) != 'a'
518 if (this.getColor(eggSquare
[0], eggSquare
[1]) == 'a') {
524 p: this.getPiece(eggSquare
[0], eggSquare
[1])
532 // For (wa)luigi effect:
533 const changePieceColor
= (color
) => {
535 const oppLastRank
= (color
== 'w' ? 7 : 0);
536 for (let i
=0; i
<8; i
++) {
537 for (let j
=0; j
<8; j
++) {
539 (i
!= move.vanish
[0].x
|| j
!= move.vanish
[0].y
) &&
540 this.board
[i
][j
] != V
.EMPTY
&&
541 this.getColor(i
, j
) == color
543 const piece
= this.getPiece(i
, j
);
544 if (piece
!= V
.KING
&& (piece
!= V
.PAWN
|| i
!= oppLastRank
))
545 pieces
.push({ x: i
, y: j
, p: piece
});
549 // Special case of the current piece (still at its initial position)
551 pieces
.push({ x: move.appear
[0].x
, y: move.appear
[0].y
, p: piece1
});
552 const cp
= pieces
[randInt(pieces
.length
)];
553 if (move.appear
[0].x
!= cp
.x
|| move.appear
[0].y
!= cp
.y
) {
563 else move.appear
.shift();
568 c: V
.GetOppCol(color
),
573 const applyEggEffect
= () => {
574 if (this.subTurn
== 2)
575 // No egg effects at subTurn 2
577 // 1) Determine the effect (some may be impossible)
578 let effects
= ["kingboo", "koopa", "chomp", "bowser"];
579 if (Object
.values(this.captured
[color1
]).some(c
=> c
>= 1))
580 effects
.push("toadette");
581 const lastRank
= { 'w': 0, 'b': 7 };
582 let canPlayAgain
= undefined;
584 move.appear
[0].p
== V
.PAWN
&&
585 move.appear
[0].x
== lastRank
[color1
]
587 // Always possible: promote into a queen, rook or king
591 move.end
.effect
= "daisy";
592 V
.PlayOnBoard(this.board
, move);
593 const square
= [move.appear
[0].x
, move.appear
[0].y
];
594 canPlayAgain
= this.canMove(square
, piece1
);
595 V
.UndoOnBoard(this.board
, move);
596 delete move.end
["effect"];
598 if (canPlayAgain
) effects
.push("daisy");
600 this.board
.some((b
,i
) =>
605 (cell
[1] != V
.PAWN
|| i
!= lastRank
[color1
])
610 effects
.push("luigi");
615 (piece1
!= V
.PAWN
|| move.appear
[0].x
!= lastRank
[oppCol
])
617 this.board
.some((b
,i
) =>
622 (cell
[1] != V
.PAWN
|| i
!= lastRank
[oppCol
])
627 effects
.push("waluigi");
629 const effect
= effects
[randInt(effects
.length
)];
630 move.end
.effect
= effect
;
631 // 2) Apply it if possible
632 if (!(["kingboo", "toadette", "daisy"].includes(effect
))) {
636 // Maybe egg effect was applied after others,
637 // so just shift vanish array:
644 move.appear
[0].p
= V
.IMMOBILIZE_CODE
[piece1
];
647 changePieceColor(oppCol
);
650 changePieceColor(color1
);
655 const applyMushroomEffect
= () => {
656 if ([V
.PAWN
, V
.KING
, V
.KNIGHT
].includes(piece1
)) {
657 // Just make another similar step, if possible (non-capturing)
659 move.appear
[0].x
+ (x2
- x1
),
660 move.appear
[0].y
+ (y2
- y1
)
664 (this.board
[i
][j
] == V
.EMPTY
|| this.getColor(i
, j
) == 'a')
666 move.appear
[0].x
= i
;
667 move.appear
[0].y
= j
;
668 if (this.board
[i
][j
] != V
.EMPTY
) {
669 const object
= this.getPiece(i
, j
);
681 const steps
= V
.steps
[object
== V
.BANANA
? V
.ROOK : V
.BISHOP
];
682 move.next
= this.getRandomSquare([i
, j
], steps
);
688 applyMushroomEffect();
695 // Queen, bishop or rook:
697 (x2
- x1
) / Math
.abs(x2
- x1
) || 0,
698 (y2
- y1
) / Math
.abs(y2
- y1
) || 0
700 const next
= [move.appear
[0].x
+ step
[0], move.appear
[0].y
+ step
[1]];
702 V
.OnBoard(next
[0], next
[1]) &&
703 this.board
[next
[0]][next
[1]] != V
.EMPTY
&&
704 this.getColor(next
[0], next
[1]) != 'a'
706 const afterNext
= [next
[0] + step
[0], next
[1] + step
[1]];
707 if (V
.OnBoard(afterNext
[0], afterNext
[1])) {
708 const afterColor
= this.getColor(afterNext
[0], afterNext
[1])
710 this.board
[afterNext
[0]][afterNext
[1]] == V
.EMPTY
||
713 move.appear
[0].x
= afterNext
[0];
714 move.appear
[0].y
= afterNext
[1];
715 if (this.board
[afterNext
[0]][afterNext
[1]] != V
.EMPTY
) {
716 // The "object" could also be an opponent's piece
717 const object
= this.getPiece(afterNext
[0], afterNext
[1]);
730 V
.steps
[object
== V
.BANANA
? V
.ROOK : V
.BISHOP
];
731 move.next
= this.getRandomSquare(
732 [afterNext
[0], afterNext
[1]], steps
);
738 applyMushroomEffect();
747 const color2
= this.getColor(x2
, y2
);
748 const piece2
= this.getPiece(x2
, y2
);
753 const steps
= V
.steps
[piece2
== V
.BANANA
? V
.ROOK : V
.BISHOP
];
754 move.next
= this.getRandomSquare([x2
, y2
], steps
);
757 applyMushroomEffect();
760 if (this.subTurn
== 1)
761 // No egg effect at subTurn 2
769 move.appear
.length
> 0 &&
770 [V
.ROOK
, V
.BISHOP
].includes(piece1
)
772 const finalSquare
= [move.appear
[0].x
, move.appear
[0].y
];
775 this.getColor(finalSquare
[0], finalSquare
[1]) != 'a' ||
776 this.getPiece(finalSquare
[0], finalSquare
[1]) != V
.EGG
779 V
.steps
[piece1
== V
.ROOK
? V
.BISHOP : V
.ROOK
].filter(s
=> {
780 const [i
, j
] = [finalSquare
[0] + s
[0], finalSquare
[1] + s
[1]];
783 (this.board
[i
][j
] == V
.EMPTY
|| this.getColor(i
, j
) == 'a')
786 if (validSteps
.length
>= 1) {
788 finalSquare
[0] + validSteps
[0][0],
789 finalSquare
[1] + validSteps
[0][1]
796 p: (piece1
== V
.ROOK
? V
.BANANA : V
.BOMB
)
799 if (this.board
[x
][y
] != V
.EMPTY
) {
801 new PiPo({ x: x
, y: y
, c: 'a', p: this.getPiece(x
, y
) }));
809 getBasicMove(psq1
, sq2
, tr
) {
811 if (Array
.isArray(psq1
)) psq1
= { x: psq1
[0], y: psq1
[1] };
812 let m
= this.getBasicMove_aux(psq1
, sq2
, tr
, "initMove");
814 // Last move ended on bomb or banana, direction change
815 V
.PlayOnBoard(this.board
, m
);
817 m
= this.getBasicMove_aux(
818 { x: m
.appear
[0].x
, y: m
.appear
[0].y
}, m
.next
);
820 for (let i
=moves
.length
-1; i
>=0; i
--) V
.UndoOnBoard(this.board
, moves
[i
]);
822 // Now merge moves into one
824 // start is wrong for Toadette moves --> it's fixed later
825 move.start
= { x: psq1
.x
, y: psq1
.y
};
826 move.end
= !!sq2
? { x: sq2
[0], y: sq2
[1] } : { x: psq1
.x
, y: psq1
.y
};
827 if (!!tr
) move.promoteInto
= moves
[0].promoteInto
;
828 let lm
= moves
[moves
.length
-1];
829 if (this.subTurn
== 1 && !!lm
.end
.effect
)
830 move.end
.effect
= lm
.end
.effect
;
831 if (moves
.length
== 1) {
832 move.appear
= moves
[0].appear
;
833 move.vanish
= moves
[0].vanish
;
836 // Keep first vanish and last appear (if any)
837 move.appear
= lm
.appear
;
838 move.vanish
= moves
[0].vanish
;
840 move.vanish
.length
>= 1 &&
841 move.appear
.length
>= 1 &&
842 move.vanish
[0].x
== move.appear
[0].x
&&
843 move.vanish
[0].y
== move.appear
[0].y
845 // Loopback on initial square:
849 for (let i
=1; i
< moves
.length
- 1; i
++) {
850 for (let v
of moves
[i
].vanish
) {
851 // Only vanishing objects, not appearing at init move
855 moves
[0].appear
.length
== 1 ||
856 moves
[0].appear
[1].x
!= v
.x
||
857 moves
[0].appear
[1].y
!= v
.y
864 // Final vanish is our piece, but others might be relevant
865 // (for some egg bonuses at least).
866 for (let i
=1; i
< lm
.vanish
.length
; i
++) {
868 lm
.vanish
[i
].c
!= 'a' ||
869 moves
[0].appear
.length
== 1 ||
870 moves
[0].appear
[1].x
!= lm
.vanish
[i
].x
||
871 moves
[0].appear
[1].y
!= lm
.vanish
[i
].y
873 move.vanish
.push(lm
.vanish
[i
]);
880 getPotentialPawnMoves([x
, y
]) {
881 const color
= this.turn
;
882 const oppCol
= V
.GetOppCol(color
);
883 const [sizeX
, sizeY
] = [V
.size
.x
, V
.size
.y
];
884 const shiftX
= V
.PawnSpecs
.directions
[color
];
885 const firstRank
= (color
== "w" ? sizeX
- 1 : 0);
888 this.board
[x
+ shiftX
][y
] == V
.EMPTY
||
889 this.getColor(x
+ shiftX
, y
) == 'a'
891 this.addPawnMoves([x
, y
], [x
+ shiftX
, y
], moves
);
893 [firstRank
, firstRank
+ shiftX
].includes(x
) &&
895 this.board
[x
+ 2 * shiftX
][y
] == V
.EMPTY
||
896 this.getColor(x
+ 2 * shiftX
, y
) == 'a'
899 moves
.push(this.getBasicMove({ x: x
, y: y
}, [x
+ 2 * shiftX
, y
]));
902 for (let shiftY
of [-1, 1]) {
905 y
+ shiftY
< sizeY
&&
906 this.board
[x
+ shiftX
][y
+ shiftY
] != V
.EMPTY
&&
907 ['a', oppCol
].includes(this.getColor(x
+ shiftX
, y
+ shiftY
))
909 this.addPawnMoves([x
, y
], [x
+ shiftX
, y
+ shiftY
], moves
);
915 getPotentialQueenMoves(sq
) {
916 const normalMoves
= super.getPotentialQueenMoves(sq
);
917 // If flag allows it, add 'invisible movements'
918 let invisibleMoves
= [];
919 if (this.powerFlags
[this.turn
][V
.QUEEN
]) {
920 normalMoves
.forEach(m
=> {
922 m
.appear
.length
== 1 &&
923 m
.vanish
.length
== 1 &&
924 // Only simple non-capturing moves:
927 let im
= JSON
.parse(JSON
.stringify(m
));
928 im
.appear
[0].p
= V
.INVISIBLE_QUEEN
;
929 im
.end
.noHighlight
= true;
930 invisibleMoves
.push(im
);
934 return normalMoves
.concat(invisibleMoves
);
937 getPotentialKingMoves([x
, y
]) {
938 let moves
= super.getPotentialKingMoves([x
, y
]);
939 const color
= this.turn
;
940 // If flag allows it, add 'remote shell captures'
941 if (this.powerFlags
[this.turn
][V
.KING
]) {
942 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]).forEach(step
=> {
943 const [nextX
, nextY
] = [x
+ step
[0], y
+ step
[1]];
945 V
.OnBoard(nextX
, nextY
) &&
947 this.board
[nextX
][nextY
] == V
.EMPTY
||
949 this.getColor(nextX
, nextY
) == 'a' &&
950 [V
.EGG
, V
.MUSHROOM
].includes(this.getPiece(nextX
, nextY
))
954 let [i
, j
] = [x
+ 2 * step
[0], y
+ 2 * step
[1]];
958 this.board
[i
][j
] == V
.EMPTY
||
960 this.getColor(i
, j
) == 'a' &&
961 [V
.EGG
, V
.MUSHROOM
].includes(this.getPiece(i
, j
))
968 if (V
.OnBoard(i
, j
)) {
969 const colIJ
= this.getColor(i
, j
);
970 if (colIJ
!= color
) {
971 // May just destroy a bomb or banana:
974 start: { x: x
, y: y
},
979 x: i
, y: j
, c: colIJ
, p: this.getPiece(i
, j
)
992 getSlideNJumpMoves([x
, y
], steps
, oneStep
) {
994 outerLoop: for (let step
of steps
) {
1000 this.board
[i
][j
] == V
.EMPTY
||
1001 this.getPiece(i
, j
) == V
.INVISIBLE_QUEEN
||
1003 this.getColor(i
, j
) == 'a' &&
1004 [V
.EGG
, V
.MUSHROOM
].includes(this.getPiece(i
, j
))
1008 moves
.push(this.getBasicMove({ x: x
, y: y
}, [i
, j
]));
1009 if (oneStep
) continue outerLoop
;
1013 if (V
.OnBoard(i
, j
) && this.canTake([x
, y
], [i
, j
]))
1014 moves
.push(this.getBasicMove({ x: x
, y: y
}, [i
, j
]));
1019 getAllPotentialMoves() {
1020 if (this.subTurn
== 1) return super.getAllPotentialMoves();
1022 const L
= this.firstMove
.length
;
1023 const fm
= this.firstMove
[L
-1];
1024 switch (fm
.end
.effect
) {
1027 start: { x: -1, y: -1 },
1028 end: { x: -1, y: -1 },
1034 (fm
.vanish
[0].p
== V
.ROOK
? V
.steps
[V
.BISHOP
] : V
.steps
[V
.ROOK
])
1036 const [i
, j
] = [fm
.appear
[0].x
+ step
[0], fm
.appear
[0].y
+ step
[1]];
1039 (this.board
[i
][j
] == V
.EMPTY
|| this.getColor(i
, j
) == 'a')
1042 start: { x: -1, y: -1 },
1043 end: { x: i
, y: j
},
1049 p: (fm
.vanish
[0].p
== V
.ROOK
? V
.BANANA : V
.BOMB
)
1054 if (this.board
[i
][j
] != V
.EMPTY
) {
1056 new PiPo({ x: i
, y: j
, c: 'a', p: this.getPiece(i
, j
) }));
1063 const [x
, y
] = [fm
.appear
[0].x
, fm
.appear
[0].y
];
1064 for (let i
=0; i
<8; i
++) {
1065 for (let j
=0; j
<8; j
++) {
1066 const colIJ
= this.getColor(i
, j
);
1070 this.board
[i
][j
] != V
.EMPTY
&&
1073 const movedUnit
= new PiPo({
1077 p: this.getPiece(i
, j
)
1079 let mMove
= this.getBasicMove({ x: x
, y: y
}, [i
, j
]);
1080 mMove
.appear
.push(movedUnit
);
1088 const x
= V
.size
.x
+ (this.turn
== 'w' ? 0 : 1);
1089 for (let y
= 0; y
< 8; y
++)
1090 Array
.prototype.push
.apply(moves
, this.getReserveMoves([x
, y
]));
1094 moves
= super.getPotentialMovesFrom([fm
.appear
[0].x
, fm
.appear
[0].y
]);
1101 const L
= this.firstMove
.length
;
1102 const fm
= (L
> 0 ? this.firstMove
[L
-1] : null);
1105 this.subTurn
== 1 ||
1106 !([0, "daisy"].includes(fm
.end
.effect
))
1110 const [x
, y
] = [square
[0], square
[1]];
1111 const deltaX
= Math
.abs(fm
.appear
[0].x
- x
);
1112 const deltaY
= Math
.abs(fm
.appear
[0].y
- y
);
1114 fm
.end
.effect
== 0 &&
1115 (this.board
[x
][y
] == V
.EMPTY
|| this.getColor(x
, y
) == 'a') &&
1117 (fm
.vanish
[0].p
== V
.ROOK
&& deltaX
== 1 && deltaY
== 1) ||
1118 (fm
.vanish
[0].p
== V
.BISHOP
&& deltaX
+ deltaY
== 1)
1122 start: { x: -1, y: -1 },
1123 end: { x: x
, y: y
},
1129 p: (fm
.vanish
[0].p
== V
.ROOK
? V
.BANANA : V
.BOMB
)
1134 if (this.board
[x
][y
] != V
.EMPTY
) {
1136 new PiPo({ x: x
, y: y
, c: 'a', p: this.getPiece(x
, y
) }));
1141 fm
.end
.effect
== "daisy" &&
1142 deltaX
== 0 && deltaY
== 0 &&
1143 !this.canMove([x
, y
])
1145 // No possible move: return empty move
1147 start: { x: -1, y: -1 },
1148 end: { x: -1, y: -1 },
1157 // if (!this.states) this.states = [];
1158 // const stateFen = this.getFen();
1159 // this.states.push(stateFen);
1161 move.flags
= JSON
.stringify(this.aggregateFlags());
1162 V
.PlayOnBoard(this.board
, move);
1163 move.turn
= [this.turn
, this.subTurn
];
1164 if ([0, "kingboo", "toadette", "daisy"].includes(move.end
.effect
)) {
1165 this.firstMove
.push(move);
1169 this.turn
= V
.GetOppCol(this.turn
);
1173 this.postPlay(move);
1177 if (move.end
.effect
== "toadette") this.reserve
= this.captured
;
1178 else this.reserve
= undefined;
1179 const color
= move.turn
[0];
1180 if (move.vanish
.length
== 2 && move.vanish
[1].c
!= 'a') {
1181 // Capture: update this.captured
1182 let capturedPiece
= move.vanish
[1].p
;
1183 if (capturedPiece
== V
.INVISIBLE_QUEEN
) capturedPiece
= V
.QUEEN
;
1184 else if (Object
.keys(V
.IMMOBILIZE_DECODE
).includes(capturedPiece
))
1185 capturedPiece
= V
.IMMOBILIZE_DECODE
[capturedPiece
];
1186 this.captured
[move.vanish
[1].c
][capturedPiece
]++;
1188 else if (move.vanish
.length
== 0) {
1189 if (move.appear
.length
== 0 || move.appear
[0].c
== 'a') return;
1190 // A piece is back on board
1191 this.captured
[move.appear
[0].c
][move.appear
[0].p
]--;
1193 if (move.appear
.length
== 0) {
1194 // Three cases: king "shell capture", Chomp or Koopa
1195 if (this.getPiece(move.start
.x
, move.start
.y
) == V
.KING
)
1196 // King remote capture:
1197 this.powerFlags
[color
][V
.KING
] = false;
1198 else if (move.end
.effect
== "chomp")
1199 this.captured
[color
][move.vanish
[0].p
]++;
1201 else if (move.appear
[0].p
== V
.INVISIBLE_QUEEN
)
1202 this.powerFlags
[move.appear
[0].c
][V
.QUEEN
] = false;
1203 if (this.subTurn
== 2) return;
1205 move.turn
[1] == 1 &&
1206 move.appear
.length
== 0 ||
1207 !(Object
.keys(V
.IMMOBILIZE_DECODE
).includes(move.appear
[0].p
))
1209 // Look for an immobilized piece of my color: it can now move
1210 for (let i
=0; i
<8; i
++) {
1211 for (let j
=0; j
<8; j
++) {
1212 if (this.board
[i
][j
] != V
.EMPTY
) {
1213 const piece
= this.getPiece(i
, j
);
1215 this.getColor(i
, j
) == color
&&
1216 Object
.keys(V
.IMMOBILIZE_DECODE
).includes(piece
)
1218 this.board
[i
][j
] = color
+ V
.IMMOBILIZE_DECODE
[piece
];
1219 move.wasImmobilized
= [i
, j
];
1225 // Also make opponent invisible queen visible again, if any
1226 const oppCol
= V
.GetOppCol(color
);
1227 for (let i
=0; i
<8; i
++) {
1228 for (let j
=0; j
<8; j
++) {
1230 this.board
[i
][j
] != V
.EMPTY
&&
1231 this.getColor(i
, j
) == oppCol
&&
1232 this.getPiece(i
, j
) == V
.INVISIBLE_QUEEN
1234 this.board
[i
][j
] = oppCol
+ V
.QUEEN
;
1235 move.wasInvisible
= [i
, j
];
1242 this.disaggregateFlags(JSON
.parse(move.flags
));
1243 V
.UndoOnBoard(this.board
, move);
1244 if ([0, "kingboo", "toadette", "daisy"].includes(move.end
.effect
))
1245 this.firstMove
.pop();
1246 else this.movesCount
--;
1247 this.turn
= move.turn
[0];
1248 this.subTurn
= move.turn
[1];
1249 this.postUndo(move);
1251 // const stateFen = this.getFen();
1252 // if (stateFen != this.states[this.states.length-1]) debugger;
1253 // this.states.pop();
1257 if (!!move.wasImmobilized
) {
1258 const [i
, j
] = move.wasImmobilized
;
1260 this.getColor(i
, j
) + V
.IMMOBILIZE_CODE
[this.getPiece(i
, j
)];
1262 if (!!move.wasInvisible
) {
1263 const [i
, j
] = move.wasInvisible
;
1264 this.board
[i
][j
] = this.getColor(i
, j
) + V
.INVISIBLE_QUEEN
;
1266 if (move.vanish
.length
== 2 && move.vanish
[1].c
!= 'a') {
1267 let capturedPiece
= move.vanish
[1].p
;
1268 if (capturedPiece
== V
.INVISIBLE_QUEEN
) capturedPiece
= V
.QUEEN
;
1269 else if (Object
.keys(V
.IMMOBILIZE_DECODE
).includes(capturedPiece
))
1270 capturedPiece
= V
.IMMOBILIZE_DECODE
[capturedPiece
];
1271 this.captured
[move.vanish
[1].c
][capturedPiece
]--;
1273 else if (move.vanish
.length
== 0) {
1274 if (move.appear
.length
== 0 || move.appear
[0].c
== 'a') return;
1275 // A piece was back on board
1276 this.captured
[move.appear
[0].c
][move.appear
[0].p
]++;
1278 else if (move.appear
.length
== 0 && move.end
.effect
== "chomp")
1279 this.captured
[move.vanish
[0].c
][move.vanish
[0].p
]--;
1280 if (move.vanish
.length
== 0) this.reserve
= this.captured
;
1281 else this.reserve
= undefined;
1289 // Find kings (not tracked in this variant)
1290 let kingThere
= { w: false, b: false };
1291 for (let i
=0; i
<8; i
++) {
1292 for (let j
=0; j
<8; j
++) {
1294 this.board
[i
][j
] != V
.EMPTY
&&
1295 ['k', 'l'].includes(this.getPiece(i
, j
))
1297 kingThere
[this.getColor(i
, j
)] = true;
1301 if (!kingThere
['w']) return "0-1";
1302 if (!kingThere
['b']) return "1-0";
1303 if (!this.atLeastOneMove()) return (this.turn
== 'w' ? "0-1" : "1-0");
1307 static GenRandInitFen(randomness
) {
1309 SuicideRules
.GenRandInitFen(randomness
).slice(0, -1) +
1310 // Add Peach + Mario flags + capture counts
1315 filterValid(moves
) {
1319 static get VALUES() {
1320 return Object
.assign(
1338 static get SEARCH_DEPTH() {
1343 const moves
= this.getAllValidMoves();
1344 // Split into "normal" and "random" moves:
1345 // (Next splitting condition is OK because cannot take self object
1346 // without a banana or bomb on the way).
1347 const deterministicMoves
= moves
.filter(m
=> {
1348 return m
.vanish
.every(a
=> a
.c
!= 'a' || a
.p
== V
.MUSHROOM
);
1350 const randomMoves
= moves
.filter(m
=> {
1351 return m
.vanish
.some(a
=> a
.c
== 'a' && a
.p
!= V
.MUSHROOM
);
1353 if (Math
.random() < deterministicMoves
.length
/ randomMoves
.length
)
1354 // Play a deterministic one: capture king or material if possible
1355 return super.getComputerMove(deterministicMoves
);
1356 // Play a random effect move, at random:
1357 let move1
= randomMoves
[randInt(moves
.length
)];
1359 let move2
= undefined;
1360 if (this.subTurn
== 2) {
1361 const moves2
= this.getAllValidMoves();
1362 move2
= moves2
[randInt(moves2
.length
)];
1365 if (!move2
) return move1
;
1366 return [move1
, move2
];
1370 if (move.vanish
.length
== 0 && move.appear
.length
== 0) return "-";
1373 move.appear
.length
> 0 &&
1374 move.appear
[0].p
== V
.INVISIBLE_QUEEN
1378 const finalSquare
= V
.CoordsToSquare(move.end
);
1379 // Next condition also includes Toadette placements:
1380 if (move.appear
.length
> 0 && move.vanish
.every(a
=> a
.c
== 'a')) {
1382 move.appear
[0].p
!= V
.PAWN
? move.appear
[0].p
.toUpperCase() : "";
1383 return piece
+ "@" + finalSquare
;
1385 else if (move.appear
.length
== 0) {
1386 const piece
= this.getPiece(move.start
.x
, move.start
.y
);
1387 if (piece
== V
.KING
&& !move.end
.effect
)
1388 // King remote capture
1389 return "Kx" + finalSquare
;
1390 // Koopa or Chomp, or loopback after bananas, bombs & mushrooms:
1392 piece
.toUpperCase() + "x" + finalSquare
+
1395 ? "*" + (move.end
.effect
== "koopa" ? "K" : "C")
1401 move.appear
.length
== 1 &&
1402 move.vanish
.length
== 1 &&
1403 move.appear
[0].c
== 'a' &&
1404 move.vanish
[0].c
== 'a'
1406 // Bonus replacement:
1407 return move.appear
[0].p
.toUpperCase() + "@" + finalSquare
;
1410 move.appear
.length
== 2 &&
1411 move.vanish
.length
== 2 &&
1412 move.appear
.every(a
=> a
.c
!= 'a') &&
1413 move.vanish
.every(v
=> v
.c
!= 'a')
1415 // King Boo exchange
1416 return move.vanish
[1].p
.toUpperCase() + finalSquare
;
1418 const piece
= move.vanish
[0].p
;
1419 let notation
= undefined;
1420 if (piece
== V
.PAWN
) {
1422 if (move.vanish
.length
>= 2) {
1424 const startColumn
= V
.CoordToColumn(move.start
.y
);
1425 notation
= startColumn
+ "x" + finalSquare
;
1427 else notation
= finalSquare
;
1428 if (move.appear
[0].p
!= V
.PAWN
)
1430 notation
+= "=" + move.appear
[0].p
.toUpperCase();
1434 piece
.toUpperCase() +
1435 (move.vanish
.length
>= 2 ? "x" : "") +
1438 if (!!move.end
.effect
) {
1439 switch (move.end
.effect
) {
1454 const lastAppear
= move.appear
[move.appear
.length
- 1];
1456 V
.CoordsToSquare({ x: lastAppear
.x
, y : lastAppear
.y
});
1457 notation
+= "*" + move.end
.effect
[0].toUpperCase() + effectOn
;