Fixed Checkered variant
[xogo.git] / variants / Checkered / class.js
index 9d9bf24..f73ed0e 100644 (file)
@@ -12,7 +12,7 @@ export default class CheckeredRules extends ChessRules {
           label: "Allow switching",
           variable: "withswitch",
           type: "checkbox",
-          defaut: false
+          defaut: true
         }
       ],
       styles: [
@@ -28,7 +28,13 @@ export default class CheckeredRules extends ChessRules {
     };
   }
 
-  static board2fen(b) {
+  static GetColorClass(c) {
+    if (c == 'c')
+      return "checkered";
+    return C.GetColorClass(c);
+  }
+
+  board2fen(b) {
     const checkered_codes = {
       p: "s",
       q: "t",
@@ -41,7 +47,7 @@ export default class CheckeredRules extends ChessRules {
     return super.board2fen(b);
   }
 
-  static fen2board(f) {
+  fen2board(f) {
     // Tolerate upper-case versions of checkered pieces (why not?)
     const checkered_pieces = {
       s: "p",
@@ -60,11 +66,20 @@ export default class CheckeredRules extends ChessRules {
     return super.fen2board(f);
   }
 
+  genRandInitBaseFen() {
+    let res = super.genRandInitBaseFen();
+    res.o.flags += "1".repeat(16); //pawns flags
+    return res;
+  }
+
   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 +97,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,26 +108,44 @@ 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 (
-      this.getPiece(x, y) == 'p' &&
-      this.stage == 2 &&
-      this.getColor(x, y) == 'c'
-    ) {
+    if (this.getPiece(x, y) == 'p' && color == 'c') {
+      const pawnShift = this.getPawnShift(this.turn); //cannot trust color
+      const initRank = (
+        (this.stage == 2 && [1, 6].includes(x)) ||
+        (this.stage == 1 &&
+          ((x == 1 && this.turn == 'b') || (x == 6 && this.turn == 'w'))
+        )
+      );
       // Checkered pawns on stage 2 are bidirectional
-      const initRank = ((color == 'w' && x >= 6) || (color == 'b' && x <= 1));
+      let moveSteps = [[pawnShift, 0]],
+          attackSteps = [[pawnShift, 1], [pawnShift, -1]];
+      if (this.stage == 2) {
+        moveSteps.push([-pawnShift, 0]);
+        Array.prototype.push.apply(attackSteps,
+          [[-pawnShift, 1], [-pawnShift, -1]]);
+      }
       baseRes['p'] = {
         "class": "pawn",
         moves: [
           {
-            steps: [[1, 0], [-1, 0]],
+            steps: moveSteps,
             range: (initRank ? 2 : 1)
           }
         ],
         attack: [
           {
-            steps: [[1, 1], [1, -1], [-1, 1], [-1, -1]],
+            steps: attackSteps,
             range: 1
           }
         ]
@@ -157,15 +191,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 (
@@ -202,27 +227,26 @@ export default class CheckeredRules extends ChessRules {
   }
 
   postProcessPotentialMoves(moves) {
+    moves = super.postProcessPotentialMoves(moves);
     if (this.stage == 2 || moves.length == 0)
       return moves;
     const color = this.turn;
     // 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 +257,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]
@@ -241,7 +265,7 @@ export default class CheckeredRules extends ChessRules {
             return false; //forbidden 2-squares jumps
           }
           if (
-            this.board[m.end.x][m.end.y] == V.EMPTY &&
+            this.board[m.end.x][m.end.y] == "" &&
             m.vanish.length == 2 &&
             this.getColor(m.start.x, m.start.y) == "c"
           ) {
@@ -253,7 +277,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 +295,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 +304,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)
@@ -299,36 +326,42 @@ export default class CheckeredRules extends ChessRules {
 
   filterValid(moves) {
     const color = this.turn;
-    if (stage == 2 && this.sideCheckered == color)
+    if (this.stage == 2 && this.sideCheckered == color)
       // 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)];
-    return moves.filter(m => {
+      kingPos.push(super.searchKingPos(C.GetOppTurn(this.turn))[0]);
+    const oppCols = this.getOppCols(color);
+    const filteredMoves = moves.filter(m => {
+      if (m.vanish.length == 0 && m.appear.length == 0)
+        return true; //switch move
       this.playOnBoard(m);
+      if (m.vanish[0].p == 'k')
+        kingPos[0] = [m.appear[0].x, m.appear[0].y];
       let res = true;
-      if (stage == 1)
+      if (this.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);
+      if (m.vanish[0].p == 'k')
+        kingPos[0] = [m.vanish[0].x, m.vanish[0].y];
       this.undoOnBoard(m);
       return res;
     });
+    return filteredMoves;
   }
 
   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 +379,15 @@ 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 && this.turn == this.sideCheckered)
       return false; //checkered pieces is me, I'm not under check
-    return super.underAttack(square_s, 'c');
+    // Artificial turn change required, because canTake uses turn info.
+    // canTake is called from underCheck --> ... --> findDestSquares
+    this.turn = C.GetOppTurn(this.turn);
+    const res = square_s.some(sq => super.underAttack(sq, oppCols));
+    this.turn = C.GetOppTurn(this.turn);
+    return res;
   }
 
   prePlay(move) {
@@ -359,7 +395,7 @@ export default class CheckeredRules extends ChessRules {
       super.prePlay(move);
       if (
         [1, 6].includes(move.start.x) &&
-        move.vanish[0].p == V.PAWN &&
+        move.vanish[0].p == 'p' &&
         Math.abs(move.end.x - move.start.x) == 2
       ) {
         // This move turns off a 2-squares pawn flag
@@ -372,6 +408,8 @@ export default class CheckeredRules extends ChessRules {
     if (move.appear.length == 0 && move.vanish.length == 0) {
       this.stage = 2;
       this.sideCheckered = this.turn;
+      if (this.playerColor != this.turn)
+        super.displayMessage(null, "Autonomous checkered!", "info-text", 2000);
     }
     else
       super.postPlay(move);
@@ -389,11 +427,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 +448,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";