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