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