export class XiangqiRules extends ChessRules {
+ static get Options() {
+ return null;
+ }
+
+ // NOTE (TODO?) scanKings() could be more efficient (in Jangqi too)
+
static get Monochrome() {
return true;
}
return false;
}
+ static get LoseOnRepetition() {
+ return true;
+ }
+
static get ELEPHANT() {
return "e";
}
}
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]) {
if (y > 0) steps.push([0, -1]);
if (y < 9) steps.push([0, 1]);
}
- return super.getSlideNJumpMoves([x, y], steps, "oneStep");
+ return super.getSlideNJumpMoves([x, y], steps, 1);
}
knightStepsFromRookStep(step) {
this.knightStepsFromRookStep(rookStep));
}
}
- return super.getSlideNJumpMoves([x, y], steps, "oneStep");
+ return super.getSlideNJumpMoves([x, y], steps, 1);
}
getPotentialElephantMoves([x, y]) {
// "out of board" checks delayed to next method
}
}
- return super.getSlideNJumpMoves([x, y], steps, "oneStep");
- }
-
- insidePalace(x, y, c) {
- return (
- (y >= 3 && y <= 5) &&
- (
- (c == 'w' && x >= 7) ||
- (c == 'b' && x <= 2)
- )
- );
+ return super.getSlideNJumpMoves([x, y], steps, 1);
}
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], 1);
}
- return super.getSlideNJumpMoves([x, y], steps, "oneStep");
+ // In the middle of the palace:
+ return (
+ super.getSlideNJumpMoves([x, y], ChessRules.steps[V.BISHOP], 1)
+ );
}
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, 1);
}
- return super.getSlideNJumpMoves([x, y], steps, "oneStep");
+ // In the middle of the palace:
+ return (
+ super.getSlideNJumpMoves([x, y], ChessRules.steps[V.ROOK], 1)
+ );
}
// NOTE: duplicated from Shako (TODO?)
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]], 1);
}
knightStepsFromBishopStep(step) {
}
}
return (
- super.isAttackedBySlideNJump([x, y], color, V.KNIGHT, steps, "oneStep")
+ super.isAttackedBySlideNJump([x, y], color, V.KNIGHT, steps, 1)
);
}
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,
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" + notation.substr(1);
+ return notation;
+ }
+
};