X-Git-Url: https://git.auder.net/?a=blobdiff_plain;f=client%2Fsrc%2Fvariants%2FXiangqi.js;h=b3024332b68c22c281461018fef6f9225167aa1f;hb=dbc79ee67847c36aad6b640b15d25d6fb7f361e5;hp=3889b20a78deb4319a7e5d4bc7b8d5773549a76c;hpb=7e107b8f9dc9a4e7d9676fd2c48ba19f6cfc1661;p=vchess.git diff --git a/client/src/variants/Xiangqi.js b/client/src/variants/Xiangqi.js index 3889b20a..b3024332 100644 --- a/client/src/variants/Xiangqi.js +++ b/client/src/variants/Xiangqi.js @@ -2,6 +2,8 @@ import { ChessRules } from "@/base_rules"; export class XiangqiRules extends ChessRules { + // NOTE (TODO?) scanKings() could be more efficient (in Jangqi too) + static get Monochrome() { return true; } @@ -36,6 +38,10 @@ export class XiangqiRules extends ChessRules { return false; } + static get LoseOnRepetition() { + return true; + } + static get ELEPHANT() { return "e"; } @@ -61,16 +67,80 @@ export class XiangqiRules extends ChessRules { } getPotentialMovesFrom(sq) { - switch (this.getPiece(sq[0], sq[1])) { - case V.PAWN: return this.getPotentialPawnMoves(sq); - case V.ROOK: return super.getPotentialRookMoves(sq); - case V.KNIGHT: return this.getPotentialKnightMoves(sq); - case V.ELEPHANT: return this.getPotentialElephantMoves(sq); - case V.ADVISOR: return this.getPotentialAdvisorMoves(sq); - case V.KING: return this.getPotentialKingMoves(sq); - case V.CANNON: return this.getPotentialCannonMoves(sq); + let moves = []; + const piece = this.getPiece(sq[0], sq[1]); + switch (piece) { + case V.PAWN: + moves = this.getPotentialPawnMoves(sq); + break; + case V.ROOK: + moves = super.getPotentialRookMoves(sq); + break; + case V.KNIGHT: + moves = this.getPotentialKnightMoves(sq); + break; + case V.ELEPHANT: + moves = this.getPotentialElephantMoves(sq); + break; + case V.ADVISOR: + moves = this.getPotentialAdvisorMoves(sq); + break; + case V.KING: + moves = this.getPotentialKingMoves(sq); + break; + case V.CANNON: + moves = this.getPotentialCannonMoves(sq); + break; } - return []; //never reached + if (piece != V.KING && this.kingPos['w'][1] != this.kingPos['b'][1]) + return moves; + if (this.kingPos['w'][1] == this.kingPos['b'][1]) { + const colKing = this.kingPos['w'][1]; + let intercept = 0; //count intercepting pieces + for (let i = this.kingPos['b'][0] + 1; i < this.kingPos['w'][0]; i++) { + if (this.board[i][colKing] != V.EMPTY) intercept++; + } + if (intercept >= 2) return moves; + // intercept == 1 (0 is impossible): + // Any move not removing intercept is OK + return moves.filter(m => { + return ( + // From another column? + m.start.y != colKing || + // From behind a king? (including kings themselves!) + m.start.x <= this.kingPos['b'][0] || + m.start.x >= this.kingPos['w'][0] || + // Intercept piece moving: must remain in-between + ( + m.end.y == colKing && + m.end.x > this.kingPos['b'][0] && + m.end.x < this.kingPos['w'][0] + ) + ); + }); + } + // piece == king: check only if move.end.y == enemy king column + const color = this.getColor(sq[0], sq[1]); + const oppCol = V.GetOppCol(color); + // colCheck == -1 if unchecked, 1 if checked and occupied, + // 0 if checked and clear + let colCheck = -1; + return moves.filter(m => { + if (m.end.y != this.kingPos[oppCol][1]) return true; + if (colCheck < 0) { + // Do the check: + colCheck = 0; + for (let i = this.kingPos['b'][0] + 1; i < this.kingPos['w'][0]; i++) { + if (this.board[i][m.end.y] != V.EMPTY) { + colCheck++; + break; + } + } + return colCheck == 1; + } + // Check already done: + return colCheck == 1; + }); } getPotentialPawnMoves([x, y]) { @@ -121,34 +191,55 @@ export class XiangqiRules extends ChessRules { return super.getSlideNJumpMoves([x, y], steps, "oneStep"); } - insidePalace(x, y, c) { - return ( - (y >= 3 && y <= 5) && - ( - (c == 'w' && x >= 7) || - (c == 'b' && x <= 2) - ) - ); - } - getPotentialAdvisorMoves([x, y]) { // Diagonal steps inside palace - let steps = []; const c = this.getColor(x, y); - for (let s of ChessRules.steps[V.BISHOP]) { - if (this.insidePalace(x + s[0], y + s[1], c)) steps.push(s); + if ( + y != 4 || + (c == 'w' && x != V.size.x - 2) || + (c == 'b' && x != 1) + ) { + // In a corner: only one step available + let step = null; + const direction = (c == 'w' ? -1 : 1); + if ((c == 'w' && x == V.size.x - 1) || (c == 'b' && x == 0)) { + // On first line + if (y == 3) step = [direction, 1]; + else step = [direction, -1]; + } + else { + // On third line + if (y == 3) step = [-direction, 1]; + else step = [-direction, -1]; + } + return super.getSlideNJumpMoves([x, y], [step], "oneStep"); } - return super.getSlideNJumpMoves([x, y], steps, "oneStep"); + // In the middle of the palace: + return ( + super.getSlideNJumpMoves([x, y], ChessRules.steps[V.BISHOP], "oneStep") + ); } getPotentialKingMoves([x, y]) { // Orthogonal steps inside palace - let steps = []; const c = this.getColor(x, y); - for (let s of ChessRules.steps[V.ROOK]) { - if (this.insidePalace(x + s[0], y + s[1], c)) steps.push(s); + if ( + y != 4 || + (c == 'w' && x != V.size.x - 2) || + (c == 'b' && x != 1) + ) { + // On the edge: only two steps available + let steps = []; + if (x < (c == 'w' ? V.size.x - 1 : 2)) steps.push([1, 0]); + if (x > (c == 'w' ? V.size.x - 3 : 0)) steps.push([-1, 0]); + if (y > 3) steps.push([0, -1]); + if (y < 5) steps.push([0, 1]); + return super.getSlideNJumpMoves([x, y], steps, "oneStep"); } - return super.getSlideNJumpMoves([x, y], steps, "oneStep"); + // In the middle of the palace: + return ( + super.getSlideNJumpMoves([x, y], ChessRules.steps[V.ROOK], "oneStep") + ); } // NOTE: duplicated from Shako (TODO?) @@ -191,17 +282,8 @@ export class XiangqiRules extends ChessRules { isAttackedByPawn([x, y], color) { // The pawn necessarily crossed the river (attack on king) const shiftX = (color == 'w' ? 1 : -1); //shift from king - for (let s of [[shiftX, 0], [0, 1], [0, -1]]) { - const [i, j] = [x + s[0], y + s[1]]; - if ( - this.board[i][j] != V.EMPTY && - this.getColor(i, j) == color && - this.getPiece(i, j) == V.PAWN - ) { - return true; - } - } - return false; + return super.isAttackedBySlideNJump( + [x, y], color, V.PAWN, [[shiftX, 0], [0, 1], [0, -1]], "oneStep"); } knightStepsFromBishopStep(step) { @@ -255,6 +337,14 @@ export class XiangqiRules extends ChessRules { return false; } + getCurrentScore() { + if (this.atLeastOneMove()) return "*"; + // Game over + const color = this.turn; + // No valid move: I lose! + return (color == "w" ? "0-1" : "1-0"); + } + static get VALUES() { return { p: 1, @@ -293,9 +383,20 @@ export class XiangqiRules extends ChessRules { return evaluation; } + static get SEARCH_DEPTH() { + return 2; + } + static GenRandInitFen() { // No randomization here (TODO?) return "rneakaenr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNEAKAENR w 0"; } + getNotation(move) { + let notation = super.getNotation(move); + if (move.vanish.length == 2 && move.vanish[0].p == V.PAWN) + notation = "P" + substr(notation, 1); + return notation; + } + };