ef2ef21544cd7763ecd981dd890ce289bc8f2531
1 import ChessRules
from "/base_rules";
2 import { SuicideRules
} from "/variants/Suicide"; ////////:TODO generalize genRandInitFen ?!
3 // constraints satisfaction ? + Chakart display bonus messages
4 // + animation + multi-moves for bananas/bombs/mushrooms
8 import { ArrayFun
} from "/utils/array";
9 import { randInt
} from "/utils/alea";
10 import PiPo
from "/utils/PiPo.js";
11 import Move
from "/utils/Move.js";
13 export class ChakartRules
extends ChessRules
{
15 static get Options() {
20 variable: "randomness",
23 { label: "Deterministic", value: 0 },
24 { label: "Symmetric random", value: 1 },
25 { label: "Asymmetric random", value: 2 }
32 static get PawnSpecs() {
33 return SuicideRules
.PawnSpecs
;
42 static get IMMOBILIZE_CODE() {
53 static get IMMOBILIZE_DECODE() {
64 static get INVISIBLE_QUEEN() {
68 // Fictive color 'a', bomb banana mushroom egg
73 return 'd'; //"Donkey"
78 static get MUSHROOM() {
85 ? "w" + f
.toLowerCase()
86 : (['w', 'd', 'e', 'm'].includes(f
) ? "a" : "b") + f
92 ChessRules
.PIECES
.concat(
93 Object
.keys(V
.IMMOBILIZE_DECODE
)).concat(
94 [V
.BANANA
, V
.BOMB
, V
.EGG
, V
.MUSHROOM
, V
.INVISIBLE_QUEEN
])
102 b
[1] == V
.INVISIBLE_QUEEN
||
103 Object
.keys(V
.IMMOBILIZE_DECODE
).includes(b
[1])
111 if (!!m
.promoteInto
) return m
.promoteInto
;
112 if (m
.appear
.length
== 0 && m
.vanish
.length
== 1)
113 // King 'remote shell capture', on an adjacent square:
114 return this.getPpath(m
.vanish
[0].c
+ m
.vanish
[0].p
);
115 let piece
= m
.appear
[0].p
;
116 if (Object
.keys(V
.IMMOBILIZE_DECODE
).includes(piece
))
117 // Promotion by capture into immobilized piece: do not reveal!
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
[4] }
130 static IsGoodFen(fen
) {
131 if (!ChessRules
.IsGoodFen(fen
)) return false;
132 const captured
= V
.ParseFen(fen
).captured
;
133 if (!captured
|| !captured
.match(/^[0-9]{12,12}$/)) return false;
137 // King can be l or L (immobilized) --> similar to Alice variant
138 static IsGoodPosition(position
) {
139 if (position
.length
== 0) return false;
140 const rows
= position
.split("/");
141 if (rows
.length
!= V
.size
.x
) return false;
142 let kings
= { "k": 0, "K": 0, 'l': 0, 'L': 0 };
143 for (let row
of rows
) {
145 for (let i
= 0; i
< row
.length
; i
++) {
146 if (['K', 'k', 'L', 'l'].includes(row
[i
])) kings
[row
[i
]]++;
147 if (V
.PIECES
.includes(row
[i
].toLowerCase())) sumElts
++;
149 const num
= parseInt(row
[i
], 10);
150 if (isNaN(num
)) return false;
154 if (sumElts
!= V
.size
.y
) return false;
156 if (kings
['k'] + kings
['l'] == 0 || kings
['K'] + kings
['L'] == 0)
161 static IsGoodFlags(flags
) {
162 // 4 for Peach + Mario w, b
163 return !!flags
.match(/^[01]{4,4}$/);
167 // King can send shell? Queen can be invisible?
169 w: { 'k': false, 'q': false },
170 b: { 'k': false, 'q': false }
172 for (let c
of ["w", "b"]) {
173 for (let p
of ['k', 'q']) {
174 this.powerFlags
[c
][p
] =
175 fenflags
.charAt((c
== "w" ? 0 : 2) + (p
== 'k' ? 0 : 1)) == "1";
181 return this.powerFlags
;
184 disaggregateFlags(flags
) {
185 this.powerFlags
= flags
;
189 return super.getFen() + " " + this.getCapturedFen();
193 return super.getFenForRepeat() + "_" + this.getCapturedFen();
197 let counts
= [...Array(12).fill(0)];
199 for (let p
of V
.RESERVE_PIECES
) {
200 counts
[i
] = this.captured
["w"][p
];
201 counts
[6 + i
] = this.captured
["b"][p
];
204 return counts
.join("");
209 setOtherVariables(fen
) {
210 super.setOtherVariables(fen
);
211 // Initialize captured pieces' counts from FEN
213 V
.ParseFen(fen
).captured
.split("").map(x
=> parseInt(x
, 10));
216 [V
.PAWN
]: captured
[0],
217 [V
.ROOK
]: captured
[1],
218 [V
.KNIGHT
]: captured
[2],
219 [V
.BISHOP
]: captured
[3],
220 [V
.QUEEN
]: captured
[4],
221 [V
.KING
]: captured
[5]
224 [V
.PAWN
]: captured
[6],
225 [V
.ROOK
]: captured
[7],
226 [V
.KNIGHT
]: captured
[8],
227 [V
.BISHOP
]: captured
[9],
228 [V
.QUEEN
]: captured
[10],
229 [V
.KING
]: 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
++) {
272 this.board
[i
][j
] == V
.EMPTY
||
273 this.getColor(i
, j
) == 'a' ||
274 this.getPiece(i
, j
) == V
.INVISIBLE_QUEEN
276 let m
= this.getBasicMove({ p: p
, x: i
, y: j
});
277 m
.start
= { x: x
, y: y
};
285 getPotentialMovesFrom([x
, y
]) {
287 if (this.subTurn
== 1) {
288 moves
= super.getPotentialMovesFrom([x
, y
]);
289 const finalPieces
= V
.PawnSpecs
.promotions
;
290 const color
= this.turn
;
291 const lastRank
= (color
== "w" ? 0 : 7);
295 m
.appear
.length
> 0 &&
296 ['p', 's'].includes(m
.appear
[0].p
) &&
297 m
.appear
[0].x
== lastRank
299 for (let i
= 1; i
< finalPieces
.length
; i
++) {
300 const piece
= finalPieces
[i
];
301 let otherM
= JSON
.parse(JSON
.stringify(m
));
303 m
.appear
[0].p
== V
.PAWN
305 : V
.IMMOBILIZE_CODE
[finalPieces
[i
]];
308 // Finally alter m itself:
310 m
.appear
[0].p
== V
.PAWN
312 : V
.IMMOBILIZE_CODE
[finalPieces
[0]];
315 Array
.prototype.push
.apply(moves
, pMoves
);
319 const L
= this.effects
.length
;
320 switch (this.effects
[L
-1]) {
322 // Exchange position with any visible piece,
323 // except pawns if arriving on last rank.
324 const lastRank
= { 'w': 0, 'b': 7 };
325 const color
= this.turn
;
326 const allowLastRank
= (this.getPiece(x
, y
) != V
.PAWN
);
327 for (let i
=0; i
<8; i
++) {
328 for (let j
=0; j
<8; j
++) {
329 const colIJ
= this.getColor(i
, j
);
330 const pieceIJ
= this.getPiece(i
, j
);
332 (i
!= x
|| j
!= y
) &&
333 this.board
[i
][j
] != V
.EMPTY
&&
334 pieceIJ
!= V
.INVISIBLE_QUEEN
&&
338 (pieceIJ
!= V
.PAWN
|| x
!= lastRank
[colIJ
]) &&
339 (allowLastRank
|| i
!= lastRank
[color
])
341 const movedUnit
= new PiPo({
345 p: this.getPiece(i
, j
)
347 let mMove
= this.getBasicMove({ x: x
, y: y
}, [i
, j
]);
348 mMove
.appear
.push(movedUnit
);
356 // Resurrect a captured piece
357 if (x
>= V
.size
.x
) moves
= this.getReserveMoves([x
, y
]);
360 // Play again with any piece
361 moves
= super.getPotentialMovesFrom([x
, y
]);
368 // Helper for getBasicMove(): banana/bomb effect
369 getRandomSquare([x
, y
], steps
) {
370 const validSteps
= steps
.filter(s
=> V
.OnBoard(x
+ s
[0], y
+ s
[1]));
371 const step
= validSteps
[randInt(validSteps
.length
)];
372 return [x
+ step
[0], y
+ step
[1]];
375 // Apply mushroom, bomb or banana effect (hidden to the player).
376 // Determine egg effect, too, and apply its first part if possible.
377 getBasicMove_aux(psq1
, sq2
, tr
, initMove
) {
378 const [x1
, y1
] = [psq1
.x
, psq1
.y
];
379 const color1
= this.turn
;
380 const piece1
= (!!tr
? tr
.p : (psq1
.p
|| this.getPiece(x1
, y1
)));
381 const oppCol
= V
.GetOppCol(color1
);
387 // banana or bomb defines next square, or the move ends there
396 if (this.board
[x1
][y1
] != V
.EMPTY
) {
397 const initP1
= this.getPiece(x1
, y1
);
402 c: this.getColor(x1
, y1
),
406 if ([V
.BANANA
, V
.BOMB
].includes(initP1
)) {
407 const steps
= V
.steps
[initP1
== V
.BANANA
? V
.ROOK : V
.BISHOP
];
408 move.next
= this.getRandomSquare([x1
, y1
], steps
);
411 move.end
= { x: x1
, y: y1
};
414 const [x2
, y2
] = [sq2
[0], sq2
[1]];
415 // The move starts normally, on board:
416 let move = super.getBasicMove([x1
, y1
], [x2
, y2
], tr
);
417 if (!!tr
) move.promoteInto
= tr
.c
+ tr
.p
; //in case of (chomped...)
418 const L
= this.effects
.length
;
420 [V
.PAWN
, V
.KNIGHT
].includes(piece1
) &&
422 (this.subTurn
== 1 || this.effects
[L
-1] == "daisy")
426 const twoSquaresMove
= (Math
.abs(x2
- x1
) == 2);
427 const mushroomX
= x1
+ (twoSquaresMove
? (x2
- x1
) / 2 : 0);
436 if (this.getColor(mushroomX
, y1
) == 'a') {
442 p: this.getPiece(mushroomX
, y1
)
449 const deltaX
= Math
.abs(x2
- x1
);
450 const deltaY
= Math
.abs(y2
- y1
);
452 x1
+ (deltaX
== 2 ? (x2
- x1
) / 2 : 0),
453 y1
+ (deltaY
== 2 ? (y2
- y1
) / 2 : 0)
456 this.board
[eggSquare
[0]][eggSquare
[1]] != V
.EMPTY
&&
457 this.getColor(eggSquare
[0], eggSquare
[1]) != 'a'
470 if (this.getColor(eggSquare
[0], eggSquare
[1]) == 'a') {
476 p: this.getPiece(eggSquare
[0], eggSquare
[1])
484 // For (wa)luigi effect:
485 const changePieceColor
= (color
) => {
487 const oppLastRank
= (color
== 'w' ? 7 : 0);
488 for (let i
=0; i
<8; i
++) {
489 for (let j
=0; j
<8; j
++) {
490 const piece
= this.getPiece(i
, j
);
492 (i
!= move.vanish
[0].x
|| j
!= move.vanish
[0].y
) &&
493 this.board
[i
][j
] != V
.EMPTY
&&
494 piece
!= V
.INVISIBLE_QUEEN
&&
495 this.getColor(i
, j
) == color
497 if (piece
!= V
.KING
&& (piece
!= V
.PAWN
|| i
!= oppLastRank
))
498 pieces
.push({ x: i
, y: j
, p: piece
});
502 // Special case of the current piece (still at its initial position)
504 pieces
.push({ x: move.appear
[0].x
, y: move.appear
[0].y
, p: piece1
});
505 const cp
= pieces
[randInt(pieces
.length
)];
506 if (move.appear
[0].x
!= cp
.x
|| move.appear
[0].y
!= cp
.y
) {
516 else move.appear
.shift();
521 c: V
.GetOppCol(color
),
526 const applyEggEffect
= () => {
527 if (this.subTurn
== 2)
528 // No egg effects at subTurn 2
530 // 1) Determine the effect (some may be impossible)
531 let effects
= ["kingboo", "koopa", "chomp", "bowser", "daisy"];
532 if (Object
.values(this.captured
[color1
]).some(c
=> c
>= 1))
533 effects
.push("toadette");
534 const lastRank
= { 'w': 0, 'b': 7 };
536 this.board
.some((b
,i
) =>
541 (cell
[1] != V
.PAWN
|| i
!= lastRank
[color1
])
546 effects
.push("luigi");
551 (piece1
!= V
.PAWN
|| move.appear
[0].x
!= lastRank
[oppCol
])
553 this.board
.some((b
,i
) =>
558 (cell
[1] != V
.PAWN
|| i
!= lastRank
[oppCol
])
563 effects
.push("waluigi");
565 const effect
= effects
[randInt(effects
.length
)];
566 move.end
.effect
= effect
;
567 // 2) Apply it if possible
568 if (!(["kingboo", "toadette", "daisy"].includes(effect
))) {
572 // Maybe egg effect was applied after others,
573 // so just shift vanish array:
580 move.appear
[0].p
= V
.IMMOBILIZE_CODE
[piece1
];
583 changePieceColor(oppCol
);
586 changePieceColor(color1
);
591 const applyMushroomEffect
= () => {
592 if ([V
.PAWN
, V
.KING
, V
.KNIGHT
].includes(piece1
)) {
593 // Just make another similar step, if possible (non-capturing)
595 move.appear
[0].x
+ (x2
- x1
),
596 move.appear
[0].y
+ (y2
- y1
)
601 this.board
[i
][j
] == V
.EMPTY
||
602 this.getPiece(i
, j
) == V
.INVISIBLE_QUEEN
||
603 this.getColor(i
, j
) == 'a'
606 move.appear
[0].x
= i
;
607 move.appear
[0].y
= j
;
608 if (this.board
[i
][j
] != V
.EMPTY
) {
609 const object
= this.getPiece(i
, j
);
610 const color
= this.getColor(i
, j
);
622 const steps
= V
.steps
[object
== V
.BANANA
? V
.ROOK : V
.BISHOP
];
623 move.next
= this.getRandomSquare([i
, j
], steps
);
629 applyMushroomEffect();
636 // Queen, bishop or rook:
638 (x2
- x1
) / Math
.abs(x2
- x1
) || 0,
639 (y2
- y1
) / Math
.abs(y2
- y1
) || 0
641 const next
= [move.appear
[0].x
+ step
[0], move.appear
[0].y
+ step
[1]];
643 V
.OnBoard(next
[0], next
[1]) &&
644 this.board
[next
[0]][next
[1]] != V
.EMPTY
&&
645 this.getPiece(next
[0], next
[1]) != V
.INVISIBLE_QUEEN
&&
646 this.getColor(next
[0], next
[1]) != 'a'
648 const afterNext
= [next
[0] + step
[0], next
[1] + step
[1]];
649 if (V
.OnBoard(afterNext
[0], afterNext
[1])) {
650 const afterColor
= this.getColor(afterNext
[0], afterNext
[1]);
652 this.board
[afterNext
[0]][afterNext
[1]] == V
.EMPTY
||
655 move.appear
[0].x
= afterNext
[0];
656 move.appear
[0].y
= afterNext
[1];
657 if (this.board
[afterNext
[0]][afterNext
[1]] != V
.EMPTY
) {
658 // object = banana, bomb, mushroom or egg
659 const object
= this.getPiece(afterNext
[0], afterNext
[1]);
672 V
.steps
[object
== V
.BANANA
? V
.ROOK : V
.BISHOP
];
673 move.next
= this.getRandomSquare(
674 [afterNext
[0], afterNext
[1]], steps
);
680 applyMushroomEffect();
689 const color2
= this.getColor(x2
, y2
);
690 const piece2
= this.getPiece(x2
, y2
);
695 const steps
= V
.steps
[piece2
== V
.BANANA
? V
.ROOK : V
.BISHOP
];
696 move.next
= this.getRandomSquare([x2
, y2
], steps
);
699 applyMushroomEffect();
702 if (this.subTurn
== 1)
703 // No egg effect at subTurn 2
711 move.appear
.length
> 0 &&
712 [V
.ROOK
, V
.BISHOP
].includes(piece1
)
714 const finalSquare
= [move.appear
[0].x
, move.appear
[0].y
];
717 this.getColor(finalSquare
[0], finalSquare
[1]) != 'a' ||
718 this.getPiece(finalSquare
[0], finalSquare
[1]) != V
.EGG
721 V
.steps
[piece1
== V
.ROOK
? V
.BISHOP : V
.ROOK
].filter(s
=> {
722 const [i
, j
] = [finalSquare
[0] + s
[0], finalSquare
[1] + s
[1]];
725 // NOTE: do not place a bomb or banana on the invisible queen!
726 (this.board
[i
][j
] == V
.EMPTY
|| this.getColor(i
, j
) == 'a')
729 if (validSteps
.length
>= 1) {
730 const randIdx
= randInt(validSteps
.length
);
732 finalSquare
[0] + validSteps
[randIdx
][0],
733 finalSquare
[1] + validSteps
[randIdx
][1]
740 p: (piece1
== V
.ROOK
? V
.BANANA : V
.BOMB
)
743 if (this.board
[x
][y
] != V
.EMPTY
) {
745 new PiPo({ x: x
, y: y
, c: 'a', p: this.getPiece(x
, y
) }));
753 getBasicMove(psq1
, sq2
, tr
) {
755 if (Array
.isArray(psq1
)) psq1
= { x: psq1
[0], y: psq1
[1] };
756 let m
= this.getBasicMove_aux(psq1
, sq2
, tr
, "initMove");
758 // Last move ended on bomb or banana, direction change
759 V
.PlayOnBoard(this.board
, m
);
761 m
= this.getBasicMove_aux(
762 { x: m
.appear
[0].x
, y: m
.appear
[0].y
}, m
.next
);
764 for (let i
=moves
.length
-1; i
>=0; i
--) V
.UndoOnBoard(this.board
, moves
[i
]);
766 // Now merge moves into one
768 // start is wrong for Toadette moves --> it's fixed later
769 move.start
= { x: psq1
.x
, y: psq1
.y
};
770 move.end
= !!sq2
? { x: sq2
[0], y: sq2
[1] } : { x: psq1
.x
, y: psq1
.y
};
771 if (!!tr
) move.promoteInto
= moves
[0].promoteInto
;
772 let lm
= moves
[moves
.length
-1];
773 if (this.subTurn
== 1 && !!lm
.end
.effect
)
774 move.end
.effect
= lm
.end
.effect
;
775 if (moves
.length
== 1) {
776 move.appear
= moves
[0].appear
;
777 move.vanish
= moves
[0].vanish
;
780 // Keep first vanish and last appear (if any)
781 move.appear
= lm
.appear
;
782 move.vanish
= moves
[0].vanish
;
784 move.vanish
.length
>= 1 &&
785 move.appear
.length
>= 1 &&
786 move.vanish
[0].x
== move.appear
[0].x
&&
787 move.vanish
[0].y
== move.appear
[0].y
789 // Loopback on initial square:
793 for (let i
=1; i
< moves
.length
- 1; i
++) {
794 for (let v
of moves
[i
].vanish
) {
795 // Only vanishing objects, not appearing at init move
799 moves
[0].appear
.length
== 1 ||
800 moves
[0].appear
[1].x
!= v
.x
||
801 moves
[0].appear
[1].y
!= v
.y
808 // Final vanish is our piece, but others might be relevant
809 // (for some egg bonuses at least).
810 for (let i
=1; i
< lm
.vanish
.length
; i
++) {
812 lm
.vanish
[i
].c
!= 'a' ||
813 moves
[0].appear
.length
== 1 ||
814 moves
[0].appear
[1].x
!= lm
.vanish
[i
].x
||
815 moves
[0].appear
[1].y
!= lm
.vanish
[i
].y
817 move.vanish
.push(lm
.vanish
[i
]);
824 getPotentialPawnMoves([x
, y
]) {
825 const color
= this.turn
;
826 const oppCol
= V
.GetOppCol(color
);
827 const [sizeX
, sizeY
] = [V
.size
.x
, V
.size
.y
];
828 const shiftX
= V
.PawnSpecs
.directions
[color
];
829 const firstRank
= (color
== "w" ? sizeX
- 1 : 0);
832 this.board
[x
+ shiftX
][y
] == V
.EMPTY
||
833 this.getColor(x
+ shiftX
, y
) == 'a' ||
834 this.getPiece(x
+ shiftX
, y
) == V
.INVISIBLE_QUEEN
836 this.addPawnMoves([x
, y
], [x
+ shiftX
, y
], moves
);
838 [firstRank
, firstRank
+ shiftX
].includes(x
) &&
840 this.board
[x
+ 2 * shiftX
][y
] == V
.EMPTY
||
841 this.getColor(x
+ 2 * shiftX
, y
) == 'a' ||
842 this.getPiece(x
+ 2 * shiftX
, y
) == V
.INVISIBLE_QUEEN
845 moves
.push(this.getBasicMove({ x: x
, y: y
}, [x
+ 2 * shiftX
, y
]));
848 for (let shiftY
of [-1, 1]) {
851 y
+ shiftY
< sizeY
&&
852 this.board
[x
+ shiftX
][y
+ shiftY
] != V
.EMPTY
&&
853 // Pawns cannot capture invisible queen this way!
854 this.getPiece(x
+ shiftX
, y
+ shiftY
) != V
.INVISIBLE_QUEEN
&&
855 ['a', oppCol
].includes(this.getColor(x
+ shiftX
, y
+ shiftY
))
857 this.addPawnMoves([x
, y
], [x
+ shiftX
, y
+ shiftY
], moves
);
863 getPotentialQueenMoves(sq
) {
864 const normalMoves
= super.getPotentialQueenMoves(sq
);
865 // If flag allows it, add 'invisible movements'
866 let invisibleMoves
= [];
867 if (this.powerFlags
[this.turn
][V
.QUEEN
]) {
868 normalMoves
.forEach(m
=> {
870 m
.appear
.length
== 1 &&
871 m
.vanish
.length
== 1 &&
872 // Only simple non-capturing moves:
875 let im
= JSON
.parse(JSON
.stringify(m
));
876 im
.appear
[0].p
= V
.INVISIBLE_QUEEN
;
877 im
.end
.noHighlight
= true;
878 invisibleMoves
.push(im
);
882 return normalMoves
.concat(invisibleMoves
);
885 getPotentialKingMoves([x
, y
]) {
886 let moves
= super.getPotentialKingMoves([x
, y
]);
887 const color
= this.turn
;
888 // If flag allows it, add 'remote shell captures'
889 if (this.powerFlags
[this.turn
][V
.KING
]) {
890 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]).forEach(step
=> {
891 let [i
, j
] = [x
+ step
[0], y
+ step
[1]];
895 this.board
[i
][j
] == V
.EMPTY
||
896 this.getPiece(i
, j
) == V
.INVISIBLE_QUEEN
||
898 this.getColor(i
, j
) == 'a' &&
899 [V
.EGG
, V
.MUSHROOM
].includes(this.getPiece(i
, j
))
906 if (V
.OnBoard(i
, j
)) {
907 const colIJ
= this.getColor(i
, j
);
908 if (colIJ
!= color
) {
909 // May just destroy a bomb or banana:
912 start: { x: x
, y: y
},
917 x: i
, y: j
, c: colIJ
, p: this.getPiece(i
, j
)
929 getSlideNJumpMoves([x
, y
], steps
, oneStep
) {
931 outerLoop: for (let step
of steps
) {
937 this.board
[i
][j
] == V
.EMPTY
||
938 this.getPiece(i
, j
) == V
.INVISIBLE_QUEEN
||
940 this.getColor(i
, j
) == 'a' &&
941 [V
.EGG
, V
.MUSHROOM
].includes(this.getPiece(i
, j
))
945 moves
.push(this.getBasicMove({ x: x
, y: y
}, [i
, j
]));
946 if (oneStep
) continue outerLoop
;
950 if (V
.OnBoard(i
, j
) && this.canTake([x
, y
], [i
, j
]))
951 moves
.push(this.getBasicMove({ x: x
, y: y
}, [i
, j
]));
956 getAllPotentialMoves() {
957 if (this.subTurn
== 1) return super.getAllPotentialMoves();
959 const color
= this.turn
;
960 const L
= this.effects
.length
;
961 switch (this.effects
[L
-1]) {
964 for (let i
=0; i
<8; i
++) {
965 for (let j
=0; j
<8; j
++) {
966 const colIJ
= this.getColor(i
, j
);
967 const pieceIJ
= this.getPiece(i
, j
);
970 this.board
[i
][j
] != V
.EMPTY
&&
972 pieceIJ
!= V
.INVISIBLE_QUEEN
974 allPieces
.push({ x: i
, y: j
, c: colIJ
, p: pieceIJ
});
978 for (let x
=0; x
<8; x
++) {
979 for (let y
=0; y
<8; y
++) {
980 if (this.getColor(i
, j
) == color
) {
981 // Add exchange with something
982 allPieces
.forEach(pp
=> {
983 if (pp
.x
!= i
|| pp
.y
!= j
) {
984 const movedUnit
= new PiPo({
990 let mMove
= this.getBasicMove({ x: x
, y: y
}, [pp
.x
, pp
.y
]);
991 mMove
.appear
.push(movedUnit
);
1001 const x
= V
.size
.x
+ (this.turn
== 'w' ? 0 : 1);
1002 for (let y
= 0; y
< 8; y
++)
1003 Array
.prototype.push
.apply(moves
, this.getReserveMoves([x
, y
]));
1007 moves
= super.getAllPotentialMoves();
1014 // if (!this.states) this.states = [];
1015 // const stateFen = this.getFen();
1016 // this.states.push(stateFen);
1018 move.flags
= JSON
.stringify(this.aggregateFlags());
1019 V
.PlayOnBoard(this.board
, move);
1020 move.turn
= [this.turn
, this.subTurn
];
1021 if (["kingboo", "toadette", "daisy"].includes(move.end
.effect
)) {
1022 this.effects
.push(move.end
.effect
);
1026 this.turn
= V
.GetOppCol(this.turn
);
1030 this.postPlay(move);
1034 if (move.end
.effect
== "toadette") this.reserve
= this.captured
;
1035 else this.reserve
= undefined;
1036 const color
= move.turn
[0];
1038 move.vanish
.length
== 2 &&
1039 move.vanish
[1].c
!= 'a' &&
1040 move.appear
.length
== 1 //avoid king Boo!
1042 // Capture: update this.captured
1043 let capturedPiece
= move.vanish
[1].p
;
1044 if (capturedPiece
== V
.INVISIBLE_QUEEN
) capturedPiece
= V
.QUEEN
;
1045 else if (Object
.keys(V
.IMMOBILIZE_DECODE
).includes(capturedPiece
))
1046 capturedPiece
= V
.IMMOBILIZE_DECODE
[capturedPiece
];
1047 this.captured
[move.vanish
[1].c
][capturedPiece
]++;
1049 else if (move.vanish
.length
== 0) {
1050 if (move.appear
.length
== 0 || move.appear
[0].c
== 'a') return;
1051 // A piece is back on board
1052 this.captured
[move.appear
[0].c
][move.appear
[0].p
]--;
1054 if (move.appear
.length
== 0) {
1055 // Three cases: king "shell capture", Chomp or Koopa
1056 if (this.getPiece(move.start
.x
, move.start
.y
) == V
.KING
)
1057 // King remote capture:
1058 this.powerFlags
[color
][V
.KING
] = false;
1059 else if (move.end
.effect
== "chomp")
1060 this.captured
[color
][move.vanish
[0].p
]++;
1062 else if (move.appear
[0].p
== V
.INVISIBLE_QUEEN
)
1063 this.powerFlags
[move.appear
[0].c
][V
.QUEEN
] = false;
1064 if (this.subTurn
== 2) return;
1066 move.turn
[1] == 1 &&
1067 move.appear
.length
== 0 ||
1068 !(Object
.keys(V
.IMMOBILIZE_DECODE
).includes(move.appear
[0].p
))
1070 // Look for an immobilized piece of my color: it can now move
1071 for (let i
=0; i
<8; i
++) {
1072 for (let j
=0; j
<8; j
++) {
1073 if (this.board
[i
][j
] != V
.EMPTY
) {
1074 const piece
= this.getPiece(i
, j
);
1076 this.getColor(i
, j
) == color
&&
1077 Object
.keys(V
.IMMOBILIZE_DECODE
).includes(piece
)
1079 this.board
[i
][j
] = color
+ V
.IMMOBILIZE_DECODE
[piece
];
1080 move.wasImmobilized
= [i
, j
];
1086 // Also make opponent invisible queen visible again, if any
1087 const oppCol
= V
.GetOppCol(color
);
1088 for (let i
=0; i
<8; i
++) {
1089 for (let j
=0; j
<8; j
++) {
1091 this.board
[i
][j
] != V
.EMPTY
&&
1092 this.getColor(i
, j
) == oppCol
&&
1093 this.getPiece(i
, j
) == V
.INVISIBLE_QUEEN
1095 this.board
[i
][j
] = oppCol
+ V
.QUEEN
;
1096 move.wasInvisible
= [i
, j
];
1103 this.disaggregateFlags(JSON
.parse(move.flags
));
1104 V
.UndoOnBoard(this.board
, move);
1105 if (["kingboo", "toadette", "daisy"].includes(move.end
.effect
))
1107 else this.movesCount
--;
1108 this.turn
= move.turn
[0];
1109 this.subTurn
= move.turn
[1];
1110 this.postUndo(move);
1112 // const stateFen = this.getFen();
1113 // if (stateFen != this.states[this.states.length-1]) debugger;
1114 // this.states.pop();
1118 if (!!move.wasImmobilized
) {
1119 const [i
, j
] = move.wasImmobilized
;
1121 this.getColor(i
, j
) + V
.IMMOBILIZE_CODE
[this.getPiece(i
, j
)];
1123 if (!!move.wasInvisible
) {
1124 const [i
, j
] = move.wasInvisible
;
1125 this.board
[i
][j
] = this.getColor(i
, j
) + V
.INVISIBLE_QUEEN
;
1127 if (move.vanish
.length
== 2 && move.vanish
[1].c
!= 'a') {
1128 let capturedPiece
= move.vanish
[1].p
;
1129 if (capturedPiece
== V
.INVISIBLE_QUEEN
) capturedPiece
= V
.QUEEN
;
1130 else if (Object
.keys(V
.IMMOBILIZE_DECODE
).includes(capturedPiece
))
1131 capturedPiece
= V
.IMMOBILIZE_DECODE
[capturedPiece
];
1132 this.captured
[move.vanish
[1].c
][capturedPiece
]--;
1134 else if (move.vanish
.length
== 0) {
1135 if (move.appear
.length
== 0 || move.appear
[0].c
== 'a') return;
1136 // A piece was back on board
1137 this.captured
[move.appear
[0].c
][move.appear
[0].p
]++;
1139 else if (move.appear
.length
== 0 && move.end
.effect
== "chomp")
1140 this.captured
[move.vanish
[0].c
][move.vanish
[0].p
]--;
1141 if (move.vanish
.length
== 0) this.reserve
= this.captured
;
1142 else this.reserve
= undefined;
1150 // Find kings (not tracked in this variant)
1151 let kingThere
= { w: false, b: false };
1152 for (let i
=0; i
<8; i
++) {
1153 for (let j
=0; j
<8; j
++) {
1155 this.board
[i
][j
] != V
.EMPTY
&&
1156 ['k', 'l'].includes(this.getPiece(i
, j
))
1158 kingThere
[this.getColor(i
, j
)] = true;
1162 if (!kingThere
['w']) return "0-1";
1163 if (!kingThere
['b']) return "1-0";
1164 if (!this.atLeastOneMove()) return (this.turn
== 'w' ? "0-1" : "1-0");
1168 static GenRandInitFen(options
) {
1170 SuicideRules
.GenRandInitFen(options
).slice(0, -1) +
1171 // Add Peach + Mario flags + capture counts
1176 filterValid(moves
) {
1180 static get VALUES() {
1181 return Object
.assign(
1199 static get SEARCH_DEPTH() {
1204 const moves
= this.getAllValidMoves();
1205 // Split into "normal" and "random" moves:
1206 // (Next splitting condition is OK because cannot take self object
1207 // without a banana or bomb on the way).
1208 const deterministicMoves
= moves
.filter(m
=> {
1209 return m
.vanish
.every(a
=> a
.c
!= 'a' || a
.p
== V
.MUSHROOM
);
1211 const randomMoves
= moves
.filter(m
=> {
1212 return m
.vanish
.some(a
=> a
.c
== 'a' && a
.p
!= V
.MUSHROOM
);
1214 if (Math
.random() < deterministicMoves
.length
/ randomMoves
.length
)
1215 // Play a deterministic one: capture king or material if possible
1216 return super.getComputerMove(deterministicMoves
);
1217 // Play a random effect move, at random:
1218 let move1
= randomMoves
[randInt(randomMoves
.length
)];
1220 let move2
= undefined;
1221 if (this.subTurn
== 2) {
1222 const moves2
= this.getAllValidMoves();
1223 move2
= moves2
[randInt(moves2
.length
)];
1226 if (!move2
) return move1
;
1227 return [move1
, move2
];
1231 if (move.vanish
.length
== 0 && move.appear
.length
== 0) return "-";
1234 move.appear
.length
> 0 &&
1235 move.appear
[0].p
== V
.INVISIBLE_QUEEN
1239 const finalSquare
= V
.CoordsToSquare(move.end
);
1240 // Next condition also includes Toadette placements:
1241 if (move.appear
.length
> 0 && move.vanish
.every(a
=> a
.c
== 'a')) {
1243 move.appear
[0].p
!= V
.PAWN
? move.appear
[0].p
.toUpperCase() : "";
1244 return piece
+ "@" + finalSquare
;
1246 else if (move.appear
.length
== 0) {
1247 const piece
= this.getPiece(move.start
.x
, move.start
.y
);
1248 if (piece
== V
.KING
&& !move.end
.effect
)
1249 // King remote capture
1250 return "Kx" + finalSquare
;
1251 // Koopa or Chomp, or loopback after bananas, bombs & mushrooms:
1253 piece
.toUpperCase() + "x" + finalSquare
+
1256 ? "*" + (move.end
.effect
== "koopa" ? "K" : "C")
1261 if (move.appear
.length
== 1 && move.vanish
.length
== 1) {
1262 const moveStart
= move.appear
[0].p
.toUpperCase() + "@";
1263 if (move.appear
[0].c
== 'a' && move.vanish
[0].c
== 'a')
1264 // Bonus replacement:
1265 return moveStart
+ finalSquare
;
1267 move.vanish
[0].p
== V
.INVISIBLE_QUEEN
&&
1268 move.appear
[0].x
== move.vanish
[0].x
&&
1269 move.appear
[0].y
== move.vanish
[0].y
1271 // Toadette takes invisible queen
1272 return moveStart
+ "Q" + finalSquare
;
1276 move.appear
.length
== 2 &&
1277 move.vanish
.length
== 2 &&
1278 move.appear
.every(a
=> a
.c
!= 'a') &&
1279 move.vanish
.every(v
=> v
.c
!= 'a')
1281 // King Boo exchange
1282 return V
.CoordsToSquare(move.start
) + finalSquare
;
1284 const piece
= move.vanish
[0].p
;
1285 let notation
= undefined;
1286 if (piece
== V
.PAWN
) {
1288 if (this.board
[move.end
.x
][move.end
.y
] != V
.EMPTY
) {
1290 const startColumn
= V
.CoordToColumn(move.start
.y
);
1291 notation
= startColumn
+ "x" + finalSquare
;
1293 else notation
= finalSquare
;
1294 if (move.appear
[0].p
!= V
.PAWN
)
1296 notation
+= "=" + move.appear
[0].p
.toUpperCase();
1300 piece
.toUpperCase() +
1301 (this.board
[move.end
.x
][move.end
.y
] != V
.EMPTY
? "x" : "") +
1304 if (!!move.end
.effect
) {
1305 switch (move.end
.effect
) {
1320 const lastAppear
= move.appear
[move.appear
.length
- 1];
1322 V
.CoordsToSquare({ x: lastAppear
.x
, y : lastAppear
.y
});
1323 notation
+= "*" + move.end
.effect
[0].toUpperCase() + effectOn
;