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