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