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