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