Experimental: improve animation, reduce lags in stack moves sending. Add Allmate
[xogo.git] / variants / Chakart / class.js
CommitLineData
bc2bc396
BA
1import ChessRules from "/base_rules.js";
2import GiveawayRules from "/variants/Giveaway/class.js";
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
bc2bc396 8export default class ChakartRules extends ChessRules {
f8b43ef7
BA
9
10 static get Options() {
11 return {
12 select: [
13 {
14 label: "Randomness",
15 variable: "randomness",
16 defaut: 2,
17 options: [
37481d1e
BA
18 {label: "Deterministic", value: 0},
19 {label: "Symmetric random", value: 1},
20 {label: "Asymmetric random", value: 2}
f8b43ef7
BA
21 ]
22 }
3b641716
BA
23 ],
24 styles: ["cylinder"]
f8b43ef7
BA
25 };
26 }
27
8f57fbf2
BA
28 get pawnPromotions() {
29 return ['q', 'r', 'n', 'b', 'k'];
f8b43ef7 30 }
8f57fbf2 31
2b9b90da 32 get hasCastle() {
f8b43ef7
BA
33 return false;
34 }
2b9b90da 35 get hasEnpassant() {
f8b43ef7
BA
36 return false;
37 }
24872b22
BA
38 get hasReserve() {
39 return true;
40 }
41 get hasReserveFen() {
42 return false;
43 }
5f08c59b
BA
44 get hasMoveStack() {
45 return true;
46 }
f8b43ef7 47
f8b43ef7
BA
48 static get IMMOBILIZE_CODE() {
49 return {
50 'p': 's',
51 'r': 'u',
52 'n': 'o',
53 'b': 'c',
54 'q': 't',
55 'k': 'l'
56 };
57 }
58
59 static get IMMOBILIZE_DECODE() {
60 return {
61 's': 'p',
62 'u': 'r',
63 'o': 'n',
64 'c': 'b',
65 't': 'q',
66 'l': 'k'
67 };
68 }
69
f8b43ef7
BA
70 // Fictive color 'a', bomb banana mushroom egg
71 static get BOMB() {
72 return 'w'; //"Wario"
73 }
74 static get BANANA() {
75 return 'd'; //"Donkey"
76 }
77 static get EGG() {
78 return 'e';
79 }
80 static get MUSHROOM() {
81 return 'm';
82 }
83
24872b22
BA
84 static get EGG_SURPRISE() {
85 return [
86 "kingboo", "bowser", "daisy", "koopa",
87 "luigi", "waluigi", "toadette", "chomp"];
88 }
89
3b641716
BA
90 canIplay(x, y) {
91 if (
92 this.playerColor != this.turn ||
93 Object.keys(V.IMMOBILIZE_DECODE).includes(this.getPiece(x, y))
94 ) {
95 return false;
96 }
97 return this.egg == "kingboo" || this.getColor(x, y) == this.turn;
98 }
99
24872b22 100 pieces(color, x, y) {
3b641716 101 const specials = {
24872b22 102 'i': {"class": "invisible"}, //queen
cc9fe4f1 103 '?': {"class": "mystery"}, //...initial square
24872b22
BA
104 'e': {"class": "egg"},
105 'm': {"class": "mushroom"},
106 'd': {"class": "banana"},
3b641716
BA
107 'w': {"class": "bomb"},
108 'z': {"class": "remote-capture"}
24872b22 109 };
3b641716
BA
110 const bowsered = {
111 's': {"class": ["immobilized", "pawn"]},
112 'u': {"class": ["immobilized", "rook"]},
113 'o': {"class": ["immobilized", "knight"]},
114 'c': {"class": ["immobilized", "bishop"]},
115 't': {"class": ["immobilized", "queen"]},
116 'l': {"class": ["immobilized", "king"]}
117 };
f5435757
BA
118 return Object.assign(
119 {
120 'y': {
121 // Virtual piece for "king remote shell captures"
122 moves: [],
123 attack: [
124 {
125 steps: [
126 [0, 1], [0, -1], [1, 0], [-1, 0],
127 [1, 1], [1, -1], [-1, 1], [-1, -1]
128 ]
129 }
130 ]
131 }
132 },
133 specials, bowsered, super.pieces(color, x, y));
24872b22
BA
134 }
135
91339921 136 genRandInitFen(seed) {
554e3ad3
BA
137 const options = Object.assign({mode: "suicide"}, this.options);
138 const gr = new GiveawayRules({options: options, genFenOnly: true});
fc12475f
BA
139 const baseFen = gr.genRandInitFen(seed);
140 const fenParts = baseFen.split(" ");
141 let others = JSON.parse(fenParts[3]);
142 delete others["enpassant"];
143 others["flags"] = "1111"; //Peach + Mario flags
144 return fenParts.slice(0, 3).join(" ") + " " + JSON.stringify(others);
91339921
BA
145 }
146
8f57fbf2 147 fen2board(f) {
f8b43ef7
BA
148 return (
149 f.charCodeAt() <= 90
150 ? "w" + f.toLowerCase()
151 : (['w', 'd', 'e', 'm'].includes(f) ? "a" : "b") + f
152 );
153 }
154
f8b43ef7
BA
155 setFlags(fenflags) {
156 // King can send shell? Queen can be invisible?
157 this.powerFlags = {
8f57fbf2
BA
158 w: {k: false, q: false},
159 b: {k: false, q: false}
f8b43ef7 160 };
8f57fbf2 161 for (let c of ['w', 'b']) {
f8b43ef7
BA
162 for (let p of ['k', 'q']) {
163 this.powerFlags[c][p] =
164 fenflags.charAt((c == "w" ? 0 : 2) + (p == 'k' ? 0 : 1)) == "1";
165 }
166 }
167 }
168
169 aggregateFlags() {
170 return this.powerFlags;
171 }
172
173 disaggregateFlags(flags) {
174 this.powerFlags = flags;
175 }
176
91339921
BA
177 getFlagsFen() {
178 return ['w', 'b'].map(c => {
179 return ['k', 'q'].map(p => this.powerFlags[c][p] ? "1" : "0").join("");
180 }).join("");
181 }
182
8f57fbf2 183 setOtherVariables(fenParsed) {
5f08c59b 184 super.setOtherVariables(fenParsed);
bc2bc396 185 this.egg = null;
3b641716
BA
186 // Change seed (after FEN generation!!)
187 // so that further calls differ between players:
554e3ad3 188 Random.setSeed(Math.floor(19840 * Math.random()));
f8b43ef7
BA
189 }
190
5f08c59b
BA
191 initReserves() {
192 this.reserve = {}; //to be filled later
193 }
194
c7c2f41c 195 // For Toadette bonus
91339921 196 getDropMovesFrom([c, p]) {
be3cb9d1 197 if (typeof c != "string" || this.reserve[c][p] == 0)
c7c2f41c 198 return [];
f8b43ef7 199 let moves = [];
91339921 200 const start = (c == 'w' && p == 'p' ? 1 : 0);
3b641716 201 const end = (c == 'b' && p == 'p' ? 7 : 8);
f8b43ef7 202 for (let i = start; i < end; i++) {
91339921
BA
203 for (let j = 0; j < this.size.y; j++) {
204 const pieceIJ = this.getPiece(i, j);
3b641716 205 const colIJ = this.getColor(i, j);
cc9fe4f1 206 if (this.board[i][j] == "" || colIJ == 'a' || pieceIJ == 'i') {
91339921
BA
207 let m = new Move({
208 start: {x: c, y: p},
91339921
BA
209 appear: [new PiPo({x: i, y: j, c: c, p: p})],
210 vanish: []
211 });
212 // A drop move may remove a bonus (or hidden queen!)
213 if (this.board[i][j] != "")
3b641716 214 m.vanish.push(new PiPo({x: i, y: j, c: colIJ, p: pieceIJ}));
f8b43ef7
BA
215 moves.push(m);
216 }
217 }
218 }
219 return moves;
220 }
221
bc2bc396 222 getPotentialMovesFrom([x, y]) {
f8b43ef7 223 let moves = [];
3b641716 224 const piece = this.getPiece(x, y);
bc2bc396 225 if (this.egg == "toadette")
24872b22
BA
226 moves = this.getDropMovesFrom([x, y]);
227 else if (this.egg == "kingboo") {
3b641716 228 const color = this.turn;
37481d1e 229 const oppCol = C.GetOppCol(color);
3b641716 230 // Only allow to swap (non-immobilized!) pieces
37481d1e
BA
231 for (let i=0; i<this.size.x; i++) {
232 for (let j=0; j<this.size.y; j++) {
bc2bc396
BA
233 const colIJ = this.getColor(i, j);
234 const pieceIJ = this.getPiece(i, j);
235 if (
236 (i != x || j != y) &&
237 ['w', 'b'].includes(colIJ) &&
3b641716 238 !Object.keys(V.IMMOBILIZE_DECODE).includes(pieceIJ) &&
bc2bc396
BA
239 // Next conditions = no pawn on last rank
240 (
3b641716 241 piece != 'p' ||
bc2bc396
BA
242 (
243 (color != 'w' || i != 0) &&
244 (color != 'b' || i != this.size.x - 1)
245 )
246 )
247 &&
248 (
249 pieceIJ != 'p' ||
250 (
251 (colIJ != 'w' || x != 0) &&
252 (colIJ != 'b' || x != this.size.x - 1)
253 )
254 )
255 ) {
37481d1e 256 let m = this.getBasicMove([x, y], [i, j]);
3b641716
BA
257 m.appear.push(new PiPo({x: x, y: y, p: pieceIJ, c: colIJ}));
258 m.kingboo = true; //avoid some side effects (bananas/bombs)
37481d1e
BA
259 moves.push(m);
260 }
261 }
262 }
be3cb9d1 263 }
24872b22
BA
264 else {
265 // Normal case (including bonus daisy)
24872b22
BA
266 switch (piece) {
267 case 'p':
268 moves = this.getPawnMovesFrom([x, y]); //apply promotions
269 break;
270 case 'q':
271 moves = this.getQueenMovesFrom([x, y]);
272 break;
273 case 'k':
274 moves = this.getKingMovesFrom([x, y]);
275 break;
276 case 'n':
277 moves = this.getKnightMovesFrom([x, y]);
278 break;
279 case 'b':
280 case 'r':
3b641716 281 // Explicitely listing types to avoid moving immobilized piece
f5435757 282 moves = this.getPotentialMovesOf(piece, [x, y]);
24872b22
BA
283 break;
284 }
285 }
24872b22
BA
286 return moves;
287 }
288
3b641716
BA
289 canStepOver(i, j) {
290 return (
291 this.board[i][j] == "" ||
cc9fe4f1
BA
292 ['i', V.EGG, V.MUSHROOM].includes(this.getPiece(i, j))
293 );
b0cf998b
BA
294 }
295
37481d1e 296 getPawnMovesFrom([x, y]) {
f8b43ef7 297 const color = this.turn;
be3cb9d1
BA
298 const oppCol = C.GetOppCol(color);
299 const shiftX = (color == 'w' ? -1 : 1);
300 const firstRank = (color == "w" ? this.size.x - 1 : 0);
f8b43ef7 301 let moves = [];
cc9fe4f1 302 const frontPiece = this.getPiece(x + shiftX, y);
f8b43ef7 303 if (
be3cb9d1 304 this.board[x + shiftX][y] == "" ||
f8b43ef7 305 this.getColor(x + shiftX, y) == 'a' ||
cc9fe4f1 306 frontPiece == 'i'
f8b43ef7 307 ) {
7562d2c2 308 moves.push(this.getBasicMove([x, y], [x + shiftX, y]));
f8b43ef7
BA
309 if (
310 [firstRank, firstRank + shiftX].includes(x) &&
cc9fe4f1 311 ![V.BANANA, V.BOMB].includes(frontPiece) &&
f8b43ef7 312 (
7562d2c2 313 this.board[x + 2 * shiftX][y] == "" ||
f8b43ef7 314 this.getColor(x + 2 * shiftX, y) == 'a' ||
cc9fe4f1 315 this.getPiece(x + 2 * shiftX, y) == 'i'
f8b43ef7
BA
316 )
317 ) {
7562d2c2 318 moves.push(this.getBasicMove([x, y], [x + 2 * shiftX, y]));
f8b43ef7
BA
319 }
320 }
321 for (let shiftY of [-1, 1]) {
f5435757 322 const nextY = this.getY(y + shiftY);
f8b43ef7 323 if (
f5435757
BA
324 nextY >= 0 &&
325 nextY < this.size.y &&
326 this.board[x + shiftX][nextY] != "" &&
f8b43ef7 327 // Pawns cannot capture invisible queen this way!
f5435757
BA
328 this.getPiece(x + shiftX, nextY) != 'i' &&
329 ['a', oppCol].includes(this.getColor(x + shiftX, nextY))
f8b43ef7 330 ) {
f5435757 331 moves.push(this.getBasicMove([x, y], [x + shiftX, nextY]));
f8b43ef7
BA
332 }
333 }
f5435757
BA
334 this.pawnPostProcess(moves, color, oppCol);
335 // Add mushroom on before-last square (+ potential segments)
24872b22 336 moves.forEach(m => {
f5435757
BA
337 let [mx, my] = [x, y];
338 if (Math.abs(m.end.x - m.start.x) == 2)
339 mx = (m.start.x + m.end.x) / 2;
340 m.appear.push(new PiPo({x: mx, y: my, c: 'a', p: 'm'}));
341 if (mx != x && this.board[mx][my] != "") {
3b641716 342 m.vanish.push(new PiPo({
f5435757
BA
343 x: mx,
344 y: my,
345 c: this.getColor(mx, my),
346 p: this.getPiece(mx, my)
3b641716 347 }));
24872b22 348 }
f5435757
BA
349 if (Math.abs(m.end.y - m.start.y) > 1) {
350 m.segments = [
351 [[x, y], [x, y]],
352 [[m.end.x, m.end.y], [m.end.x, m.end.y]]
353 ];
354 }
24872b22 355 });
3b641716 356 return moves;
24872b22
BA
357 }
358
359 getKnightMovesFrom([x, y]) {
360 // Add egg on initial square:
361 return this.getPotentialMovesOf('n', [x, y]).map(m => {
362 m.appear.push(new PiPo({p: "e", c: "a", x: x, y: y}));
363 return m;
364 });
365 }
366
37481d1e 367 getQueenMovesFrom(sq) {
24872b22 368 const normalMoves = this.getPotentialMovesOf('q', sq);
f8b43ef7
BA
369 // If flag allows it, add 'invisible movements'
370 let invisibleMoves = [];
7562d2c2 371 if (this.powerFlags[this.turn]['q']) {
f8b43ef7
BA
372 normalMoves.forEach(m => {
373 if (
374 m.appear.length == 1 &&
375 m.vanish.length == 1 &&
376 // Only simple non-capturing moves:
377 m.vanish[0].c != 'a'
378 ) {
379 let im = JSON.parse(JSON.stringify(m));
cc9fe4f1 380 im.appear[0].p = 'i';
24872b22 381 im.noAnimate = true;
f8b43ef7
BA
382 invisibleMoves.push(im);
383 }
384 });
385 }
386 return normalMoves.concat(invisibleMoves);
387 }
388
37481d1e 389 getKingMovesFrom([x, y]) {
24872b22 390 let moves = this.getPotentialMovesOf('k', [x, y]);
f8b43ef7 391 // If flag allows it, add 'remote shell captures'
7562d2c2 392 if (this.powerFlags[this.turn]['k']) {
f5435757
BA
393 let shellCaptures = this.getPotentialMovesOf('y', [x, y]);
394 shellCaptures.forEach(sc => {
395 sc.shell = true; //easier play()
396 sc.choice = 'z'; //to display in showChoices()
397 // Fix move (Rifle style):
398 sc.vanish.shift();
399 sc.appear.shift();
f8b43ef7 400 });
f5435757 401 Array.prototype.push.apply(moves, shellCaptures);
f8b43ef7
BA
402 }
403 return moves;
404 }
405
7562d2c2 406 play(move) {
a2bb7e06
BA
407 const color = this.turn;
408 const oppCol = C.GetOppCol(color);
409 if (
410 move.appear.length > 0 &&
411 move.appear[0].p == 'p' &&
412 (
413 (color == 'w' && move.end.x == 0) ||
414 (color == 'b' && move.end.x == this.size.x - 1)
415 )
416 ) {
417 // "Forgotten" promotion, which occurred after some effect
418 let moves = [move];
419 super.pawnPostProcess(moves, color, oppCol);
420 super.showChoices(moves);
421 return false;
422 }
5f08c59b
BA
423 this.postPlay(move, color, oppCol);
424 return true;
425 }
426
427 postPlay(move, color, oppCol) {
24872b22 428 this.egg = move.egg;
24872b22
BA
429 if (move.egg == "toadette") {
430 this.reserve = { w: {}, b: {} };
431 // Randomly select a piece in pawnPromotions
3b641716
BA
432 if (!move.toadette)
433 move.toadette = Random.sample(this.pawnPromotions);
434 this.reserve[color][move.toadette] = 1;
24872b22 435 this.re_drawReserve([color]);
f8b43ef7 436 }
24872b22
BA
437 else if (Object.keys(this.reserve).length > 0) {
438 this.reserve = {};
439 this.re_drawReserve([color]);
f8b43ef7 440 }
24872b22
BA
441 if (move.shell)
442 this.powerFlags[color]['k'] = false;
cc9fe4f1 443 else if (move.appear.length > 0 && move.appear[0].p == 'i') {
24872b22 444 this.powerFlags[move.appear[0].c]['q'] = false;
cc9fe4f1
BA
445 if (color == this.playerColor) {
446 move.appear.push(
447 new PiPo({x: move.start.x, y: move.start.y, c: color, p: '?'}));
448 }
3b641716
BA
449 }
450 if (color == this.playerColor) {
451 // Look for an immobilized piece of my color: it can now move
452 for (let i=0; i<8; i++) {
453 for (let j=0; j<8; j++) {
454 if ((i != move.end.x || j != move.end.y) && this.board[i][j] != "") {
455 const piece = this.getPiece(i, j);
456 if (
457 this.getColor(i, j) == color &&
458 Object.keys(V.IMMOBILIZE_DECODE).includes(piece)
459 ) {
460 move.vanish.push(new PiPo({
461 x: i, y: j, c: color, p: piece
462 }));
463 move.appear.push(new PiPo({
464 x: i, y: j, c: color, p: V.IMMOBILIZE_DECODE[piece]
465 }));
466 }
f8b43ef7
BA
467 }
468 }
469 }
3b641716
BA
470 // Also make opponent invisible queen visible again, if any
471 for (let i=0; i<8; i++) {
472 for (let j=0; j<8; j++) {
473 if (
474 this.board[i][j] != "" &&
cc9fe4f1 475 this.getColor(i, j) == oppCol
3b641716 476 ) {
cc9fe4f1
BA
477 const pieceIJ = this.getPiece(i, j);
478 if (pieceIJ == 'i') {
479 move.vanish.push(new PiPo({x: i, y: j, c: oppCol, p: 'i'}));
480 move.appear.push(new PiPo({x: i, y: j, c: oppCol, p: 'q'}));
481 }
482 else if (pieceIJ == '?')
483 move.vanish.push(new PiPo({x: i, y: j, c: oppCol, p: '?'}));
3b641716 484 }
f8b43ef7
BA
485 }
486 }
487 }
5f08c59b
BA
488 this.playOnBoard(move);
489 super.postPlay(move);
490 }
491
492 playVisual(move, r) {
493 super.playVisual(move, r);
bc2bc396 494 if (move.egg)
cc9fe4f1 495 this.displayBonus(move);
5f08c59b
BA
496 }
497
498 computeNextMove(move) {
499 // Set potential random effects, so that play() is deterministic
500 // from opponent viewpoint:
501 const endPiece = this.getPiece(move.end.x, move.end.y);
502 switch (endPiece) {
503 case V.EGG:
504 move.egg = Random.sample(V.EGG_SURPRISE);
505 move.next = this.getEggEffect(move);
506 break;
507 case V.MUSHROOM:
508 move.next = this.getMushroomEffect(move);
509 break;
510 case V.BANANA:
511 case V.BOMB:
512 move.next = this.getBombBananaEffect(move, endPiece);
513 break;
514 }
515 // NOTE: Chakart has also some side-effects:
516 if (
517 !move.next && move.appear.length > 0 &&
518 !move.kingboo && !move.luigiEffect
519 ) {
520 const movingPiece = move.appear[0].p;
521 if (['b', 'r'].includes(movingPiece)) {
522 // Drop a banana or bomb:
523 const bs =
524 this.getRandomSquare([move.end.x, move.end.y],
525 movingPiece == 'r'
526 ? [[1, 1], [1, -1], [-1, 1], [-1, -1]]
527 : [[1, 0], [-1, 0], [0, 1], [0, -1]],
528 "freeSquare");
529 if (bs) {
530 move.appear.push(
531 new PiPo({
532 x: bs[0],
533 y: bs[1],
534 c: 'a',
535 p: movingPiece == 'r' ? 'd' : 'w'
536 })
537 );
538 if (this.board[bs[0]][bs[1]] != "") {
539 move.vanish.push(
540 new PiPo({
541 x: bs[0],
542 y: bs[1],
543 c: this.getColor(bs[0], bs[1]),
544 p: this.getPiece(bs[0], bs[1])
545 })
546 );
547 }
548 }
549 }
550 }
551 }
552
553 isLastMove(move) {
554 return !move.next && !["daisy", "toadette", "kingboo"].includes(move.egg);
bc2bc396
BA
555 }
556
3b641716
BA
557 // Helper to set and apply banana/bomb effect
558 getRandomSquare([x, y], steps, freeSquare) {
559 let validSteps = steps.filter(s => this.onBoard(x + s[0], y + s[1]));
560 if (freeSquare) {
561 // Square to put banana/bomb cannot be occupied by a piece
562 validSteps = validSteps.filter(s => {
563 return ["", 'a'].includes(this.getColor(x + s[0], y + s[1]))
564 });
565 }
566 if (validSteps.length == 0)
567 return null;
568 const step = validSteps[Random.randInt(validSteps.length)];
569 return [x + step[0], y + step[1]];
f8b43ef7
BA
570 }
571
3b641716
BA
572 getEggEffect(move) {
573 const getRandomPiece = (c) => {
574 let bagOfPieces = [];
575 for (let i=0; i<this.size.x; i++) {
576 for (let j=0; j<this.size.y; j++) {
577 if (this.getColor(i, j) == c && this.getPiece(i, j) != 'k')
578 bagOfPieces.push([i, j]);
579 }
580 }
581 if (bagOfPieces.length >= 1)
582 return Random.sample(bagOfPieces);
583 return null;
584 };
585 const color = this.turn;
586 let em = null;
587 switch (move.egg) {
588 case "luigi":
589 case "waluigi":
590 // Change color of friendly or enemy piece, king excepted
591 const oldColor = (move.egg == "waluigi" ? color : C.GetOppCol(color));
592 const newColor = C.GetOppCol(oldColor);
593 const coords = getRandomPiece(oldColor);
594 if (coords) {
595 const piece = this.getPiece(coords[0], coords[1]);
596 em = new Move({
597 appear: [
598 new PiPo({x: coords[0], y: coords[1], c: newColor, p: piece})
599 ],
600 vanish: [
601 new PiPo({x: coords[0], y: coords[1], c: oldColor, p: piece})
602 ]
603 });
5f08c59b 604 em.luigiEffect = true; //avoid dropping bomb/banana by mistake
3b641716
BA
605 }
606 break;
607 case "bowser":
608 em = new Move({
609 appear: [
610 new PiPo({
611 x: move.end.x,
612 y: move.end.y,
613 c: color,
614 p: V.IMMOBILIZE_CODE[move.appear[0].p]
615 })
616 ],
617 vanish: [
618 new PiPo({
619 x: move.end.x,
620 y: move.end.y,
621 c: color,
622 p: move.appear[0].p
623 })
624 ]
625 });
626 break;
627 case "koopa":
628 // Reverse move
629 em = new Move({
630 appear: [
631 new PiPo({
632 x: move.start.x, y: move.start.y, c: color, p: move.appear[0].p
633 })
634 ],
635 vanish: [
636 new PiPo({
637 x: move.end.x, y: move.end.y, c: color, p: move.appear[0].p
638 })
639 ]
640 });
641 if (this.board[move.start.x][move.start.y] != "") {
642 // Pawn or knight let something on init square
643 em.vanish.push(new PiPo({
644 x: move.start.x,
645 y: move.start.y,
646 c: 'a',
647 p: this.getPiece(move.start.x, move.start.y)
648 }));
649 }
650 break;
651 case "chomp":
652 // Eat piece
653 em = new Move({
654 appear: [],
655 vanish: [
656 new PiPo({
657 x: move.end.x, y: move.end.y, c: color, p: move.appear[0].p
658 })
659 ],
660 end: {x: move.end.x, y: move.end.y}
661 });
662 break;
663 }
664 if (em && move.egg != "koopa")
665 em.noAnimate = true; //static move
666 return em;
f8b43ef7
BA
667 }
668
24872b22 669 getMushroomEffect(move) {
fc12475f
BA
670 if (
671 typeof move.start.x == "string" || //drop move (toadette)
672 ['b', 'r', 'q'].includes(move.vanish[0].p) //slider
673 ) {
cc9fe4f1 674 return null;
37481d1e 675 }
fc12475f
BA
676 let step = [move.end.x - move.start.x, move.end.y - move.start.y];
677 if (Math.abs(step[0]) == 2 && Math.abs(step[1]) == 0)
678 // Pawn initial 2-squares move: normalize step
679 step[0] /= 2;
24872b22 680 const nextSquare = [move.end.x + step[0], move.end.y + step[1]];
24872b22 681 let nextMove = null;
fc12475f
BA
682 if (
683 this.onBoard(nextSquare[0], nextSquare[1]) &&
684 (
685 this.board[nextSquare[0]][nextSquare[1]] == "" ||
686 this.getColor(nextSquare[0], nextSquare[1]) == 'a'
687 )
688 ) {
bc5d61a7 689 this.playOnBoard(move); //HACK for getBasicMove()
24872b22 690 nextMove = this.getBasicMove([move.end.x, move.end.y], nextSquare);
bc5d61a7 691 this.undoOnBoard(move);
24872b22 692 }
24872b22 693 return nextMove;
37481d1e
BA
694 }
695
3b641716
BA
696 getBombBananaEffect(move, item) {
697 const steps = item == V.BANANA
698 ? [[1, 0], [-1, 0], [0, 1], [0, -1]]
699 : [[1, 1], [1, -1], [-1, 1], [-1, -1]];
700 const nextSquare = this.getRandomSquare([move.end.x, move.end.y], steps);
701 this.playOnBoard(move); //HACK for getBasicMove()
702 const res = this.getBasicMove([move.end.x, move.end.y], nextSquare);
703 this.undoOnBoard(move);
704 return res;
705 }
706
cc9fe4f1
BA
707 displayBonus(move) {
708 let divBonus = document.createElement("div");
709 divBonus.classList.add("bonus-text");
710 divBonus.innerHTML = move.egg;
711 let container = document.getElementById(this.containerId);
712 container.appendChild(divBonus);
713 setTimeout(() => container.removeChild(divBonus), 2000);
3b641716
BA
714 }
715
716 atLeastOneMove() {
717 return true;
718 }
719
720 filterValid(moves) {
721 return moves;
722 }
723
5f08c59b
BA
724 // Kingboo bonus can be animated better:
725 customAnimate(move, segments, cb) {
726 if (!move.kingboo)
727 return 0;
728 super.animateMoving(move.end, move.start, null,
729 segments.reverse().map(s => s.reverse()), cb);
730 return 1;
be3cb9d1 731 }
f8b43ef7
BA
732
733};