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