d6c683dcf3d06c6a8d447c816c90682a9d80c841
[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.moveStack = [];
145 }
146
147 // For Toadette bonus
148 getDropMovesFrom([c, p]) {
149 if (typeof c != "string" || this.reserve[c][p] == 0)
150 return [];
151 let moves = [];
152 const start = (c == 'w' && p == 'p' ? 1 : 0);
153 const end = (color == 'b' && p == 'p' ? 7 : 8);
154 for (let i = start; i < end; i++) {
155 for (let j = 0; j < this.size.y; j++) {
156 const pieceIJ = this.getPiece(i, j);
157 if (
158 this.board[i][j] == "" ||
159 this.getColor(i, j) == 'a' ||
160 pieceIJ == V.INVISIBLE_QUEEN
161 ) {
162 let m = new Move({
163 start: {x: c, y: p},
164 end: {x: i, y: j},
165 appear: [new PiPo({x: i, y: j, c: c, p: p})],
166 vanish: []
167 });
168 // A drop move may remove a bonus (or hidden queen!)
169 if (this.board[i][j] != "")
170 m.vanish.push(new PiPo({x: i, y: j, c: 'a', p: pieceIJ}));
171 moves.push(m);
172 }
173 }
174 }
175 return moves;
176 }
177
178 // TODO: rethink from here:
179
180 // allow pawns
181 // queen invisible move, king shell: special functions
182
183 // prevent pawns from capturing invisible queen (post)
184 // post-process:
185
186 //events : playPlusVisual after mouse up, playReceived (include animation) on opp move
187 // ==> if move.cont (banana...) self re-call playPlusVisual (rec ?)
188
189 // Moving something. Potential effects resolved after playing
190 getPotentialMovesFrom([x, y], bonus) {
191 let moves = [];
192 if (bonus == "toadette")
193 return this.getDropMovesFrom([x, y]);
194 else if (bonus == "kingboo") {
195 // Only allow to swap pieces
196 // TODO (end of move, as for toadette)
197 return moves;
198 }
199 // Normal case (including bonus daisy)
200 switch (this.getPiece(x, y)) {
201 case 'p':
202 moves = this.getPawnMovesFrom([x, y]); //apply promotions
203 break;
204 case 'q':
205 moves = this.getQueenMovesFrom([x, y]);
206 break;
207 case 'k',
208 moves = this.getKingMoves([x, y]);
209 break;
210 default:
211 moves = super.getPotentialMovesFrom([x, y]);
212 }
213 return moves;
214 }
215
216 // idée : on joue le coup, puis son effet est déterminé, puis la suite (si suite)
217 // est jouée automatiquement ou demande action utilisateur, etc jusqu'à coup terminal.
218
219 tryMoveFollowup(move) {
220 if (this.getColor(move.end.x, move.end.y) == 'a') {
221 // effect, or bonus/malus
222 const endType = this.getPiece(m.end.x, m.end.y);
223 if (endType == V.EGG)
224 this.applyRandomBonus(m);
225 else {
226 this.moveStack.push(m);
227 switch (endType) {
228 case V.BANANA:
229 this.randomRedirect(
230 case V.BOMB:
231 case V.MUSHROOM:
232 // aller dans direction, saut par dessus pièce adverse
233 // ou amie (tjours), new step si roi caval pion
234 }
235 }
236 }
237
238
239 const finalPieces = V.PawnSpecs.promotions;
240 const color = this.turn;
241 const lastRank = (color == "w" ? 0 : 7);
242 let pMoves = [];
243 moves.forEach(m => {
244 if (
245 m.appear.length > 0 &&
246 ['p', 's'].includes(m.appear[0].p) &&
247 m.appear[0].x == lastRank
248 ) {
249 for (let i = 1; i < finalPieces.length; i++) {
250 const piece = finalPieces[i];
251 let otherM = JSON.parse(JSON.stringify(m));
252 otherM.appear[0].p =
253 m.appear[0].p == V.PAWN
254 ? finalPieces[i]
255 : V.IMMOBILIZE_CODE[finalPieces[i]];
256 pMoves.push(otherM);
257 }
258 // Finally alter m itself:
259 m.appear[0].p =
260 m.appear[0].p == V.PAWN
261 ? finalPieces[0]
262 : V.IMMOBILIZE_CODE[finalPieces[0]];
263 }
264 });
265 Array.prototype.push.apply(moves, pMoves);
266 }
267 else {
268 // Subturn == 2
269 const L = this.effects.length;
270 switch (this.effects[L-1]) {
271 case "kingboo":
272 // Exchange position with any visible piece,
273 // except pawns if arriving on last rank.
274 const lastRank = { 'w': 0, 'b': 7 };
275 const color = this.turn;
276 const allowLastRank = (this.getPiece(x, y) != V.PAWN);
277 for (let i=0; i<8; i++) {
278 for (let j=0; j<8; j++) {
279 const colIJ = this.getColor(i, j);
280 const pieceIJ = this.getPiece(i, j);
281 if (
282 (i != x || j != y) &&
283 this.board[i][j] != V.EMPTY &&
284 pieceIJ != V.INVISIBLE_QUEEN &&
285 colIJ != 'a'
286 ) {
287 if (
288 (pieceIJ != V.PAWN || x != lastRank[colIJ]) &&
289 (allowLastRank || i != lastRank[color])
290 ) {
291 const movedUnit = new PiPo({
292 x: x,
293 y: y,
294 c: colIJ,
295 p: this.getPiece(i, j)
296 });
297 let mMove = this.getBasicMove({ x: x, y: y }, [i, j]);
298 mMove.appear.push(movedUnit);
299 moves.push(mMove);
300 }
301 }
302 }
303 }
304 break;
305 case "toadette":
306 // Resurrect a captured piece
307 if (x >= V.size.x) moves = this.getReserveMoves([x, y]);
308 break;
309 case "daisy":
310 // Play again with any piece
311 moves = super.getPotentialMovesFrom([x, y]);
312 break;
313 }
314 }
315 return moves;
316 }
317
318 // Helper for getBasicMove(): banana/bomb effect
319 getRandomSquare([x, y], steps) {
320 const validSteps = steps.filter(s => this.onBoard(x + s[0], y + s[1]));
321 const step = validSteps[Random.randInt(validSteps.length)];
322 return [x + step[0], y + step[1]];
323 }
324
325 // Apply mushroom, bomb or banana effect (hidden to the player).
326 // Determine egg effect, too, and apply its first part if possible.
327 getBasicMove_aux(psq1, sq2, tr, initMove) {
328 const [x1, y1] = [psq1.x, psq1.y];
329 const color1 = this.turn;
330 const piece1 = (!!tr ? tr.p : (psq1.p || this.getPiece(x1, y1)));
331 const oppCol = V.GetOppCol(color1);
332 if (!sq2) {
333 let move = {
334 appear: [],
335 vanish: []
336 };
337 // banana or bomb defines next square, or the move ends there
338 move.appear = [
339 new PiPo({
340 x: x1,
341 y: y1,
342 c: color1,
343 p: piece1
344 })
345 ];
346 if (this.board[x1][y1] != V.EMPTY) {
347 const initP1 = this.getPiece(x1, y1);
348 move.vanish = [
349 new PiPo({
350 x: x1,
351 y: y1,
352 c: this.getColor(x1, y1),
353 p: initP1
354 })
355 ];
356 if ([V.BANANA, V.BOMB].includes(initP1)) {
357 const steps = V.steps[initP1 == V.BANANA ? V.ROOK : V.BISHOP];
358 move.next = this.getRandomSquare([x1, y1], steps);
359 }
360 }
361 move.end = { x: x1, y: y1 };
362 return move;
363 }
364 const [x2, y2] = [sq2[0], sq2[1]];
365 // The move starts normally, on board:
366 let move = super.getBasicMove([x1, y1], [x2, y2], tr);
367 if (!!tr) move.promoteInto = tr.c + tr.p; //in case of (chomped...)
368 const L = this.effects.length;
369 if (
370 [V.PAWN, V.KNIGHT].includes(piece1) &&
371 !!initMove &&
372 (this.subTurn == 1 || this.effects[L-1] == "daisy")
373 ) {
374 switch (piece1) {
375 case V.PAWN: {
376 const twoSquaresMove = (Math.abs(x2 - x1) == 2);
377 const mushroomX = x1 + (twoSquaresMove ? (x2 - x1) / 2 : 0);
378 move.appear.push(
379 new PiPo({
380 x: mushroomX,
381 y: y1,
382 c: 'a',
383 p: V.MUSHROOM
384 })
385 );
386 if (this.getColor(mushroomX, y1) == 'a') {
387 move.vanish.push(
388 new PiPo({
389 x: mushroomX,
390 y: y1,
391 c: 'a',
392 p: this.getPiece(mushroomX, y1)
393 })
394 );
395 }
396 break;
397 }
398 case V.KNIGHT: {
399 const deltaX = Math.abs(x2 - x1);
400 const deltaY = Math.abs(y2 - y1);
401 let eggSquare = [
402 x1 + (deltaX == 2 ? (x2 - x1) / 2 : 0),
403 y1 + (deltaY == 2 ? (y2 - y1) / 2 : 0)
404 ];
405 if (
406 this.board[eggSquare[0]][eggSquare[1]] != V.EMPTY &&
407 this.getColor(eggSquare[0], eggSquare[1]) != 'a'
408 ) {
409 eggSquare[0] = x1;
410 eggSquare[1] = y1;
411 }
412 move.appear.push(
413 new PiPo({
414 x: eggSquare[0],
415 y: eggSquare[1],
416 c: 'a',
417 p: V.EGG
418 })
419 );
420 if (this.getColor(eggSquare[0], eggSquare[1]) == 'a') {
421 move.vanish.push(
422 new PiPo({
423 x: eggSquare[0],
424 y: eggSquare[1],
425 c: 'a',
426 p: this.getPiece(eggSquare[0], eggSquare[1])
427 })
428 );
429 }
430 break;
431 }
432 }
433 }
434 // For (wa)luigi effect:
435 const changePieceColor = (color) => {
436 let pieces = [];
437 const oppLastRank = (color == 'w' ? 7 : 0);
438 for (let i=0; i<8; i++) {
439 for (let j=0; j<8; j++) {
440 const piece = this.getPiece(i, j);
441 if (
442 (i != move.vanish[0].x || j != move.vanish[0].y) &&
443 this.board[i][j] != V.EMPTY &&
444 piece != V.INVISIBLE_QUEEN &&
445 this.getColor(i, j) == color
446 ) {
447 if (piece != V.KING && (piece != V.PAWN || i != oppLastRank))
448 pieces.push({ x: i, y: j, p: piece });
449 }
450 }
451 }
452 // Special case of the current piece (still at its initial position)
453 if (color == color1)
454 pieces.push({ x: move.appear[0].x, y: move.appear[0].y, p: piece1 });
455 const cp = pieces[randInt(pieces.length)];
456 if (move.appear[0].x != cp.x || move.appear[0].y != cp.y) {
457 move.vanish.push(
458 new PiPo({
459 x: cp.x,
460 y: cp.y,
461 c: color,
462 p: cp.p
463 })
464 );
465 }
466 else move.appear.shift();
467 move.appear.push(
468 new PiPo({
469 x: cp.x,
470 y: cp.y,
471 c: V.GetOppCol(color),
472 p: cp.p
473 })
474 );
475 };
476 const applyEggEffect = () => {
477 if (this.subTurn == 2)
478 // No egg effects at subTurn 2
479 return;
480 // 1) Determine the effect (some may be impossible)
481 let effects = ["kingboo", "koopa", "chomp", "bowser", "daisy"];
482 if (Object.values(this.captured[color1]).some(c => c >= 1))
483 effects.push("toadette");
484 const lastRank = { 'w': 0, 'b': 7 };
485 if (
486 this.board.some((b,i) =>
487 b.some(cell => {
488 return (
489 cell[0] == oppCol &&
490 cell[1] != V.KING &&
491 (cell[1] != V.PAWN || i != lastRank[color1])
492 );
493 })
494 )
495 ) {
496 effects.push("luigi");
497 }
498 if (
499 (
500 piece1 != V.KING &&
501 (piece1 != V.PAWN || move.appear[0].x != lastRank[oppCol])
502 ) ||
503 this.board.some((b,i) =>
504 b.some(cell => {
505 return (
506 cell[0] == color1 &&
507 cell[1] != V.KING &&
508 (cell[1] != V.PAWN || i != lastRank[oppCol])
509 );
510 })
511 )
512 ) {
513 effects.push("waluigi");
514 }
515 const effect = effects[randInt(effects.length)];
516 move.end.effect = effect;
517 // 2) Apply it if possible
518 if (!(["kingboo", "toadette", "daisy"].includes(effect))) {
519 switch (effect) {
520 case "koopa":
521 move.appear = [];
522 // Maybe egg effect was applied after others,
523 // so just shift vanish array:
524 move.vanish.shift();
525 break;
526 case "chomp":
527 move.appear = [];
528 break;
529 case "bowser":
530 move.appear[0].p = V.IMMOBILIZE_CODE[piece1];
531 break;
532 case "luigi":
533 changePieceColor(oppCol);
534 break;
535 case "waluigi":
536 changePieceColor(color1);
537 break;
538 }
539 }
540 };
541 const applyMushroomEffect = () => {
542 if ([V.PAWN, V.KING, V.KNIGHT].includes(piece1)) {
543 // Just make another similar step, if possible (non-capturing)
544 const [i, j] = [
545 move.appear[0].x + (x2 - x1),
546 move.appear[0].y + (y2 - y1)
547 ];
548 if (
549 V.OnBoard(i, j) &&
550 (
551 this.board[i][j] == V.EMPTY ||
552 this.getPiece(i, j) == V.INVISIBLE_QUEEN ||
553 this.getColor(i, j) == 'a'
554 )
555 ) {
556 move.appear[0].x = i;
557 move.appear[0].y = j;
558 if (this.board[i][j] != V.EMPTY) {
559 const object = this.getPiece(i, j);
560 const color = this.getColor(i, j);
561 move.vanish.push(
562 new PiPo({
563 x: i,
564 y: j,
565 c: color,
566 p: object
567 })
568 );
569 switch (object) {
570 case V.BANANA:
571 case V.BOMB:
572 const steps = V.steps[object == V.BANANA ? V.ROOK : V.BISHOP];
573 move.next = this.getRandomSquare([i, j], steps);
574 break;
575 case V.EGG:
576 applyEggEffect();
577 break;
578 case V.MUSHROOM:
579 applyMushroomEffect();
580 break;
581 }
582 }
583 }
584 }
585 else {
586 // Queen, bishop or rook:
587 const step = [
588 (x2 - x1) / Math.abs(x2 - x1) || 0,
589 (y2 - y1) / Math.abs(y2 - y1) || 0
590 ];
591 const next = [move.appear[0].x + step[0], move.appear[0].y + step[1]];
592 if (
593 V.OnBoard(next[0], next[1]) &&
594 this.board[next[0]][next[1]] != V.EMPTY &&
595 this.getPiece(next[0], next[1]) != V.INVISIBLE_QUEEN &&
596 this.getColor(next[0], next[1]) != 'a'
597 ) {
598 const afterNext = [next[0] + step[0], next[1] + step[1]];
599 if (V.OnBoard(afterNext[0], afterNext[1])) {
600 const afterColor = this.getColor(afterNext[0], afterNext[1]);
601 if (
602 this.board[afterNext[0]][afterNext[1]] == V.EMPTY ||
603 afterColor == 'a'
604 ) {
605 move.appear[0].x = afterNext[0];
606 move.appear[0].y = afterNext[1];
607 if (this.board[afterNext[0]][afterNext[1]] != V.EMPTY) {
608 // object = banana, bomb, mushroom or egg
609 const object = this.getPiece(afterNext[0], afterNext[1]);
610 move.vanish.push(
611 new PiPo({
612 x: afterNext[0],
613 y: afterNext[1],
614 c: afterColor,
615 p: object
616 })
617 );
618 switch (object) {
619 case V.BANANA:
620 case V.BOMB:
621 const steps =
622 V.steps[object == V.BANANA ? V.ROOK : V.BISHOP];
623 move.next = this.getRandomSquare(
624 [afterNext[0], afterNext[1]], steps);
625 break;
626 case V.EGG:
627 applyEggEffect();
628 break;
629 case V.MUSHROOM:
630 applyMushroomEffect();
631 break;
632 }
633 }
634 }
635 }
636 }
637 }
638 };
639 const color2 = this.getColor(x2, y2);
640 const piece2 = this.getPiece(x2, y2);
641 if (color2 == 'a') {
642 switch (piece2) {
643 case V.BANANA:
644 case V.BOMB:
645 const steps = V.steps[piece2 == V.BANANA ? V.ROOK : V.BISHOP];
646 move.next = this.getRandomSquare([x2, y2], steps);
647 break;
648 case V.MUSHROOM:
649 applyMushroomEffect();
650 break;
651 case V.EGG:
652 if (this.subTurn == 1)
653 // No egg effect at subTurn 2
654 applyEggEffect();
655 break;
656 }
657 }
658 if (
659 this.subTurn == 1 &&
660 !move.next &&
661 move.appear.length > 0 &&
662 [V.ROOK, V.BISHOP].includes(piece1)
663 ) {
664 const finalSquare = [move.appear[0].x, move.appear[0].y];
665 if (
666 color2 != 'a' ||
667 this.getColor(finalSquare[0], finalSquare[1]) != 'a' ||
668 this.getPiece(finalSquare[0], finalSquare[1]) != V.EGG
669 ) {
670 const validSteps =
671 V.steps[piece1 == V.ROOK ? V.BISHOP : V.ROOK].filter(s => {
672 const [i, j] = [finalSquare[0] + s[0], finalSquare[1] + s[1]];
673 return (
674 V.OnBoard(i, j) &&
675 // NOTE: do not place a bomb or banana on the invisible queen!
676 (this.board[i][j] == V.EMPTY || this.getColor(i, j) == 'a')
677 );
678 });
679 if (validSteps.length >= 1) {
680 const randIdx = randInt(validSteps.length);
681 const [x, y] = [
682 finalSquare[0] + validSteps[randIdx][0],
683 finalSquare[1] + validSteps[randIdx][1]
684 ];
685 move.appear.push(
686 new PiPo({
687 x: x,
688 y: y,
689 c: 'a',
690 p: (piece1 == V.ROOK ? V.BANANA : V.BOMB)
691 })
692 );
693 if (this.board[x][y] != V.EMPTY) {
694 move.vanish.push(
695 new PiPo({ x: x, y: y, c: 'a', p: this.getPiece(x, y) }));
696 }
697 }
698 }
699 }
700 return move;
701 }
702
703 getBasicMove(psq1, sq2, tr) {
704 let moves = [];
705 if (Array.isArray(psq1)) psq1 = { x: psq1[0], y: psq1[1] };
706 let m = this.getBasicMove_aux(psq1, sq2, tr, "initMove");
707 while (!!m.next) {
708 // Last move ended on bomb or banana, direction change
709 V.PlayOnBoard(this.board, m);
710 moves.push(m);
711 m = this.getBasicMove_aux(
712 { x: m.appear[0].x, y: m.appear[0].y }, m.next);
713 }
714 for (let i=moves.length-1; i>=0; i--) V.UndoOnBoard(this.board, moves[i]);
715 moves.push(m);
716 // Now merge moves into one
717 let move = {};
718 // start is wrong for Toadette moves --> it's fixed later
719 move.start = { x: psq1.x, y: psq1.y };
720 move.end = !!sq2 ? { x: sq2[0], y: sq2[1] } : { x: psq1.x, y: psq1.y };
721 if (!!tr) move.promoteInto = moves[0].promoteInto;
722 let lm = moves[moves.length-1];
723 if (this.subTurn == 1 && !!lm.end.effect)
724 move.end.effect = lm.end.effect;
725 if (moves.length == 1) {
726 move.appear = moves[0].appear;
727 move.vanish = moves[0].vanish;
728 }
729 else {
730 // Keep first vanish and last appear (if any)
731 move.appear = lm.appear;
732 move.vanish = moves[0].vanish;
733 if (
734 move.vanish.length >= 1 &&
735 move.appear.length >= 1 &&
736 move.vanish[0].x == move.appear[0].x &&
737 move.vanish[0].y == move.appear[0].y
738 ) {
739 // Loopback on initial square:
740 move.vanish.shift();
741 move.appear.shift();
742 }
743 for (let i=1; i < moves.length - 1; i++) {
744 for (let v of moves[i].vanish) {
745 // Only vanishing objects, not appearing at init move
746 if (
747 v.c == 'a' &&
748 (
749 moves[0].appear.length == 1 ||
750 moves[0].appear[1].x != v.x ||
751 moves[0].appear[1].y != v.y
752 )
753 ) {
754 move.vanish.push(v);
755 }
756 }
757 }
758 // Final vanish is our piece, but others might be relevant
759 // (for some egg bonuses at least).
760 for (let i=1; i < lm.vanish.length; i++) {
761 if (
762 lm.vanish[i].c != 'a' ||
763 moves[0].appear.length == 1 ||
764 moves[0].appear[1].x != lm.vanish[i].x ||
765 moves[0].appear[1].y != lm.vanish[i].y
766 ) {
767 move.vanish.push(lm.vanish[i]);
768 }
769 }
770 }
771 return move;
772 }
773
774
775
776
777
778 getPotentialPawnMoves([x, y]) {
779 const color = this.turn;
780 const oppCol = C.GetOppCol(color);
781 const shiftX = (color == 'w' ? -1 : 1);
782 const firstRank = (color == "w" ? this.size.x - 1 : 0);
783 let moves = [];
784 if (
785 this.board[x + shiftX][y] == "" ||
786 this.getColor(x + shiftX, y) == 'a' ||
787 this.getPiece(x + shiftX, y) == V.INVISIBLE_QUEEN
788 ) {
789
790 // TODO:
791 this.addPawnMoves([x, y], [x + shiftX, y], moves);
792 if (
793 [firstRank, firstRank + shiftX].includes(x) &&
794 (
795 this.board[x + 2 * shiftX][y] == V.EMPTY ||
796 this.getColor(x + 2 * shiftX, y) == 'a' ||
797 this.getPiece(x + 2 * shiftX, y) == V.INVISIBLE_QUEEN
798 )
799 ) {
800 moves.push(this.getBasicMove({ x: x, y: y }, [x + 2 * shiftX, y]));
801 }
802 }
803 for (let shiftY of [-1, 1]) {
804 if (
805 y + shiftY >= 0 &&
806 y + shiftY < sizeY &&
807 this.board[x + shiftX][y + shiftY] != V.EMPTY &&
808 // Pawns cannot capture invisible queen this way!
809 this.getPiece(x + shiftX, y + shiftY) != V.INVISIBLE_QUEEN &&
810 ['a', oppCol].includes(this.getColor(x + shiftX, y + shiftY))
811 ) {
812 this.addPawnMoves([x, y], [x + shiftX, y + shiftY], moves);
813 }
814 }
815 return moves;
816 }
817
818 getPotentialQueenMoves(sq) {
819 const normalMoves = super.getPotentialQueenMoves(sq);
820 // If flag allows it, add 'invisible movements'
821 let invisibleMoves = [];
822 if (this.powerFlags[this.turn][V.QUEEN]) {
823 normalMoves.forEach(m => {
824 if (
825 m.appear.length == 1 &&
826 m.vanish.length == 1 &&
827 // Only simple non-capturing moves:
828 m.vanish[0].c != 'a'
829 ) {
830 let im = JSON.parse(JSON.stringify(m));
831 im.appear[0].p = V.INVISIBLE_QUEEN;
832 im.end.noHighlight = true;
833 invisibleMoves.push(im);
834 }
835 });
836 }
837 return normalMoves.concat(invisibleMoves);
838 }
839
840 getPotentialKingMoves([x, y]) {
841 let moves = super.getPotentialKingMoves([x, y]);
842 const color = this.turn;
843 // If flag allows it, add 'remote shell captures'
844 if (this.powerFlags[this.turn][V.KING]) {
845 V.steps[V.ROOK].concat(V.steps[V.BISHOP]).forEach(step => {
846 let [i, j] = [x + step[0], y + step[1]];
847 while (
848 V.OnBoard(i, j) &&
849 (
850 this.board[i][j] == V.EMPTY ||
851 this.getPiece(i, j) == V.INVISIBLE_QUEEN ||
852 (
853 this.getColor(i, j) == 'a' &&
854 [V.EGG, V.MUSHROOM].includes(this.getPiece(i, j))
855 )
856 )
857 ) {
858 i += step[0];
859 j += step[1];
860 }
861 if (V.OnBoard(i, j)) {
862 const colIJ = this.getColor(i, j);
863 if (colIJ != color) {
864 // May just destroy a bomb or banana:
865 moves.push(
866 new Move({
867 start: { x: x, y: y},
868 end: { x: i, y: j },
869 appear: [],
870 vanish: [
871 new PiPo({
872 x: i, y: j, c: colIJ, p: this.getPiece(i, j)
873 })
874 ]
875 })
876 );
877 }
878 }
879 });
880 }
881 return moves;
882 }
883
884 getSlideNJumpMoves([x, y], steps, oneStep) {
885 let moves = [];
886 outerLoop: for (let step of steps) {
887 let i = x + step[0];
888 let j = y + step[1];
889 while (
890 V.OnBoard(i, j) &&
891 (
892 this.board[i][j] == V.EMPTY ||
893 this.getPiece(i, j) == V.INVISIBLE_QUEEN ||
894 (
895 this.getColor(i, j) == 'a' &&
896 [V.EGG, V.MUSHROOM].includes(this.getPiece(i, j))
897 )
898 )
899 ) {
900 moves.push(this.getBasicMove({ x: x, y: y }, [i, j]));
901 if (oneStep) continue outerLoop;
902 i += step[0];
903 j += step[1];
904 }
905 if (V.OnBoard(i, j) && this.canTake([x, y], [i, j]))
906 moves.push(this.getBasicMove({ x: x, y: y }, [i, j]));
907 }
908 return moves;
909 }
910
911 getAllPotentialMoves() {
912 if (this.subTurn == 1) return super.getAllPotentialMoves();
913 let moves = [];
914 const color = this.turn;
915 const L = this.effects.length;
916 switch (this.effects[L-1]) {
917 case "kingboo": {
918 let allPieces = [];
919 for (let i=0; i<8; i++) {
920 for (let j=0; j<8; j++) {
921 const colIJ = this.getColor(i, j);
922 const pieceIJ = this.getPiece(i, j);
923 if (
924 i != x && j != y &&
925 this.board[i][j] != V.EMPTY &&
926 colIJ != 'a' &&
927 pieceIJ != V.INVISIBLE_QUEEN
928 ) {
929 allPieces.push({ x: i, y: j, c: colIJ, p: pieceIJ });
930 }
931 }
932 }
933 for (let x=0; x<8; x++) {
934 for (let y=0; y<8; y++) {
935 if (this.getColor(i, j) == color) {
936 // Add exchange with something
937 allPieces.forEach(pp => {
938 if (pp.x != i || pp.y != j) {
939 const movedUnit = new PiPo({
940 x: x,
941 y: y,
942 c: pp.c,
943 p: pp.p
944 });
945 let mMove = this.getBasicMove({ x: x, y: y }, [pp.x, pp.y]);
946 mMove.appear.push(movedUnit);
947 moves.push(mMove);
948 }
949 });
950 }
951 }
952 }
953 break;
954 }
955 case "toadette": {
956 const x = V.size.x + (this.turn == 'w' ? 0 : 1);
957 for (let y = 0; y < 8; y++)
958 Array.prototype.push.apply(moves, this.getReserveMoves([x, y]));
959 break;
960 }
961 case "daisy":
962 moves = super.getAllPotentialMoves();
963 break;
964 }
965 return moves;
966 }
967
968
969
970
971
972
973
974
975
976
977 /// if any of my pieces was immobilized, it's not anymore.
978 //if play set a piece immobilized, then mark it
979 prePlay(move) {
980 if (move.effect == "toadette")
981 this.reserve = this.captured;
982 else
983 this.reserve = { w: {}, b: {} };;
984 const color = this.turn;
985 if (
986 move.vanish.length == 2 &&
987 move.vanish[1].c != 'a' &&
988 move.appear.length == 1 //avoid king Boo!
989 ) {
990 // Capture: update this.captured
991 let capturedPiece = move.vanish[1].p;
992 if (capturedPiece == V.INVISIBLE_QUEEN)
993 capturedPiece = V.QUEEN;
994 else if (Object.keys(V.IMMOBILIZE_DECODE).includes(capturedPiece))
995 capturedPiece = V.IMMOBILIZE_DECODE[capturedPiece];
996 this.captured[move.vanish[1].c][capturedPiece]++;
997 }
998 else if (move.vanish.length == 0) {
999 if (move.appear.length == 0 || move.appear[0].c == 'a') return;
1000 // A piece is back on board
1001 this.captured[move.appear[0].c][move.appear[0].p]--;
1002 }
1003 if (move.appear.length == 0) {
1004 // Three cases: king "shell capture", Chomp or Koopa
1005 if (this.getPiece(move.start.x, move.start.y) == V.KING)
1006 // King remote capture:
1007 this.powerFlags[color][V.KING] = false;
1008 else if (move.end.effect == "chomp")
1009 this.captured[color][move.vanish[0].p]++;
1010 }
1011 else if (move.appear[0].p == V.INVISIBLE_QUEEN)
1012 this.powerFlags[move.appear[0].c][V.QUEEN] = false;
1013 if (this.subTurn == 2) return;
1014 if (
1015 move.turn[1] == 1 &&
1016 move.appear.length == 0 ||
1017 !(Object.keys(V.IMMOBILIZE_DECODE).includes(move.appear[0].p))
1018 ) {
1019 // Look for an immobilized piece of my color: it can now move
1020 for (let i=0; i<8; i++) {
1021 for (let j=0; j<8; j++) {
1022 if (this.board[i][j] != V.EMPTY) {
1023 const piece = this.getPiece(i, j);
1024 if (
1025 this.getColor(i, j) == color &&
1026 Object.keys(V.IMMOBILIZE_DECODE).includes(piece)
1027 ) {
1028 this.board[i][j] = color + V.IMMOBILIZE_DECODE[piece];
1029 move.wasImmobilized = [i, j];
1030 }
1031 }
1032 }
1033 }
1034 }
1035 // Also make opponent invisible queen visible again, if any
1036 const oppCol = V.GetOppCol(color);
1037 for (let i=0; i<8; i++) {
1038 for (let j=0; j<8; j++) {
1039 if (
1040 this.board[i][j] != V.EMPTY &&
1041 this.getColor(i, j) == oppCol &&
1042 this.getPiece(i, j) == V.INVISIBLE_QUEEN
1043 ) {
1044 this.board[i][j] = oppCol + V.QUEEN;
1045 move.wasInvisible = [i, j];
1046 }
1047 }
1048 }
1049 }
1050
1051 play(move) {
1052 this.prePlay(move);
1053 this.playOnBoard(move);
1054 if (["kingboo", "toadette", "daisy"].includes(move.effect)) {
1055 this.effect = move.effect;
1056 this.subTurn = 2;
1057 }
1058 else {
1059 this.turn = C.GetOppCol(this.turn);
1060 this.movesCount++;
1061 this.subTurn = 1;
1062 }
1063 }
1064
1065 filterValid(moves) {
1066 return moves;
1067 }
1068
1069 playPlusVisual(move, r) {
1070 this.play(move);
1071 this.playVisual(move, r);
1072
1073
1074 // TODO: display bonus messages
1075 // TODO: si continuation, continuer, et sinon :
1076 this.afterPlay(this.moveStack); //user method
1077 }
1078
1079 };