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