X-Git-Url: https://git.auder.net/?a=blobdiff_plain;f=client%2Fsrc%2Fvariants%2FAtomic.js;h=831fb2f239d7f8912c0937296ec1a25d634d43a3;hb=10cceb25109739fa39b9b968be2707dee1d25a07;hp=e1b1c16163784f36685ad2026113f477803c735b;hpb=0c3fe8a6c3e02af46e0bc646b40c1a0c420f9dcd;p=vchess.git diff --git a/client/src/variants/Atomic.js b/client/src/variants/Atomic.js index e1b1c161..831fb2f2 100644 --- a/client/src/variants/Atomic.js +++ b/client/src/variants/Atomic.js @@ -1,146 +1,264 @@ -import { ChessRules, PiPo } from "@/base_rules"; - -export const VariantRules = class AtomicRules extends ChessRules -{ - getPotentialMovesFrom([x,y]) - { - let moves = super.getPotentialMovesFrom([x,y]); - - // Handle explosions - moves.forEach(m => { - if (m.vanish.length > 1 && m.appear.length <= 1) //avoid castles - { - // Explosion! TODO(?): drop moves which explode our king here - let steps = [ [-1,-1],[-1,0],[-1,1],[0,-1],[0,1],[1,-1],[1,0],[1,1] ]; - for (let step of steps) - { - let x = m.end.x + step[0]; - let y = m.end.y + step[1]; - if (V.OnBoard(x,y) && this.board[x][y] != V.EMPTY - && this.getPiece(x,y) != V.PAWN) - { - m.vanish.push( - new PiPo({p:this.getPiece(x,y),c:this.getColor(x,y),x:x,y:y})); - } - } - m.end = {x:m.appear[0].x, y:m.appear[0].y}; - m.appear.pop(); //Nothin appears in this case - } - }); - - return moves; - } - - getPotentialKingMoves([x,y]) - { - // King cannot capture: - let moves = []; - const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]); - for (let step of steps) - { - const i = x + step[0]; - const j = y + step[1]; - if (V.OnBoard(i,j) && this.board[i][j] == V.EMPTY) - moves.push(this.getBasicMove([x,y], [i,j])); - } - return moves.concat(this.getCastleMoves([x,y])); - } - - isAttacked(sq, colors) - { - if (this.getPiece(sq[0],sq[1]) == V.KING && this.isAttackedByKing(sq, colors)) - return false; //king cannot take... - return (this.isAttackedByPawn(sq, colors) - || this.isAttackedByRook(sq, colors) - || this.isAttackedByKnight(sq, colors) - || this.isAttackedByBishop(sq, colors) - || this.isAttackedByQueen(sq, colors)); - } - - updateVariables(move) - { - super.updateVariables(move); - const color = move.vanish[0].c; - if (move.appear.length == 0) //capture - { - const firstRank = {"w": 7, "b": 0}; - for (let c of ["w","b"]) - { - // Did we explode king of color c ? (TODO: remove move earlier) - if (Math.abs(this.kingPos[c][0]-move.end.x) <= 1 - && Math.abs(this.kingPos[c][1]-move.end.y) <= 1) - { - this.kingPos[c] = [-1,-1]; - this.castleFlags[c] = [false,false]; - } - else - { - // Now check if init rook(s) exploded - if (Math.abs(move.end.x-firstRank[c]) <= 1) - { - if (Math.abs(move.end.y-this.INIT_COL_ROOK[c][0]) <= 1) - this.castleFlags[c][0] = false; - if (Math.abs(move.end.y-this.INIT_COL_ROOK[c][1]) <= 1) - this.castleFlags[c][1] = false; - } - } - } - } - } - - unupdateVariables(move) - { - super.unupdateVariables(move); - const c = move.vanish[0].c; - const oppCol = V.GetOppCol(c); - if ([this.kingPos[c][0],this.kingPos[oppCol][0]].some(e => { return e < 0; })) - { - // There is a chance that last move blowed some king away.. - for (let psq of move.vanish) - { - if (psq.p == 'k') - this.kingPos[psq.c==c ? c : oppCol] = [psq.x, psq.y]; - } - } - } - - underCheck(color) - { - const oppCol = V.GetOppCol(color); - let res = undefined; - // If our king disappeared, move is not valid - if (this.kingPos[color][0] < 0) - res = true; - // If opponent king disappeared, move is valid - else if (this.kingPos[oppCol][0] < 0) - res = false; - // Otherwise, if we remain under check, move is not valid - else - res = this.isAttacked(this.kingPos[color], [oppCol]); - return res; - } - - getCheckSquares(color) - { - let res = [ ]; - if (this.kingPos[color][0] >= 0 //king might have exploded - && this.isAttacked(this.kingPos[color], [V.GetOppCol(color)])) - { - res = [ JSON.parse(JSON.stringify(this.kingPos[color])) ] - } - return res; - } - - getCurrentScore() - { - const color = this.turn; - const kp = this.kingPos[color]; - if (kp[0] < 0) //king disappeared - return color == "w" ? "0-1" : "1-0"; - if (this.atLeastOneMove()) // game not over - return "*"; - if (!this.isAttacked(kp, [V.GetOppCol(color)])) - return "1/2"; - return color == "w" ? "0-1" : "1-0"; //checkmate - } -} +import { ChessRules, Move, PiPo } from "@/base_rules"; + +export class AtomicRules extends ChessRules { + + static get Options() { + return { + check: [ + { + label: "Balanced", + defaut: false, + variable: "balanced" + } + ], + select: ChessRules.Options.select + }; + } + + static AbbreviateOptions(opts) { + return opts["balanced"] ? 'B' : ''; + } + + static GenRandInitFen(options) { + return ChessRules.GenRandInitFen(options) + (options.balanced ? " B" : ""); + } + + setOtherVariables(fen) { + super.setOtherVariables(fen); + this.balanced = !!V.ParseFen(fen).balanced; + } + + static ParseFen(fen) { + return Object.assign( + { balanced: fen.split(" ")[5] }, + ChessRules.ParseFen(fen) + ); + } + + static IsGoodFen(fen) { + if (!ChessRules.IsGoodFen(fen)) return false; + const balanced = V.ParseFen(fen).balanced; + return (!balanced || balanced == 'B'); + } + + getFen() { + return super.getFen() + (this.balanced ? " B" : ""); + } + + hoverHighlight([x, y]) { + return this.balanced && this.movesCount == 0 && [1, 6].includes(x); + } + + canIplay(side, [x, y]) { + if (this.balanced && this.movesCount == 0) + return (this.turn == side && this.getPiece(x, y) == V.PAWN); + return super.canIplay(side, [x, y]); + } + + doClick(square) { + if (!this.balanced || this.movesCount >= 1) return null; + const [x, y] = [square[0], square[1]]; + if (![1, 6].includes(x)) return null; + return new Move({ + appear: [], + vanish: [ + new PiPo({ + x: x, + y: y, + c: this.getColor(x, y), + p: V.PAWN + }) + ], + start: { x: x, y: y }, + end: { x: x, y: y } + }); + } + + getPotentialMovesFrom([x, y]) { + if (this.balanced && this.movesCount == 0) { + if ([1, 6].includes(x)) { + const c = this.getColor(x, y); + return [ + new Move({ + appear: [], + vanish: [ + new PiPo({ x: x, y: y, p: V.PAWN, c: c }) + ], + start: { x: x, y: y }, + end: { x: x, y: y } + }) + ]; + } + return []; + } + + let moves = super.getPotentialMovesFrom([x, y]); + if (this.getPiece(x, y) == V.PAWN) { + // Promotions by captures can be reduced to only one deterministic + // move (because of the explosion). + moves = moves.filter(m => { + return ( + m.vanish.length == 1 || + [V.PAWN, V.QUEEN].includes(m.appear[0].p) + ); + }); + } + // Handle explosions + moves.forEach(m => { + // NOTE: if vanish.length==2 and appear.length==2, this is castle + if (m.vanish.length > 1 && m.appear.length <= 1) { + // Explosion! (TODO?: drop moves which explode our king here) + let steps = [ + [-1, -1], + [-1, 0], + [-1, 1], + [0, -1], + [0, 1], + [1, -1], + [1, 0], + [1, 1] + ]; + for (let step of steps) { + let x = m.end.x + step[0]; + let y = m.end.y + step[1]; + if ( + V.OnBoard(x, y) && + this.board[x][y] != V.EMPTY && + this.getPiece(x, y) != V.PAWN + ) { + m.vanish.push( + new PiPo({ + p: this.getPiece(x, y), + c: this.getColor(x, y), + x: x, + y: y + }) + ); + } + } + m.end = { x: m.appear[0].x, y: m.appear[0].y }; + m.appear.pop(); //Nothin appears in this case + } + }); + return moves; + } + + getPotentialKingMoves([x, y]) { + // King cannot capture: + let moves = []; + const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]); + for (let step of steps) { + const i = x + step[0]; + const j = y + step[1]; + if (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) + moves.push(this.getBasicMove([x, y], [i, j])); + } + return moves.concat(this.getCastleMoves([x, y])); + } + + isAttacked(sq, color) { + if ( + this.getPiece(sq[0], sq[1]) == V.KING && + this.isAttackedByKing(sq, color) + ) { + // A king next to the enemy king is immune to attacks + return false; + } + return ( + this.isAttackedByPawn(sq, color) || + this.isAttackedByRook(sq, color) || + this.isAttackedByKnight(sq, color) || + this.isAttackedByBishop(sq, color) || + this.isAttackedByQueen(sq, color) + // No "attackedByKing": it cannot take + ); + } + + postPlay(move) { + super.postPlay(move); + // NOTE: (harmless) condition on movesCount for Atomic2 + if (move.appear.length == 0 && this.movesCount >= 2) { + // Capture + const firstRank = { w: 7, b: 0 }; + for (let c of ["w", "b"]) { + // Did we explode king of color c ? (TODO: remove move earlier) + if ( + Math.abs(this.kingPos[c][0] - move.end.x) <= 1 && + Math.abs(this.kingPos[c][1] - move.end.y) <= 1 + ) { + this.kingPos[c] = [-1, -1]; + this.castleFlags[c] = [8, 8]; + } + else { + // Now check if init rook(s) exploded + if (Math.abs(move.end.x - firstRank[c]) <= 1) { + if (Math.abs(move.end.y - this.castleFlags[c][0]) <= 1) + this.castleFlags[c][0] = 8; + if (Math.abs(move.end.y - this.castleFlags[c][1]) <= 1) + this.castleFlags[c][1] = 8; + } + } + } + } + } + + postUndo(move) { + super.postUndo(move); + const c = this.turn; + const oppCol = V.GetOppCol(c); + // NOTE: condition on movesCount for balanced setting + if ( + this.movesCount >= 1 && + [this.kingPos[c][0], this.kingPos[oppCol][0]].some(e => e < 0) + ) { + // There is a chance that last move blowed some king away.. + for (let psq of move.vanish) { + if (psq.p == "k") + this.kingPos[psq.c == c ? c : oppCol] = [psq.x, psq.y]; + } + } + } + + underCheck(color) { + const oppCol = V.GetOppCol(color); + let res = undefined; + // If our king disappeared, move is not valid + if (this.kingPos[color][0] < 0) res = true; + // If opponent king disappeared, move is valid + else if (this.kingPos[oppCol][0] < 0) res = false; + // Otherwise, if we remain under check, move is not valid + else res = this.isAttacked(this.kingPos[color], oppCol); + return res; + } + + getCheckSquares() { + const color = this.turn; + let res = []; + if ( + this.kingPos[color][0] >= 0 && //king might have exploded + this.isAttacked(this.kingPos[color], V.GetOppCol(color)) + ) { + res = [JSON.parse(JSON.stringify(this.kingPos[color]))]; + } + return res; + } + + getCurrentScore() { + const color = this.turn; + const kp = this.kingPos[color]; + if (kp[0] < 0) + // King disappeared + return color == "w" ? "0-1" : "1-0"; + if (this.atLeastOneMove()) return "*"; + if (!this.isAttacked(kp, V.GetOppCol(color))) return "1/2"; + return color == "w" ? "0-1" : "1-0"; //checkmate + } + + getNotation(move) { + if (move.appear.length == 0 && move.vanish.length == 1) + // First move in game (balanced == true) + return V.CoordsToSquare(move.start) + "X"; + return super.getNotation(move); + } + +};