+ getColor(i, j) {
+ if (i >= V.size.x) return i == V.size.x ? "w" : "b";
+ return this.board[i][j].charAt(0);
+ }
+
+ getPiece(i, j) {
+ if (i >= V.size.x) return V.RESERVE_PIECES[j];
+ return this.board[i][j].charAt(1);
+ }
+
+ getReservePpath(index, color) {
+ return color + V.RESERVE_PIECES[index];
+ }
+
+ static get RESERVE_PIECES() {
+ return [V.PAWN, V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN, V.KING];
+ }
+
+ getReserveMoves([x, y]) {
+ const color = this.turn;
+ const p = V.RESERVE_PIECES[y];
+ if (this.reserve[color][p] == 0) return [];
+ let moves = [];
+ const start = (color == 'w' && p == V.PAWN ? 1 : 0);
+ const end = (color == 'b' && p == V.PAWN ? 7 : 8);
+ for (let i = start; i < end; i++) {
+ for (let j = 0; j < V.size.y; j++) {
+ if (this.board[i][j] == V.EMPTY || this.getColor(i, j) == 'a') {
+ let m = this.getBasicMove({ p: p, x: i, y: j});
+ m.start = { x: x, y: y };
+ moves.push(m);
+ }
+ }
+ }
+ return moves;
+ }
+
+ getPotentialMovesFrom([x, y]) {
+ let moves = [];
+ if (this.subTurn == 1) {
+ moves = super.getPotentialMovesFrom([x, y]);
+ const finalPieces = V.PawnSpecs.promotions;
+ const color = this.turn;
+ const lastRank = (color == "w" ? 0 : 7);
+ let pMoves = [];
+ moves.forEach(m => {
+ if (
+ m.appear.length > 0 &&
+ ['p', 's'].includes(m.appear[0].p) &&
+ m.appear[0].x == lastRank
+ ) {
+ for (let i = 1; i < finalPieces.length; i++) {
+ const piece = finalPieces[i];
+ let otherM = JSON.parse(JSON.stringify(m));
+ otherM.appear[0].p =
+ m.appear[0].p == V.PAWN
+ ? finalPieces[i]
+ : V.IMMOBILIZE_CODE[finalPieces[i]];
+ pMoves.push(otherM);
+ }
+ // Finally alter m itself:
+ m.appear[0].p =
+ m.appear[0].p == V.PAWN
+ ? finalPieces[0]
+ : V.IMMOBILIZE_CODE[finalPieces[0]];
+ }
+ });
+ Array.prototype.push.apply(moves, pMoves);
+ }
+ else {
+ // Subturn == 2
+ const L = this.firstMove.length;
+ const fm = this.firstMove[L-1];
+ switch (fm.end.effect) {
+ // case 0: a click is required (banana or bomb)
+ case "kingboo":
+ // Exchange position with any piece,
+ // except pawns if arriving on last rank.
+ const lastRank = { 'w': 0, 'b': 7 };
+ const color = this.turn;
+ const allowLastRank = (this.getPiece(x, y) != V.PAWN);
+ for (let i=0; i<8; i++) {
+ for (let j=0; j<8; j++) {
+ const colIJ = this.getColor(i, j);
+ if (
+ (i != x || j != y) &&
+ this.board[i][j] != V.EMPTY &&
+ colIJ != 'a'
+ ) {
+ const pieceIJ = this.getPiece(i, j);
+ if (
+ (pieceIJ != V.PAWN || x != lastRank[colIJ]) &&
+ (allowLastRank || i != lastRank[color])
+ ) {
+ const movedUnit = new PiPo({
+ x: x,
+ y: y,
+ c: colIJ,
+ p: this.getPiece(i, j)
+ });
+ let mMove = this.getBasicMove({ x: x, y: y }, [i, j]);
+ mMove.appear.push(movedUnit);
+ moves.push(mMove);
+ }
+ }
+ }
+ }
+ break;
+ case "toadette":
+ // Resurrect a captured piece
+ if (x >= V.size.x) moves = this.getReserveMoves([x, y]);
+ break;
+ case "daisy":
+ // Play again with the same piece
+ if (fm.appear[0].x == x && fm.appear[0].y == y)
+ moves = super.getPotentialMovesFrom([x, y]);
+ break;
+ }
+ }
+ return moves;
+ }
+
+ // Helper for getBasicMove()
+ getRandomSquare([x, y], steps) {
+ const validSteps = steps.filter(s => {
+ const [i, j] = [x + s[0], y + s[1]];
+ return (
+ V.OnBoard(i, j) &&
+ (this.board[i][j] == V.EMPTY || this.getColor(i, j) == 'a')
+ );
+ });
+ if (validSteps.length == 0)
+ // Can happen after mushroom jump
+ return [x, y];
+ const step = validSteps[randInt(validSteps.length)];
+ return [x + step[0], y + step[1]];
+ }
+
+ canMove([x, y], piece) {
+ const color = this.getColor(x, y);
+ const oppCol = V.GetOppCol(color);
+ piece = piece || this.getPiece(x, y);
+ if (piece == V.PAWN) {
+ const forward = (color == 'w' ? -1 : 1);
+ return (
+ this.board[x + forward][y] != oppCol ||
+ (
+ V.OnBoard(x + forward, y + 1) &&
+ this.board[x + forward][y + 1] != V.EMPTY &&
+ this.getColor[x + forward, y + 1] == oppCol
+ ) ||
+ (
+ V.OnBoard(x + forward, y - 1) &&
+ this.board[x + forward][y - 1] != V.EMPTY &&
+ this.getColor[x + forward, y - 1] == oppCol
+ )
+ );
+ }
+ // Checking one step is enough:
+ const steps =
+ [V.KING, V.QUEEN].includes(piece)
+ ? V.steps[V.ROOK].concat(V.steps[V.BISHOP])
+ : V.steps[piece];
+ for (let step of steps) {
+ const [i, j] = [x + step[0], y + step[1]];
+ if (
+ V.OnBoard(i, j) &&
+ (this.board[i][j] == V.EMPTY || this.getColor(i, j) != color)
+ ) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // Apply mushroom, bomb or banana effect (hidden to the player).
+ // Determine egg effect, too, and apply its first part if possible.
+ getBasicMove_aux(psq1, sq2, tr, initMove) {
+ const [x1, y1] = [psq1.x, psq1.y];
+ const color1 = this.turn;
+ const piece1 = psq1.p || this.getPiece(x1, y1);
+ const oppCol = V.GetOppCol(color1);
+ if (!sq2) {
+ let move = {
+ appear: [],
+ vanish: []
+ };
+ // banana or bomb defines next square, or the move ends there
+ move.appear = [
+ new PiPo({
+ x: x1,
+ y: y1,
+ c: color1,
+ p: piece1
+ })
+ ];
+ if (this.board[x1][y1] != V.EMPTY) {
+ const initP1 = this.getPiece(x1, y1);
+ move.vanish = [
+ new PiPo({
+ x: x1,
+ y: y1,
+ c: this.getColor(x1, y1),
+ p: initP1
+ })
+ ];
+ if ([V.BANANA, V.BOMB].includes(initP1)) {
+ const steps = V.steps[initP1 == V.BANANA ? V.ROOK : V.BISHOP];
+ move.next = this.getRandomSquare([x1, y1], steps);
+ }
+ }
+ move.end = { x: x1, y: y1 };
+ return move;
+ }
+ const [x2, y2] = [sq2[0], sq2[1]];
+ // The move starts normally, on board:
+ let move = super.getBasicMove([x1, y1], [x2, y2], tr);
+ const L = this.firstMove.length;
+ if (
+ [V.PAWN, V.KNIGHT].includes(piece1) &&
+ !!initMove &&
+ (this.subTurn == 1 || this.firstMove[L-1].end.effect == "daisy")
+ ) {
+ switch (piece1) {
+ case V.PAWN: {
+ const twoSquaresMove = (Math.abs(x2 - x1) == 2);
+ const mushroomX = x1 + (twoSquaresMove ? (x2 - x1) / 2 : 0);
+ move.appear.push(
+ new PiPo({
+ x: mushroomX,
+ y: y1,
+ c: 'a',
+ p: V.MUSHROOM
+ })
+ );
+ if (this.getColor(mushroomX, y1) == 'a') {
+ move.vanish.push(
+ new PiPo({
+ x: mushroomX,
+ y: y1,
+ c: 'a',
+ p: this.getPiece(mushroomX, y1)
+ })
+ );
+ }
+ break;
+ }
+ case V.KNIGHT: {
+ const deltaX = Math.abs(x2 - x1);
+ const deltaY = Math.abs(y2 - y1);
+ let eggSquare = [
+ x1 + (deltaX == 2 ? (x2 - x1) / 2 : 0),
+ y1 + (deltaY == 2 ? (y2 - y1) / 2 : 0)
+ ];
+ if (
+ this.board[eggSquare[0]][eggSquare[1]] != V.EMPTY &&
+ this.getColor(eggSquare[0], eggSquare[1]) != 'a'
+ ) {
+ eggSquare[0] = x1;
+ eggSquare[1] = y1;
+ }
+ move.appear.push(
+ new PiPo({
+ x: eggSquare[0],
+ y: eggSquare[1],
+ c: 'a',
+ p: V.EGG
+ })
+ );
+ if (this.getColor(eggSquare[0], eggSquare[1]) == 'a') {
+ move.vanish.push(
+ new PiPo({
+ x: eggSquare[0],
+ y: eggSquare[1],
+ c: 'a',
+ p: this.getPiece(eggSquare[0], eggSquare[1])
+ })
+ );
+ }
+ break;
+ }
+ }
+ }
+ // For (wa)luigi effect:
+ const changePieceColor = (color) => {
+ let pieces = [];
+ const oppLastRank = (color == 'w' ? 7 : 0);
+ for (let i=0; i<8; i++) {
+ for (let j=0; j<8; j++) {
+ if (
+ (i != move.vanish[0].x || j != move.vanish[0].y) &&
+ this.board[i][j] != V.EMPTY &&
+ this.getColor(i, j) == color
+ ) {
+ const piece = this.getPiece(i, j);
+ if (piece != V.KING && (piece != V.PAWN || i != oppLastRank))
+ pieces.push({ x: i, y: j, p: piece });
+ }
+ }
+ }
+ // Special case of the current piece (still at its initial position)
+ if (color == color1)
+ pieces.push({ x: move.appear[0].x, y: move.appear[0].y, p: piece1 });
+ const cp = pieces[randInt(pieces.length)];
+ if (move.appear[0].x != cp.x || move.appear[0].y != cp.y) {
+ move.vanish.push(
+ new PiPo({
+ x: cp.x,
+ y: cp.y,
+ c: color,
+ p: cp.p
+ })
+ );
+ }
+ else move.appear.shift();
+ move.appear.push(
+ new PiPo({
+ x: cp.x,
+ y: cp.y,
+ c: V.GetOppCol(color),
+ p: cp.p
+ })
+ );
+ };
+ const applyEggEffect = () => {
+ if (this.subTurn == 2)
+ // No egg effects at subTurn 2
+ return;
+ // 1) Determine the effect (some may be impossible)
+ let effects = ["kingboo", "koopa", "chomp", "bowser"];
+ if (Object.values(this.captured[color1]).some(c => c >= 1))
+ effects.push("toadette");
+ const lastRank = { 'w': 0, 'b': 7 };
+ let canPlayAgain = undefined;
+ if (
+ move.appear[0].p == V.PAWN &&
+ move.appear[0].x == lastRank[color1]
+ ) {
+ // Always possible: promote into a queen, rook or king
+ canPlayAgain = true;
+ }
+ else {
+ move.end.effect = "daisy";
+ V.PlayOnBoard(this.board, move);
+ const square = [move.appear[0].x, move.appear[0].y];
+ canPlayAgain = this.canMove(square, piece1);
+ V.UndoOnBoard(this.board, move);
+ delete move.end["effect"];
+ }
+ if (canPlayAgain) effects.push("daisy");
+ if (
+ this.board.some((b,i) =>
+ b.some(cell => {
+ return (
+ cell[0] == oppCol &&
+ cell[1] != V.KING &&
+ (cell[1] != V.PAWN || i != lastRank[color1])
+ );
+ })
+ )
+ ) {
+ effects.push("luigi");
+ }
+ if (
+ (
+ piece1 != V.KING &&
+ (piece1 != V.PAWN || move.appear[0].x != lastRank[oppCol])
+ ) ||
+ this.board.some((b,i) =>
+ b.some(cell => {
+ return (
+ cell[0] == color1 &&
+ cell[1] != V.KING &&
+ (cell[1] != V.PAWN || i != lastRank[oppCol])
+ );
+ })
+ )
+ ) {
+ effects.push("waluigi");
+ }
+ const effect = effects[randInt(effects.length)];
+ move.end.effect = effect;
+ // 2) Apply it if possible
+ if (!(["kingboo", "toadette", "daisy"].includes(effect))) {
+ switch (effect) {
+ case "koopa":
+ move.appear = [];
+ // Maybe egg effect was applied after others,
+ // so just shift vanish array:
+ move.vanish.shift();
+ break;
+ case "chomp":
+ move.appear = [];
+ break;
+ case "bowser":
+ move.appear[0].p = V.IMMOBILIZE_CODE[piece1];
+ break;
+ case "luigi":
+ changePieceColor(oppCol);
+ break;
+ case "waluigi":
+ changePieceColor(color1);
+ break;
+ }
+ }
+ };
+ const applyMushroomEffect = () => {
+ if ([V.PAWN, V.KING, V.KNIGHT].includes(piece1)) {
+ // Just make another similar step, if possible (non-capturing)
+ const [i, j] = [
+ move.appear[0].x + (x2 - x1),
+ move.appear[0].y + (y2 - y1)
+ ];
+ if (
+ V.OnBoard(i, j) &&
+ (this.board[i][j] == V.EMPTY || this.getColor(i, j) == 'a')
+ ) {
+ move.appear[0].x = i;
+ move.appear[0].y = j;
+ if (this.board[i][j] != V.EMPTY) {
+ const object = this.getPiece(i, j);
+ move.vanish.push(
+ new PiPo({
+ x: i,
+ y: j,
+ c: 'a',
+ p: object
+ })
+ );
+ switch (object) {
+ case V.BANANA:
+ case V.BOMB:
+ const steps = V.steps[object == V.BANANA ? V.ROOK : V.BISHOP];
+ move.next = this.getRandomSquare([i, j], steps);
+ break;
+ case V.EGG:
+ applyEggEffect();
+ break;
+ case V.MUSHROOM:
+ applyMushroomEffect();
+ break;
+ }
+ }
+ }
+ }
+ else {
+ // Queen, bishop or rook:
+ const step = [
+ (x2 - x1) / Math.abs(x2 - x1) || 0,
+ (y2 - y1) / Math.abs(y2 - y1) || 0
+ ];
+ const next = [move.appear[0].x + step[0], move.appear[0].y + step[1]];
+ if (
+ V.OnBoard(next[0], next[1]) &&
+ this.board[next[0]][next[1]] != V.EMPTY &&
+ this.getColor(next[0], next[1]) != 'a'
+ ) {
+ const afterNext = [next[0] + step[0], next[1] + step[1]];
+ if (V.OnBoard(afterNext[0], afterNext[1])) {
+ const afterColor = this.getColor(afterNext[0], afterNext[1])
+ if (
+ this.board[afterNext[0]][afterNext[1]] == V.EMPTY ||
+ afterColor != color1
+ ) {
+ move.appear[0].x = afterNext[0];
+ move.appear[0].y = afterNext[1];
+ if (this.board[afterNext[0]][afterNext[1]] != V.EMPTY) {
+ // The "object" could also be an opponent's piece
+ const object = this.getPiece(afterNext[0], afterNext[1]);
+ move.vanish.push(
+ new PiPo({
+ x: afterNext[0],
+ y: afterNext[1],
+ c: afterColor,
+ p: object
+ })
+ );
+ switch (object) {
+ case V.BANANA:
+ case V.BOMB:
+ const steps =
+ V.steps[object == V.BANANA ? V.ROOK : V.BISHOP];
+ move.next = this.getRandomSquare(
+ [afterNext[0], afterNext[1]], steps);
+ break;
+ case V.EGG:
+ applyEggEffect();
+ break;
+ case V.MUSHROOM:
+ applyMushroomEffect();
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ };
+ const color2 = this.getColor(x2, y2);
+ const piece2 = this.getPiece(x2, y2);
+ if (color2 == 'a') {
+ switch (piece2) {
+ case V.BANANA:
+ case V.BOMB:
+ const steps = V.steps[piece2 == V.BANANA ? V.ROOK : V.BISHOP];
+ move.next = this.getRandomSquare([x2, y2], steps);
+ break;
+ case V.MUSHROOM:
+ applyMushroomEffect();
+ break;
+ case V.EGG:
+ if (this.subTurn == 1)
+ // No egg effect at subTurn 2
+ applyEggEffect();
+ break;
+ }
+ }
+ if (
+ this.subTurn == 1 &&
+ !move.next &&
+ move.appear.length > 0 &&
+ [V.ROOK, V.BISHOP].includes(piece1)
+ ) {
+ const finalSquare = [move.appear[0].x, move.appear[0].y];
+ if (
+ color2 != 'a' ||
+ this.getColor(finalSquare[0], finalSquare[1]) != 'a' ||
+ this.getPiece(finalSquare[0], finalSquare[1]) != V.EGG
+ ) {
+ const validSteps =
+ V.steps[piece1 == V.ROOK ? V.BISHOP : V.ROOK].filter(s => {
+ const [i, j] = [finalSquare[0] + s[0], finalSquare[1] + s[1]];
+ return (
+ V.OnBoard(i, j) &&
+ (this.board[i][j] == V.EMPTY || this.getColor(i, j) == 'a')
+ );
+ });
+ if (validSteps.length >= 1) {
+ const [x, y] = [
+ finalSquare[0] + validSteps[0][0],
+ finalSquare[1] + validSteps[0][1]
+ ];
+ move.appear.push(
+ new PiPo({
+ x: x,
+ y: y,
+ c: 'a',
+ p: (piece1 == V.ROOK ? V.BANANA : V.BOMB)
+ })
+ );
+ if (this.board[x][y] != V.EMPTY) {
+ move.vanish.push(
+ new PiPo({ x: x, y: y, c: 'a', p: this.getPiece(x, y) }));
+ }
+ }
+ }