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