1 import ChessRules
from "/base_rules";
2 import GiveawayRules
from "/variants/Giveaway";
3 import { ArrayFun
} from "/utils/array.js";
4 import { Random
} from "/utils/alea.js";
5 import PiPo
from "/utils/PiPo.js";
6 import Move
from "/utils/Move.js";
8 // TODO + display bonus messages
9 // + animation + multi-moves for bananas/bombs/mushrooms
11 export class ChakartRules
extends ChessRules
{
13 static get Options() {
18 variable: "randomness",
21 { label: "Deterministic", value: 0 },
22 { label: "Symmetric random", value: 1 },
23 { label: "Asymmetric random", value: 2 }
30 get pawnPromotions() {
31 return ['q', 'r', 'n', 'b', 'k'];
41 static get IMMOBILIZE_CODE() {
52 static get IMMOBILIZE_DECODE() {
63 static get INVISIBLE_QUEEN() {
67 // Fictive color 'a', bomb banana mushroom egg
72 return 'd'; //"Donkey"
77 static get MUSHROOM() {
84 ? "w" + f
.toLowerCase()
85 : (['w', 'd', 'e', 'm'].includes(f
) ? "a" : "b") + f
90 // King can send shell? Queen can be invisible?
92 w: {k: false, q: false},
93 b: {k: false, q: false}
95 for (let c
of ['w', 'b']) {
96 for (let p
of ['k', 'q']) {
97 this.powerFlags
[c
][p
] =
98 fenflags
.charAt((c
== "w" ? 0 : 2) + (p
== 'k' ? 0 : 1)) == "1";
104 return this.powerFlags
;
107 disaggregateFlags(flags
) {
108 this.powerFlags
= flags
;
112 return super.getFen() + " " + this.getCapturedFen();
116 const res
= ['w', 'b'].map(c
=> {
117 Object
.values(this.captured
[c
])
119 return res
[0].concat(res
[1]).join("");
122 setOtherVariables(fenParsed
) {
123 super.setOtherVariables(fenParsed
);
124 // Initialize captured pieces' counts from FEN
125 const allCapts
= fenParsed
.captured
.split("").map(x
=> parseInt(x
, 10));
126 const pieces
= ['p', 'r', 'n', 'b', 'q', 'k'];
128 w: Array
.toObject(pieces
, allCapts
.slice(0, 6)),
129 b: Array
.toObject(pieces
, allCapts
.slice(6, 12))
135 // TODO from here ::::::::
140 for (let c
of ["w", "b"])
141 for (let p
of ['k', 'q']) fen
+= (this.powerFlags
[c
][p
] ? "1" : "0");
145 static get RESERVE_PIECES() {
146 return [V
.PAWN
, V
.ROOK
, V
.KNIGHT
, V
.BISHOP
, V
.QUEEN
, V
.KING
];
149 getReserveMoves([x
, y
]) {
150 const color
= this.turn
;
151 const p
= V
.RESERVE_PIECES
[y
];
152 if (this.reserve
[color
][p
] == 0) return [];
154 const start
= (color
== 'w' && p
== V
.PAWN
? 1 : 0);
155 const end
= (color
== 'b' && p
== V
.PAWN
? 7 : 8);
156 for (let i
= start
; i
< end
; i
++) {
157 for (let j
= 0; j
< V
.size
.y
; j
++) {
159 this.board
[i
][j
] == V
.EMPTY
||
160 this.getColor(i
, j
) == 'a' ||
161 this.getPiece(i
, j
) == V
.INVISIBLE_QUEEN
163 let m
= this.getBasicMove({ p: p
, x: i
, y: j
});
164 m
.start
= { x: x
, y: y
};
172 getPotentialMovesFrom([x
, y
]) {
174 if (this.subTurn
== 1) {
175 moves
= super.getPotentialMovesFrom([x
, y
]);
176 const finalPieces
= V
.PawnSpecs
.promotions
;
177 const color
= this.turn
;
178 const lastRank
= (color
== "w" ? 0 : 7);
182 m
.appear
.length
> 0 &&
183 ['p', 's'].includes(m
.appear
[0].p
) &&
184 m
.appear
[0].x
== lastRank
186 for (let i
= 1; i
< finalPieces
.length
; i
++) {
187 const piece
= finalPieces
[i
];
188 let otherM
= JSON
.parse(JSON
.stringify(m
));
190 m
.appear
[0].p
== V
.PAWN
192 : V
.IMMOBILIZE_CODE
[finalPieces
[i
]];
195 // Finally alter m itself:
197 m
.appear
[0].p
== V
.PAWN
199 : V
.IMMOBILIZE_CODE
[finalPieces
[0]];
202 Array
.prototype.push
.apply(moves
, pMoves
);
206 const L
= this.effects
.length
;
207 switch (this.effects
[L
-1]) {
209 // Exchange position with any visible piece,
210 // except pawns if arriving on last rank.
211 const lastRank
= { 'w': 0, 'b': 7 };
212 const color
= this.turn
;
213 const allowLastRank
= (this.getPiece(x
, y
) != V
.PAWN
);
214 for (let i
=0; i
<8; i
++) {
215 for (let j
=0; j
<8; j
++) {
216 const colIJ
= this.getColor(i
, j
);
217 const pieceIJ
= this.getPiece(i
, j
);
219 (i
!= x
|| j
!= y
) &&
220 this.board
[i
][j
] != V
.EMPTY
&&
221 pieceIJ
!= V
.INVISIBLE_QUEEN
&&
225 (pieceIJ
!= V
.PAWN
|| x
!= lastRank
[colIJ
]) &&
226 (allowLastRank
|| i
!= lastRank
[color
])
228 const movedUnit
= new PiPo({
232 p: this.getPiece(i
, j
)
234 let mMove
= this.getBasicMove({ x: x
, y: y
}, [i
, j
]);
235 mMove
.appear
.push(movedUnit
);
243 // Resurrect a captured piece
244 if (x
>= V
.size
.x
) moves
= this.getReserveMoves([x
, y
]);
247 // Play again with any piece
248 moves
= super.getPotentialMovesFrom([x
, y
]);
255 // Helper for getBasicMove(): banana/bomb effect
256 getRandomSquare([x
, y
], steps
) {
257 const validSteps
= steps
.filter(s
=> V
.OnBoard(x
+ s
[0], y
+ s
[1]));
258 const step
= validSteps
[randInt(validSteps
.length
)];
259 return [x
+ step
[0], y
+ step
[1]];
262 // Apply mushroom, bomb or banana effect (hidden to the player).
263 // Determine egg effect, too, and apply its first part if possible.
264 getBasicMove_aux(psq1
, sq2
, tr
, initMove
) {
265 const [x1
, y1
] = [psq1
.x
, psq1
.y
];
266 const color1
= this.turn
;
267 const piece1
= (!!tr
? tr
.p : (psq1
.p
|| this.getPiece(x1
, y1
)));
268 const oppCol
= V
.GetOppCol(color1
);
274 // banana or bomb defines next square, or the move ends there
283 if (this.board
[x1
][y1
] != V
.EMPTY
) {
284 const initP1
= this.getPiece(x1
, y1
);
289 c: this.getColor(x1
, y1
),
293 if ([V
.BANANA
, V
.BOMB
].includes(initP1
)) {
294 const steps
= V
.steps
[initP1
== V
.BANANA
? V
.ROOK : V
.BISHOP
];
295 move.next
= this.getRandomSquare([x1
, y1
], steps
);
298 move.end
= { x: x1
, y: y1
};
301 const [x2
, y2
] = [sq2
[0], sq2
[1]];
302 // The move starts normally, on board:
303 let move = super.getBasicMove([x1
, y1
], [x2
, y2
], tr
);
304 if (!!tr
) move.promoteInto
= tr
.c
+ tr
.p
; //in case of (chomped...)
305 const L
= this.effects
.length
;
307 [V
.PAWN
, V
.KNIGHT
].includes(piece1
) &&
309 (this.subTurn
== 1 || this.effects
[L
-1] == "daisy")
313 const twoSquaresMove
= (Math
.abs(x2
- x1
) == 2);
314 const mushroomX
= x1
+ (twoSquaresMove
? (x2
- x1
) / 2 : 0);
323 if (this.getColor(mushroomX
, y1
) == 'a') {
329 p: this.getPiece(mushroomX
, y1
)
336 const deltaX
= Math
.abs(x2
- x1
);
337 const deltaY
= Math
.abs(y2
- y1
);
339 x1
+ (deltaX
== 2 ? (x2
- x1
) / 2 : 0),
340 y1
+ (deltaY
== 2 ? (y2
- y1
) / 2 : 0)
343 this.board
[eggSquare
[0]][eggSquare
[1]] != V
.EMPTY
&&
344 this.getColor(eggSquare
[0], eggSquare
[1]) != 'a'
357 if (this.getColor(eggSquare
[0], eggSquare
[1]) == 'a') {
363 p: this.getPiece(eggSquare
[0], eggSquare
[1])
371 // For (wa)luigi effect:
372 const changePieceColor
= (color
) => {
374 const oppLastRank
= (color
== 'w' ? 7 : 0);
375 for (let i
=0; i
<8; i
++) {
376 for (let j
=0; j
<8; j
++) {
377 const piece
= this.getPiece(i
, j
);
379 (i
!= move.vanish
[0].x
|| j
!= move.vanish
[0].y
) &&
380 this.board
[i
][j
] != V
.EMPTY
&&
381 piece
!= V
.INVISIBLE_QUEEN
&&
382 this.getColor(i
, j
) == color
384 if (piece
!= V
.KING
&& (piece
!= V
.PAWN
|| i
!= oppLastRank
))
385 pieces
.push({ x: i
, y: j
, p: piece
});
389 // Special case of the current piece (still at its initial position)
391 pieces
.push({ x: move.appear
[0].x
, y: move.appear
[0].y
, p: piece1
});
392 const cp
= pieces
[randInt(pieces
.length
)];
393 if (move.appear
[0].x
!= cp
.x
|| move.appear
[0].y
!= cp
.y
) {
403 else move.appear
.shift();
408 c: V
.GetOppCol(color
),
413 const applyEggEffect
= () => {
414 if (this.subTurn
== 2)
415 // No egg effects at subTurn 2
417 // 1) Determine the effect (some may be impossible)
418 let effects
= ["kingboo", "koopa", "chomp", "bowser", "daisy"];
419 if (Object
.values(this.captured
[color1
]).some(c
=> c
>= 1))
420 effects
.push("toadette");
421 const lastRank
= { 'w': 0, 'b': 7 };
423 this.board
.some((b
,i
) =>
428 (cell
[1] != V
.PAWN
|| i
!= lastRank
[color1
])
433 effects
.push("luigi");
438 (piece1
!= V
.PAWN
|| move.appear
[0].x
!= lastRank
[oppCol
])
440 this.board
.some((b
,i
) =>
445 (cell
[1] != V
.PAWN
|| i
!= lastRank
[oppCol
])
450 effects
.push("waluigi");
452 const effect
= effects
[randInt(effects
.length
)];
453 move.end
.effect
= effect
;
454 // 2) Apply it if possible
455 if (!(["kingboo", "toadette", "daisy"].includes(effect
))) {
459 // Maybe egg effect was applied after others,
460 // so just shift vanish array:
467 move.appear
[0].p
= V
.IMMOBILIZE_CODE
[piece1
];
470 changePieceColor(oppCol
);
473 changePieceColor(color1
);
478 const applyMushroomEffect
= () => {
479 if ([V
.PAWN
, V
.KING
, V
.KNIGHT
].includes(piece1
)) {
480 // Just make another similar step, if possible (non-capturing)
482 move.appear
[0].x
+ (x2
- x1
),
483 move.appear
[0].y
+ (y2
- y1
)
488 this.board
[i
][j
] == V
.EMPTY
||
489 this.getPiece(i
, j
) == V
.INVISIBLE_QUEEN
||
490 this.getColor(i
, j
) == 'a'
493 move.appear
[0].x
= i
;
494 move.appear
[0].y
= j
;
495 if (this.board
[i
][j
] != V
.EMPTY
) {
496 const object
= this.getPiece(i
, j
);
497 const color
= this.getColor(i
, j
);
509 const steps
= V
.steps
[object
== V
.BANANA
? V
.ROOK : V
.BISHOP
];
510 move.next
= this.getRandomSquare([i
, j
], steps
);
516 applyMushroomEffect();
523 // Queen, bishop or rook:
525 (x2
- x1
) / Math
.abs(x2
- x1
) || 0,
526 (y2
- y1
) / Math
.abs(y2
- y1
) || 0
528 const next
= [move.appear
[0].x
+ step
[0], move.appear
[0].y
+ step
[1]];
530 V
.OnBoard(next
[0], next
[1]) &&
531 this.board
[next
[0]][next
[1]] != V
.EMPTY
&&
532 this.getPiece(next
[0], next
[1]) != V
.INVISIBLE_QUEEN
&&
533 this.getColor(next
[0], next
[1]) != 'a'
535 const afterNext
= [next
[0] + step
[0], next
[1] + step
[1]];
536 if (V
.OnBoard(afterNext
[0], afterNext
[1])) {
537 const afterColor
= this.getColor(afterNext
[0], afterNext
[1]);
539 this.board
[afterNext
[0]][afterNext
[1]] == V
.EMPTY
||
542 move.appear
[0].x
= afterNext
[0];
543 move.appear
[0].y
= afterNext
[1];
544 if (this.board
[afterNext
[0]][afterNext
[1]] != V
.EMPTY
) {
545 // object = banana, bomb, mushroom or egg
546 const object
= this.getPiece(afterNext
[0], afterNext
[1]);
559 V
.steps
[object
== V
.BANANA
? V
.ROOK : V
.BISHOP
];
560 move.next
= this.getRandomSquare(
561 [afterNext
[0], afterNext
[1]], steps
);
567 applyMushroomEffect();
576 const color2
= this.getColor(x2
, y2
);
577 const piece2
= this.getPiece(x2
, y2
);
582 const steps
= V
.steps
[piece2
== V
.BANANA
? V
.ROOK : V
.BISHOP
];
583 move.next
= this.getRandomSquare([x2
, y2
], steps
);
586 applyMushroomEffect();
589 if (this.subTurn
== 1)
590 // No egg effect at subTurn 2
598 move.appear
.length
> 0 &&
599 [V
.ROOK
, V
.BISHOP
].includes(piece1
)
601 const finalSquare
= [move.appear
[0].x
, move.appear
[0].y
];
604 this.getColor(finalSquare
[0], finalSquare
[1]) != 'a' ||
605 this.getPiece(finalSquare
[0], finalSquare
[1]) != V
.EGG
608 V
.steps
[piece1
== V
.ROOK
? V
.BISHOP : V
.ROOK
].filter(s
=> {
609 const [i
, j
] = [finalSquare
[0] + s
[0], finalSquare
[1] + s
[1]];
612 // NOTE: do not place a bomb or banana on the invisible queen!
613 (this.board
[i
][j
] == V
.EMPTY
|| this.getColor(i
, j
) == 'a')
616 if (validSteps
.length
>= 1) {
617 const randIdx
= randInt(validSteps
.length
);
619 finalSquare
[0] + validSteps
[randIdx
][0],
620 finalSquare
[1] + validSteps
[randIdx
][1]
627 p: (piece1
== V
.ROOK
? V
.BANANA : V
.BOMB
)
630 if (this.board
[x
][y
] != V
.EMPTY
) {
632 new PiPo({ x: x
, y: y
, c: 'a', p: this.getPiece(x
, y
) }));
640 getBasicMove(psq1
, sq2
, tr
) {
642 if (Array
.isArray(psq1
)) psq1
= { x: psq1
[0], y: psq1
[1] };
643 let m
= this.getBasicMove_aux(psq1
, sq2
, tr
, "initMove");
645 // Last move ended on bomb or banana, direction change
646 V
.PlayOnBoard(this.board
, m
);
648 m
= this.getBasicMove_aux(
649 { x: m
.appear
[0].x
, y: m
.appear
[0].y
}, m
.next
);
651 for (let i
=moves
.length
-1; i
>=0; i
--) V
.UndoOnBoard(this.board
, moves
[i
]);
653 // Now merge moves into one
655 // start is wrong for Toadette moves --> it's fixed later
656 move.start
= { x: psq1
.x
, y: psq1
.y
};
657 move.end
= !!sq2
? { x: sq2
[0], y: sq2
[1] } : { x: psq1
.x
, y: psq1
.y
};
658 if (!!tr
) move.promoteInto
= moves
[0].promoteInto
;
659 let lm
= moves
[moves
.length
-1];
660 if (this.subTurn
== 1 && !!lm
.end
.effect
)
661 move.end
.effect
= lm
.end
.effect
;
662 if (moves
.length
== 1) {
663 move.appear
= moves
[0].appear
;
664 move.vanish
= moves
[0].vanish
;
667 // Keep first vanish and last appear (if any)
668 move.appear
= lm
.appear
;
669 move.vanish
= moves
[0].vanish
;
671 move.vanish
.length
>= 1 &&
672 move.appear
.length
>= 1 &&
673 move.vanish
[0].x
== move.appear
[0].x
&&
674 move.vanish
[0].y
== move.appear
[0].y
676 // Loopback on initial square:
680 for (let i
=1; i
< moves
.length
- 1; i
++) {
681 for (let v
of moves
[i
].vanish
) {
682 // Only vanishing objects, not appearing at init move
686 moves
[0].appear
.length
== 1 ||
687 moves
[0].appear
[1].x
!= v
.x
||
688 moves
[0].appear
[1].y
!= v
.y
695 // Final vanish is our piece, but others might be relevant
696 // (for some egg bonuses at least).
697 for (let i
=1; i
< lm
.vanish
.length
; i
++) {
699 lm
.vanish
[i
].c
!= 'a' ||
700 moves
[0].appear
.length
== 1 ||
701 moves
[0].appear
[1].x
!= lm
.vanish
[i
].x
||
702 moves
[0].appear
[1].y
!= lm
.vanish
[i
].y
704 move.vanish
.push(lm
.vanish
[i
]);
711 getPotentialPawnMoves([x
, y
]) {
712 const color
= this.turn
;
713 const oppCol
= V
.GetOppCol(color
);
714 const [sizeX
, sizeY
] = [V
.size
.x
, V
.size
.y
];
715 const shiftX
= V
.PawnSpecs
.directions
[color
];
716 const firstRank
= (color
== "w" ? sizeX
- 1 : 0);
719 this.board
[x
+ shiftX
][y
] == V
.EMPTY
||
720 this.getColor(x
+ shiftX
, y
) == 'a' ||
721 this.getPiece(x
+ shiftX
, y
) == V
.INVISIBLE_QUEEN
723 this.addPawnMoves([x
, y
], [x
+ shiftX
, y
], moves
);
725 [firstRank
, firstRank
+ shiftX
].includes(x
) &&
727 this.board
[x
+ 2 * shiftX
][y
] == V
.EMPTY
||
728 this.getColor(x
+ 2 * shiftX
, y
) == 'a' ||
729 this.getPiece(x
+ 2 * shiftX
, y
) == V
.INVISIBLE_QUEEN
732 moves
.push(this.getBasicMove({ x: x
, y: y
}, [x
+ 2 * shiftX
, y
]));
735 for (let shiftY
of [-1, 1]) {
738 y
+ shiftY
< sizeY
&&
739 this.board
[x
+ shiftX
][y
+ shiftY
] != V
.EMPTY
&&
740 // Pawns cannot capture invisible queen this way!
741 this.getPiece(x
+ shiftX
, y
+ shiftY
) != V
.INVISIBLE_QUEEN
&&
742 ['a', oppCol
].includes(this.getColor(x
+ shiftX
, y
+ shiftY
))
744 this.addPawnMoves([x
, y
], [x
+ shiftX
, y
+ shiftY
], moves
);
750 getPotentialQueenMoves(sq
) {
751 const normalMoves
= super.getPotentialQueenMoves(sq
);
752 // If flag allows it, add 'invisible movements'
753 let invisibleMoves
= [];
754 if (this.powerFlags
[this.turn
][V
.QUEEN
]) {
755 normalMoves
.forEach(m
=> {
757 m
.appear
.length
== 1 &&
758 m
.vanish
.length
== 1 &&
759 // Only simple non-capturing moves:
762 let im
= JSON
.parse(JSON
.stringify(m
));
763 im
.appear
[0].p
= V
.INVISIBLE_QUEEN
;
764 im
.end
.noHighlight
= true;
765 invisibleMoves
.push(im
);
769 return normalMoves
.concat(invisibleMoves
);
772 getPotentialKingMoves([x
, y
]) {
773 let moves
= super.getPotentialKingMoves([x
, y
]);
774 const color
= this.turn
;
775 // If flag allows it, add 'remote shell captures'
776 if (this.powerFlags
[this.turn
][V
.KING
]) {
777 V
.steps
[V
.ROOK
].concat(V
.steps
[V
.BISHOP
]).forEach(step
=> {
778 let [i
, j
] = [x
+ step
[0], y
+ step
[1]];
782 this.board
[i
][j
] == V
.EMPTY
||
783 this.getPiece(i
, j
) == V
.INVISIBLE_QUEEN
||
785 this.getColor(i
, j
) == 'a' &&
786 [V
.EGG
, V
.MUSHROOM
].includes(this.getPiece(i
, j
))
793 if (V
.OnBoard(i
, j
)) {
794 const colIJ
= this.getColor(i
, j
);
795 if (colIJ
!= color
) {
796 // May just destroy a bomb or banana:
799 start: { x: x
, y: y
},
804 x: i
, y: j
, c: colIJ
, p: this.getPiece(i
, j
)
816 getSlideNJumpMoves([x
, y
], steps
, oneStep
) {
818 outerLoop: for (let step
of steps
) {
824 this.board
[i
][j
] == V
.EMPTY
||
825 this.getPiece(i
, j
) == V
.INVISIBLE_QUEEN
||
827 this.getColor(i
, j
) == 'a' &&
828 [V
.EGG
, V
.MUSHROOM
].includes(this.getPiece(i
, j
))
832 moves
.push(this.getBasicMove({ x: x
, y: y
}, [i
, j
]));
833 if (oneStep
) continue outerLoop
;
837 if (V
.OnBoard(i
, j
) && this.canTake([x
, y
], [i
, j
]))
838 moves
.push(this.getBasicMove({ x: x
, y: y
}, [i
, j
]));
843 getAllPotentialMoves() {
844 if (this.subTurn
== 1) return super.getAllPotentialMoves();
846 const color
= this.turn
;
847 const L
= this.effects
.length
;
848 switch (this.effects
[L
-1]) {
851 for (let i
=0; i
<8; i
++) {
852 for (let j
=0; j
<8; j
++) {
853 const colIJ
= this.getColor(i
, j
);
854 const pieceIJ
= this.getPiece(i
, j
);
857 this.board
[i
][j
] != V
.EMPTY
&&
859 pieceIJ
!= V
.INVISIBLE_QUEEN
861 allPieces
.push({ x: i
, y: j
, c: colIJ
, p: pieceIJ
});
865 for (let x
=0; x
<8; x
++) {
866 for (let y
=0; y
<8; y
++) {
867 if (this.getColor(i
, j
) == color
) {
868 // Add exchange with something
869 allPieces
.forEach(pp
=> {
870 if (pp
.x
!= i
|| pp
.y
!= j
) {
871 const movedUnit
= new PiPo({
877 let mMove
= this.getBasicMove({ x: x
, y: y
}, [pp
.x
, pp
.y
]);
878 mMove
.appear
.push(movedUnit
);
888 const x
= V
.size
.x
+ (this.turn
== 'w' ? 0 : 1);
889 for (let y
= 0; y
< 8; y
++)
890 Array
.prototype.push
.apply(moves
, this.getReserveMoves([x
, y
]));
894 moves
= super.getAllPotentialMoves();
901 // if (!this.states) this.states = [];
902 // const stateFen = this.getFen();
903 // this.states.push(stateFen);
905 move.flags
= JSON
.stringify(this.aggregateFlags());
906 V
.PlayOnBoard(this.board
, move);
907 move.turn
= [this.turn
, this.subTurn
];
908 if (["kingboo", "toadette", "daisy"].includes(move.end
.effect
)) {
909 this.effects
.push(move.end
.effect
);
913 this.turn
= V
.GetOppCol(this.turn
);
921 if (move.end
.effect
== "toadette") this.reserve
= this.captured
;
922 else this.reserve
= undefined;
923 const color
= move.turn
[0];
925 move.vanish
.length
== 2 &&
926 move.vanish
[1].c
!= 'a' &&
927 move.appear
.length
== 1 //avoid king Boo!
929 // Capture: update this.captured
930 let capturedPiece
= move.vanish
[1].p
;
931 if (capturedPiece
== V
.INVISIBLE_QUEEN
) capturedPiece
= V
.QUEEN
;
932 else if (Object
.keys(V
.IMMOBILIZE_DECODE
).includes(capturedPiece
))
933 capturedPiece
= V
.IMMOBILIZE_DECODE
[capturedPiece
];
934 this.captured
[move.vanish
[1].c
][capturedPiece
]++;
936 else if (move.vanish
.length
== 0) {
937 if (move.appear
.length
== 0 || move.appear
[0].c
== 'a') return;
938 // A piece is back on board
939 this.captured
[move.appear
[0].c
][move.appear
[0].p
]--;
941 if (move.appear
.length
== 0) {
942 // Three cases: king "shell capture", Chomp or Koopa
943 if (this.getPiece(move.start
.x
, move.start
.y
) == V
.KING
)
944 // King remote capture:
945 this.powerFlags
[color
][V
.KING
] = false;
946 else if (move.end
.effect
== "chomp")
947 this.captured
[color
][move.vanish
[0].p
]++;
949 else if (move.appear
[0].p
== V
.INVISIBLE_QUEEN
)
950 this.powerFlags
[move.appear
[0].c
][V
.QUEEN
] = false;
951 if (this.subTurn
== 2) return;
954 move.appear
.length
== 0 ||
955 !(Object
.keys(V
.IMMOBILIZE_DECODE
).includes(move.appear
[0].p
))
957 // Look for an immobilized piece of my color: it can now move
958 for (let i
=0; i
<8; i
++) {
959 for (let j
=0; j
<8; j
++) {
960 if (this.board
[i
][j
] != V
.EMPTY
) {
961 const piece
= this.getPiece(i
, j
);
963 this.getColor(i
, j
) == color
&&
964 Object
.keys(V
.IMMOBILIZE_DECODE
).includes(piece
)
966 this.board
[i
][j
] = color
+ V
.IMMOBILIZE_DECODE
[piece
];
967 move.wasImmobilized
= [i
, j
];
973 // Also make opponent invisible queen visible again, if any
974 const oppCol
= V
.GetOppCol(color
);
975 for (let i
=0; i
<8; i
++) {
976 for (let j
=0; j
<8; j
++) {
978 this.board
[i
][j
] != V
.EMPTY
&&
979 this.getColor(i
, j
) == oppCol
&&
980 this.getPiece(i
, j
) == V
.INVISIBLE_QUEEN
982 this.board
[i
][j
] = oppCol
+ V
.QUEEN
;
983 move.wasInvisible
= [i
, j
];
990 this.disaggregateFlags(JSON
.parse(move.flags
));
991 V
.UndoOnBoard(this.board
, move);
992 if (["kingboo", "toadette", "daisy"].includes(move.end
.effect
))
994 else this.movesCount
--;
995 this.turn
= move.turn
[0];
996 this.subTurn
= move.turn
[1];
999 // const stateFen = this.getFen();
1000 // if (stateFen != this.states[this.states.length-1]) debugger;
1001 // this.states.pop();
1005 if (!!move.wasImmobilized
) {
1006 const [i
, j
] = move.wasImmobilized
;
1008 this.getColor(i
, j
) + V
.IMMOBILIZE_CODE
[this.getPiece(i
, j
)];
1010 if (!!move.wasInvisible
) {
1011 const [i
, j
] = move.wasInvisible
;
1012 this.board
[i
][j
] = this.getColor(i
, j
) + V
.INVISIBLE_QUEEN
;
1014 if (move.vanish
.length
== 2 && move.vanish
[1].c
!= 'a') {
1015 let capturedPiece
= move.vanish
[1].p
;
1016 if (capturedPiece
== V
.INVISIBLE_QUEEN
) capturedPiece
= V
.QUEEN
;
1017 else if (Object
.keys(V
.IMMOBILIZE_DECODE
).includes(capturedPiece
))
1018 capturedPiece
= V
.IMMOBILIZE_DECODE
[capturedPiece
];
1019 this.captured
[move.vanish
[1].c
][capturedPiece
]--;
1021 else if (move.vanish
.length
== 0) {
1022 if (move.appear
.length
== 0 || move.appear
[0].c
== 'a') return;
1023 // A piece was back on board
1024 this.captured
[move.appear
[0].c
][move.appear
[0].p
]++;
1026 else if (move.appear
.length
== 0 && move.end
.effect
== "chomp")
1027 this.captured
[move.vanish
[0].c
][move.vanish
[0].p
]--;
1028 if (move.vanish
.length
== 0) this.reserve
= this.captured
;
1029 else this.reserve
= undefined;
1037 // Find kings (not tracked in this variant)
1038 let kingThere
= { w: false, b: false };
1039 for (let i
=0; i
<8; i
++) {
1040 for (let j
=0; j
<8; j
++) {
1042 this.board
[i
][j
] != V
.EMPTY
&&
1043 ['k', 'l'].includes(this.getPiece(i
, j
))
1045 kingThere
[this.getColor(i
, j
)] = true;
1049 if (!kingThere
['w']) return "0-1";
1050 if (!kingThere
['b']) return "1-0";
1051 if (!this.atLeastOneMove()) return (this.turn
== 'w' ? "0-1" : "1-0");
1055 genRandInitFen(seed
) {
1056 const gr
= new GiveawayRules({mode: "suicide"}, true);
1058 gr
.genRandInitFen(seed
).slice(0, -1) +
1059 // Add Peach + Mario flags + capture counts
1060 '{"flags": "1111", "ccount": "000000000000"}'
1064 filterValid(moves
) {
1068 static get VALUES() {
1069 return Object
.assign(
1087 static get SEARCH_DEPTH() {
1092 const moves
= this.getAllValidMoves();
1093 // Split into "normal" and "random" moves:
1094 // (Next splitting condition is OK because cannot take self object
1095 // without a banana or bomb on the way).
1096 const deterministicMoves
= moves
.filter(m
=> {
1097 return m
.vanish
.every(a
=> a
.c
!= 'a' || a
.p
== V
.MUSHROOM
);
1099 const randomMoves
= moves
.filter(m
=> {
1100 return m
.vanish
.some(a
=> a
.c
== 'a' && a
.p
!= V
.MUSHROOM
);
1102 if (Math
.random() < deterministicMoves
.length
/ randomMoves
.length
)
1103 // Play a deterministic one: capture king or material if possible
1104 return super.getComputerMove(deterministicMoves
);
1105 // Play a random effect move, at random:
1106 let move1
= randomMoves
[randInt(randomMoves
.length
)];
1108 let move2
= undefined;
1109 if (this.subTurn
== 2) {
1110 const moves2
= this.getAllValidMoves();
1111 move2
= moves2
[randInt(moves2
.length
)];
1114 if (!move2
) return move1
;
1115 return [move1
, move2
];
1119 if (move.vanish
.length
== 0 && move.appear
.length
== 0) return "-";
1122 move.appear
.length
> 0 &&
1123 move.appear
[0].p
== V
.INVISIBLE_QUEEN
1127 const finalSquare
= V
.CoordsToSquare(move.end
);
1128 // Next condition also includes Toadette placements:
1129 if (move.appear
.length
> 0 && move.vanish
.every(a
=> a
.c
== 'a')) {
1131 move.appear
[0].p
!= V
.PAWN
? move.appear
[0].p
.toUpperCase() : "";
1132 return piece
+ "@" + finalSquare
;
1134 else if (move.appear
.length
== 0) {
1135 const piece
= this.getPiece(move.start
.x
, move.start
.y
);
1136 if (piece
== V
.KING
&& !move.end
.effect
)
1137 // King remote capture
1138 return "Kx" + finalSquare
;
1139 // Koopa or Chomp, or loopback after bananas, bombs & mushrooms:
1141 piece
.toUpperCase() + "x" + finalSquare
+
1144 ? "*" + (move.end
.effect
== "koopa" ? "K" : "C")
1149 if (move.appear
.length
== 1 && move.vanish
.length
== 1) {
1150 const moveStart
= move.appear
[0].p
.toUpperCase() + "@";
1151 if (move.appear
[0].c
== 'a' && move.vanish
[0].c
== 'a')
1152 // Bonus replacement:
1153 return moveStart
+ finalSquare
;
1155 move.vanish
[0].p
== V
.INVISIBLE_QUEEN
&&
1156 move.appear
[0].x
== move.vanish
[0].x
&&
1157 move.appear
[0].y
== move.vanish
[0].y
1159 // Toadette takes invisible queen
1160 return moveStart
+ "Q" + finalSquare
;
1164 move.appear
.length
== 2 &&
1165 move.vanish
.length
== 2 &&
1166 move.appear
.every(a
=> a
.c
!= 'a') &&
1167 move.vanish
.every(v
=> v
.c
!= 'a')
1169 // King Boo exchange
1170 return V
.CoordsToSquare(move.start
) + finalSquare
;
1172 const piece
= move.vanish
[0].p
;
1173 let notation
= undefined;
1174 if (piece
== V
.PAWN
) {
1176 if (this.board
[move.end
.x
][move.end
.y
] != V
.EMPTY
) {
1178 const startColumn
= V
.CoordToColumn(move.start
.y
);
1179 notation
= startColumn
+ "x" + finalSquare
;
1181 else notation
= finalSquare
;
1182 if (move.appear
[0].p
!= V
.PAWN
)
1184 notation
+= "=" + move.appear
[0].p
.toUpperCase();
1188 piece
.toUpperCase() +
1189 (this.board
[move.end
.x
][move.end
.y
] != V
.EMPTY
? "x" : "") +
1192 if (!!move.end
.effect
) {
1193 switch (move.end
.effect
) {
1208 const lastAppear
= move.appear
[move.appear
.length
- 1];
1210 V
.CoordsToSquare({ x: lastAppear
.x
, y : lastAppear
.y
});
1211 notation
+= "*" + move.end
.effect
[0].toUpperCase() + effectOn
;