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