Fix parseInt() usage, rename Doubleorda --> Ordamirror, implement Clorange variant
[vchess.git] / client / src / variants / Chakart.js
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";
5
6 export class ChakartRules extends ChessRules {
7 static get PawnSpecs() {
8 return SuicideRules.PawnSpecs;
9 }
10
11 static get HasCastle() {
12 return false;
13 }
14
15 static get HasEnpassant() {
16 return false;
17 }
18
19 static get CorrConfirm() {
20 // Because of bonus effects
21 return false;
22 }
23
24 static get CanAnalyze() {
25 return false;
26 }
27
28 static get SomeHiddenMoves() {
29 return true;
30 }
31
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);
39 return (
40 (this.board[x][y] == V.EMPTY || this.getColor(x, y) == 'a') &&
41 (
42 (fm.vanish[0].p == V.ROOK && deltaX == 1 && deltaY == 1) ||
43 (fm.vanish[0].p == V.BISHOP && deltaX + deltaY == 1)
44 )
45 );
46 }
47
48 static get IMMOBILIZE_CODE() {
49 return {
50 'p': 's',
51 'r': 'u',
52 'n': 'o',
53 'b': 'c',
54 'q': 't',
55 'k': 'l'
56 };
57 }
58
59 static get IMMOBILIZE_DECODE() {
60 return {
61 's': 'p',
62 'u': 'r',
63 'o': 'n',
64 'c': 'b',
65 't': 'q',
66 'l': 'k'
67 };
68 }
69
70 static get INVISIBLE_QUEEN() {
71 return 'i';
72 }
73
74 // Fictive color 'a', bomb banana mushroom egg
75 static get BOMB() {
76 return 'w'; //"Wario"
77 }
78 static get BANANA() {
79 return 'd'; //"Donkey"
80 }
81 static get EGG() {
82 return 'e';
83 }
84 static get MUSHROOM() {
85 return 'm';
86 }
87
88 static fen2board(f) {
89 return (
90 f.charCodeAt() <= 90
91 ? "w" + f.toLowerCase()
92 : (['w', 'd', 'e', 'm'].includes(f) ? "a" : "b") + f
93 );
94 }
95
96 static get PIECES() {
97 return (
98 ChessRules.PIECES.concat(
99 Object.keys(V.IMMOBILIZE_DECODE)).concat(
100 [V.BANANA, V.BOMB, V.EGG, V.MUSHROOM, V.INVISIBLE_QUEEN])
101 );
102 }
103
104 getPpath(b) {
105 let prefix = "";
106 if (
107 b[0] == 'a' ||
108 b[1] == V.INVISIBLE_QUEEN ||
109 Object.keys(V.IMMOBILIZE_DECODE).includes(b[1])
110 ) {
111 prefix = "Chakart/";
112 }
113 return prefix + b;
114 }
115
116 getPPpath(m) {
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);
122 }
123
124 static ParseFen(fen) {
125 const fenParts = fen.split(" ");
126 return Object.assign(
127 ChessRules.ParseFen(fen),
128 { captured: fenParts[4] }
129 );
130 }
131
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;
136 return true;
137 }
138
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) {
146 let sumElts = 0;
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++;
150 else {
151 const num = parseInt(row[i], 10);
152 if (isNaN(num)) return false;
153 sumElts += num;
154 }
155 }
156 if (sumElts != V.size.y) return false;
157 }
158 if (kings['k'] + kings['l'] == 0 || kings['K'] + kings['L'] == 0)
159 return false;
160 return true;
161 }
162
163 static IsGoodFlags(flags) {
164 // 4 for Peach + Mario w, b
165 return !!flags.match(/^[01]{4,4}$/);
166 }
167
168 setFlags(fenflags) {
169 // King can send shell? Queen can be invisible?
170 this.powerFlags = {
171 w: { 'k': false, 'q': false },
172 b: { 'k': false, 'q': false }
173 };
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";
178 }
179 }
180 }
181
182 aggregateFlags() {
183 return this.powerFlags;
184 }
185
186 disaggregateFlags(flags) {
187 this.powerFlags = flags;
188 }
189
190 getFen() {
191 return super.getFen() + " " + this.getCapturedFen();
192 }
193
194 getFenForRepeat() {
195 return super.getFenForRepeat() + "_" + this.getCapturedFen();
196 }
197
198 getCapturedFen() {
199 let counts = [...Array(12).fill(0)];
200 let i = 0;
201 for (let p of V.RESERVE_PIECES) {
202 counts[i] = this.captured["w"][p];
203 counts[6 + i] = this.captured["b"][p];
204 i++;
205 }
206 return counts.join("");
207 }
208
209 scanKings() {}
210
211 setOtherVariables(fen) {
212 super.setOtherVariables(fen);
213 // Initialize captured pieces' counts from FEN
214 const captured =
215 V.ParseFen(fen).captured.split("").map(x => parseInt(x, 10));
216 this.captured = {
217 w: {
218 [V.PAWN]: captured[0],
219 [V.ROOK]: captured[1],
220 [V.KNIGHT]: captured[2],
221 [V.BISHOP]: captured[3],
222 [V.QUEEN]: captured[4],
223 [V.KING]: captured[5]
224 },
225 b: {
226 [V.PAWN]: captured[6],
227 [V.ROOK]: captured[7],
228 [V.KNIGHT]: captured[8],
229 [V.BISHOP]: captured[9],
230 [V.QUEEN]: captured[10],
231 [V.KING]: captured[11]
232 }
233 };
234 this.firstMove = [];
235 this.subTurn = 1;
236 }
237
238 getFlagsFen() {
239 let fen = "";
240 // Add power flags
241 for (let c of ["w", "b"])
242 for (let p of ['k', 'q']) fen += (this.powerFlags[c][p] ? "1" : "0");
243 return fen;
244 }
245
246 getColor(i, j) {
247 if (i >= V.size.x) return i == V.size.x ? "w" : "b";
248 return this.board[i][j].charAt(0);
249 }
250
251 getPiece(i, j) {
252 if (i >= V.size.x) return V.RESERVE_PIECES[j];
253 return this.board[i][j].charAt(1);
254 }
255
256 getReservePpath(index, color) {
257 return color + V.RESERVE_PIECES[index];
258 }
259
260 static get RESERVE_PIECES() {
261 return [V.PAWN, V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN, V.KING];
262 }
263
264 getReserveMoves([x, y]) {
265 const color = this.turn;
266 const p = V.RESERVE_PIECES[y];
267 if (this.reserve[color][p] == 0) return [];
268 let moves = [];
269 const start = (color == 'w' && p == V.PAWN ? 1 : 0);
270 const end = (color == 'b' && p == V.PAWN ? 7 : 8);
271 for (let i = start; i < end; i++) {
272 for (let j = 0; j < V.size.y; j++) {
273 if (
274 this.board[i][j] == V.EMPTY ||
275 this.getColor(i, j) == 'a' ||
276 this.getPiece(i, j) == V.INVISIBLE_QUEEN
277 ) {
278 let m = this.getBasicMove({ p: p, x: i, y: j});
279 m.start = { x: x, y: y };
280 moves.push(m);
281 }
282 }
283 }
284 return moves;
285 }
286
287 getPotentialMovesFrom([x, y]) {
288 let moves = [];
289 if (this.subTurn == 1) {
290 moves = super.getPotentialMovesFrom([x, y]);
291 const finalPieces = V.PawnSpecs.promotions;
292 const color = this.turn;
293 const lastRank = (color == "w" ? 0 : 7);
294 let pMoves = [];
295 moves.forEach(m => {
296 if (
297 m.appear.length > 0 &&
298 ['p', 's'].includes(m.appear[0].p) &&
299 m.appear[0].x == lastRank
300 ) {
301 for (let i = 1; i < finalPieces.length; i++) {
302 const piece = finalPieces[i];
303 let otherM = JSON.parse(JSON.stringify(m));
304 otherM.appear[0].p =
305 m.appear[0].p == V.PAWN
306 ? finalPieces[i]
307 : V.IMMOBILIZE_CODE[finalPieces[i]];
308 pMoves.push(otherM);
309 }
310 // Finally alter m itself:
311 m.appear[0].p =
312 m.appear[0].p == V.PAWN
313 ? finalPieces[0]
314 : V.IMMOBILIZE_CODE[finalPieces[0]];
315 }
316 });
317 Array.prototype.push.apply(moves, pMoves);
318 }
319 else {
320 // Subturn == 2
321 const L = this.firstMove.length;
322 const fm = this.firstMove[L-1];
323 switch (fm.end.effect) {
324 case "kingboo":
325 // Exchange position with any piece,
326 // except pawns if arriving on last rank.
327 const lastRank = { 'w': 0, 'b': 7 };
328 const color = this.turn;
329 const allowLastRank = (this.getPiece(x, y) != V.PAWN);
330 for (let i=0; i<8; i++) {
331 for (let j=0; j<8; j++) {
332 const colIJ = this.getColor(i, j);
333 if (
334 (i != x || j != y) &&
335 this.board[i][j] != V.EMPTY &&
336 colIJ != 'a'
337 ) {
338 const pieceIJ = this.getPiece(i, j);
339 if (
340 (pieceIJ != V.PAWN || x != lastRank[colIJ]) &&
341 (allowLastRank || i != lastRank[color])
342 ) {
343 const movedUnit = new PiPo({
344 x: x,
345 y: y,
346 c: colIJ,
347 p: this.getPiece(i, j)
348 });
349 let mMove = this.getBasicMove({ x: x, y: y }, [i, j]);
350 mMove.appear.push(movedUnit);
351 moves.push(mMove);
352 }
353 }
354 }
355 }
356 break;
357 case "toadette":
358 // Resurrect a captured piece
359 if (x >= V.size.x) moves = this.getReserveMoves([x, y]);
360 break;
361 case "daisy":
362 // Play again with the same piece
363 if (fm.appear[0].x == x && fm.appear[0].y == y)
364 moves = super.getPotentialMovesFrom([x, y]);
365 break;
366 }
367 }
368 return moves;
369 }
370
371 // Helper for getBasicMove()
372 getRandomSquare([x, y], steps) {
373 const color = this.turn;
374 const validSteps = steps.filter(s => {
375 const [i, j] = [x + s[0], y + s[1]];
376 return (
377 V.OnBoard(i, j) &&
378 (this.board[i][j] == V.EMPTY || this.getColor(i, j) != color)
379 );
380 });
381 if (validSteps.length == 0)
382 // Can happen after mushroom jump
383 return [x, y];
384 const step = validSteps[randInt(validSteps.length)];
385 return [x + step[0], y + step[1]];
386 }
387
388 canMove([x, y], piece) {
389 const color = this.getColor(x, y);
390 const oppCol = V.GetOppCol(color);
391 piece = piece || this.getPiece(x, y);
392 if (piece == V.PAWN) {
393 const forward = (color == 'w' ? -1 : 1);
394 return (
395 V.OnBoard(x + forward, y) &&
396 (
397 this.board[x + forward][y] != oppCol ||
398 (
399 V.OnBoard(x + forward, y + 1) &&
400 this.board[x + forward][y + 1] != V.EMPTY &&
401 this.getColor[x + forward, y + 1] == oppCol
402 ) ||
403 (
404 V.OnBoard(x + forward, y - 1) &&
405 this.board[x + forward][y - 1] != V.EMPTY &&
406 this.getColor[x + forward, y - 1] == oppCol
407 )
408 )
409 );
410 }
411 // Checking one step is enough:
412 const steps =
413 [V.KING, V.QUEEN].includes(piece)
414 ? V.steps[V.ROOK].concat(V.steps[V.BISHOP])
415 : V.steps[piece];
416 for (let step of steps) {
417 const [i, j] = [x + step[0], y + step[1]];
418 if (
419 V.OnBoard(i, j) &&
420 (this.board[i][j] == V.EMPTY || this.getColor(i, j) != color)
421 ) {
422 return true;
423 }
424 }
425 return false;
426 }
427
428 // Apply mushroom, bomb or banana effect (hidden to the player).
429 // Determine egg effect, too, and apply its first part if possible.
430 getBasicMove_aux(psq1, sq2, tr, initMove) {
431 const [x1, y1] = [psq1.x, psq1.y];
432 const color1 = this.turn;
433 const piece1 = (!!tr ? tr.p : (psq1.p || this.getPiece(x1, y1)));
434 const oppCol = V.GetOppCol(color1);
435 if (!sq2) {
436 let move = {
437 appear: [],
438 vanish: []
439 };
440 // banana or bomb defines next square, or the move ends there
441 move.appear = [
442 new PiPo({
443 x: x1,
444 y: y1,
445 c: color1,
446 p: piece1
447 })
448 ];
449 if (this.board[x1][y1] != V.EMPTY) {
450 const initP1 = this.getPiece(x1, y1);
451 move.vanish = [
452 new PiPo({
453 x: x1,
454 y: y1,
455 c: this.getColor(x1, y1),
456 p: initP1
457 })
458 ];
459 if ([V.BANANA, V.BOMB].includes(initP1)) {
460 const steps = V.steps[initP1 == V.BANANA ? V.ROOK : V.BISHOP];
461 move.next = this.getRandomSquare([x1, y1], steps);
462 }
463 }
464 move.end = { x: x1, y: y1 };
465 return move;
466 }
467 const [x2, y2] = [sq2[0], sq2[1]];
468 // The move starts normally, on board:
469 let move = super.getBasicMove([x1, y1], [x2, y2], tr);
470 if (!!tr) move.promoteInto = tr.c + tr.p; //in case of (chomped...)
471 const L = this.firstMove.length;
472 if (
473 [V.PAWN, V.KNIGHT].includes(piece1) &&
474 !!initMove &&
475 (this.subTurn == 1 || this.firstMove[L-1].end.effect == "daisy")
476 ) {
477 switch (piece1) {
478 case V.PAWN: {
479 const twoSquaresMove = (Math.abs(x2 - x1) == 2);
480 const mushroomX = x1 + (twoSquaresMove ? (x2 - x1) / 2 : 0);
481 move.appear.push(
482 new PiPo({
483 x: mushroomX,
484 y: y1,
485 c: 'a',
486 p: V.MUSHROOM
487 })
488 );
489 if (this.getColor(mushroomX, y1) == 'a') {
490 move.vanish.push(
491 new PiPo({
492 x: mushroomX,
493 y: y1,
494 c: 'a',
495 p: this.getPiece(mushroomX, y1)
496 })
497 );
498 }
499 break;
500 }
501 case V.KNIGHT: {
502 const deltaX = Math.abs(x2 - x1);
503 const deltaY = Math.abs(y2 - y1);
504 let eggSquare = [
505 x1 + (deltaX == 2 ? (x2 - x1) / 2 : 0),
506 y1 + (deltaY == 2 ? (y2 - y1) / 2 : 0)
507 ];
508 if (
509 this.board[eggSquare[0]][eggSquare[1]] != V.EMPTY &&
510 this.getColor(eggSquare[0], eggSquare[1]) != 'a'
511 ) {
512 eggSquare[0] = x1;
513 eggSquare[1] = y1;
514 }
515 move.appear.push(
516 new PiPo({
517 x: eggSquare[0],
518 y: eggSquare[1],
519 c: 'a',
520 p: V.EGG
521 })
522 );
523 if (this.getColor(eggSquare[0], eggSquare[1]) == 'a') {
524 move.vanish.push(
525 new PiPo({
526 x: eggSquare[0],
527 y: eggSquare[1],
528 c: 'a',
529 p: this.getPiece(eggSquare[0], eggSquare[1])
530 })
531 );
532 }
533 break;
534 }
535 }
536 }
537 // For (wa)luigi effect:
538 const changePieceColor = (color) => {
539 let pieces = [];
540 const oppLastRank = (color == 'w' ? 7 : 0);
541 for (let i=0; i<8; i++) {
542 for (let j=0; j<8; j++) {
543 if (
544 (i != move.vanish[0].x || j != move.vanish[0].y) &&
545 this.board[i][j] != V.EMPTY &&
546 this.getColor(i, j) == color
547 ) {
548 const piece = this.getPiece(i, j);
549 if (piece != V.KING && (piece != V.PAWN || i != oppLastRank))
550 pieces.push({ x: i, y: j, p: piece });
551 }
552 }
553 }
554 // Special case of the current piece (still at its initial position)
555 if (color == color1)
556 pieces.push({ x: move.appear[0].x, y: move.appear[0].y, p: piece1 });
557 const cp = pieces[randInt(pieces.length)];
558 if (move.appear[0].x != cp.x || move.appear[0].y != cp.y) {
559 move.vanish.push(
560 new PiPo({
561 x: cp.x,
562 y: cp.y,
563 c: color,
564 p: cp.p
565 })
566 );
567 }
568 else move.appear.shift();
569 move.appear.push(
570 new PiPo({
571 x: cp.x,
572 y: cp.y,
573 c: V.GetOppCol(color),
574 p: cp.p
575 })
576 );
577 };
578 const applyEggEffect = () => {
579 if (this.subTurn == 2)
580 // No egg effects at subTurn 2
581 return;
582 // 1) Determine the effect (some may be impossible)
583 let effects = ["kingboo", "koopa", "chomp", "bowser"];
584 if (Object.values(this.captured[color1]).some(c => c >= 1))
585 effects.push("toadette");
586 const lastRank = { 'w': 0, 'b': 7 };
587 let canPlayAgain = undefined;
588 if (
589 move.appear[0].p == V.PAWN &&
590 move.appear[0].x == lastRank[color1]
591 ) {
592 // Always possible: promote into a queen, rook or king
593 canPlayAgain = true;
594 }
595 else {
596 move.end.effect = "daisy";
597 V.PlayOnBoard(this.board, move);
598 const square = [move.appear[0].x, move.appear[0].y];
599 canPlayAgain = this.canMove(square, piece1);
600 V.UndoOnBoard(this.board, move);
601 delete move.end["effect"];
602 }
603 if (canPlayAgain) effects.push("daisy");
604 if (
605 this.board.some((b,i) =>
606 b.some(cell => {
607 return (
608 cell[0] == oppCol &&
609 cell[1] != V.KING &&
610 (cell[1] != V.PAWN || i != lastRank[color1])
611 );
612 })
613 )
614 ) {
615 effects.push("luigi");
616 }
617 if (
618 (
619 piece1 != V.KING &&
620 (piece1 != V.PAWN || move.appear[0].x != lastRank[oppCol])
621 ) ||
622 this.board.some((b,i) =>
623 b.some(cell => {
624 return (
625 cell[0] == color1 &&
626 cell[1] != V.KING &&
627 (cell[1] != V.PAWN || i != lastRank[oppCol])
628 );
629 })
630 )
631 ) {
632 effects.push("waluigi");
633 }
634 const effect = effects[randInt(effects.length)];
635 move.end.effect = effect;
636 // 2) Apply it if possible
637 if (!(["kingboo", "toadette", "daisy"].includes(effect))) {
638 switch (effect) {
639 case "koopa":
640 move.appear = [];
641 // Maybe egg effect was applied after others,
642 // so just shift vanish array:
643 move.vanish.shift();
644 break;
645 case "chomp":
646 move.appear = [];
647 break;
648 case "bowser":
649 move.appear[0].p = V.IMMOBILIZE_CODE[piece1];
650 break;
651 case "luigi":
652 changePieceColor(oppCol);
653 break;
654 case "waluigi":
655 changePieceColor(color1);
656 break;
657 }
658 }
659 };
660 const applyMushroomEffect = () => {
661 if ([V.PAWN, V.KING, V.KNIGHT].includes(piece1)) {
662 // Just make another similar step, if possible (non-capturing)
663 const [i, j] = [
664 move.appear[0].x + (x2 - x1),
665 move.appear[0].y + (y2 - y1)
666 ];
667 if (
668 V.OnBoard(i, j) &&
669 (this.board[i][j] == V.EMPTY || this.getColor(i, j) == 'a')
670 ) {
671 move.appear[0].x = i;
672 move.appear[0].y = j;
673 if (this.board[i][j] != V.EMPTY) {
674 const object = this.getPiece(i, j);
675 move.vanish.push(
676 new PiPo({
677 x: i,
678 y: j,
679 c: 'a',
680 p: object
681 })
682 );
683 switch (object) {
684 case V.BANANA:
685 case V.BOMB:
686 const steps = V.steps[object == V.BANANA ? V.ROOK : V.BISHOP];
687 move.next = this.getRandomSquare([i, j], steps);
688 break;
689 case V.EGG:
690 applyEggEffect();
691 break;
692 case V.MUSHROOM:
693 applyMushroomEffect();
694 break;
695 }
696 }
697 }
698 }
699 else {
700 // Queen, bishop or rook:
701 const step = [
702 (x2 - x1) / Math.abs(x2 - x1) || 0,
703 (y2 - y1) / Math.abs(y2 - y1) || 0
704 ];
705 const next = [move.appear[0].x + step[0], move.appear[0].y + step[1]];
706 if (
707 V.OnBoard(next[0], next[1]) &&
708 this.board[next[0]][next[1]] != V.EMPTY &&
709 this.getColor(next[0], next[1]) != 'a'
710 ) {
711 const afterNext = [next[0] + step[0], next[1] + step[1]];
712 if (V.OnBoard(afterNext[0], afterNext[1])) {
713 const afterColor = this.getColor(afterNext[0], afterNext[1])
714 if (
715 this.board[afterNext[0]][afterNext[1]] == V.EMPTY ||
716 afterColor != color1
717 ) {
718 move.appear[0].x = afterNext[0];
719 move.appear[0].y = afterNext[1];
720 if (this.board[afterNext[0]][afterNext[1]] != V.EMPTY) {
721 // The "object" could also be an opponent's piece
722 const object = this.getPiece(afterNext[0], afterNext[1]);
723 move.vanish.push(
724 new PiPo({
725 x: afterNext[0],
726 y: afterNext[1],
727 c: afterColor,
728 p: object
729 })
730 );
731 switch (object) {
732 case V.BANANA:
733 case V.BOMB:
734 const steps =
735 V.steps[object == V.BANANA ? V.ROOK : V.BISHOP];
736 move.next = this.getRandomSquare(
737 [afterNext[0], afterNext[1]], steps);
738 break;
739 case V.EGG:
740 applyEggEffect();
741 break;
742 case V.MUSHROOM:
743 applyMushroomEffect();
744 break;
745 }
746 }
747 }
748 }
749 }
750 }
751 };
752 const color2 = this.getColor(x2, y2);
753 const piece2 = this.getPiece(x2, y2);
754 if (color2 == 'a') {
755 switch (piece2) {
756 case V.BANANA:
757 case V.BOMB:
758 const steps = V.steps[piece2 == V.BANANA ? V.ROOK : V.BISHOP];
759 move.next = this.getRandomSquare([x2, y2], steps);
760 break;
761 case V.MUSHROOM:
762 applyMushroomEffect();
763 break;
764 case V.EGG:
765 if (this.subTurn == 1)
766 // No egg effect at subTurn 2
767 applyEggEffect();
768 break;
769 }
770 }
771 if (
772 this.subTurn == 1 &&
773 !move.next &&
774 move.appear.length > 0 &&
775 [V.ROOK, V.BISHOP].includes(piece1)
776 ) {
777 const finalSquare = [move.appear[0].x, move.appear[0].y];
778 if (
779 color2 != 'a' ||
780 this.getColor(finalSquare[0], finalSquare[1]) != 'a' ||
781 this.getPiece(finalSquare[0], finalSquare[1]) != V.EGG
782 ) {
783 const validSteps =
784 V.steps[piece1 == V.ROOK ? V.BISHOP : V.ROOK].filter(s => {
785 const [i, j] = [finalSquare[0] + s[0], finalSquare[1] + s[1]];
786 return (
787 V.OnBoard(i, j) &&
788 (this.board[i][j] == V.EMPTY || this.getColor(i, j) == 'a')
789 );
790 });
791 if (validSteps.length >= 1) {
792 const [x, y] = [
793 finalSquare[0] + validSteps[0][0],
794 finalSquare[1] + validSteps[0][1]
795 ];
796 move.appear.push(
797 new PiPo({
798 x: x,
799 y: y,
800 c: 'a',
801 p: (piece1 == V.ROOK ? V.BANANA : V.BOMB)
802 })
803 );
804 if (this.board[x][y] != V.EMPTY) {
805 move.vanish.push(
806 new PiPo({ x: x, y: y, c: 'a', p: this.getPiece(x, y) }));
807 }
808 }
809 }
810 }
811 return move;
812 }
813
814 getBasicMove(psq1, sq2, tr) {
815 let moves = [];
816 if (Array.isArray(psq1)) psq1 = { x: psq1[0], y: psq1[1] };
817 let m = this.getBasicMove_aux(psq1, sq2, tr, "initMove");
818 while (!!m.next) {
819 // Last move ended on bomb or banana, direction change
820 V.PlayOnBoard(this.board, m);
821 moves.push(m);
822 m = this.getBasicMove_aux(
823 { x: m.appear[0].x, y: m.appear[0].y }, m.next);
824 }
825 for (let i=moves.length-1; i>=0; i--) V.UndoOnBoard(this.board, moves[i]);
826 moves.push(m);
827 // Now merge moves into one
828 let move = {};
829 // start is wrong for Toadette moves --> it's fixed later
830 move.start = { x: psq1.x, y: psq1.y };
831 move.end = !!sq2 ? { x: sq2[0], y: sq2[1] } : { x: psq1.x, y: psq1.y };
832 if (!!tr) move.promoteInto = moves[0].promoteInto;
833 let lm = moves[moves.length-1];
834 if (this.subTurn == 1 && !!lm.end.effect)
835 move.end.effect = lm.end.effect;
836 if (moves.length == 1) {
837 move.appear = moves[0].appear;
838 move.vanish = moves[0].vanish;
839 }
840 else {
841 // Keep first vanish and last appear (if any)
842 move.appear = lm.appear;
843 move.vanish = moves[0].vanish;
844 if (
845 move.vanish.length >= 1 &&
846 move.appear.length >= 1 &&
847 move.vanish[0].x == move.appear[0].x &&
848 move.vanish[0].y == move.appear[0].y
849 ) {
850 // Loopback on initial square:
851 move.vanish.shift();
852 move.appear.shift();
853 }
854 for (let i=1; i < moves.length - 1; i++) {
855 for (let v of moves[i].vanish) {
856 // Only vanishing objects, not appearing at init move
857 if (
858 v.c == 'a' &&
859 (
860 moves[0].appear.length == 1 ||
861 moves[0].appear[1].x != v.x ||
862 moves[0].appear[1].y != v.y
863 )
864 ) {
865 move.vanish.push(v);
866 }
867 }
868 }
869 // Final vanish is our piece, but others might be relevant
870 // (for some egg bonuses at least).
871 for (let i=1; i < lm.vanish.length; i++) {
872 if (
873 lm.vanish[i].c != 'a' ||
874 moves[0].appear.length == 1 ||
875 moves[0].appear[1].x != lm.vanish[i].x ||
876 moves[0].appear[1].y != lm.vanish[i].y
877 ) {
878 move.vanish.push(lm.vanish[i]);
879 }
880 }
881 }
882 return move;
883 }
884
885 getPotentialPawnMoves([x, y]) {
886 const color = this.turn;
887 const oppCol = V.GetOppCol(color);
888 const [sizeX, sizeY] = [V.size.x, V.size.y];
889 const shiftX = V.PawnSpecs.directions[color];
890 const firstRank = (color == "w" ? sizeX - 1 : 0);
891 let moves = [];
892 if (
893 this.board[x + shiftX][y] == V.EMPTY ||
894 this.getColor(x + shiftX, y) == 'a'
895 ) {
896 this.addPawnMoves([x, y], [x + shiftX, y], moves);
897 if (
898 [firstRank, firstRank + shiftX].includes(x) &&
899 (
900 this.board[x + 2 * shiftX][y] == V.EMPTY ||
901 this.getColor(x + 2 * shiftX, y) == 'a'
902 )
903 ) {
904 moves.push(this.getBasicMove({ x: x, y: y }, [x + 2 * shiftX, y]));
905 }
906 }
907 for (let shiftY of [-1, 1]) {
908 if (
909 y + shiftY >= 0 &&
910 y + shiftY < sizeY &&
911 this.board[x + shiftX][y + shiftY] != V.EMPTY &&
912 ['a', oppCol].includes(this.getColor(x + shiftX, y + shiftY))
913 ) {
914 this.addPawnMoves([x, y], [x + shiftX, y + shiftY], moves);
915 }
916 }
917 return moves;
918 }
919
920 getPotentialQueenMoves(sq) {
921 const normalMoves = super.getPotentialQueenMoves(sq);
922 // If flag allows it, add 'invisible movements'
923 let invisibleMoves = [];
924 if (this.powerFlags[this.turn][V.QUEEN]) {
925 normalMoves.forEach(m => {
926 if (
927 m.appear.length == 1 &&
928 m.vanish.length == 1 &&
929 // Only simple non-capturing moves:
930 m.vanish[0].c != 'a'
931 ) {
932 let im = JSON.parse(JSON.stringify(m));
933 im.appear[0].p = V.INVISIBLE_QUEEN;
934 im.end.noHighlight = true;
935 invisibleMoves.push(im);
936 }
937 });
938 }
939 return normalMoves.concat(invisibleMoves);
940 }
941
942 getPotentialKingMoves([x, y]) {
943 let moves = super.getPotentialKingMoves([x, y]);
944 const color = this.turn;
945 // If flag allows it, add 'remote shell captures'
946 if (this.powerFlags[this.turn][V.KING]) {
947 V.steps[V.ROOK].concat(V.steps[V.BISHOP]).forEach(step => {
948 const [nextX, nextY] = [x + step[0], y + step[1]];
949 if (
950 V.OnBoard(nextX, nextY) &&
951 (
952 this.board[nextX][nextY] == V.EMPTY ||
953 (
954 this.getColor(nextX, nextY) == 'a' &&
955 [V.EGG, V.MUSHROOM].includes(this.getPiece(nextX, nextY))
956 )
957 )
958 ) {
959 let [i, j] = [x + 2 * step[0], y + 2 * step[1]];
960 while (
961 V.OnBoard(i, j) &&
962 (
963 this.board[i][j] == V.EMPTY ||
964 (
965 this.getColor(i, j) == 'a' &&
966 [V.EGG, V.MUSHROOM].includes(this.getPiece(i, j))
967 )
968 )
969 ) {
970 i += step[0];
971 j += step[1];
972 }
973 if (V.OnBoard(i, j)) {
974 const colIJ = this.getColor(i, j);
975 if (colIJ != color) {
976 // May just destroy a bomb or banana:
977 moves.push(
978 new Move({
979 start: { x: x, y: y},
980 end: { x: i, y: j },
981 appear: [],
982 vanish: [
983 new PiPo({
984 x: i, y: j, c: colIJ, p: this.getPiece(i, j)
985 })
986 ]
987 })
988 );
989 }
990 }
991 }
992 });
993 }
994 return moves;
995 }
996
997 getSlideNJumpMoves([x, y], steps, oneStep) {
998 let moves = [];
999 outerLoop: for (let step of steps) {
1000 let i = x + step[0];
1001 let j = y + step[1];
1002 while (
1003 V.OnBoard(i, j) &&
1004 (
1005 this.board[i][j] == V.EMPTY ||
1006 this.getPiece(i, j) == V.INVISIBLE_QUEEN ||
1007 (
1008 this.getColor(i, j) == 'a' &&
1009 [V.EGG, V.MUSHROOM].includes(this.getPiece(i, j))
1010 )
1011 )
1012 ) {
1013 moves.push(this.getBasicMove({ x: x, y: y }, [i, j]));
1014 if (oneStep) continue outerLoop;
1015 i += step[0];
1016 j += step[1];
1017 }
1018 if (V.OnBoard(i, j) && this.canTake([x, y], [i, j]))
1019 moves.push(this.getBasicMove({ x: x, y: y }, [i, j]));
1020 }
1021 return moves;
1022 }
1023
1024 getAllPotentialMoves() {
1025 if (this.subTurn == 1) return super.getAllPotentialMoves();
1026 let moves = [];
1027 const L = this.firstMove.length;
1028 const fm = this.firstMove[L-1];
1029 switch (fm.end.effect) {
1030 case 0:
1031 moves.push({
1032 start: { x: -1, y: -1 },
1033 end: { x: -1, y: -1 },
1034 appear: [],
1035 vanish: []
1036 });
1037 for (
1038 let step of
1039 (fm.vanish[0].p == V.ROOK ? V.steps[V.BISHOP] : V.steps[V.ROOK])
1040 ) {
1041 const [i, j] = [fm.appear[0].x + step[0], fm.appear[0].y + step[1]];
1042 if (
1043 V.OnBoard(i, j) &&
1044 (this.board[i][j] == V.EMPTY || this.getColor(i, j) == 'a')
1045 ) {
1046 let m = new Move({
1047 start: { x: -1, y: -1 },
1048 end: { x: i, y: j },
1049 appear: [
1050 new PiPo({
1051 x: i,
1052 y: j,
1053 c: 'a',
1054 p: (fm.vanish[0].p == V.ROOK ? V.BANANA : V.BOMB)
1055 })
1056 ],
1057 vanish: []
1058 });
1059 if (this.board[i][j] != V.EMPTY) {
1060 m.vanish.push(
1061 new PiPo({ x: i, y: j, c: 'a', p: this.getPiece(i, j) }));
1062 }
1063 moves.push(m);
1064 }
1065 }
1066 break;
1067 case "kingboo": {
1068 const [x, y] = [fm.appear[0].x, fm.appear[0].y];
1069 for (let i=0; i<8; i++) {
1070 for (let j=0; j<8; j++) {
1071 const colIJ = this.getColor(i, j);
1072 if (
1073 i != x &&
1074 j != y &&
1075 this.board[i][j] != V.EMPTY &&
1076 colIJ != 'a'
1077 ) {
1078 const movedUnit = new PiPo({
1079 x: x,
1080 y: y,
1081 c: colIJ,
1082 p: this.getPiece(i, j)
1083 });
1084 let mMove = this.getBasicMove({ x: x, y: y }, [i, j]);
1085 mMove.appear.push(movedUnit);
1086 moves.push(mMove);
1087 }
1088 }
1089 }
1090 break;
1091 }
1092 case "toadette": {
1093 const x = V.size.x + (this.turn == 'w' ? 0 : 1);
1094 for (let y = 0; y < 8; y++)
1095 Array.prototype.push.apply(moves, this.getReserveMoves([x, y]));
1096 break;
1097 }
1098 case "daisy":
1099 moves = super.getPotentialMovesFrom([fm.appear[0].x, fm.appear[0].y]);
1100 break;
1101 }
1102 return moves;
1103 }
1104
1105 doClick(square) {
1106 const L = this.firstMove.length;
1107 const fm = (L > 0 ? this.firstMove[L-1] : null);
1108 if (
1109 isNaN(square[0]) ||
1110 this.subTurn == 1 ||
1111 !([0, "daisy"].includes(fm.end.effect))
1112 ) {
1113 return null;
1114 }
1115 const [x, y] = [square[0], square[1]];
1116 const deltaX = Math.abs(fm.appear[0].x - x);
1117 const deltaY = Math.abs(fm.appear[0].y - y);
1118 if (
1119 fm.end.effect == 0 &&
1120 (this.board[x][y] == V.EMPTY || this.getColor(x, y) == 'a') &&
1121 (
1122 (fm.vanish[0].p == V.ROOK && deltaX == 1 && deltaY == 1) ||
1123 (fm.vanish[0].p == V.BISHOP && deltaX + deltaY == 1)
1124 )
1125 ) {
1126 let m = new Move({
1127 start: { x: -1, y: -1 },
1128 end: { x: x, y: y },
1129 appear: [
1130 new PiPo({
1131 x: x,
1132 y: y,
1133 c: 'a',
1134 p: (fm.vanish[0].p == V.ROOK ? V.BANANA : V.BOMB)
1135 })
1136 ],
1137 vanish: []
1138 });
1139 if (this.board[x][y] != V.EMPTY) {
1140 m.vanish.push(
1141 new PiPo({ x: x, y: y, c: 'a', p: this.getPiece(x, y) }));
1142 }
1143 return m;
1144 }
1145 else if (
1146 fm.end.effect == "daisy" &&
1147 deltaX == 0 && deltaY == 0 &&
1148 !this.canMove([x, y])
1149 ) {
1150 // No possible move: return empty move
1151 return {
1152 start: { x: -1, y: -1 },
1153 end: { x: -1, y: -1 },
1154 appear: [],
1155 vanish: []
1156 };
1157 }
1158 return null;
1159 }
1160
1161 play(move) {
1162 // if (!this.states) this.states = [];
1163 // const stateFen = this.getFen();
1164 // this.states.push(stateFen);
1165
1166 move.flags = JSON.stringify(this.aggregateFlags());
1167 V.PlayOnBoard(this.board, move);
1168 move.turn = [this.turn, this.subTurn];
1169 if ([0, "kingboo", "toadette", "daisy"].includes(move.end.effect)) {
1170 this.firstMove.push(move);
1171 this.subTurn = 2;
1172 }
1173 else {
1174 this.turn = V.GetOppCol(this.turn);
1175 this.movesCount++;
1176 this.subTurn = 1;
1177 }
1178 this.postPlay(move);
1179 }
1180
1181 postPlay(move) {
1182 if (move.end.effect == "toadette") this.reserve = this.captured;
1183 else this.reserve = undefined;
1184 const color = move.turn[0];
1185 if (move.vanish.length == 2 && move.vanish[1].c != 'a') {
1186 // Capture: update this.captured
1187 let capturedPiece = move.vanish[1].p;
1188 if (capturedPiece == V.INVISIBLE_QUEEN) capturedPiece = V.QUEEN;
1189 else if (Object.keys(V.IMMOBILIZE_DECODE).includes(capturedPiece))
1190 capturedPiece = V.IMMOBILIZE_DECODE[capturedPiece];
1191 this.captured[move.vanish[1].c][capturedPiece]++;
1192 }
1193 else if (move.vanish.length == 0) {
1194 if (move.appear.length == 0 || move.appear[0].c == 'a') return;
1195 // A piece is back on board
1196 this.captured[move.appear[0].c][move.appear[0].p]--;
1197 }
1198 if (move.appear.length == 0) {
1199 // Three cases: king "shell capture", Chomp or Koopa
1200 if (this.getPiece(move.start.x, move.start.y) == V.KING)
1201 // King remote capture:
1202 this.powerFlags[color][V.KING] = false;
1203 else if (move.end.effect == "chomp")
1204 this.captured[color][move.vanish[0].p]++;
1205 }
1206 else if (move.appear[0].p == V.INVISIBLE_QUEEN)
1207 this.powerFlags[move.appear[0].c][V.QUEEN] = false;
1208 if (this.subTurn == 2) return;
1209 if (
1210 move.turn[1] == 1 &&
1211 move.appear.length == 0 ||
1212 !(Object.keys(V.IMMOBILIZE_DECODE).includes(move.appear[0].p))
1213 ) {
1214 // Look for an immobilized piece of my color: it can now move
1215 for (let i=0; i<8; i++) {
1216 for (let j=0; j<8; j++) {
1217 if (this.board[i][j] != V.EMPTY) {
1218 const piece = this.getPiece(i, j);
1219 if (
1220 this.getColor(i, j) == color &&
1221 Object.keys(V.IMMOBILIZE_DECODE).includes(piece)
1222 ) {
1223 this.board[i][j] = color + V.IMMOBILIZE_DECODE[piece];
1224 move.wasImmobilized = [i, j];
1225 }
1226 }
1227 }
1228 }
1229 }
1230 // Also make opponent invisible queen visible again, if any
1231 const oppCol = V.GetOppCol(color);
1232 for (let i=0; i<8; i++) {
1233 for (let j=0; j<8; j++) {
1234 if (
1235 this.board[i][j] != V.EMPTY &&
1236 this.getColor(i, j) == oppCol &&
1237 this.getPiece(i, j) == V.INVISIBLE_QUEEN
1238 ) {
1239 this.board[i][j] = oppCol + V.QUEEN;
1240 move.wasInvisible = [i, j];
1241 }
1242 }
1243 }
1244 }
1245
1246 undo(move) {
1247 this.disaggregateFlags(JSON.parse(move.flags));
1248 V.UndoOnBoard(this.board, move);
1249 if ([0, "kingboo", "toadette", "daisy"].includes(move.end.effect))
1250 this.firstMove.pop();
1251 else this.movesCount--;
1252 this.turn = move.turn[0];
1253 this.subTurn = move.turn[1];
1254 this.postUndo(move);
1255
1256 // const stateFen = this.getFen();
1257 // if (stateFen != this.states[this.states.length-1]) debugger;
1258 // this.states.pop();
1259 }
1260
1261 postUndo(move) {
1262 if (!!move.wasImmobilized) {
1263 const [i, j] = move.wasImmobilized;
1264 this.board[i][j] =
1265 this.getColor(i, j) + V.IMMOBILIZE_CODE[this.getPiece(i, j)];
1266 }
1267 if (!!move.wasInvisible) {
1268 const [i, j] = move.wasInvisible;
1269 this.board[i][j] = this.getColor(i, j) + V.INVISIBLE_QUEEN;
1270 }
1271 if (move.vanish.length == 2 && move.vanish[1].c != 'a') {
1272 let capturedPiece = move.vanish[1].p;
1273 if (capturedPiece == V.INVISIBLE_QUEEN) capturedPiece = V.QUEEN;
1274 else if (Object.keys(V.IMMOBILIZE_DECODE).includes(capturedPiece))
1275 capturedPiece = V.IMMOBILIZE_DECODE[capturedPiece];
1276 this.captured[move.vanish[1].c][capturedPiece]--;
1277 }
1278 else if (move.vanish.length == 0) {
1279 if (move.appear.length == 0 || move.appear[0].c == 'a') return;
1280 // A piece was back on board
1281 this.captured[move.appear[0].c][move.appear[0].p]++;
1282 }
1283 else if (move.appear.length == 0 && move.end.effect == "chomp")
1284 this.captured[move.vanish[0].c][move.vanish[0].p]--;
1285 if (move.vanish.length == 0) this.reserve = this.captured;
1286 else this.reserve = undefined;
1287 }
1288
1289 getCheckSquares() {
1290 return [];
1291 }
1292
1293 getCurrentScore() {
1294 // Find kings (not tracked in this variant)
1295 let kingThere = { w: false, b: false };
1296 for (let i=0; i<8; i++) {
1297 for (let j=0; j<8; j++) {
1298 if (
1299 this.board[i][j] != V.EMPTY &&
1300 ['k', 'l'].includes(this.getPiece(i, j))
1301 ) {
1302 kingThere[this.getColor(i, j)] = true;
1303 }
1304 }
1305 }
1306 if (!kingThere['w']) return "0-1";
1307 if (!kingThere['b']) return "1-0";
1308 if (!this.atLeastOneMove()) return (this.turn == 'w' ? "0-1" : "1-0");
1309 return "*";
1310 }
1311
1312 static GenRandInitFen(randomness) {
1313 return (
1314 SuicideRules.GenRandInitFen(randomness).slice(0, -1) +
1315 // Add Peach + Mario flags + capture counts
1316 "1111 000000000000"
1317 );
1318 }
1319
1320 filterValid(moves) {
1321 return moves;
1322 }
1323
1324 static get VALUES() {
1325 return Object.assign(
1326 {},
1327 ChessRules.VALUES,
1328 {
1329 s: 1,
1330 u: 5,
1331 o: 3,
1332 c: 3,
1333 t: 9,
1334 l: 1000,
1335 e: 0,
1336 d: 0,
1337 w: 0,
1338 m: 0
1339 }
1340 );
1341 }
1342
1343 static get SEARCH_DEPTH() {
1344 return 1;
1345 }
1346
1347 getComputerMove() {
1348 const moves = this.getAllValidMoves();
1349 // Split into "normal" and "random" moves:
1350 // (Next splitting condition is OK because cannot take self object
1351 // without a banana or bomb on the way).
1352 const deterministicMoves = moves.filter(m => {
1353 return m.vanish.every(a => a.c != 'a' || a.p == V.MUSHROOM);
1354 });
1355 const randomMoves = moves.filter(m => {
1356 return m.vanish.some(a => a.c == 'a' && a.p != V.MUSHROOM);
1357 });
1358 if (Math.random() < deterministicMoves.length / randomMoves.length)
1359 // Play a deterministic one: capture king or material if possible
1360 return super.getComputerMove(deterministicMoves);
1361 // Play a random effect move, at random:
1362 let move1 = randomMoves[randInt(randomMoves.length)];
1363 this.play(move1);
1364 let move2 = undefined;
1365 if (this.subTurn == 2) {
1366 const moves2 = this.getAllValidMoves();
1367 move2 = moves2[randInt(moves2.length)];
1368 }
1369 this.undo(move1);
1370 if (!move2) return move1;
1371 return [move1, move2];
1372 }
1373
1374 getNotation(move) {
1375 if (move.vanish.length == 0 && move.appear.length == 0) return "-";
1376 if (
1377 !move.end.effect &&
1378 move.appear.length > 0 &&
1379 move.appear[0].p == V.INVISIBLE_QUEEN
1380 ) {
1381 return "Q??";
1382 }
1383 const finalSquare = V.CoordsToSquare(move.end);
1384 // Next condition also includes Toadette placements:
1385 if (move.appear.length > 0 && move.vanish.every(a => a.c == 'a')) {
1386 const piece =
1387 move.appear[0].p != V.PAWN ? move.appear[0].p.toUpperCase() : "";
1388 return piece + "@" + finalSquare;
1389 }
1390 else if (move.appear.length == 0) {
1391 const piece = this.getPiece(move.start.x, move.start.y);
1392 if (piece == V.KING && !move.end.effect)
1393 // King remote capture
1394 return "Kx" + finalSquare;
1395 // Koopa or Chomp, or loopback after bananas, bombs & mushrooms:
1396 return (
1397 piece.toUpperCase() + "x" + finalSquare +
1398 (
1399 !!move.end.effect
1400 ? "*" + (move.end.effect == "koopa" ? "K" : "C")
1401 : ""
1402 )
1403 );
1404 }
1405 if (move.appear.length == 1 && move.vanish.length == 1) {
1406 const moveStart = move.appear[0].p.toUpperCase() + "@";
1407 if (move.appear[0].c == 'a' && move.vanish[0].c == 'a')
1408 // Bonus replacement:
1409 return moveStart + finalSquare;
1410 if (
1411 move.vanish[0].p == V.INVISIBLE_QUEEN &&
1412 move.appear[0].x == move.vanish[0].x &&
1413 move.appear[0].y == move.vanish[0].y
1414 ) {
1415 // Toadette takes invisible queen
1416 return moveStart + "Q" + finalSquare;
1417 }
1418 }
1419 if (
1420 move.appear.length == 2 &&
1421 move.vanish.length == 2 &&
1422 move.appear.every(a => a.c != 'a') &&
1423 move.vanish.every(v => v.c != 'a')
1424 ) {
1425 // King Boo exchange
1426 return move.vanish[1].p.toUpperCase() + finalSquare;
1427 }
1428 const piece = move.vanish[0].p;
1429 let notation = undefined;
1430 if (piece == V.PAWN) {
1431 // Pawn move
1432 if (move.vanish.length >= 2) {
1433 // Capture
1434 const startColumn = V.CoordToColumn(move.start.y);
1435 notation = startColumn + "x" + finalSquare;
1436 }
1437 else notation = finalSquare;
1438 if (move.appear[0].p != V.PAWN)
1439 // Promotion
1440 notation += "=" + move.appear[0].p.toUpperCase();
1441 }
1442 else {
1443 notation =
1444 piece.toUpperCase() +
1445 (move.vanish.length >= 2 ? "x" : "") +
1446 finalSquare;
1447 }
1448 if (!!move.end.effect) {
1449 switch (move.end.effect) {
1450 case "kingboo":
1451 notation += "*B";
1452 break;
1453 case "toadette":
1454 notation += "*T";
1455 break;
1456 case "daisy":
1457 notation += "*D";
1458 break;
1459 case "bowser":
1460 notation += "*M";
1461 break;
1462 case "luigi":
1463 case "waluigi":
1464 const lastAppear = move.appear[move.appear.length - 1];
1465 const effectOn =
1466 V.CoordsToSquare({ x: lastAppear.x, y : lastAppear.y });
1467 notation += "*" + move.end.effect[0].toUpperCase() + effectOn;
1468 break;
1469 }
1470 }
1471 return notation;
1472 }
1473 };