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