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