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