X-Git-Url: https://git.auder.net/?a=blobdiff_plain;f=client%2Fsrc%2Fvariants%2FChakart.js;h=86a867a9ef9ea728e5952cdfbe2bce27afdb8051;hb=ccbb9dfc6459cb3cd80a21abf945693a3fe1b2d5;hp=45230a14f4f354fa92aeae022d954531b318fa8b;hpb=596e24d030f94682a31df74799c13eb792a63cdf;p=vchess.git diff --git a/client/src/variants/Chakart.js b/client/src/variants/Chakart.js index 45230a14..86a867a9 100644 --- a/client/src/variants/Chakart.js +++ b/client/src/variants/Chakart.js @@ -4,6 +4,7 @@ import { ArrayFun } from "@/utils/array"; import { randInt } from "@/utils/alea"; export class ChakartRules extends ChessRules { + static get PawnSpecs() { return SuicideRules.PawnSpecs; } @@ -22,29 +23,13 @@ export class ChakartRules extends ChessRules { } static get CanAnalyze() { - return true; //false; + return false; } static get SomeHiddenMoves() { return true; } - hoverHighlight(x, y) { - if (this.subTurn == 1) return false; - const L = this.firstMove.length; - const fm = this.firstMove[L-1]; - if (fm.end.effect != 0) return false; - const deltaX = Math.abs(fm.appear[0].x - x); - const deltaY = Math.abs(fm.appear[0].y - y); - return ( - (this.board[x][y] == V.EMPTY || this.getColor(x, y) == 'a') && - ( - (fm.vanish[0].p == V.ROOK && deltaX == 1 && deltaY == 1) || - (fm.vanish[0].p == V.BISHOP && deltaX + deltaY == 1) - ) - ); - } - static get IMMOBILIZE_CODE() { return { 'p': 's', @@ -114,8 +99,13 @@ export class ChakartRules extends ChessRules { } getPPpath(m) { + if (!!m.promoteInto) return m.promoteInto; + if (m.appear.length == 0 && m.vanish.length == 1) + // King 'remote shell capture', on an adjacent square: + return this.getPpath(m.vanish[0].c + m.vanish[0].p); let piece = m.appear[0].p; if (Object.keys(V.IMMOBILIZE_DECODE).includes(piece)) + // Promotion by capture into immobilized piece: do not reveal! piece = V.IMMOBILIZE_DECODE[piece]; return this.getPpath(m.appear[0].c + piece); } @@ -147,14 +137,14 @@ export class ChakartRules extends ChessRules { if (['K', 'k', 'L', 'l'].includes(row[i])) kings[row[i]]++; if (V.PIECES.includes(row[i].toLowerCase())) sumElts++; else { - const num = parseInt(row[i]); + const num = parseInt(row[i], 10); if (isNaN(num)) return false; sumElts += num; } } if (sumElts != V.size.y) return false; } - if (kings['k'] + kings['l'] != 1 || kings['K'] + kings['L'] != 1) + if (kings['k'] + kings['l'] == 0 || kings['K'] + kings['L'] == 0) return false; return true; } @@ -209,27 +199,28 @@ export class ChakartRules extends ChessRules { setOtherVariables(fen) { super.setOtherVariables(fen); - const fenParsed = V.ParseFen(fen); // Initialize captured pieces' counts from FEN + const captured = + V.ParseFen(fen).captured.split("").map(x => parseInt(x, 10)); this.captured = { w: { - [V.ROOK]: parseInt(fenParsed.captured[0]), - [V.KNIGHT]: parseInt(fenParsed.captured[1]), - [V.BISHOP]: parseInt(fenParsed.captured[2]), - [V.QUEEN]: parseInt(fenParsed.captured[3]), - [V.KING]: parseInt(fenParsed.captured[4]), - [V.PAWN]: parseInt(fenParsed.captured[5]), + [V.PAWN]: captured[0], + [V.ROOK]: captured[1], + [V.KNIGHT]: captured[2], + [V.BISHOP]: captured[3], + [V.QUEEN]: captured[4], + [V.KING]: captured[5] }, b: { - [V.ROOK]: parseInt(fenParsed.captured[6]), - [V.KNIGHT]: parseInt(fenParsed.captured[7]), - [V.BISHOP]: parseInt(fenParsed.captured[8]), - [V.QUEEN]: parseInt(fenParsed.captured[9]), - [V.KING]: parseInt(fenParsed.captured[10]), - [V.PAWN]: parseInt(fenParsed.captured[11]), + [V.PAWN]: captured[6], + [V.ROOK]: captured[7], + [V.KNIGHT]: captured[8], + [V.BISHOP]: captured[9], + [V.QUEEN]: captured[10], + [V.KING]: captured[11] } }; - this.firstMove = []; + this.effects = []; this.subTurn = 1; } @@ -268,50 +259,14 @@ export class ChakartRules extends ChessRules { 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++) { - const colIJ = this.getColor(i, j); - if (this.board[i][j] == V.EMPTY || colIJ == 'a') { - let mv = new Move({ - appear: [ - new PiPo({ - x: i, - y: j, - c: color, - p: p - }) - ], - vanish: [], - start: { x: x, y: y }, //a bit artificial... - end: { x: i, y: j } - }); - if (colIJ == 'a') { - const pieceIJ = this.getPiece(i, j); - mv.vanish.push( - new PiPo({ - x: i, - y: j, - c: colIJ, - p: pieceIJ - }) - ); - if ([V.BANANA, V.BOMB].includes(pieceIJ)) { - // Apply first effect, remove bonus from board, and then - // relay to getBasicMove. Finally merge mv.appear/vanish and - // put back the bonus on the board: - const bSteps = V.steps[pieceIJ == V.BANANA ? V.ROOK : V.BISHOP]; - const sqEffect = this.getRandomSquare([i, j], bSteps); - if (sqEffect[0] != i || sqEffect[1] != j) { - this.board[i][j] = color + p; - const bMove = - this.getBasicMove([i, j], [sqEffect[0], sqEffect[1]]); - this.board[i][j] = 'a' + pieceIJ; - mv.appear[0].x = bMove.appear[0].x; - mv.appear[0].y = bMove.appear[0].y; - for (let k = 1; k < bMove.vanish.length; k++) - mv.vanish.push(bMove.vanish[k]); - } - } - } - moves.push(mv); + if ( + this.board[i][j] == V.EMPTY || + this.getColor(i, j) == 'a' || + this.getPiece(i, j) == V.INVISIBLE_QUEEN + ) { + let m = this.getBasicMove({ p: p, x: i, y: j}); + m.start = { x: x, y: y }; + moves.push(m); } } } @@ -352,12 +307,10 @@ export class ChakartRules extends ChessRules { } 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) + const L = this.effects.length; + switch (this.effects[L-1]) { case "kingboo": - // Exchange position with any piece, + // Exchange position with any visible piece, // except pawns if arriving on last rank. const lastRank = { 'w': 0, 'b': 7 }; const color = this.turn; @@ -365,12 +318,13 @@ export class ChakartRules extends ChessRules { for (let i=0; i<8; i++) { for (let j=0; j<8; j++) { const colIJ = this.getColor(i, j); + const pieceIJ = this.getPiece(i, j); if ( (i != x || j != y) && this.board[i][j] != V.EMPTY && + pieceIJ != V.INVISIBLE_QUEEN && colIJ != 'a' ) { - const pieceIJ = this.getPiece(i, j); if ( (pieceIJ != V.PAWN || x != lastRank[colIJ]) && (allowLastRank || i != lastRank[color]) @@ -381,7 +335,7 @@ export class ChakartRules extends ChessRules { c: colIJ, p: this.getPiece(i, j) }); - let mMove = this.getBasicMove([x, y], [i, j]); + let mMove = this.getBasicMove({ x: x, y: y }, [i, j]); mMove.appear.push(movedUnit); moves.push(mMove); } @@ -394,77 +348,70 @@ export class ChakartRules extends ChessRules { 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]); + // Play again with any piece + moves = super.getPotentialMovesFrom([x, y]); break; } } return moves; } - // Helper for getBasicMove() + // Helper for getBasicMove(): banana/bomb effect 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 validSteps = steps.filter(s => V.OnBoard(x + s[0], y + s[1])); 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]; - if (!Array.isArray(steps)) debugger; - 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; + // 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 = (!!tr ? tr.p : (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; } - return false; - } - - getBasicMove([x1, y1], [x2, y2], tr) { - // Apply mushroom, bomb or banana effect (hidden to the player). - // Determine egg effect, too, and apply its first part if possible. + const [x2, y2] = [sq2[0], sq2[1]]; + // The move starts normally, on board: let move = super.getBasicMove([x1, y1], [x2, y2], tr); - const color1 = this.getColor(x1, y1); - const piece1 = this.getPiece(x1, y1); - const oppCol = V.GetOppCol(color1); - if ([V.PAWN, V.KNIGHT].includes(piece1)) { + if (!!tr) move.promoteInto = tr.c + tr.p; //in case of (chomped...) + const L = this.effects.length; + if ( + [V.PAWN, V.KNIGHT].includes(piece1) && + !!initMove && + (this.subTurn == 1 || this.effects[L-1] == "daisy") + ) { switch (piece1) { case V.PAWN: { const twoSquaresMove = (Math.abs(x2 - x1) == 2); @@ -525,62 +472,25 @@ export class ChakartRules extends ChessRules { } } } - // Avoid looping back on effect already applied: - let usedEffect = ArrayFun.init(8, 8, false); - const applyBeffect = (steps) => { - const [x, y] = [move.appear[0].x, move.appear[0].y]; - if (usedEffect[x][y]) return; - usedEffect[x][y] = true; - const moveTo = this.getRandomSquare([x, y], steps); - move.appear[0].x = moveTo[0]; - move.appear[0].y = moveTo[1]; - if ( - this.board[moveTo[0]][moveTo[1]] != V.EMPTY && - this.getColor(moveTo[0], moveTo[1]) == 'a' - ) { - move.vanish.push( - new PiPo({ - x: moveTo[0], - y: moveTo[1], - c: 'a', - p: this.getPiece(moveTo[0], moveTo[1]) - }) - ); - // Artificially change direction, before applying new effects: - x1 = x; - y1 = y; - x2 = moveTo[0]; - y2 = moveTo[1]; - switch (this.getPiece(moveTo[0], moveTo[1])) { - case V.BANANA: - applyBeffect(V.steps[V.ROOK]); - break; - case V.BOMB: - applyBeffect(V.steps[V.BISHOP]); - break; - case V.MUSHROOM: - applyMushroomEffect(); - break; - case V.EGG: - applyEggEffect(); - 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 (this.board[i][j] != V.EMPTY && this.getColor(i, j) == color) { - const piece = this.getPiece(i, j); + const piece = this.getPiece(i, j); + if ( + (i != move.vanish[0].x || j != move.vanish[0].y) && + this.board[i][j] != V.EMPTY && + piece != V.INVISIBLE_QUEEN && + this.getColor(i, j) == color + ) { if (piece != V.KING && (piece != V.PAWN || i != oppLastRank)) pieces.push({ x: i, y: j, p: piece }); } } } - // Special case of the current piece (temporarily off board): + // 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)]; @@ -594,7 +504,7 @@ export class ChakartRules extends ChessRules { }) ); } - else move.appear.pop(); + else move.appear.shift(); move.appear.push( new PiPo({ x: cp.x, @@ -609,29 +519,10 @@ export class ChakartRules extends ChessRules { // No egg effects at subTurn 2 return; // 1) Determine the effect (some may be impossible) - let effects = ["kingboo", "koopa", "chomp", "bowser"]; + let effects = ["kingboo", "koopa", "chomp", "bowser", "daisy"]; 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"; - this.board[move.start.x][move.start.y] = saveCurrent; - 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); - this.board[move.start.x][move.start.y] = V.EMPTY; - delete move.end["effect"]; - } - if (canPlayAgain) effects.push("daisy"); if ( this.board.some((b,i) => b.some(cell => { @@ -689,8 +580,6 @@ export class ChakartRules extends ChessRules { } }; const applyMushroomEffect = () => { - if (usedEffect[move.appear[0].x][move.appear[0].y]) return; - usedEffect[move.appear[0].x][move.appear[0].y] = true; if ([V.PAWN, V.KING, V.KNIGHT].includes(piece1)) { // Just make another similar step, if possible (non-capturing) const [i, j] = [ @@ -699,26 +588,30 @@ export class ChakartRules extends ChessRules { ]; if ( V.OnBoard(i, j) && - (this.board[i][j] == V.EMPTY || this.getColor(i, j) == 'a') + ( + this.board[i][j] == V.EMPTY || + this.getPiece(i, j) == V.INVISIBLE_QUEEN || + 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); + const color = this.getColor(i, j); move.vanish.push( new PiPo({ x: i, y: j, - c: 'a', + c: color, p: object }) ); switch (object) { case V.BANANA: - applyBeffect(V.steps[V.ROOK]); - break; case V.BOMB: - applyBeffect(V.steps[V.BISHOP]); + const steps = V.steps[object == V.BANANA ? V.ROOK : V.BISHOP]; + move.next = this.getRandomSquare([i, j], steps); break; case V.EGG: applyEggEffect(); @@ -740,11 +633,12 @@ export class ChakartRules extends ChessRules { if ( V.OnBoard(next[0], next[1]) && this.board[next[0]][next[1]] != V.EMPTY && + this.getPiece(next[0], next[1]) != V.INVISIBLE_QUEEN && 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]) + const afterColor = this.getColor(afterNext[0], afterNext[1]); if ( this.board[afterNext[0]][afterNext[1]] == V.EMPTY || afterColor != color1 @@ -764,10 +658,11 @@ export class ChakartRules extends ChessRules { ); switch (object) { case V.BANANA: - applyBeffect(V.steps[V.ROOK]); - break; case V.BOMB: - applyBeffect(V.steps[V.BISHOP]); + 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(); @@ -784,18 +679,12 @@ export class ChakartRules extends ChessRules { }; const color2 = this.getColor(x2, y2); const piece2 = this.getPiece(x2, y2); - // In case of (bonus effects might go through initial square): - // TODO: push the idea further, objects left initially should alter the - // trajectory or move as well (mushroom or egg). - const saveCurrent = this.board[move.start.x][move.start.y]; - this.board[move.start.x][move.start.y] = V.EMPTY; if (color2 == 'a') { switch (piece2) { case V.BANANA: - applyBeffect(V.steps[V.ROOK]); - break; case V.BOMB: - applyBeffect(V.steps[V.BISHOP]); + const steps = V.steps[piece2 == V.BANANA ? V.ROOK : V.BISHOP]; + move.next = this.getRandomSquare([x2, y2], steps); break; case V.MUSHROOM: applyMushroomEffect(); @@ -809,6 +698,7 @@ export class ChakartRules extends ChessRules { } if ( this.subTurn == 1 && + !move.next && move.appear.length > 0 && [V.ROOK, V.BISHOP].includes(piece1) ) { @@ -823,14 +713,15 @@ export class ChakartRules extends ChessRules { const [i, j] = [finalSquare[0] + s[0], finalSquare[1] + s[1]]; return ( V.OnBoard(i, j) && + // NOTE: do not place a bomb or banana on the invisible queen! (this.board[i][j] == V.EMPTY || this.getColor(i, j) == 'a') ); }); - if (validSteps.length >= 2) move.end.effect = 0; - else if (validSteps.length == 1) { + if (validSteps.length >= 1) { + const randIdx = randInt(validSteps.length); const [x, y] = [ - finalSquare[0] + validSteps[0][0], - finalSquare[1] + validSteps[0][1] + finalSquare[0] + validSteps[randIdx][0], + finalSquare[1] + validSteps[randIdx][1] ]; move.appear.push( new PiPo({ @@ -847,17 +738,78 @@ export class ChakartRules extends ChessRules { } } } - this.board[move.start.x][move.start.y] = saveCurrent; - if ( - move.appear.length == 2 && - move.appear[0].x == move.appear[1].x && - move.appear[0].y == move.appear[1].y - ) { - // Arrive on bonus left initially: - move.appear.pop(); + return move; + } + + getBasicMove(psq1, sq2, tr) { + let moves = []; + if (Array.isArray(psq1)) psq1 = { x: psq1[0], y: psq1[1] }; + let m = this.getBasicMove_aux(psq1, sq2, tr, "initMove"); + while (!!m.next) { + // Last move ended on bomb or banana, direction change + V.PlayOnBoard(this.board, m); + moves.push(m); + m = this.getBasicMove_aux( + { x: m.appear[0].x, y: m.appear[0].y }, m.next); + } + for (let i=moves.length-1; i>=0; i--) V.UndoOnBoard(this.board, moves[i]); + moves.push(m); + // Now merge moves into one + let move = {}; + // start is wrong for Toadette moves --> it's fixed later + move.start = { x: psq1.x, y: psq1.y }; + move.end = !!sq2 ? { x: sq2[0], y: sq2[1] } : { x: psq1.x, y: psq1.y }; + if (!!tr) move.promoteInto = moves[0].promoteInto; + let lm = moves[moves.length-1]; + if (this.subTurn == 1 && !!lm.end.effect) + move.end.effect = lm.end.effect; + if (moves.length == 1) { + move.appear = moves[0].appear; + move.vanish = moves[0].vanish; + } + else { + // Keep first vanish and last appear (if any) + move.appear = lm.appear; + move.vanish = moves[0].vanish; + if ( + move.vanish.length >= 1 && + move.appear.length >= 1 && + move.vanish[0].x == move.appear[0].x && + move.vanish[0].y == move.appear[0].y + ) { + // Loopback on initial square: + move.vanish.shift(); + move.appear.shift(); + } + for (let i=1; i < moves.length - 1; i++) { + for (let v of moves[i].vanish) { + // Only vanishing objects, not appearing at init move + if ( + v.c == 'a' && + ( + moves[0].appear.length == 1 || + moves[0].appear[1].x != v.x || + moves[0].appear[1].y != v.y + ) + ) { + move.vanish.push(v); + } + } + } + // Final vanish is our piece, but others might be relevant + // (for some egg bonuses at least). + for (let i=1; i < lm.vanish.length; i++) { + if ( + lm.vanish[i].c != 'a' || + moves[0].appear.length == 1 || + moves[0].appear[1].x != lm.vanish[i].x || + moves[0].appear[1].y != lm.vanish[i].y + ) { + move.vanish.push(lm.vanish[i]); + } + } } return move; - // TODO: if loopback to initial square, simplify move. } getPotentialPawnMoves([x, y]) { @@ -869,17 +821,19 @@ export class ChakartRules extends ChessRules { let moves = []; if ( this.board[x + shiftX][y] == V.EMPTY || - this.getColor(x + shiftX, y) == 'a' + this.getColor(x + shiftX, y) == 'a' || + this.getPiece(x + shiftX, y) == V.INVISIBLE_QUEEN ) { this.addPawnMoves([x, y], [x + shiftX, y], moves); if ( [firstRank, firstRank + shiftX].includes(x) && ( this.board[x + 2 * shiftX][y] == V.EMPTY || - this.getColor(x + 2 * shiftX, y) == 'a' + this.getColor(x + 2 * shiftX, y) == 'a' || + this.getPiece(x + 2 * shiftX, y) == V.INVISIBLE_QUEEN ) ) { - moves.push(this.getBasicMove([x, y], [x + 2 * shiftX, y])); + moves.push(this.getBasicMove({ x: x, y: y }, [x + 2 * shiftX, y])); } } for (let shiftY of [-1, 1]) { @@ -887,6 +841,8 @@ export class ChakartRules extends ChessRules { y + shiftY >= 0 && y + shiftY < sizeY && this.board[x + shiftX][y + shiftY] != V.EMPTY && + // Pawns cannot capture invisible queen this way! + this.getPiece(x + shiftX, y + shiftY) != V.INVISIBLE_QUEEN && ['a', oppCol].includes(this.getColor(x + shiftX, y + shiftY)) ) { this.addPawnMoves([x, y], [x + shiftX, y + shiftY], moves); @@ -923,48 +879,37 @@ export class ChakartRules extends ChessRules { // If flag allows it, add 'remote shell captures' if (this.powerFlags[this.turn][V.KING]) { V.steps[V.ROOK].concat(V.steps[V.BISHOP]).forEach(step => { - const [nextX, nextY] = [x + step[0], y + step[1]]; - if ( - V.OnBoard(nextX, nextY) && + let [i, j] = [x + step[0], y + step[1]]; + while ( + V.OnBoard(i, j) && ( - this.board[nextX][nextY] == V.EMPTY || + this.board[i][j] == V.EMPTY || + this.getPiece(i, j) == V.INVISIBLE_QUEEN || ( - this.getColor(nextX, nextY) == 'a' && - [V.EGG, V.MUSHROOM].includes(this.getPiece(nextX, nextY)) + this.getColor(i, j) == 'a' && + [V.EGG, V.MUSHROOM].includes(this.getPiece(i, j)) ) ) ) { - let [i, j] = [x + 2 * step[0], y + 2 * step[1]]; - while ( - V.OnBoard(i, j) && - ( - this.board[i][j] == V.EMPTY || - ( - this.getColor(i, j) == 'a' && - [V.EGG, V.MUSHROOM].includes(this.getPiece(i, j)) - ) - ) - ) { - i += step[0]; - j += step[1]; - } - if (V.OnBoard(i, j)) { - const colIJ = this.getColor(i, j); - if (colIJ != color) { - // May just destroy a bomb or banana: - moves.push( - new Move({ - start: { x: x, y: y}, - end: { x: i, y: j }, - appear: [], - vanish: [ - new PiPo({ - x: i, y: j, c: colIJ, p: this.getPiece(i, j) - }) - ] - }) - ); - } + i += step[0]; + j += step[1]; + } + if (V.OnBoard(i, j)) { + const colIJ = this.getColor(i, j); + if (colIJ != color) { + // May just destroy a bomb or banana: + moves.push( + new Move({ + start: { x: x, y: y}, + end: { x: i, y: j }, + appear: [], + vanish: [ + new PiPo({ + x: i, y: j, c: colIJ, p: this.getPiece(i, j) + }) + ] + }) + ); } } }); @@ -981,19 +926,20 @@ export class ChakartRules extends ChessRules { V.OnBoard(i, j) && ( this.board[i][j] == V.EMPTY || + this.getPiece(i, j) == V.INVISIBLE_QUEEN || ( this.getColor(i, j) == 'a' && [V.EGG, V.MUSHROOM].includes(this.getPiece(i, j)) ) ) ) { - moves.push(this.getBasicMove([x, y], [i, j])); + moves.push(this.getBasicMove({ x: x, y: y }, [i, j])); if (oneStep) continue outerLoop; i += step[0]; j += step[1]; } if (V.OnBoard(i, j) && this.canTake([x, y], [i, j])) - moves.push(this.getBasicMove([x, y], [i, j])); + moves.push(this.getBasicMove({ x: x, y: y }, [i, j])); } return moves; } @@ -1001,66 +947,42 @@ export class ChakartRules extends ChessRules { getAllPotentialMoves() { if (this.subTurn == 1) return super.getAllPotentialMoves(); let moves = []; - const L = this.firstMove.length; - const fm = this.firstMove[L-1]; - switch (fm.end.effect) { - case 0: - moves.push({ - start: { x: -1, y: -1 }, - end: { x: -1, y: -1 }, - appear: [], - vanish: [] - }); - for ( - let step of - (fm.vanish[0].p == V.ROOK ? V.steps[V.BISHOP] : V.steps[V.ROOK]) - ) { - const [i, j] = [fm.appear[0].x + step[0], fm.appear[0].y + step[1]]; - if ( - V.OnBoard(i, j) && - (this.board[i][j] == V.EMPTY || this.getColor(i, j) == 'a') - ) { - let m = new Move({ - start: { x: -1, y: -1 }, - end: { x: i, y: j }, - appear: [ - new PiPo({ - x: i, - y: j, - c: 'a', - p: (fm.vanish[0].p == V.ROOK ? V.BANANA : V.BOMB) - }) - ], - vanish: [] - }); - if (this.board[i][j] != V.EMPTY) { - m.vanish.push( - new PiPo({ x: i, y: j, c: 'a', p: this.getPiece(i, j) })); - } - moves.push(m); - } - } - break; + const color = this.turn; + const L = this.effects.length; + switch (this.effects[L-1]) { case "kingboo": { - const [x, y] = [fm.appear[0].x, fm.appear[0].y]; + let allPieces = []; for (let i=0; i<8; i++) { for (let j=0; j<8; j++) { const colIJ = this.getColor(i, j); + const pieceIJ = this.getPiece(i, j); if ( - i != x && - j != y && + i != x && j != y && this.board[i][j] != V.EMPTY && - colIJ != 'a' + colIJ != 'a' && + pieceIJ != V.INVISIBLE_QUEEN ) { - const movedUnit = new PiPo({ - x: x, - y: y, - c: colIJ, - p: this.getPiece(i, j) + allPieces.push({ x: i, y: j, c: colIJ, p: pieceIJ }); + } + } + } + for (let x=0; x<8; x++) { + for (let y=0; y<8; y++) { + if (this.getColor(i, j) == color) { + // Add exchange with something + allPieces.forEach(pp => { + if (pp.x != i || pp.y != j) { + const movedUnit = new PiPo({ + x: x, + y: y, + c: pp.c, + p: pp.p + }); + let mMove = this.getBasicMove({ x: x, y: y }, [pp.x, pp.y]); + mMove.appear.push(movedUnit); + moves.push(mMove); + } }); - let mMove = this.getBasicMove([x, y], [i, j]); - mMove.appear.push(movedUnit); - moves.push(mMove); } } } @@ -1073,68 +995,12 @@ export class ChakartRules extends ChessRules { break; } case "daisy": - moves = super.getPotentialMovesFrom([fm.appear[0].x, fm.appear[0].y]); + moves = super.getAllPotentialMoves(); break; } return moves; } - doClick(square) { - const L = this.firstMove.length; - const fm = (L > 0 ? this.firstMove[L-1] : null); - if ( - isNaN(square[0]) || - this.subTurn == 1 || - !([0, "daisy"].includes(fm.end.effect)) - ) { - return null; - } - const [x, y] = [square[0], square[1]]; - const deltaX = Math.abs(fm.appear[0].x - x); - const deltaY = Math.abs(fm.appear[0].y - y); - if ( - fm.end.effect == 0 && - (this.board[x][y] == V.EMPTY || this.getColor(x, y) == 'a') && - ( - (fm.vanish[0].p == V.ROOK && deltaX == 1 && deltaY == 1) || - (fm.vanish[0].p == V.BISHOP && deltaX + deltaY == 1) - ) - ) { - let m = new Move({ - start: { x: -1, y: -1 }, - end: { x: x, y: y }, - appear: [ - new PiPo({ - x: x, - y: y, - c: 'a', - p: (fm.vanish[0].p == V.ROOK ? V.BANANA : V.BOMB) - }) - ], - vanish: [] - }); - if (this.board[x][y] != V.EMPTY) { - m.vanish.push( - new PiPo({ x: x, y: y, c: 'a', p: this.getPiece(x, y) })); - } - return m; - } - else if ( - fm.end.effect == "daisy" && - deltaX == 0 && deltaY == 0 && - !this.canMove([x, y]) - ) { - // No possible move: return empty move - return { - start: { x: -1, y: -1 }, - end: { x: -1, y: -1 }, - appear: [], - vanish: [] - }; - } - return null; - } - play(move) { // if (!this.states) this.states = []; // const stateFen = this.getFen(); @@ -1143,8 +1009,8 @@ export class ChakartRules extends ChessRules { move.flags = JSON.stringify(this.aggregateFlags()); V.PlayOnBoard(this.board, move); move.turn = [this.turn, this.subTurn]; - if ([0, "kingboo", "toadette", "daisy"].includes(move.end.effect)) { - this.firstMove.push(move); + if (["kingboo", "toadette", "daisy"].includes(move.end.effect)) { + this.effects.push(move.end.effect); this.subTurn = 2; } else { @@ -1159,9 +1025,18 @@ export class ChakartRules extends ChessRules { if (move.end.effect == "toadette") this.reserve = this.captured; else this.reserve = undefined; const color = move.turn[0]; - if (move.vanish.length == 2 && move.vanish[1].c != 'a') + if ( + move.vanish.length == 2 && + move.vanish[1].c != 'a' && + move.appear.length == 1 //avoid king Boo! + ) { // Capture: update this.captured - this.captured[move.vanish[1].c][move.vanish[1].p]++; + let capturedPiece = move.vanish[1].p; + if (capturedPiece == V.INVISIBLE_QUEEN) capturedPiece = V.QUEEN; + else if (Object.keys(V.IMMOBILIZE_DECODE).includes(capturedPiece)) + capturedPiece = V.IMMOBILIZE_DECODE[capturedPiece]; + this.captured[move.vanish[1].c][capturedPiece]++; + } else if (move.vanish.length == 0) { if (move.appear.length == 0 || move.appear[0].c == 'a') return; // A piece is back on board @@ -1177,44 +1052,49 @@ export class ChakartRules extends ChessRules { } else if (move.appear[0].p == V.INVISIBLE_QUEEN) this.powerFlags[move.appear[0].c][V.QUEEN] = false; - if (move.turn[1] == 2) return; + if (this.subTurn == 2) return; if ( + move.turn[1] == 1 && move.appear.length == 0 || !(Object.keys(V.IMMOBILIZE_DECODE).includes(move.appear[0].p)) ) { // Look for an immobilized piece of my color: it can now move - // Also make opponent invisible queen visible again, if any - const oppCol = V.GetOppCol(color); for (let i=0; i<8; i++) { for (let j=0; j<8; j++) { if (this.board[i][j] != V.EMPTY) { - const colIJ = this.getColor(i, j); const piece = this.getPiece(i, j); if ( - colIJ == color && + this.getColor(i, j) == color && Object.keys(V.IMMOBILIZE_DECODE).includes(piece) ) { this.board[i][j] = color + V.IMMOBILIZE_DECODE[piece]; move.wasImmobilized = [i, j]; } - else if ( - colIJ == oppCol && - piece == V.INVISIBLE_QUEEN - ) { - this.board[i][j] = oppCol + V.QUEEN; - move.wasInvisible = [i, j]; - } } } } } + // Also make opponent invisible queen visible again, if any + const oppCol = V.GetOppCol(color); + for (let i=0; i<8; i++) { + for (let j=0; j<8; j++) { + if ( + this.board[i][j] != V.EMPTY && + this.getColor(i, j) == oppCol && + this.getPiece(i, j) == V.INVISIBLE_QUEEN + ) { + this.board[i][j] = oppCol + V.QUEEN; + move.wasInvisible = [i, j]; + } + } + } } undo(move) { this.disaggregateFlags(JSON.parse(move.flags)); V.UndoOnBoard(this.board, move); - if ([0, "kingboo", "toadette", "daisy"].includes(move.end.effect)) - this.firstMove.pop(); + if (["kingboo", "toadette", "daisy"].includes(move.end.effect)) + this.effects.pop(); else this.movesCount--; this.turn = move.turn[0]; this.subTurn = move.turn[1]; @@ -1235,8 +1115,13 @@ export class ChakartRules extends ChessRules { const [i, j] = move.wasInvisible; this.board[i][j] = this.getColor(i, j) + V.INVISIBLE_QUEEN; } - if (move.vanish.length == 2 && move.vanish[1].c != 'a') - this.captured[move.vanish[1].c][move.vanish[1].p]--; + if (move.vanish.length == 2 && move.vanish[1].c != 'a') { + let capturedPiece = move.vanish[1].p; + if (capturedPiece == V.INVISIBLE_QUEEN) capturedPiece = V.QUEEN; + else if (Object.keys(V.IMMOBILIZE_DECODE).includes(capturedPiece)) + capturedPiece = V.IMMOBILIZE_DECODE[capturedPiece]; + this.captured[move.vanish[1].c][capturedPiece]--; + } else if (move.vanish.length == 0) { if (move.appear.length == 0 || move.appear[0].c == 'a') return; // A piece was back on board @@ -1271,9 +1156,9 @@ export class ChakartRules extends ChessRules { return "*"; } - static GenRandInitFen(randomness) { + static GenRandInitFen(options) { return ( - SuicideRules.GenRandInitFen(randomness).slice(0, -1) + + SuicideRules.GenRandInitFen(options).slice(0, -1) + // Add Peach + Mario flags + capture counts "1111 000000000000" ); @@ -1283,10 +1168,45 @@ export class ChakartRules extends ChessRules { return moves; } + static get VALUES() { + return Object.assign( + {}, + ChessRules.VALUES, + { + s: 1, + u: 5, + o: 3, + c: 3, + t: 9, + l: 1000, + e: 0, + d: 0, + w: 0, + m: 0 + } + ); + } + + static get SEARCH_DEPTH() { + return 1; + } + getComputerMove() { - // Random mover: const moves = this.getAllValidMoves(); - let move1 = moves[randInt(moves.length)]; + // Split into "normal" and "random" moves: + // (Next splitting condition is OK because cannot take self object + // without a banana or bomb on the way). + const deterministicMoves = moves.filter(m => { + return m.vanish.every(a => a.c != 'a' || a.p == V.MUSHROOM); + }); + const randomMoves = moves.filter(m => { + return m.vanish.some(a => a.c == 'a' && a.p != V.MUSHROOM); + }); + if (Math.random() < deterministicMoves.length / randomMoves.length) + // Play a deterministic one: capture king or material if possible + return super.getComputerMove(deterministicMoves); + // Play a random effect move, at random: + let move1 = randomMoves[randInt(randomMoves.length)]; this.play(move1); let move2 = undefined; if (this.subTurn == 2) { @@ -1298,16 +1218,8 @@ export class ChakartRules extends ChessRules { return [move1, move2]; } - // TODO: king chomped, king koopa: notation is incomplete. - // Also, king boo effect should be better written like "e4Sg1". - // Toadette placements on bonus square are badly written as well. getNotation(move) { - if (move.vanish.length == 0) { - if (move.appear.length == 0) return "-"; - const piece = - move.appear[0].p != V.PAWN ? move.appear[0].p.toUpperCase() : ""; - return piece + "@" + V.CoordsToSquare(move.end); - } + if (move.vanish.length == 0 && move.appear.length == 0) return "-"; if ( !move.end.effect && move.appear.length > 0 && @@ -1316,31 +1228,55 @@ export class ChakartRules extends ChessRules { return "Q??"; } const finalSquare = V.CoordsToSquare(move.end); - let piece = undefined; - if (move.appear.length == 0) { - piece = this.getPiece(move.start.x, move.start.y); - if (piece == V.KING) return "Kx" + finalSquare; - // Koopa or Chomp: + // Next condition also includes Toadette placements: + if (move.appear.length > 0 && move.vanish.every(a => a.c == 'a')) { + const piece = + move.appear[0].p != V.PAWN ? move.appear[0].p.toUpperCase() : ""; + return piece + "@" + finalSquare; + } + else if (move.appear.length == 0) { + const piece = this.getPiece(move.start.x, move.start.y); + if (piece == V.KING && !move.end.effect) + // King remote capture + return "Kx" + finalSquare; + // Koopa or Chomp, or loopback after bananas, bombs & mushrooms: return ( piece.toUpperCase() + "x" + finalSquare + - "*" + (move.end.effect == "koopa" ? "K" : "C") + ( + !!move.end.effect + ? "*" + (move.end.effect == "koopa" ? "K" : "C") + : "" + ) ); } - else if ( - move.appear.length == 1 && - move.vanish.length == 1 && - move.appear[0].c == 'a' && - move.vanish[0].c == 'a' + if (move.appear.length == 1 && move.vanish.length == 1) { + const moveStart = move.appear[0].p.toUpperCase() + "@"; + if (move.appear[0].c == 'a' && move.vanish[0].c == 'a') + // Bonus replacement: + return moveStart + finalSquare; + if ( + move.vanish[0].p == V.INVISIBLE_QUEEN && + move.appear[0].x == move.vanish[0].x && + move.appear[0].y == move.vanish[0].y + ) { + // Toadette takes invisible queen + return moveStart + "Q" + finalSquare; + } + } + if ( + move.appear.length == 2 && + move.vanish.length == 2 && + move.appear.every(a => a.c != 'a') && + move.vanish.every(v => v.c != 'a') ) { - // Bonus replacement: - piece = move.appear[0].p.toUpperCase(); - return piece + "@" + finalSquare; + // King Boo exchange + return V.CoordsToSquare(move.start) + finalSquare; } - piece = move.vanish[0].p; + const piece = move.vanish[0].p; let notation = undefined; if (piece == V.PAWN) { // Pawn move - if (move.vanish.length >= 2) { + if (this.board[move.end.x][move.end.y] != V.EMPTY) { // Capture const startColumn = V.CoordToColumn(move.start.y); notation = startColumn + "x" + finalSquare; @@ -1353,7 +1289,7 @@ export class ChakartRules extends ChessRules { else { notation = piece.toUpperCase() + - (move.vanish.length >= 2 ? "x" : "") + + (this.board[move.end.x][move.end.y] != V.EMPTY ? "x" : "") + finalSquare; } if (!!move.end.effect) { @@ -1371,13 +1307,15 @@ export class ChakartRules extends ChessRules { notation += "*M"; break; case "luigi": - notation += "*L"; - break; case "waluigi": - notation += "*W"; + const lastAppear = move.appear[move.appear.length - 1]; + const effectOn = + V.CoordsToSquare({ x: lastAppear.x, y : lastAppear.y }); + notation += "*" + move.end.effect[0].toUpperCase() + effectOn; break; } } return notation; } + };