+ const oppCol = V.GetOppCol(color);
+ let potentialMoves = [];
+ for (let i = 0; i < V.size.x; i++) {
+ for (let j = 0; j < V.size.y; j++) {
+ const colIJ = this.getColor(i, j);
+ if (
+ this.board[i][j] != V.EMPTY &&
+ (
+ (this.stage == 1 && colIJ != oppCol) ||
+ (this.stage == 2 &&
+ (
+ (this.sideCheckered == color && colIJ == 'c') ||
+ (this.sideCheckered != color && ['w', 'b'].includes(colIJ))
+ )
+ )
+ )
+ ) {
+ Array.prototype.push.apply(
+ potentialMoves,
+ this.getPotentialMovesFrom([i, j])
+ );
+ }
+ }
+ }
+ return potentialMoves;
+ }
+
+ atLeastOneMove() {
+ const color = this.turn;
+ const oppCol = V.GetOppCol(color);
+ for (let i = 0; i < V.size.x; i++) {
+ for (let j = 0; j < V.size.y; j++) {
+ const colIJ = this.getColor(i, j);
+ if (
+ this.board[i][j] != V.EMPTY &&
+ (
+ (this.stage == 1 && colIJ != oppCol) ||
+ (this.stage == 2 &&
+ (
+ (this.sideCheckered == color && colIJ == 'c') ||
+ (this.sideCheckered != color && ['w', 'b'].includes(colIJ))
+ )
+ )
+ )
+ ) {
+ const moves = this.getPotentialMovesFrom([i, j], "noswitch");
+ if (moves.length > 0) {
+ for (let k = 0; k < moves.length; k++)
+ if (this.filterValid([moves[k]]).length > 0) return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ // colors: array, 'w' and 'c' or 'b' and 'c' at stage 1,
+ // just 'c' (or unused) at stage 2
+ isAttacked(sq, colors) {
+ if (!Array.isArray(colors)) colors = [colors];
+ return (
+ this.isAttackedByPawn(sq, colors) ||
+ this.isAttackedByRook(sq, colors) ||
+ this.isAttackedByKnight(sq, colors) ||
+ this.isAttackedByBishop(sq, colors) ||
+ this.isAttackedByQueen(sq, colors) ||
+ this.isAttackedByKing(sq, colors)
+ );
+ }
+
+ isAttackedByPawn([x, y], colors) {
+ for (let c of colors) {
+ let shifts = [];
+ if (this.stage == 1) {
+ const color = (c == "c" ? this.turn : c);
+ shifts = [color == "w" ? 1 : -1];
+ }
+ else {
+ // Stage 2: checkered pawns are bidirectional
+ if (c == 'c') shifts = [-1, 1];
+ else shifts = [c == "w" ? 1 : -1];
+ }
+ for (let pawnShift of shifts) {
+ if (x + pawnShift >= 0 && x + pawnShift < 8) {
+ for (let i of [-1, 1]) {
+ if (
+ y + i >= 0 &&
+ y + i < 8 &&
+ this.getPiece(x + pawnShift, y + i) == V.PAWN &&
+ this.getColor(x + pawnShift, y + i) == c
+ ) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ isAttackedBySlideNJump([x, y], colors, piece, steps, oneStep) {
+ for (let step of steps) {
+ let rx = x + step[0],
+ ry = y + step[1];
+ while (V.OnBoard(rx, ry) && this.board[rx][ry] == V.EMPTY && !oneStep) {
+ rx += step[0];
+ ry += step[1];
+ }
+ if (
+ V.OnBoard(rx, ry) &&
+ this.getPiece(rx, ry) === piece &&
+ colors.includes(this.getColor(rx, ry))
+ ) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ isAttackedByRook(sq, colors) {
+ return this.isAttackedBySlideNJump(sq, colors, V.ROOK, V.steps[V.ROOK]);
+ }
+
+ isAttackedByKnight(sq, colors) {
+ return this.isAttackedBySlideNJump(
+ sq, colors, V.KNIGHT, V.steps[V.KNIGHT], "oneStep");
+ }
+
+ isAttackedByBishop(sq, colors) {
+ return this.isAttackedBySlideNJump(
+ sq, colors, V.BISHOP, V.steps[V.BISHOP]);
+ }
+
+ isAttackedByQueen(sq, colors) {
+ return this.isAttackedBySlideNJump(
+ sq, colors, V.QUEEN, V.steps[V.ROOK].concat(V.steps[V.BISHOP]));
+ }
+
+ isAttackedByKing(sq, colors) {
+ return this.isAttackedBySlideNJump(
+ sq, colors, V.KING,
+ V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep"
+ );
+ }
+
+ underCheck(color) {
+ if (this.stage == 1)
+ return this.isAttacked(this.kingPos[color], [V.GetOppCol(color), "c"]);
+ if (color == this.sideCheckered) return false;
+ return (
+ this.isAttacked(this.kingPos['w'], ["c"]) ||
+ this.isAttacked(this.kingPos['b'], ["c"])
+ );
+ }
+
+ getCheckSquares() {
+ const color = this.turn;
+ if (this.stage == 1) {
+ // Artifically change turn, for checkered pawns
+ this.turn = V.GetOppCol(color);
+ const kingAttacked =
+ this.isAttacked(
+ this.kingPos[color],
+ [V.GetOppCol(color), "c"]
+ );
+ let res = kingAttacked
+ ? [JSON.parse(JSON.stringify(this.kingPos[color]))]
+ : [];
+ this.turn = color;
+ return res;
+ }
+ if (this.sideCheckered == color) return [];
+ let res = [];
+ for (let c of ['w', 'b']) {
+ if (this.isAttacked(this.kingPos[c], ['c']))
+ res.push(JSON.parse(JSON.stringify(this.kingPos[c])));
+ }
+ return res;
+ }
+
+ play(move) {
+ move.flags = JSON.stringify(this.aggregateFlags());
+ this.epSquares.push(this.getEpSquare(move));
+ V.PlayOnBoard(this.board, move);
+ if (move.appear.length > 0 || move.vanish.length > 0)
+ {
+ this.turn = V.GetOppCol(this.turn);
+ this.movesCount++;
+ }
+ this.postPlay(move);
+ }
+
+ postPlay(move) {
+ if (move.appear.length == 0 && move.vanish.length == 0) {
+ this.stage = 2;
+ this.sideCheckered = this.turn;
+ }
+ else {
+ const c = move.vanish[0].c;
+ const piece = move.vanish[0].p;
+ if (piece == V.KING) {
+ this.kingPos[c][0] = move.appear[0].x;
+ this.kingPos[c][1] = move.appear[0].y;
+ }
+ super.updateCastleFlags(move, piece);
+ if (
+ [1, 6].includes(move.start.x) &&
+ move.vanish[0].p == V.PAWN &&
+ Math.abs(move.end.x - move.start.x) == 2
+ ) {
+ // This move turns off a 2-squares pawn flag
+ this.pawnFlags[move.start.x == 6 ? "w" : "b"][move.start.y] = false;
+ }
+ }
+ this.cmoves.push(this.getCmove(move));
+ }
+
+ undo(move) {
+ this.epSquares.pop();
+ this.disaggregateFlags(JSON.parse(move.flags));
+ V.UndoOnBoard(this.board, move);
+ if (move.appear.length > 0 || move.vanish.length > 0)
+ {
+ this.turn = V.GetOppCol(this.turn);
+ this.movesCount--;
+ }
+ this.postUndo(move);
+ }
+
+ postUndo(move) {
+ if (move.appear.length == 0 && move.vanish.length == 0) this.stage = 1;
+ else super.postUndo(move);
+ this.cmoves.pop();
+ }
+
+ getCurrentScore() {
+ const color = this.turn;
+ if (this.stage == 1) {
+ if (this.atLeastOneMove()) return "*";
+ // Artifically change turn, for checkered pawns
+ this.turn = V.GetOppCol(this.turn);
+ const res =
+ this.isAttacked(this.kingPos[color], [V.GetOppCol(color), "c"])
+ ? color == "w"
+ ? "0-1"
+ : "1-0"
+ : "1/2";
+ this.turn = V.GetOppCol(this.turn);
+ return res;
+ }
+ // Stage == 2:
+ if (this.sideCheckered == this.turn) {
+ // Check if remaining checkered pieces: if none, I lost
+ if (this.board.some(b => b.some(cell => cell[0] == 'c'))) {
+ if (!this.atLeastOneMove()) return "1/2";
+ return "*";
+ }
+ return color == 'w' ? "0-1" : "1-0";
+ }
+ if (this.atLeastOneMove()) return "*";
+ let res = this.isAttacked(this.kingPos['w'], ["c"]);
+ if (!res) res = this.isAttacked(this.kingPos['b'], ["c"]);
+ if (res) return color == 'w' ? "0-1" : "1-0";
+ return "1/2";
+ }
+
+ evalPosition() {
+ let evaluation = 0;
+ // Just count material for now, considering checkered neutral at stage 1.
+ const baseSign = (this.turn == 'w' ? 1 : -1);
+ for (let i = 0; i < V.size.x; i++) {
+ for (let j = 0; j < V.size.y; j++) {
+ if (this.board[i][j] != V.EMPTY) {
+ const sqColor = this.getColor(i, j);
+ if (this.stage == 1) {
+ if (["w", "b"].includes(sqColor)) {
+ const sign = sqColor == "w" ? 1 : -1;
+ evaluation += sign * V.VALUES[this.getPiece(i, j)];
+ }
+ }
+ else {
+ const sign =
+ this.sideCheckered == this.turn
+ ? (sqColor == 'c' ? 1 : -1) * baseSign
+ : (sqColor == 'c' ? -1 : 1) * baseSign;
+ evaluation += sign * V.VALUES[this.getPiece(i, j)];
+ }
+ }
+ }
+ }
+ return evaluation;
+ }
+
+ static GenRandInitFen(options) {
+ const baseFen = ChessRules.GenRandInitFen(options.randomness);
+ return (
+ // Add 16 pawns flags + empty cmove + stage == 1:
+ baseFen.slice(0, -2) + "1111111111111111 - - 1" +
+ (!options["switch"] ? '-' : "")
+ );
+ }
+
+ static ParseFen(fen) {
+ const fenParts = fen.split(" ");
+ return Object.assign(