Finish Chakart. Debug now
[vchess.git] / client / src / variants / Chakart.js
CommitLineData
b967d5ba 1import { ChessRules, Move, PiPo } from "@/base_rules";
5d75c82c 2import { SuicideRules } from "@/variants/Suicide";
15d69043 3import { randInt } from "@/utils/alea";
6c7cbfed
BA
4
5export class ChakartRules extends ChessRules {
5d75c82c
BA
6 static get PawnSpecs() {
7 return SuicideRules.PawnSpecs;
8 }
9
10 static get HasCastle() {
11 return false;
12 }
13
ad030c7d
BA
14 static get CorrConfirm() {
15 // Because of bonus effects
16 return false;
17 }
90df90bc 18
ad030c7d
BA
19 static get CanAnalyze() {
20 return false;
21 }
6c7cbfed 22
90df90bc 23 hoverHighlight(x, y) {
82820616
BA
24 if (this.subTurn == 1) return false;
25 const L = this.firstMove.length;
26 const fm = this.firstMove[L-1];
27 if (fm.end.effect != 0) return false;
28 const deltaX = Math.abs(fm.end.x - x);
29 const deltaY = Math.abs(fm.end.y - y);
ad030c7d 30 return (
82820616 31 (deltaX == 0 && deltaY == 0) ||
ad030c7d 32 (
82820616
BA
33 this.board[x][y] == V.EMPTY &&
34 (
35 (fm.vanish[0].p == V.ROOK && deltaX == 1 && deltaY == 1) ||
36 (fm.vanish[0].p == V.BISHOP && deltaX + deltaY == 1)
37 )
ad030c7d
BA
38 )
39 );
90df90bc
BA
40 }
41
b9ce3d0f
BA
42 static get IMMOBILIZE_CODE() {
43 return {
44 'p': 's',
45 'r': 'u',
46 'n': 'o',
47 'b': 'c',
48 'q': 't',
49 'k': 'l'
50 };
51 }
52
53 static get IMMOBILIZE_DECODE() {
54 return {
55 's': 'p',
56 'u': 'r',
57 'o': 'n',
58 'c': 'b',
59 't': 'q',
60 'l': 'k'
61 };
62 }
63
64 static get INVISIBLE_QUEEN() {
65 return 'i';
66 }
67
ad030c7d
BA
68 // Fictive color 'a', bomb banana mushroom egg
69 static get BOMB() {
15d69043 70 // Doesn't collide with bishop because color is 'a'
ad030c7d
BA
71 return 'b';
72 }
73 static get BANANA() {
74 return 'n';
75 }
76 static get EGG() {
77 return 'e';
78 }
79 static get MUSHROOM() {
80 return 'm';
81 }
82
83 static get PIECES() {
84 return (
85 ChessRules.PIECES.concat(
86 Object.keys(V.IMMOBILIZE_DECODE)).concat(
87 [V.BANANA, V.BOMB, V.EGG, V.MUSHROOM, V.INVISIBLE_QUEEN])
88 );
89 }
90
b9ce3d0f
BA
91 getPpath(b) {
92 let prefix = "";
93 if (
ad030c7d 94 b[0] == 'a' ||
b9ce3d0f
BA
95 b[1] == V.INVISIBLE_QUEEN ||
96 Object.keys(V.IMMOBILIZE_DECODE).includes(b[1])
97 ) {
98 prefix = "Chakart/";
99 }
100 return prefix + b;
101 }
102
103 static ParseFen(fen) {
104 const fenParts = fen.split(" ");
105 return Object.assign(
106 ChessRules.ParseFen(fen),
b967d5ba 107 { captured: fenParts[5] }
b9ce3d0f
BA
108 );
109 }
110
111 // King can be l or L (immobilized) --> similar to Alice variant
90df90bc
BA
112 static IsGoodPosition(position) {
113 if (position.length == 0) return false;
114 const rows = position.split("/");
115 if (rows.length != V.size.x) return false;
b9ce3d0f 116 let kings = { "k": 0, "K": 0, 'l': 0, 'L': 0 };
90df90bc
BA
117 for (let row of rows) {
118 let sumElts = 0;
119 for (let i = 0; i < row.length; i++) {
b9ce3d0f 120 if (['K','k','L','l'].includes(row[i])) kings[row[i]]++;
90df90bc
BA
121 if (V.PIECES.includes(row[i].toLowerCase())) sumElts++;
122 else {
123 const num = parseInt(row[i]);
124 if (isNaN(num)) return false;
125 sumElts += num;
126 }
127 }
128 if (sumElts != V.size.y) return false;
129 }
b9ce3d0f
BA
130 if (kings['k'] + kings['l'] != 1 || kings['K'] + kings['L'] != 1)
131 return false;
90df90bc
BA
132 return true;
133 }
134
b9ce3d0f 135 static IsGoodFlags(flags) {
5d75c82c
BA
136 // 4 for Peach + Mario w, b
137 return !!flags.match(/^[01]{4,4}$/);
b9ce3d0f
BA
138 }
139
140 setFlags(fenflags) {
b967d5ba 141 // King can send shell? Queen can be invisible?
b9ce3d0f 142 this.powerFlags = {
b967d5ba
BA
143 w: [{ 'k': false, 'q': false }],
144 b: [{ 'k': false, 'q': false }]
b9ce3d0f 145 };
b9ce3d0f 146 for (let c of ["w", "b"]) {
b967d5ba
BA
147 for (let p of ['k', 'q']) {
148 this.powerFlags[c][p] =
15d69043 149 fenflags.charAt((c == "w" ? 0 : 2) + (p == 'k' ? 0 : 1)) == "1";
b967d5ba 150 }
b9ce3d0f
BA
151 }
152 }
153
154 aggregateFlags() {
5d75c82c 155 return this.powerFlags;
b9ce3d0f
BA
156 }
157
158 disaggregateFlags(flags) {
5d75c82c 159 this.powerFlags = flags;
b9ce3d0f
BA
160 }
161
162 getFen() {
163 return super.getFen() + " " + this.getCapturedFen();
164 }
165
166 getFenForRepeat() {
167 return super.getFenForRepeat() + "_" + this.getCapturedFen();
168 }
169
170 getCapturedFen() {
171 let counts = [...Array(10).fill(0)];
172 let i = 0;
5333b500 173 for (let p of [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN, V.PAWN]) {
b9ce3d0f
BA
174 counts[i] = this.captured["w"][p];
175 counts[5 + i] = this.captured["b"][p];
176 i++;
177 }
178 return counts.join("");
179 }
180
15d69043
BA
181 scanKings() {}
182
6c7cbfed 183 setOtherVariables(fen) {
15d69043 184 super.setOtherVariables(fen);
b9ce3d0f
BA
185 const fenParsed = V.ParseFen(fen);
186 // Initialize captured pieces' counts from FEN
187 this.captured = {
188 w: {
189 [V.ROOK]: parseInt(fenParsed.captured[0]),
190 [V.KNIGHT]: parseInt(fenParsed.captured[1]),
191 [V.BISHOP]: parseInt(fenParsed.captured[2]),
192 [V.QUEEN]: parseInt(fenParsed.captured[3]),
193 [V.PAWN]: parseInt(fenParsed.captured[4]),
194 },
195 b: {
196 [V.ROOK]: parseInt(fenParsed.captured[5]),
197 [V.KNIGHT]: parseInt(fenParsed.captured[6]),
198 [V.BISHOP]: parseInt(fenParsed.captured[7]),
199 [V.QUEEN]: parseInt(fenParsed.captured[8]),
200 [V.PAWN]: parseInt(fenParsed.captured[9]),
201 }
202 };
b967d5ba 203 this.firstMove = [];
6c7cbfed
BA
204 this.subTurn = 1;
205 }
206
b9ce3d0f 207 getFlagsFen() {
5d75c82c 208 let fen = "";
b9ce3d0f
BA
209 // Add power flags
210 for (let c of ["w", "b"])
b967d5ba 211 for (let p of ['k', 'q']) fen += (this.powerFlags[c][p] ? "1" : "0");
b9ce3d0f
BA
212 return fen;
213 }
214
b967d5ba
BA
215 static get RESERVE_PIECES() {
216 return [V.PAWN, V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN];
217 }
218
219 getReserveMoves([x, y]) {
220 const color = this.turn;
221 const p = V.RESERVE_PIECES[y];
222 if (this.reserve[color][p] == 0) return [];
223 let moves = [];
224 const start = (color == 'w' && p == V.PAWN ? 1 : 0);
225 const end = (color == 'b' && p == V.PAWN ? 7 : 8);
226 for (let i = start; i < end; i++) {
227 for (let j = 0; j < V.size.y; j++) {
228 if (this.board[i][j] == V.EMPTY) {
229 let mv = new Move({
230 appear: [
231 new PiPo({
232 x: i,
233 y: j,
234 c: color,
235 p: p
236 })
237 ],
238 vanish: [],
239 start: { x: x, y: y }, //a bit artificial...
240 end: { x: i, y: j }
241 });
242 moves.push(mv);
243 }
244 }
245 }
246 return moves;
247 }
248
249 getPotentialMovesFrom([x, y]) {
250 if (this.subTurn == 1) return super.getPotentialMovesFrom([x, y]);
6c7cbfed 251 if (this.subTurn == 2) {
b967d5ba
BA
252 let moves = [];
253 const L = this.firstMove.length;
254 const fm = this.firstMove[L-1];
255 switch (fm.end.effect) {
256 // case 0: a click is required (banana or bomb)
257 case 1:
258 // Exchange position with any piece
259 for (let i=0; i<8; i++) {
260 for (let j=0; j<8; j++) {
261 const colIJ = this.getColor(i, j);
262 if (
263 i != x &&
264 j != y &&
265 this.board[i][j] != V.EMPTY &&
266 colIJ != 'a'
267 ) {
268 const movedUnit = new PiPo({
269 x: x,
270 y: y,
271 c: colIJ,
272 p: this.getPiece(i, j)
273 });
274 let mMove = this.getBasicMove([x, y], [i, j]);
275 mMove.appear.push(movedUnit);
276 moves.push(mMove);
277 }
278 }
279 }
280 break;
281 case 2:
282 // Resurrect a captured piece
283 if (x >= V.size.x) moves = this.getReserveMoves([x, y]);
284 break;
285 case 3:
286 // Play again with the same piece
287 if (fm.end.x == x && fm.end.y == y)
288 moves = super.getPotentialMovesFrom([x, y]);
289 break;
290 }
291 return moves;
6c7cbfed
BA
292 }
293 }
294
15d69043
BA
295 // Helper for getBasicMove()
296 getRandomSquare([x, y], steps) {
297 const validSteps = steps.filter(s => {
298 const [i, j] = [x + s[0], y + s[1]];
299 return (
300 V.OnBoard(i, j) &&
301 (this.board[i][j] == V.EMPTY || this.getColor(i, j) == 'a')
302 );
303 });
304 if (validSteps.length == 0)
305 // Can happen after mushroom jump
306 return [x, y];
307 const step = validSteps[randInt(validSteps.length)];
308 return [x + step[0], y + step[1]];
309 }
310
b967d5ba 311 getBasicMove([x1, y1], [x2, y2], tr) {
5d75c82c
BA
312 // Apply mushroom, bomb or banana effect (hidden to the player).
313 // Determine egg effect, too, and apply its first part if possible.
15d69043
BA
314 let move = super.getBasicMove([x1, y1], [x2, y2], tr);
315 const color1 = this.getColor(x1, y1);
316 const color2 = this.getColor(x2, y2);
317 const piece1 = this.getPiece(x1, y1);
318 const piece2 = this.getPiece(x2, y2);
319 const oppCol = V.GetOppCol(color1);
320 if (
321 [V.PAWN, V.KNIGHT].includes(piece1) &&
322 (color2 != 'a' || !([V.BANANA, V.BOMB].includes(piece2)))
323 ) {
324 switch (piece1) {
325 case V.PAWN: {
326 const twoSquaresMove = (Math.abs(x2 - x1) == 2);
327 const mushroomX = x1 + (twoSquaresMove ? (x2 - x1) / 2 : 0);
328 move.appear.push(
329 new PiPo({
330 x: mushroomX,
331 y: y1,
332 c: 'a',
333 p: V.MUSHROOM
334 })
335 );
336 if (this.getColor(mushroomX, y1) == 'a') {
337 move.vanish.push(
338 new PiPo({
339 x: mushroomX,
340 y: y1,
341 c: 'a',
342 p: this.getPiece(mushroomX, y1)
343 })
344 );
345 }
346 break;
347 }
348 case V.KNIGHT: {
349 const deltaX = Math.abs(x2 - x1);
350 const deltaY = Math.abs(y2 - y1);
351 const eggSquare = [
352 x1 + (deltaX == 2 ? (x2 - x1) / 2 : 0),
353 y1 + (deltaY == 2 ? (y2 - y1) / 2 : 0)
354 ];
355 if (
356 this.board[eggSquare[0]][eggSquare[1]] == V.EMPTY ||
357 this.getColor(eggSquare[0], eggSquare[1]) == 'a'
358 ) {
359 move.appear.push(
360 new PiPo({
361 x: eggSquare[0],
362 y: eggSquare[1],
363 c: 'a',
364 p: V.EGG
365 })
366 );
367 if (this.getColor(eggSquare[0], eggSquare[1]) == 'a') {
368 move.vanish.push(
369 new PiPo({
370 x: eggSquare[0],
371 y: eggSquare[1],
372 c: 'a',
373 p: this.getPiece(eggSquare[0], eggSquare[1])
374 })
375 );
376 }
377 }
378 break;
379 }
380 }
381 }
382 const applyBeffect = (steps) => {
383 const [x, y] = [move.appear[0].x, move.appear[0].y];
384 const moveTo = this.getRandomSquare([x, y], steps);
385 move.appear[0].x = moveTo[0];
386 move.appear[0].y = moveTo[1];
387 };
388 // For (wa)luigi effect:
389 const changePieceColor = (color) => {
390 let pieces = [];
391 for (let i=0; i<8; i++) {
392 for (let j=0; j<8; j++) {
393 if (
394 this.board[i][j] != V.EMPTY &&
395 this.getColor(i, j) == color
396 ) {
397 const piece = this.getPiece(i, j);
398 if (piece != V.KING)
399 pieces.push({ x: i, y: j, p: piece });
400 }
401 }
402 }
403 const cp = pieces[randInt(pieces.length)];
404 move.vanish.push(
405 new PiPo({
406 x: cp.x,
407 y: cp.y,
408 c: color,
409 p: cp.p
410 })
411 );
412 move.appear.push(
413 new PiPo({
414 x: cp.x,
415 y: cp.y,
416 c: V.GetOppCol(color),
417 p: cp.p
418 })
419 );
420 };
421 const applyEggEffect = () => {
422 if (this.subTurn == 1) {
423 // 1) Determine the effect (some may be impossible)
424 let effects = ["kingboo", "koopa", "chomp", "bowser"];
425 if (this.captured[color1].some(c => c >= 1))
426 effects.push("toadette");
427 V.PlayOnBoard(this.board, move);
428 const canPlayAgain = this.getPotentialMovesFrom([x2, y2]).length > 0;
429 V.UndoOnBoard(this.board, move);
430 if (canPlayAgain) effects.push("daisy");
431 if (
432 board.some(b =>
433 b.some(cell => cell[0] == oppCol && cell[1] != V.KING))
434 ) {
435 effects.push("luigi");
436 }
437 if (
438 board.some(b =>
439 b.some(cell => cell[0] == color1 && cell[1] != V.KING))
440 ) {
441 effects.push("waluigi");
442 }
443 const effect = effects[randInd(effects.length)];
444 // 2) Apply it if possible, or set move.end.effect
445 if (["kingboo", "toadette", "daisy"].includes(effect))
446 move.end.effect = effect;
447 else {
448 switch (effect) {
449 case "koopa":
450 move.appear[0].x = x1;
451 move.appear[0].y = y1;
452 break;
453 case "chomp":
454 move.appear.unshift();
455 break;
456 case "bowser":
457 move.appear[0].p = V.IMMOBILIZE_CODE[piece1];
458 break;
459 case "luigi":
460 changePieceColor(oppCol);
461 break;
462 case "waluigi":
463 changePieceColor(color1);
464 break;
465 }
466 }
467 }
468 };
469 const applyMushroomEffect = () => {
470 if ([V.PAWN, V.KING, V.KNIGHT].includes(piece1)) {
471 // Just make another similar step, if possible (non-capturing)
472 const [i, j] = [
473 move.appear[0].x + 2 * (x2 - x1),
474 move.appear[0].y + 2 * (y2 - y1)
475 ];
476 if (
477 V.OnBoard(i, j) &&
478 (this.board[i][j] == V.EMPTY || this.getColor(i, j) == 'a')
479 ) {
480 move.appear[0].x = i;
481 move.appear[0].y = j;
482 if (this.board[i][j] != V.EMPTY) {
483 const object = this.getPiece(i, j);
484 move.vanish.push(
485 new PiPo({
486 x: i,
487 y: j,
488 c: 'a',
489 p: object
490 })
491 );
492 switch (object) {
493 case V.BANANA:
494 applyBeffect(V.steps[V.ROOK]);
495 break;
496 case V.BOMB:
497 applyBeffect(V.steps[V.BISHOP]);
498 break;
499 case V.EGG:
500 applyEggEffect();
501 break;
502 case V.MUSHROOM:
503 applyMushroomEffect();
504 break;
505 }
506 }
507 }
508 }
509 else {
510 // Queen, bishop or rook:
511 const step = [
512 (x2 - x1) / Math.abs(x2 - x1) || 0,
513 (y2 - y1) / Math.abs(y2 - y1) || 0
514 ];
515 const next = [move.appear[0].x + step[0], move.appear[0].y + step[1]];
516 if (
517 V.OnBoard(next[0], next[1]) &&
518 this.board[next[0]][next[1]] != V.EMPTY &&
519 this.getColor(next[0], next[1]) != 'a'
520 ) {
521 const afterNext = [next[0] + step[0], next[1] + step[1]];
522 if (
523 V.OnBoard(afterNext[0], afterNext[1]) &&
524 (
525 this.board[afterNext[0]][afterNext[1]] == V.EMPTY ||
526 this.getColor(afterNext[0], afterNext[1]) == 'a'
527 )
528 ) {
529 move.appear[0].x = afterNext[0];
530 move.appear[0].y = afterNext[1];
531 if (this.board[afterNext[0]][afterNext[1]] != V.EMPTY) {
532 const object = this.getPiece(afterNext[0], afterNext[1]);
533 move.vanish.push(
534 new PiPo({
535 x: afterNext[0],
536 y: afterNext[0],
537 c: 'a',
538 p: object
539 })
540 );
541 switch (object) {
542 case V.BANANA:
543 applyBeffect(V.steps[V.ROOK]);
544 break;
545 case V.BOMB:
546 applyBeffect(V.steps[V.BISHOP]);
547 break;
548 case V.EGG:
549 applyEggEffect();
550 break;
551 case V.MUSHROOM:
552 applyMushroomEffect();
553 break;
554 }
555 }
556 }
557 }
558 }
559 };
560 if (color2 == 'a') {
561 switch (piece2) {
562 case V.BANANA:
563 applyBeffect(V.steps[V.ROOK]);
564 break;
565 case V.BOMB:
566 applyBeffect(V.steps[V.BISHOP]);
567 break;
568 case V.MUSHROOM:
569 applyMushroomEffect();
570 break;
571 case V.EGG:
572 if (this.subTurn == 1) {
573 // No egg effect at subTurn 2
574 if ([V.ROOK, V.BISHOP].includes(piece1)) {
575 // Drop a bomb or banana at random, because even if bonus is
576 // "play again" nothing can be done after next move.
577 const steps = V.steps[piece1 == V.ROOK ? V.BISHOP : V.ROOK];
578 const object = (piece1 == V.ROOK ? V.BANANA : V.BOMB);
579 const dropOn = this.getRandomSquare([x, y], steps);
580 move.appear.push(
581 new PiPo({
582 x: dropOn[0],
583 y: dropOn[1],
584 c: 'a',
585 p: object
586 })
587 );
588 }
589 applyEggEffect();
590 }
591 break;
592 }
593 }
594 else if (
595 this.subTurn == 1 &&
596 [V.ROOK, V.BISHOP].includes(piece1)
597 ) {
598 move.end.effect = 0;
599 }
5d75c82c 600 return move;
5d75c82c
BA
601 }
602
b967d5ba
BA
603 getEnpassantCaptures([x, y], shiftX) {
604 const Lep = this.epSquares.length;
605 const epSquare = this.epSquares[Lep - 1]; //always at least one element
606 let enpassantMove = null;
607 if (
608 !!epSquare &&
609 epSquare.x == x + shiftX &&
610 Math.abs(epSquare.y - y) == 1
611 ) {
612 // Not using this.getBasicMove() because the mushroom has no effect
613 enpassantMove = super.getBasicMove([x, y], [epSquare.x, epSquare.y]);
614 enpassantMove.vanish.push({
615 x: x,
616 y: epSquare.y,
617 p: V.PAWN,
618 c: this.getColor(x, epSquare.y)
619 });
620 }
621 return !!enpassantMove ? [enpassantMove] : [];
622 }
623
15d69043
BA
624 getPotentialPawnMoves([x, y]) {
625 const color = this.turn;
626 const oppCol = V.GetOppCol(color);
627 const [sizeX, sizeY] = [V.size.x, V.size.y];
628 const shiftX = V.PawnSpecs.directions[color];
629 const firstRank = (color == "w" ? sizeX - 1 : 0);
630 let moves = [];
631 if (
632 this.board[x + shiftX][y] == V.EMPTY ||
633 this.getColor(x + shiftX, y) == 'a'
634 ) {
635 this.addPawnMoves([x, y], [x + shiftX, y], moves);
636 if (
637 [firstRank, firstRank + shiftX].includes(x) &&
638 this.board[x + 2 * shiftX][y] == V.EMPTY
639 ) {
640 moves.push(this.getBasicMove([x, y], [x + 2 * shiftX, y]));
641 }
642 }
643 for (let shiftY of [-1, 1]) {
644 if (
645 y + shiftY >= 0 &&
646 y + shiftY < sizeY &&
647 this.board[x + shiftX][y + shiftY] != V.EMPTY &&
648 this.getColor(x + shiftX, y + shiftY) == oppCol
649 ) {
650 this.addPawnMoves([x, y], [x + shiftX, y + shiftY], moves);
651 }
652 }
653 Array.prototype.push.apply(
654 moves,
655 this.getEnpassantCaptures([x, y], shiftX)
656 );
657 return moves;
658 }
659
b967d5ba
BA
660 getPotentialQueenMoves(sq) {
661 const normalMoves = super.getPotentialQueenMoves(sq);
662 // If flag allows it, add 'invisible movements'
663 let invisibleMoves = [];
664 if (this.powerFlags[this.turn][V.QUEEN]) {
665 normalMoves.forEach(m => {
666 if (m.vanish.length == 1) {
667 let im = JSON.parse(JSON.stringify(m));
668 m.appear[0].p = V.INVISIBLE_QUEEN;
669 invisibleMoves.push(im);
670 }
671 });
672 }
673 return normalMoves.concat(invisibleMoves);
674 }
675
5d75c82c
BA
676 getPotentialKingMoves([x, y]) {
677 let moves = super.getPotentialKingMoves([x, y]);
b967d5ba
BA
678 const color = this.turn;
679 // If flag allows it, add 'remote shell captures'
680 if (this.powerFlags[this.turn][V.KING]) {
681 V.steps[V.ROOK].concat(V.steps[V.BISHOP]).forEach(step => {
682 let [i, j] = [x + 2 * step[0], y + 2 * step[1]];
683 while (
684 V.OnBoard(i, j) &&
685 (
686 this.board[i][j] == V.EMPTY ||
687 (
688 this.getColor(i, j) == 'a' &&
689 [V.EGG, V.MUSHROOM].includes(this.getPiece(i, j))
690 )
691 )
692 ) {
693 i += step[0];
694 j += step[1];
695 }
696 if (V.OnBoard(i, j) && this.getColor(i, j) != color)
697 // May just destroy a bomb or banana:
698 moves.push(this.getBasicMove([x, y], [i, j]));
699 });
700 }
5d75c82c
BA
701 return moves;
702 }
703
704 getSlideNJumpMoves([x, y], steps, oneStep) {
705 let moves = [];
706 outerLoop: for (let step of steps) {
707 let i = x + step[0];
708 let j = y + step[1];
709 while (
710 V.OnBoard(i, j) &&
711 (
712 this.board[i][j] == V.EMPTY ||
713 (
714 this.getColor(i, j) == 'a' &&
715 [V.EGG, V.MUSHROOM].includes(this.getPiece(i, j))
716 )
717 )
718 ) {
719 moves.push(this.getBasicMove([x, y], [i, j]));
720 if (oneStep) continue outerLoop;
721 i += step[0];
722 j += step[1];
723 }
724 if (V.OnBoard(i, j) && this.canTake([x, y], [i, j]))
725 moves.push(this.getBasicMove([x, y], [i, j]));
726 }
727 return moves;
90df90bc
BA
728 }
729
ad030c7d 730 getAllPotentialMoves() {
5d75c82c 731 if (this.subTurn == 1) return super.getAllPotentialMoves();
b967d5ba
BA
732 let moves = [];
733 const L = this.firstMove.length;
734 const fm = this.firstMove[L-1];
15d69043
BA
735 switch (fm.end.effect) {
736 case 0:
737 moves.push({
738 start: { x: -1, y: -1 },
739 end: { x: -1, y: -1 },
740 appear: [],
741 vanish: []
742 });
743 for (
744 let step of
745 (fm.vanish[0].p == V.ROOK ? V.steps[V.BISHOP] : V.steps[V.ROOK])
746 ) {
747 const [i, j] = [fm.end.x + step[0], fm.end.y + step[1]];
748 if (
749 V.OnBoard(i, j) &&
750 (this.board[i][j] == V.EMPTY || this.getColor(i, j) == 'a')
751 ) {
752 let m = new Move({
753 start: { x: -1, y: -1 },
754 end: { x: i, y: j },
755 appear: [
756 new PiPo({
757 x: i,
758 y: j,
759 c: 'a',
760 p: (fm.vanish[0].p == V.ROOK ? V.BANANA : V.BOMB)
761 })
762 ],
763 vanish: []
764 });
765 if (this.board[i][j] != V.EMPTY) {
766 m.vanish.push(
767 new PiPo({ x: i, y: j, c: 'a', p: this.getPiece(i, j) }));
768 }
769 moves.push(m);
770 }
771 }
772 break;
773 case 1: {
774 const [x, y] = [fm.end.x, fm.end.y];
775 for (let i=0; i<8; i++) {
776 for (let j=0; j<8; j++) {
777 const colIJ = this.getColor(i, j);
778 if (
779 i != x &&
780 j != y &&
781 this.board[i][j] != V.EMPTY &&
782 colIJ != 'a'
783 ) {
784 const movedUnit = new PiPo({
785 x: x,
786 y: y,
787 c: colIJ,
788 p: this.getPiece(i, j)
789 });
790 let mMove = this.getBasicMove([x, y], [i, j]);
791 mMove.appear.push(movedUnit);
792 moves.push(mMove);
793 }
794 }
795 }
796 break;
797 }
798 case 2: {
799 const x = V.size.x + (this.turn == 'w' ? 0 : 1);
800 for (let y = 0; y < 8; y++)
801 Array.prototype.push.apply(moves, this.getReserveMoves([x, y]));
802 break;
803 }
804 case 3:
805 moves = super.getPotentialMovesFrom([fm.end.x, fm.end.y]);
806 break;
807 }
808 return moves;
6c7cbfed
BA
809 }
810
1c15969e 811 doClick(square) {
107dc1bd 812 if (isNaN(square[0])) return null;
82820616
BA
813 if (this.subTurn == 1) return null;
814 const L = this.firstMove.length;
815 const fm = this.firstMove[L-1];
816 if (fm.end.effect != 0) return null;
817 const [x, y] = [square[0], square[1]];
818 const deltaX = Math.abs(fm.end.x - x);
819 const deltaY = Math.abs(fm.end.y - y);
820 if (deltaX == 0 && deltaY == 0) {
821 // Empty move:
822 return {
823 start: { x: -1, y: -1 },
824 end: { x: -1, y: -1 },
825 appear: [],
826 vanish: []
827 };
828 }
829 if (
15d69043 830 (this.board[x][y] == V.EMPTY || this.getColor(x, y) == 'a') &&
82820616
BA
831 (
832 (fm.vanish[0].p == V.ROOK && deltaX == 1 && deltaY == 1) ||
833 (fm.vanish[0].p == V.BISHOP && deltaX + deltaY == 1)
834 )
835 ) {
15d69043 836 let m = new Move({
82820616
BA
837 start: { x: -1, y: -1 },
838 end: { x: x, y: y },
839 appear: [
840 new PiPo({
841 x: x,
842 y: y,
843 c: 'a',
844 p: (fm.vanish[0].p == V.ROOK ? V.BANANA : V.BOMB)
845 })
846 ],
847 vanish: []
848 });
15d69043
BA
849 if (this.board[x][y] != V.EMPTY) {
850 m.vanish.push(
851 new PiPo({ x: x, y: y, c: 'a', p: this.getPiece(x, y) }));
852 }
853 return m;
1c15969e
BA
854 }
855 return null;
856 }
857
5d75c82c 858 play(move) {
b967d5ba
BA
859 move.flags = JSON.stringify(this.aggregateFlags());
860 this.epSquares.push(this.getEpSquare(move));
861 V.PlayOnBoard(this.board, move);
15d69043 862 move.turn = [this.turn, this.subTurn];
b967d5ba
BA
863 if (move.end.effect !== undefined) {
864 this.firstMove.push(move);
865 this.subTurn = 2;
b967d5ba
BA
866 }
867 else {
868 this.turn = V.GetOppCol(this.turn);
15d69043 869 this.movesCount++;
b967d5ba 870 this.subTurn = 1;
b967d5ba 871 }
15d69043 872 this.postPlay(move);
1c15969e 873 }
5d75c82c 874
b9ce3d0f 875 postPlay(move) {
15d69043
BA
876 if (move.end.effect == 2) this.reserve = this.captured;
877 else this.reserve = null;
5d75c82c 878 if (move.vanish.length == 2 && move.vanish[1].c != 'a')
b9ce3d0f
BA
879 // Capture: update this.captured
880 this.captured[move.vanish[1].c][move.vanish[1].p]++;
5d75c82c
BA
881 else if (move.vanish.length == 0) {
882 // A piece is back on board
15d69043 883 this.captured[move.appear[0].c][move.appear[0].p]++;
5d75c82c
BA
884 this.reserve = null;
885 }
15d69043
BA
886 if (this.subTurn == 1) {
887 // Update flags:
888 if (
889 move.vanish[0].p == V.KING &&
890 (
891 Math.abs(move.end.x - move.start.x) >= 2 ||
892 Math.abs(move.end.y - move.start.y) >= 2
893 )
894 ) {
895 this.powerFlags[move.vanish[0].c][V.KING] = false;
896 }
897 else if (
898 move.vanish[0].p == V.QUEEN &&
899 this.getPiece(move.end.x, move.end.y) == V.INVISIBLE_QUEEN
900 ) {
901 this.powerFlags[move.vanish[0].c][V.QUEEN] = false;
902 }
903 const color = move.vanish[0].c;
904 const oppCol = V.GetOppCol(color);
905 if (!(Object.keys(V.IMMOBILIZE_DECODE).includes(move.appear[0].p))) {
906 // Look for an immobilized piece of my color: it can now move
907 // Also make opponent invisible queen visible again, if any
908 for (let i=0; i<8; i++) {
909 for (let j=0; j<8; j++) {
910 if (this.board[i][j] != V.EMPTY) {
911 const colIJ = this.getColor(i, j);
912 const piece = this.getPiece(i, j);
913 if (
914 colIJ == color &&
915 Object.keys(V.IMMOBILIZE_DECODE).includes(piece)
916 ) {
917 this.board[i][j] = color + V.IMMOBILIZE_DECODE[piece];
918 move.wasImmobilized = [i, j];
919 }
920 else if (
921 colIJ == oppCol &&
922 piece == V.INVISIBLE_QUEEN
923 ) {
924 this.board[i][j] = oppCol + V.QUEEN;
925 move.wasInvisible = [i, j];
926 }
927 }
928 }
929 }
930 }
931 }
5d75c82c
BA
932 }
933
934 undo(move) {
15d69043
BA
935 this.epSquares.pop();
936 this.disaggregateFlags(JSON.parse(move.flags));
937 V.UndoOnBoard(this.board, move);
938 if (move.end.effect !== undefined) this.firstMove.pop();
939 else this.movesCount--;
940 if (this.subTurn == 1) this.movesCount--;
941 this.turn = move.turn[0];
942 this.subTurn = move.turn[1];
943 this.postUndo(move);
b9ce3d0f
BA
944 }
945
946 postUndo(move) {
15d69043
BA
947 if (!!move.wasImmobilized) {
948 const [i, j] = move.wasImmobilized;
949 this.board[i][j] =
950 this.getColor(i, j) + V.IMMOBILIZE_CODE[this.getPiece(i, j)];
951 }
952 if (!!move.wasInvisible) {
953 const [i, j] = move.wasInvisible;
954 this.board[i][j] =
955 this.getColor(i, j) + V.INVISIBLE_QUEEN;
956 }
5d75c82c 957 if (move.vanish.length == 2 && move.vanish[1].c != 'a')
b9ce3d0f 958 this.captured[move.vanish[1].c][move.vanish[1].p]--;
15d69043
BA
959 else if (move.vanish.length == 0) {
960 // A piece is back on board
961 this.captured[move.vanish[1].c][move.vanish[1].p]++;
962 this.reserve = null;
963 }
964 if (move.vanish.length == 0) this.reserve = this.captured;
965 else this.reserve = null;
b9ce3d0f 966 }
1c15969e 967
5d75c82c
BA
968 getCheckSquares() {
969 return [];
970 }
971
6c7cbfed 972 getCurrentScore() {
5d75c82c
BA
973 // Find kings (not tracked in this variant)
974 let kingThere = { w: false, b: false };
975 for (let i=0; i<8; i++) {
976 for (let j=0; j<8; j++) {
977 if (this.board[i][j] != V.EMPTY && this.getPiece(i, j) == V.KING)
978 kingThere[this.getColor(i, j)] = true;
979 }
980 }
981 if (!kingThere['w']) return "0-1";
982 if (!kingThere['b']) return "1-0";
983 return "*";
6c7cbfed
BA
984 }
985
b9ce3d0f
BA
986 static GenRandInitFen(randomness) {
987 return (
5d75c82c 988 SuicideRules.GenRandInitFen(randomness).slice(0, -1) +
b9ce3d0f
BA
989 // Add Peach + Mario flags, re-add en-passant + capture counts
990 "0000 - 0000000000"
991 );
992 }
993
5d75c82c
BA
994 filterValid(moves) {
995 return moves;
996 }
997
1c15969e 998 getComputerMove() {
b967d5ba 999 // Random mover:
5d75c82c 1000 const moves = this.getAllValidMoves();
15d69043 1001 let move1 = moves[randInt(moves.length)];
b967d5ba
BA
1002 this.play(move1);
1003 let move2 = undefined;
1004 if (this.subTurn == 2) {
1005 const moves2 = this.getAllValidMoves();
1006 move2 = moves2[randInt(moves2.length)];
1007 }
1008 this.undo(move1);
1009 if (!move2) return move1;
1010 return [move1, move2];
1c15969e 1011 }
b9ce3d0f
BA
1012
1013 getNotation(move) {
b967d5ba
BA
1014 // TODO: invisibility used => move notation Q??
1015 // Also, bonus should be clearly indicated + bomb/bananas locations
15d69043
BA
1016 // TODO: effect name + code to help move notation
1017 if (move.vanish.length == 0) {
1018 const piece =
1019 move.appear[0].p != V.PAWN ? move.appear[0].p.toUpperCase() : "";
1020 return piece + "@" + V.CoordsToSquare(move.end);
1021 }
b967d5ba 1022 return super.getNotation(move);
b9ce3d0f 1023 }
6c7cbfed 1024};