From ceeac4e82346ffba2d87d763df3cffcddaec912a Mon Sep 17 00:00:00 2001
From: Benjamin Auder <benjamin.auder@somewhere>
Date: Mon, 23 Jan 2023 20:08:42 +0100
Subject: [PATCH] Checkered: fixing attempt (untested) + refactor/generalize
 oppCol oppTurn logic

---
 base_rules.js               | 94 ++++++++++++++++++-------------------
 variants.js                 |  2 +-
 variants/Checkered/class.js | 80 ++++++++++++++++---------------
 3 files changed, 89 insertions(+), 87 deletions(-)

diff --git a/base_rules.js b/base_rules.js
index a0defd1..aa499f8 100644
--- a/base_rules.js
+++ b/base_rules.js
@@ -470,7 +470,7 @@ export default class ChessRules {
   }
 
   getRankInReserve(c, p) {
-    const pieces = Object.keys(this.pieces());
+    const pieces = Object.keys(this.pieces(c, c, p));
     const lastIndex = pieces.findIndex(pp => pp == p)
     let toTest = pieces.slice(0, lastIndex);
     return toTest.reduce(
@@ -519,7 +519,6 @@ export default class ChessRules {
 
   re_drawBoardElements() {
     const board = this.getSvgChessboard();
-    const oppCol = C.GetOppCol(this.playerColor);
     const container = document.getElementById(this.containerId);
     const rc = container.getBoundingClientRect();
     let chessboard = container.querySelector(".chessboard");
@@ -1065,7 +1064,8 @@ export default class ChessRules {
   // Include square of the en-passant capturing square:
   enlightEnpassant() {
     // NOTE: shortcut, pawn has only one attack type, doesn't depend on square
-    const steps = this.pieces(this.playerColor)["p"].attack[0].steps;
+    // TODO: (0, 0) is wrong, would need to place an attacker here...
+    const steps = this.pieces(this.playerColor, 0, 0)["p"].attack[0].steps;
     for (let step of steps) {
       const x = this.epSquare.x - step[0],
             y = this.getY(this.epSquare.y - step[1]);
@@ -1141,7 +1141,7 @@ export default class ChessRules {
   getPieceType(x, y, p) {
     if (!p)
       p = this.getPiece(x, y);
-    return this.pieces()[p].moveas || p;
+    return this.pieces(this.getColor(x, y), x, y)[p].moveas || p;
   }
 
   isKing(x, y, p) {
@@ -1152,8 +1152,12 @@ export default class ChessRules {
     return !!C.CannibalKings[p];
   }
 
-  // Get opponent color
-  static GetOppCol(color) {
+  static GetOppTurn(color) {
+    return (color == 'w' ? 'b' : 'w');
+  }
+
+  // Get opponent color(s): may differ from turn (e.g. Checkered)
+  getOppCols(color) {
     return (color == "w" ? "b" : "w");
   }
 
@@ -1340,7 +1344,7 @@ export default class ChessRules {
     if (!this.options["madrasi"])
       return false;
     const color = this.getColor(x, y);
-    const oppCol = C.GetOppCol(color);
+    const oppCols = this.getOppCols(color);
     const piece = this.getPieceType(x, y);
     const stepSpec = this.getStepSpec(color, x, y, piece);
     const attacks = stepSpec.both.concat(stepSpec.attack);
@@ -1356,7 +1360,7 @@ export default class ChessRules {
         }
         if (
           this.onBoard(i, j) &&
-          this.getColor(i, j) == oppCol &&
+          oppCols.includes(this.getColor(i, j)) &&
           this.getPieceType(i, j) == piece
         ) {
           return true;
@@ -1368,7 +1372,6 @@ export default class ChessRules {
 
   // Stop at the first capture found
   atLeastOneCapture(color) {
-    const oppCol = C.GetOppCol(color);
     const allowed = (sq1, sq2) => {
       return (
         // NOTE: canTake is reversed for Zen.
@@ -1504,19 +1507,19 @@ export default class ChessRules {
     if (moves.length == 0)
       return [];
     const color = this.getColor(moves[0].start.x, moves[0].start.y);
-    const oppCol = C.GetOppCol(color);
+    const oppCols = this.getOppCols(color);
 
     if (this.options["capture"] && this.atLeastOneCapture(color))
-      moves = this.capturePostProcess(moves, oppCol);
+      moves = this.capturePostProcess(moves, oppCols);
 
     if (this.options["atomic"])
-      moves = this.atomicPostProcess(moves, color, oppCol);
+      moves = this.atomicPostProcess(moves, color, oppCols);
 
     if (
       moves.length > 0 &&
       this.getPieceType(moves[0].start.x, moves[0].start.y) == "p"
     ) {
-      moves = this.pawnPostProcess(moves, color, oppCol);
+      moves = this.pawnPostProcess(moves, color, oppCols);
     }
 
     if (this.options["cannibal"] && this.options["rifle"])
@@ -1526,22 +1529,22 @@ export default class ChessRules {
     return moves;
   }
 
-  capturePostProcess(moves, oppCol) {
+  capturePostProcess(moves, oppCols) {
     // Filter out non-capturing moves (not using m.vanish because of
     // self captures of Recycle and Teleport).
     return moves.filter(m => {
       return (
         this.board[m.end.x][m.end.y] != "" &&
-        this.getColor(m.end.x, m.end.y) == oppCol
+        oppCols.includes(this.getColor(m.end.x, m.end.y))
       );
     });
   }
 
-  atomicPostProcess(moves, color, oppCol) {
+  atomicPostProcess(moves, color, oppCols) {
     moves.forEach(m => {
       if (
         this.board[m.end.x][m.end.y] != "" &&
-        this.getColor(m.end.x, m.end.y) == oppCol
+        oppCols.includes(this.getColor(m.end.x, m.end.y))
       ) {
         // Explosion!
         let steps = [
@@ -1596,7 +1599,7 @@ export default class ChessRules {
     return moves;
   }
 
-  pawnPostProcess(moves, color, oppCol) {
+  pawnPostProcess(moves, color, oppCols) {
     let moreMoves = [];
     const lastRank = (color == "w" ? 0 : this.size.x - 1);
     const initPiece = this.getPiece(moves[0].start.x, moves[0].start.y);
@@ -1617,7 +1620,7 @@ export default class ChessRules {
       if (
         this.options["cannibal"] &&
         this.board[x2][y2] != "" &&
-        this.getColor(x2, y2) == oppCol
+        oppCols.includes(this.getColor(x2, y2))
       ) {
         finalPieces = [this.getPieceType(x2, y2)];
       }
@@ -1819,7 +1822,7 @@ export default class ChessRules {
   // Search for enemy (or not) pieces attacking [x, y]
   findCapturesOn([x, y], o, allowed) {
     if (!o.byCol)
-      o.byCol = [C.GetOppCol(this.getColor(x, y) || this.turn)];
+      o.byCol = this.getOppCols(this.getColor(x, y) || this.turn);
     let res = [];
     for (let i=0; i<this.size.x; i++) {
       for (let j=0; j<this.size.y; j++) {
@@ -1976,17 +1979,17 @@ export default class ChessRules {
   getEnpassantCaptures([x, y]) {
     const color = this.getColor(x, y);
     const shiftX = (color == 'w' ? -1 : 1);
-    const oppCol = C.GetOppCol(color);
+    const oppCols = this.getOppCols(color);
     if (
       this.epSquare &&
       this.epSquare.x == x + shiftX &&
       Math.abs(this.getY(this.epSquare.y - y)) == 1 &&
       // Doublemove (and Progressive?) guards:
       this.board[this.epSquare.x][this.epSquare.y] == "" &&
-      this.getColor(x, this.epSquare.y) == oppCol
+      oppCols.includes(this.getColor(x, this.epSquare.y))
     ) {
       const [epx, epy] = [this.epSquare.x, this.epSquare.y];
-      this.board[epx][epy] = oppCol + 'p';
+      this.board[epx][epy] = this.board[x][this.epSquares.y];
       let enpassantMove = this.getBasicMove([x, y], [epx, epy]);
       this.board[epx][epy] = "";
       const lastIdx = enpassantMove.vanish.length - 1; //think Rifle
@@ -2000,7 +2003,7 @@ export default class ChessRules {
     const c = this.getColor(x, y);
 
     // Castling ?
-    const oppCol = C.GetOppCol(c);
+    const oppCols = this.getOppCols(c);
     let moves = [];
     // King, then rook:
     finalSquares =
@@ -2036,7 +2039,7 @@ export default class ChessRules {
           // will be executed in filterValid() later.
           (
             i != finalSquares[castleSide][0] &&
-            this.underCheck([[x, i]], oppCol)
+            this.underCheck([[x, i]], oppCols)
           )
           ||
           (
@@ -2107,9 +2110,7 @@ export default class ChessRules {
   // MOVES VALIDATION
 
   // Is piece (or square) at given position attacked by "oppCol(s)" ?
-  underAttack([x, y], oppCol) {
-    if (!Array.isArray(oppCol))
-      oppCol = [oppCol];
+  underAttack([x, y], oppCols) {
     // An empty square is considered as king,
     // since it's used only in getCastleMoves (TODO?)
     const king = this.board[x][y] == "" || this.isKing(x, y);
@@ -2119,7 +2120,7 @@ export default class ChessRules {
         this.findCapturesOn(
           [x, y],
           {
-            byCol: oppCol,
+            byCol: oppCols,
             segments: this.options["cylinder"],
             one: true
           }
@@ -2135,17 +2136,17 @@ export default class ChessRules {
             segments: this.options["cylinder"],
             one: true
           },
-          ([i1, j1], [i2, j2]) => oppCol.includes(this.getColor(i2, j2))
+          ([i1, j1], [i2, j2]) => oppCols.includes(this.getColor(i2, j2))
         )
       )
     );
   }
 
   // Argument is (very generally) an array of squares (= arrays)
-  underCheck(square_s, oppCol) {
+  underCheck(square_s, oppCols) {
     if (this.options["taking"] || this.options["dark"])
       return false;
-    return square_s.some(sq => this.underAttack(sq, oppCol));
+    return square_s.some(sq => this.underAttack(sq, oppCols));
   }
 
   // Scan board for king(s)
@@ -2164,7 +2165,7 @@ export default class ChessRules {
   filterValid(moves, color) {
     if (!color)
       color = this.turn;
-    const oppCol = C.GetOppCol(color);
+    const oppCols = this.getOppCols(color);
     let kingPos = this.searchKingPos(color);
     let filtered = {}; //avoid re-checking similar moves (promotions...)
     return moves.filter(m => {
@@ -2188,7 +2189,7 @@ export default class ChessRules {
           else
             res = false; //king vanished
         }
-        res &&= !this.underCheck(kingPos, oppCol);
+        res &&= !this.underCheck(kingPos, oppCols);
         if (oldKingPP && newKingPP)
           kingPos[sqIdx] = [oldKingPP.x, oldKingPP.y];
         this.undoOnBoard(m);
@@ -2327,7 +2328,7 @@ export default class ChessRules {
 
   tryChangeTurn(move) {
     if (this.isLastMove(move)) {
-      this.turn = C.GetOppCol(this.turn);
+      this.turn = (this.turn == 'w' ? 'b' : 'w');
       this.movesCount++;
       this.subTurn = 1;
     }
@@ -2339,8 +2340,8 @@ export default class ChessRules {
     if (move.next)
       return false;
     const color = this.turn;
-    const oppKingPos = this.searchKingPos(C.GetOppCol(color));
-    if (oppKingPos.length == 0 || this.underCheck(oppKingPos, color))
+    const oppKingPos = this.searchKingPos(C.GetOppTurn(color));
+    if (oppKingPos.length == 0 || this.underCheck(oppKingPos, [color]))
       return true;
     return (
       (
@@ -2391,25 +2392,24 @@ export default class ChessRules {
     // Shortcut in case the score was computed before:
     if (move.result)
       return move.result;
-    const color = this.turn;
-    const oppCol = C.GetOppCol(color);
+    const oppTurn = C.GetOppTurn(this.turn);
     const kingPos = {
-      [color]: this.searchKingPos(color),
-      [oppCol]: this.searchKingPos(oppCol)
+      w: this.searchKingPos('w'),
+      b: this.searchKingPos('b')
     };
-    if (kingPos[color].length == 0 && kingPos[oppCol].length == 0)
+    if (kingPos[this.turn].length == 0 && kingPos[oppTurn].length == 0)
       return "1/2";
-    if (kingPos[color].length == 0)
+    if (kingPos[this.turn].length == 0)
       return (color == "w" ? "0-1" : "1-0");
-    if (kingPos[oppCol].length == 0)
+    if (kingPos[oppTurn].length == 0)
       return (color == "w" ? "1-0" : "0-1");
-    if (this.atLeastOneMove(color))
+    if (this.atLeastOneMove(this.turn))
       return "*";
     // No valid move: stalemate or checkmate?
-    if (!this.underCheck(kingPos[color], oppCol))
+    if (!this.underCheck(kingPos[this.turn], this.getOppCols(this.turn)))
       return "1/2";
     // OK, checkmate
-    return (color == "w" ? "0-1" : "1-0");
+    return (this.turn == "w" ? "0-1" : "1-0");
   }
 
   playVisual(move, r) {
diff --git a/variants.js b/variants.js
index cd5e55a..ef297b6 100644
--- a/variants.js
+++ b/variants.js
@@ -26,7 +26,7 @@ const variants = [
   {name: 'Capablanca', desc: 'Capablanca Chess', disp: 'Capablanca'},
   {name: 'Capture', desc: 'Mandatory captures'},
   {name: 'Chakart', desc: 'Capture the princess'},
-//  {name: 'Checkered', desc: 'Shared pieces'},
+  {name: 'Checkered', desc: 'Shared pieces'},
 //  {name: 'Checkless', desc: 'No-check mode'},
   {name: 'Chess960', disp: "Chess 960", desc: "Standard rules"},
 //  {name: 'Circular', desc: 'Run forward'},
diff --git a/variants/Checkered/class.js b/variants/Checkered/class.js
index 9d9bf24..9c78243 100644
--- a/variants/Checkered/class.js
+++ b/variants/Checkered/class.js
@@ -61,10 +61,13 @@ export default class CheckeredRules extends ChessRules {
   }
 
   getPartFen(o) {
-    let parts = super.getPartFen(o);
-    parts["cmove"] = this.getCmoveFen();
-    parts["stage"] = this.getStageFen();
-    return parts;
+    return Object.assign(
+      {
+        "cmove": o.init ? "-" : this.getCmoveFen(),
+        "stage": o.init ? "1" : this.getStageFen()
+      },
+      super.getPartFen(o)
+    );
   }
 
   getCmoveFen() {
@@ -82,9 +85,10 @@ export default class CheckeredRules extends ChessRules {
   getFlagsFen() {
     let fen = super.getFlagsFen();
     // Add pawns flags
-    for (let c of ["w", "b"])
+    for (let c of ["w", "b"]) {
       for (let i = 0; i < 8; i++)
         fen += (this.pawnFlags[c][i] ? "1" : "0");
+    }
     return fen;
   }
 
@@ -92,6 +96,15 @@ export default class CheckeredRules extends ChessRules {
     return super.getPawnShift(color == 'c' ? this.turn : color);
   }
 
+  getOppCols(color) {
+    if (this.stage == 1)
+      return super.getOppCols(color).concat(['c']);
+    // Stage 2: depends if color is w+b or checkered
+    if (color == this.sideCheckered)
+      return ['w', 'b'];
+    return ['c'];
+  }
+
   pieces(color, x, y) {
     let baseRes = super.pieces(color, x, y);
     if (
@@ -157,15 +170,6 @@ export default class CheckeredRules extends ChessRules {
     }
   }
 
-  aggregateFlags() {
-    return [this.castleFlags, this.pawnFlags];
-  }
-
-  disaggregateFlags(flags) {
-    this.castleFlags = flags[0];
-    this.pawnFlags = flags[1];
-  }
-
   getEpSquare(moveOrSquare) {
     // At stage 2, all pawns can be captured en-passant
     if (
@@ -208,21 +212,19 @@ export default class CheckeredRules extends ChessRules {
     // Apply "checkerization" of standard moves
     const lastRank = (color == "w" ? 0 : 7);
     const [x, y] = [moves[0].start.x, moves[0].start.y];
-    let moves = [];
     const piece = this.getPiece(x, y);
     // King is treated differently: it never turn checkered
-    if (piece == 'k') {
+    if (piece == 'k' && this.stage == 1) {
       // If at least one checkered piece, allow switching:
       if (
         this.options["withswitch"] &&
         this.board.some(b => b.some(cell => cell[0] == 'c'))
       ) {
-        const oppCol = C.GetOppCol(color);
-        const oppKingPos = this.searchKingPos(oppCol)[0];
+        const oppKingPos = this.searchKingPos(C.GetOppTurn(this.turn))[0];
         moves.push(
           new Move({
             start: { x: x, y: y },
-            end: { x: oppKingPos[0], y: oppKingPos[1] },
+            end: {x: oppKingPos[0], y: oppKingPos[1]},
             appear: [],
             vanish: []
           })
@@ -233,7 +235,7 @@ export default class CheckeredRules extends ChessRules {
     if (piece == 'p') {
       // Filter out forbidden pawn moves
       moves = moves.filter(m => {
-        if (m.vanish[0].p == 'p') {
+        if (m.vanish.length > 0 && m.vanish[0].p == 'p') {
           if (
             Math.abs(m.end.x - m.start.x) == 2 &&
             !this.pawnFlags[this.turn][m.start.y]
@@ -253,7 +255,7 @@ export default class CheckeredRules extends ChessRules {
     }
     let extraMoves = [];
     moves.forEach(m => {
-      if (m.vanish.length == 2 && m.appear.length == 1)
+      if (m.vanish.length == 2 && m.appear.length == 1) {
         // A capture occured
         m.appear[0].c = "c";
         if (
@@ -271,7 +273,7 @@ export default class CheckeredRules extends ChessRules {
     return moves.concat(extraMoves);
   }
 
-  canIplay(side, [x, y]) {
+  canIplay(x, y) {
     if (this.stage == 2) {
       const color = this.getColor(x, y);
       return (
@@ -280,7 +282,10 @@ export default class CheckeredRules extends ChessRules {
           : ['w', 'b'].includes(color)
       );
     }
-    return side == this.turn && [side, "c"].includes(this.getColor(x, y));
+    return (
+      this.playerColor == this.turn &&
+      [this.turn, "c"].includes(this.getColor(x, y))
+    );
   }
 
   // Does m2 un-do m1 ? (to disallow undoing checkered moves)
@@ -303,32 +308,31 @@ export default class CheckeredRules extends ChessRules {
       // Checkered cannot be under check (no king)
       return moves;
     let kingPos = super.searchKingPos(color);
-    const oppCol = C.GetOppCol(color);
     if (this.stage == 2)
       // Must consider both kings (attacked by checkered side)
-      kingPos = [kingPos, super.searchKingPos(oppCol)];
+      kingPos = [kingPos, super.searchKingPos(C.GetOppTurn(this.turn))];
+    const oppCols = this.getOppCols(color);
     return moves.filter(m => {
       this.playOnBoard(m);
       let res = true;
       if (stage == 1)
         res = !this.oppositeMoves(this.cmove, m);
       if (res && m.appear.length > 0)
-        // NOTE: oppCol might be inaccurate; fixed in underCheck()
-        res = !this.underCheck(kingPos, oppCol);
+        res = !this.underCheck(kingPos, oppCols);
       this.undoOnBoard(m);
       return res;
     });
   }
 
   atLeastOneMove(color) {
-    const oppCol = C.GetOppCol(color);
+    const myCols = [color, 'c'];
     for (let i = 0; i < this.size.x; i++) {
       for (let j = 0; j < this.size.y; j++) {
         const colIJ = this.getColor(i, j);
         if (
           this.board[i][j] != "" &&
           (
-            (this.stage == 1 && colIJ != oppCol) ||
+            (this.stage == 1 && myCols.includes(colIJ)) ||
             (this.stage == 2 &&
               (
                 (this.sideCheckered == color && colIJ == 'c') ||
@@ -346,12 +350,10 @@ export default class CheckeredRules extends ChessRules {
     return false;
   }
 
-  underCheck(square_s, oppCol) {
-    if (this.stage == 1)
-      return super.underAttack(square_s, [oppCol, 'c']);
-    if (oppCol != this.sideCheckered)
+  underCheck(square_s, oppCols) {
+    if (this.stage == 2 && oppCol != this.sideCheckered)
       return false; //checkered pieces is me, I'm not under check
-    return super.underAttack(square_s, 'c');
+    return super.underAttack(square_s, oppCols);
   }
 
   prePlay(move) {
@@ -389,11 +391,11 @@ export default class CheckeredRules extends ChessRules {
       if (this.atLeastOneMove(color))
         return "*";
       // Artifically change turn, for checkered pawns
-      const oppCol = C.GetOppCol(color);
-      this.turn = oppCol;
+      const oppTurn = C.GetOppTurn(color);
+      this.turn = oppTurn;
       const kingPos = super.searchKingPos(color)[0];
       let res = "1/2";
-      if (super.underAttack(kingPos, [oppCol, 'c']))
+      if (super.underAttack(kingPos, [oppTurn, 'c']))
         res = (color == "w" ? "0-1" : "1-0");
       this.turn = color;
       return res;
@@ -410,9 +412,9 @@ export default class CheckeredRules extends ChessRules {
     }
     if (this.atLeastOneMove(color))
       return "*";
-    let res = super.underAttack(super.searchKingPos(color)[0], 'c');
+    let res = super.underAttack(super.searchKingPos(color)[0], ['c']);
     if (!res)
-      res = super.underAttack(super.searchKingPos(oppCol)[0], 'c');
+      res = super.underAttack(super.searchKingPos(oppCol)[0], ['c']);
     if (res)
       return (color == 'w' ? "0-1" : "1-0");
     return "1/2";
-- 
2.44.0