Change castle flags. Eightpieces still not OK, but almost
authorBenjamin Auder <benjamin.auder@somewhere>
Sat, 14 Mar 2020 12:50:42 +0000 (13:50 +0100)
committerBenjamin Auder <benjamin.auder@somewhere>
Sat, 14 Mar 2020 12:50:42 +0000 (13:50 +0100)
32 files changed:
client/src/base_rules.js
client/src/components/BaseGame.vue
client/src/components/Board.vue
client/src/variants/Alice.js
client/src/variants/Allmate1.js
client/src/variants/Allmate2.js
client/src/variants/Antiking.js
client/src/variants/Arena.js
client/src/variants/Atomic.js
client/src/variants/Benedict.js
client/src/variants/Capture.js
client/src/variants/Checkered.js
client/src/variants/Circular.js
client/src/variants/Crazyhouse.js
client/src/variants/Dark.js
client/src/variants/Eightpieces.js
client/src/variants/Extinction.js
client/src/variants/Grand.js
client/src/variants/Grasshopper.js
client/src/variants/Hidden.js
client/src/variants/Hiddenqueen.js
client/src/variants/Magnetic.js
client/src/variants/Marseille.js
client/src/variants/Recycle.js
client/src/variants/Shatranj.js
client/src/variants/Suction.js
client/src/variants/Suicide.js
client/src/variants/Threechecks.js
client/src/variants/Wildebeest.js
client/src/variants/Wormhole.js
client/src/views/Analyse.vue
client/src/views/Game.vue

index 4c32fe6..890601d 100644 (file)
@@ -37,6 +37,11 @@ export const ChessRules = class ChessRules {
     return true;
   }
 
+  // Or castle
+  static get HasCastle() {
+    return V.HasFlags;
+  }
+
   // Some variants don't have en-passant
   static get HasEnpassant() {
     return true;
@@ -134,7 +139,8 @@ export const ChessRules = class ChessRules {
 
   // For FEN checking
   static IsGoodFlags(flags) {
-    return !!flags.match(/^[01]{4,4}$/);
+    // NOTE: a little too permissive to work with more variants
+    return !!flags.match(/^[a-z]{4,4}$/);
   }
 
   static IsGoodEnpassant(enpassant) {
@@ -175,6 +181,11 @@ export const ChessRules = class ChessRules {
     return b; //usual pieces in pieces/ folder
   }
 
+  // Path to promotion pieces (usually the same)
+  getPPpath(b) {
+    return this.getPpath(b);
+  }
+
   // Aggregates flags into one object
   aggregateFlags() {
     return this.castleFlags;
@@ -239,13 +250,15 @@ export const ChessRules = class ChessRules {
   static GenRandInitFen(randomness) {
     if (randomness == 0)
       // Deterministic:
-      return "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w 0 1111 -";
+      return "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w 0 ahah -";
 
     let pieces = { w: new Array(8), b: new Array(8) };
+    let flags = "";
     // Shuffle pieces on first (and last rank if randomness == 2)
     for (let c of ["w", "b"]) {
       if (c == 'b' && randomness == 1) {
         pieces['b'] = pieces['w'];
+        flags += flags;
         break;
       }
 
@@ -289,13 +302,14 @@ export const ChessRules = class ChessRules {
       pieces[c][bishop2Pos] = "b";
       pieces[c][knight2Pos] = "n";
       pieces[c][rook2Pos] = "r";
+      flags += V.CoordToColumn(rook1Pos) + V.CoordToColumn(rook2Pos);
     }
     // Add turn + flags + enpassant
     return (
       pieces["b"].join("") +
       "/pppppppp/8/8/8/8/PPPPPPPP/" +
       pieces["w"].join("").toUpperCase() +
-      " w 0 1111 -"
+      " w 0 " + flags + " -"
     );
   }
 
@@ -366,10 +380,9 @@ export const ChessRules = class ChessRules {
   // Flags part of the FEN string
   getFlagsFen() {
     let flags = "";
-    // Add castling flags
-    for (let i of ["w", "b"]) {
-      for (let j = 0; j < 2; j++) flags += this.castleFlags[i][j] ? "1" : "0";
-    }
+    // Castling flags
+    for (let c of ["w", "b"])
+      flags += this.castleFlags[c].map(V.CoordToColumn).join("");
     return flags;
   }
 
@@ -402,8 +415,10 @@ export const ChessRules = class ChessRules {
   setFlags(fenflags) {
     // white a-castle, h-castle, black a-castle, h-castle
     this.castleFlags = { w: [true, true], b: [true, true] };
-    for (let i = 0; i < 4; i++)
-      this.castleFlags[i < 2 ? "w" : "b"][i % 2] = fenflags.charAt(i) == "1";
+    for (let i = 0; i < 4; i++) {
+      this.castleFlags[i < 2 ? "w" : "b"][i % 2] =
+        V.ColumnToCoord(fenflags.charAt(i));
+    }
   }
 
   //////////////////
@@ -422,10 +437,9 @@ export const ChessRules = class ChessRules {
     this.setOtherVariables(fen);
   }
 
-  // Scan board for kings and rooks positions
-  scanKingsRooks(fen) {
+  // Scan board for kings positions
+  scanKings(fen) {
     this.INIT_COL_KING = { w: -1, b: -1 };
-    this.INIT_COL_ROOK = { w: [-1, -1], b: [-1, -1] };
     this.kingPos = { w: [-1, -1], b: [-1, -1] }; //squares of white and black king
     const fenRows = V.ParseFen(fen).position.split("/");
     const startRow = { 'w': V.size.x - 1, 'b': 0 };
@@ -441,18 +455,6 @@ export const ChessRules = class ChessRules {
             this.kingPos["w"] = [i, k];
             this.INIT_COL_KING["w"] = k;
             break;
-          case "r":
-            if (i == startRow['b']) {
-              if (this.INIT_COL_ROOK["b"][0] < 0) this.INIT_COL_ROOK["b"][0] = k;
-              else this.INIT_COL_ROOK["b"][1] = k;
-            }
-            break;
-          case "R":
-            if (i == startRow['w']) {
-              if (this.INIT_COL_ROOK["w"][0] < 0) this.INIT_COL_ROOK["w"][0] = k;
-              else this.INIT_COL_ROOK["w"][1] = k;
-            }
-            break;
           default: {
             const num = parseInt(fenRows[i].charAt(j));
             if (!isNaN(num)) k += num - 1;
@@ -475,8 +477,8 @@ export const ChessRules = class ChessRules {
           : undefined;
       this.epSquares = [epSq];
     }
-    // Search for king and rooks positions:
-    this.scanKingsRooks(fen);
+    // Search for kings positions:
+    this.scanKings(fen);
   }
 
   /////////////////////
@@ -741,7 +743,7 @@ export const ChessRules = class ChessRules {
   // What are the king moves from square x,y ?
   getPotentialKingMoves(sq) {
     // Initialize with normal moves
-    let moves = this.getSlideNJumpMoves(
+    const moves = this.getSlideNJumpMoves(
       sq,
       V.steps[V.ROOK].concat(V.steps[V.BISHOP]),
       "oneStep"
@@ -768,7 +770,7 @@ export const ChessRules = class ChessRules {
       castleSide < 2;
       castleSide++ //large, then small
     ) {
-      if (!this.castleFlags[c][castleSide]) continue;
+      if (this.castleFlags[c][castleSide] >= V.size.y) continue;
       // If this code is reached, rooks and king are on initial position
 
       // Nothing on the path of the king ? (and no checks)
@@ -790,10 +792,10 @@ export const ChessRules = class ChessRules {
 
       // Nothing on the path to the rook?
       step = castleSide == 0 ? -1 : 1;
-      for (i = y + step; i != this.INIT_COL_ROOK[c][castleSide]; i += step) {
+      const rookPos = this.castleFlags[c][castleSide];
+      for (i = y + step; i != rookPos; i += step) {
         if (this.board[x][i] != V.EMPTY) continue castlingCheck;
       }
-      const rookPos = this.INIT_COL_ROOK[c][castleSide];
 
       // Nothing on final squares, except maybe king and castling rook?
       for (i = 0; i < 2; i++) {
@@ -998,84 +1000,72 @@ export const ChessRules = class ChessRules {
     for (let psq of move.vanish) board[psq.x][psq.y] = psq.c + psq.p;
   }
 
+  prePlay() {}
+
+  play(move) {
+    // DEBUG:
+//    if (!this.states) this.states = [];
+//    const stateFen = this.getBaseFen() + this.getTurnFen();// + this.getFlagsFen();
+//    this.states.push(stateFen);
+
+    this.prePlay(move);
+    if (V.HasFlags) move.flags = JSON.stringify(this.aggregateFlags()); //save flags (for undo)
+    if (V.HasEnpassant) this.epSquares.push(this.getEpSquare(move));
+    V.PlayOnBoard(this.board, move);
+    this.turn = V.GetOppCol(this.turn);
+    this.movesCount++;
+    this.postPlay(move);
+  }
+
   // After move is played, update variables + flags
-  updateVariables(move) {
+  postPlay(move) {
+    const c = V.GetOppCol(this.turn);
     let piece = undefined;
-    // TODO: update variables before move is played, and just use this.turn?
-    // (doesn't work in general, think MarseilleChess)
-    let c = undefined;
-    if (move.vanish.length >= 1) {
+    if (move.vanish.length >= 1)
       // Usual case, something is moved
       piece = move.vanish[0].p;
-      c = move.vanish[0].c;
-    } else {
+    else
       // Crazyhouse-like variants
       piece = move.appear[0].p;
-      c = move.appear[0].c;
-    }
-    if (!['w','b'].includes(c)) {
-      // Checkered, for example
-      c = V.GetOppCol(this.turn);
-    }
     const firstRank = c == "w" ? V.size.x - 1 : 0;
 
     // Update king position + flags
     if (piece == V.KING && move.appear.length > 0) {
       this.kingPos[c][0] = move.appear[0].x;
       this.kingPos[c][1] = move.appear[0].y;
-      if (V.HasFlags) this.castleFlags[c] = [false, false];
+      if (V.HasCastle) this.castleFlags[c] = [V.size.y, V.size.y];
       return;
     }
-    if (V.HasFlags) {
+    if (V.HasCastle) {
       // Update castling flags if rooks are moved
       const oppCol = V.GetOppCol(c);
       const oppFirstRank = V.size.x - 1 - firstRank;
       if (
         move.start.x == firstRank && //our rook moves?
-        this.INIT_COL_ROOK[c].includes(move.start.y)
+        this.castleFlags[c].includes(move.start.y)
       ) {
-        const flagIdx = (move.start.y == this.INIT_COL_ROOK[c][0] ? 0 : 1);
-        this.castleFlags[c][flagIdx] = false;
+        const flagIdx = (move.start.y == this.castleFlags[c][0] ? 0 : 1);
+        this.castleFlags[c][flagIdx] = V.size.y;
       } else if (
         move.end.x == oppFirstRank && //we took opponent rook?
-        this.INIT_COL_ROOK[oppCol].includes(move.end.y)
+        this.castleFlags[oppCol].includes(move.end.y)
       ) {
-        const flagIdx = (move.end.y == this.INIT_COL_ROOK[oppCol][0] ? 0 : 1);
-        this.castleFlags[oppCol][flagIdx] = false;
+        const flagIdx = (move.end.y == this.castleFlags[oppCol][0] ? 0 : 1);
+        this.castleFlags[oppCol][flagIdx] = V.size.y;
       }
     }
   }
 
-  // After move is undo-ed *and flags resetted*, un-update other variables
-  // TODO: more symmetry, by storing flags increment in move (?!)
-  unupdateVariables(move) {
-    // (Potentially) Reset king position
-    const c = this.getColor(move.start.x, move.start.y);
-    if (this.getPiece(move.start.x, move.start.y) == V.KING)
-      this.kingPos[c] = [move.start.x, move.start.y];
-  }
-
-  play(move) {
-    // DEBUG:
-//    if (!this.states) this.states = [];
-//    const stateFen = this.getBaseFen() + this.getTurnFen();// + this.getFlagsFen();
-//    this.states.push(stateFen);
-
-    if (V.HasFlags) move.flags = JSON.stringify(this.aggregateFlags()); //save flags (for undo)
-    if (V.HasEnpassant) this.epSquares.push(this.getEpSquare(move));
-    V.PlayOnBoard(this.board, move);
-    this.turn = V.GetOppCol(this.turn);
-    this.movesCount++;
-    this.updateVariables(move);
-  }
+  preUndo() {}
 
   undo(move) {
+    this.preUndo(move);
     if (V.HasEnpassant) this.epSquares.pop();
     if (V.HasFlags) this.disaggregateFlags(JSON.parse(move.flags));
     V.UndoOnBoard(this.board, move);
     this.turn = V.GetOppCol(this.turn);
     this.movesCount--;
-    this.unupdateVariables(move);
+    this.postUndo(move);
 
     // DEBUG:
 //    const stateFen = this.getBaseFen() + this.getTurnFen();// + this.getFlagsFen();
@@ -1083,6 +1073,15 @@ export const ChessRules = class ChessRules {
 //    this.states.pop();
   }
 
+  // After move is undo-ed *and flags resetted*, un-update other variables
+  // TODO: more symmetry, by storing flags increment in move (?!)
+  postUndo(move) {
+    // (Potentially) Reset king position
+    const c = this.getColor(move.start.x, move.start.y);
+    if (this.getPiece(move.start.x, move.start.y) == V.KING)
+      this.kingPos[c] = [move.start.x, move.start.y];
+  }
+
   ///////////////
   // END OF GAME
 
index 8c96e39..c85d47e 100644 (file)
@@ -191,7 +191,8 @@ export default {
         this.moves.unshift({
           notation: "...",
           start: { x: -1, y: -1 },
-          end: { x: -1, y: -1 }
+          end: { x: -1, y: -1 },
+          fen: game.fenStart
         });
       }
       this.positionCursorTo(this.moves.length - 1);
@@ -207,9 +208,7 @@ export default {
         } else {
           this.lastMove = this.moves[index];
         }
-      }
-      else
-        this.lastMove = null;
+      } else this.lastMove = null;
     },
     analyzePosition: function() {
       let newUrl =
@@ -310,7 +309,6 @@ export default {
       const playSubmove = (smove) => {
         if (!navigate) smove.notation = this.vr.getNotation(smove);
         this.vr.play(smove);
-        this.lastMove = smove;
         // Is opponent in check?
         this.incheck = this.vr.getCheckSquares(this.vr.turn);
         if (!navigate) {
@@ -355,11 +353,11 @@ export default {
       const afterMove = (smove, initurn) => {
         if (this.vr.turn != initurn) {
           // Turn has changed: move is complete
-          if (!smove.fen) {
+          if (!smove.fen)
             // NOTE: only FEN of last sub-move is required (thus setting it here)
             smove.fen = this.vr.getFen();
-            this.emitFenIfAnalyze();
-          }
+          this.lastMove = smove;
+          this.emitFenIfAnalyze();
           this.inMultimove = false;
           if (!noemit) {
             var score = this.vr.getCurrentScore();
@@ -437,7 +435,11 @@ export default {
         this.incheck = this.vr.getCheckSquares(this.vr.turn);
       } else {
         if (!move) {
-          if (this.cursor < 0) return; //no more moves
+          const minCursor =
+            this.moves.length > 0 && this.moves[0].notation == "..."
+              ? 1
+              : 0;
+          if (this.cursor < minCursor) return; //no more moves
           move = this.moves[this.cursor];
         }
         // Caution; if multi-move, undo all submoves from last to first
@@ -469,15 +471,13 @@ export default {
     },
     gotoBegin: function() {
       if (this.inMultimove) this.cancelCurrentMultimove();
-      while (this.cursor >= 0)
-        this.undo(null, null, "light");
-      if (this.moves.length > 0 && this.moves[0].notation == "...") {
-        this.cursor = 0;
-        this.lastMove = this.moves[0];
-      } else {
-        this.lastMove = null;
-      }
-      this.incheck = [];
+      const minCursor =
+        this.moves.length > 0 && this.moves[0].notation == "..."
+          ? 1
+          : 0;
+      while (this.cursor >= minCursor) this.undo(null, null, "light");
+      this.lastMove = (minCursor == 1 ? this.moves[0] : null);
+      this.incheck = this.vr.getCheckSquares(this.vr.turn);
       this.emitFenIfAnalyze();
     },
     gotoEnd: function() {
index fdeeef3..0d531a6 100644 (file)
@@ -25,6 +25,7 @@ export default {
       selectedPiece: null, //moving piece (or clicked piece)
       start: null, //pixels coordinates + id of starting square (click or drag)
       click: "",
+      clickTime: 0,
       settings: store.state.settings
     };
   },
@@ -112,7 +113,12 @@ export default {
                   attrs: {
                     src:
                       "/images/pieces/" +
-                      this.vr.getPpath(this.vr.board[ci][cj], this.userColor, this.score) +
+                      this.vr.getPpath(
+                        this.vr.board[ci][cj],
+                        // Extra args useful for some variants:
+                        this.userColor,
+                        this.score,
+                        this.orientation) +
                       ".svg"
                   }
                 })
@@ -298,6 +304,9 @@ export default {
           // A "choice" is a move
           const applyMove = (e) => {
             e.stopPropagation();
+            // Force a delay between move is shown and clicked
+            // (otherwise a "double-click" bug might occur)
+            if (Date.now() - this.clickTime < 200) return;
             this.play(m);
             this.choices = [];
           };
@@ -322,7 +331,10 @@ export default {
                 attrs: {
                   src:
                     "/images/pieces/" +
-                    this.vr.getPpath(m.appear[0].c + m.appear[0].p) +
+                    this.vr.getPPpath(
+                      m.appear[0].c + m.appear[0].p,
+                      // Extra arg useful for some variants:
+                      this.orientation) +
                     ".svg"
                 },
                 class: { "choice-piece": true },
@@ -441,8 +453,10 @@ export default {
       let endSquare = getSquareFromId(landing.id);
       let moves = this.findMatchingMoves(endSquare);
       this.possibleMoves = [];
-      if (moves.length > 1) this.choices = moves;
-      else if (moves.length == 1) this.play(moves[0]);
+      if (moves.length > 1) {
+        this.clickTime = Date.now();
+        this.choices = moves;
+      } else if (moves.length == 1) this.play(moves[0]);
       // else: forbidden move attempt
     },
     findMatchingMoves: function(endSquare) {
index 4d6b3c3..d64c077 100644 (file)
@@ -243,22 +243,23 @@ export const VariantRules = class AliceRules extends ChessRules {
     return res;
   }
 
-  updateVariables(move) {
-    super.updateVariables(move); //standard king
+  postPlay(move) {
+    super.postPlay(move); //standard king
     const piece = move.vanish[0].p;
     const c = move.vanish[0].c;
     // "l" = Alice king
     if (piece == "l") {
       this.kingPos[c][0] = move.appear[0].x;
       this.kingPos[c][1] = move.appear[0].y;
-      this.castleFlags[c] = [false, false];
+      this.castleFlags[c] = [8, 8];
     }
   }
 
-  unupdateVariables(move) {
-    super.unupdateVariables(move);
+  postUndo(move) {
+    super.postUndo(move);
     const c = move.vanish[0].c;
-    if (move.vanish[0].p == "l") this.kingPos[c] = [move.start.x, move.start.y];
+    if (move.vanish[0].p == "l")
+      this.kingPos[c] = [move.start.x, move.start.y];
   }
 
   getCurrentScore() {
index 43062a3..bc10478 100644 (file)
@@ -11,7 +11,7 @@ export const VariantRules = class Allmate1Rules extends ChessRules {
   }
 
   static GenRandInitFen(randomness) {
-    return ChessRules.GenRandInitFen(randomness).replace(/ -$/, "");
+    return ChessRules.GenRandInitFen(randomness).slice(0, -2);
   }
 
   getPotentialMovesFrom([x, y]) {
@@ -118,7 +118,7 @@ export const VariantRules = class Allmate1Rules extends ChessRules {
       castleSide < 2;
       castleSide++ //large, then small
     ) {
-      if (!this.castleFlags[c][castleSide]) continue;
+      if (this.castleFlags[c][castleSide] >= 8) continue;
       // If this code is reached, rooks and king are on initial position
 
       // Nothing on the path of the king ? (and no checks)
@@ -137,10 +137,10 @@ export const VariantRules = class Allmate1Rules extends ChessRules {
 
       // Nothing on the path to the rook?
       step = castleSide == 0 ? -1 : 1;
-      for (i = y + step; i != this.INIT_COL_ROOK[c][castleSide]; i += step) {
+      const rookPos = this.castleFlags[c][castleSide];
+      for (i = y + step; i != rookPos; i += step) {
         if (this.board[x][i] != V.EMPTY) continue castlingCheck;
       }
-      const rookPos = this.INIT_COL_ROOK[c][castleSide];
 
       // Nothing on final squares, except maybe king and castling rook?
       for (i = 0; i < 2; i++) {
@@ -238,34 +238,32 @@ export const VariantRules = class Allmate1Rules extends ChessRules {
     });
   }
 
-  updateVariables(move) {
-    super.updateVariables(move);
-    const color = V.GetOppCol(this.turn);
+  postPlay(move) {
+    super.postPlay(move);
     if (move.vanish.length >= 2 && move.appear.length == 1) {
-      move.vanish.forEach(v => {
-        if (v.c == color)
-          return;
+      for (let i = 1; i<move.vanish.length; i++) {
+        const v = move.vanish[i];
         // Did opponent king disappeared?
         if (v.p == V.KING)
           this.kingPos[this.turn] = [-1, -1];
         // Or maybe a rook?
         else if (v.p == V.ROOK) {
           if (v.y < this.INIT_COL_KING[v.c])
-            this.castleFlags[v.c][0] = false;
+            this.castleFlags[v.c][0] = 8;
           else
             // v.y > this.INIT_COL_KING[v.c]
-            this.castleFlags[v.c][1] = false;
+            this.castleFlags[v.c][1] = 8;
         }
-      });
+      }
     }
   }
 
-  unupdateVariables(move) {
-    super.unupdateVariables(move);
-    const color = this.turn;
+  preUndo(move) {
+    super.preUndo(move);
+    const oppCol = this.turn;
     if (move.vanish.length >= 2 && move.appear.length == 1) {
       // Did opponent king disappeared?
-      const psq = move.vanish.find(v => v.p == V.KING && v.c != color)
+      const psq = move.vanish.find(v => v.p == V.KING && v.c == oppCol)
       if (psq)
         this.kingPos[psq.c] = [psq.x, psq.y];
     }
index 828e459..daa3542 100644 (file)
@@ -11,7 +11,7 @@ export const VariantRules = class Allmate2Rules extends ChessRules {
   }
 
   static GenRandInitFen(randomness) {
-    return ChessRules.GenRandInitFen(randomness).replace(/ -$/, "");
+    return ChessRules.GenRandInitFen(randomness).slice(0, -2);
   }
 
   getPotentialMovesFrom([x, y]) {
@@ -38,7 +38,7 @@ export const VariantRules = class Allmate2Rules extends ChessRules {
       }
 
       // 2) Among attacked pieces, which cannot escape capture?
-      // --> without (normal-)capturing: difference with Allmate variant
+      // --> without (normal-)capturing: difference with Allmate1 variant
       // Avoid "oppMoves = this.getAllValidMoves();" => infinite recursion
       outerLoop: for (let i=0; i<V.size.x; i++) {
         for (let j=0; j<V.size.y; j++) {
@@ -122,7 +122,7 @@ export const VariantRules = class Allmate2Rules extends ChessRules {
       castleSide < 2;
       castleSide++ //large, then small
     ) {
-      if (!this.castleFlags[c][castleSide]) continue;
+      if (this.castleFlags[c][castleSide] >= 8) continue;
       // If this code is reached, rooks and king are on initial position
 
       // Nothing on the path of the king ? (and no checks)
@@ -141,10 +141,10 @@ export const VariantRules = class Allmate2Rules extends ChessRules {
 
       // Nothing on the path to the rook?
       step = castleSide == 0 ? -1 : 1;
-      for (i = y + step; i != this.INIT_COL_ROOK[c][castleSide]; i += step) {
+      const rookPos = this.castleFlags[c][castleSide];
+      for (i = y + step; i != rookPos; i += step) {
         if (this.board[x][i] != V.EMPTY) continue castlingCheck;
       }
-      const rookPos = this.INIT_COL_ROOK[c][castleSide];
 
       // Nothing on final squares, except maybe king and castling rook?
       for (i = 0; i < 2; i++) {
@@ -242,34 +242,32 @@ export const VariantRules = class Allmate2Rules extends ChessRules {
     });
   }
 
-  updateVariables(move) {
-    super.updateVariables(move);
-    const color = V.GetOppCol(this.turn);
+  postPlay(move) {
+    super.postPlay(move);
     if (move.vanish.length >= 2 && move.appear.length == 1) {
-      move.vanish.forEach(v => {
-        if (v.c == color)
-          return;
+      for (let i = 1; i<move.vanish.length; i++) {
+        const v = move.vanish[i];
         // Did opponent king disappeared?
         if (v.p == V.KING)
           this.kingPos[this.turn] = [-1, -1];
         // Or maybe a rook?
         else if (v.p == V.ROOK) {
           if (v.y < this.INIT_COL_KING[v.c])
-            this.castleFlags[v.c][0] = false;
+            this.castleFlags[v.c][0] = 8;
           else
             // v.y > this.INIT_COL_KING[v.c]
-            this.castleFlags[v.c][1] = false;
+            this.castleFlags[v.c][1] = 8;
         }
-      });
+      }
     }
   }
 
-  unupdateVariables(move) {
-    super.unupdateVariables(move);
-    const color = this.turn;
+  preUndo(move) {
+    super.preUndo(move);
+    const oppCol = this.turn;
     if (move.vanish.length >= 2 && move.appear.length == 1) {
       // Did opponent king disappeared?
-      const psq = move.vanish.find(v => v.p == V.KING && v.c != color)
+      const psq = move.vanish.find(v => v.p == V.KING && v.c == oppCol)
       if (psq)
         this.kingPos[psq.c] = [psq.x, psq.y];
     }
index 77f0c83..c3fe013 100644 (file)
@@ -111,8 +111,8 @@ export const VariantRules = class AntikingRules extends ChessRules {
     return res;
   }
 
-  updateVariables(move) {
-    super.updateVariables(move);
+  postPlay(move) {
+    super.postPlay(move);
     const piece = move.vanish[0].p;
     const c = move.vanish[0].c;
     // Update antiking position
@@ -122,8 +122,8 @@ export const VariantRules = class AntikingRules extends ChessRules {
     }
   }
 
-  unupdateVariables(move) {
-    super.unupdateVariables(move);
+  postUndo(move) {
+    super.postUndo(move);
     const c = move.vanish[0].c;
     if (move.vanish[0].p == V.ANTIKING)
       this.antikingPos[c] = [move.start.x, move.start.y];
@@ -153,14 +153,16 @@ export const VariantRules = class AntikingRules extends ChessRules {
 
   static GenRandInitFen(randomness) {
     if (randomness == 0)
-      return "rnbqkbnr/pppppppp/3A4/8/8/3a4/PPPPPPPP/RNBQKBNR w 0 1111 -";
+      return "rnbqkbnr/pppppppp/3A4/8/8/3a4/PPPPPPPP/RNBQKBNR w 0 ahah -";
 
     let pieces = { w: new Array(8), b: new Array(8) };
+    let flags = "";
     let antikingPos = { w: -1, b: -1 };
     for (let c of ["w", "b"]) {
       if (c == 'b' && randomness == 1) {
         pieces['b'] = pieces['w'];
         antikingPos['b'] = antikingPos['w'];
+        flags += flags;
         break;
       }
 
@@ -201,6 +203,7 @@ export const VariantRules = class AntikingRules extends ChessRules {
       pieces[c][bishop2Pos] = "b";
       pieces[c][knight2Pos] = "n";
       pieces[c][rook2Pos] = "r";
+      flags += V.CoordToColumn(rook1Pos) + V.CoordToColumn(rook2Pos);
     }
     const ranks23_black =
       "pppppppp/" +
@@ -220,7 +223,7 @@ export const VariantRules = class AntikingRules extends ChessRules {
       ranks23_white +
       "/" +
       pieces["w"].join("").toUpperCase() +
-      " w 0 1111 -"
+      " w 0 " + flags + " -"
     );
   }
 
index 9ccd3cc..620b980 100644 (file)
@@ -1,14 +1,12 @@
 import { ChessRules } from "@/base_rules";
 
 export const VariantRules = class ArenaRules extends ChessRules {
-  static get hasFlags() {
+  static get HasFlags() {
     return false;
   }
 
   static GenRandInitFen(randomness) {
-    return ChessRules
-      .GenRandInitFen(randomness)
-      .replace("w 0 1111 -", "w 0 -");
+    return ChessRules.GenRandInitFen(randomness).slice(0, -6) + "-";
   }
 
   static InArena(x) {
index fdc665d..51346da 100644 (file)
@@ -80,8 +80,8 @@ export const VariantRules = class AtomicRules extends ChessRules {
     );
   }
 
-  updateVariables(move) {
-    super.updateVariables(move);
+  postPlay(move) {
+    super.postPlay(move);
     if (move.appear.length == 0) {
       // Capture
       const firstRank = { w: 7, b: 0 };
@@ -92,29 +92,25 @@ export const VariantRules = class AtomicRules extends ChessRules {
           Math.abs(this.kingPos[c][1] - move.end.y) <= 1
         ) {
           this.kingPos[c] = [-1, -1];
-          this.castleFlags[c] = [false, false];
+          this.castleFlags[c] = [8, 8];
         } else {
           // Now check if init rook(s) exploded
           if (Math.abs(move.end.x - firstRank[c]) <= 1) {
-            if (Math.abs(move.end.y - this.INIT_COL_ROOK[c][0]) <= 1)
-              this.castleFlags[c][0] = false;
-            if (Math.abs(move.end.y - this.INIT_COL_ROOK[c][1]) <= 1)
-              this.castleFlags[c][1] = false;
+            if (Math.abs(move.end.y - this.castleFlags[c][0]) <= 1)
+              this.castleFlags[c][0] = 8;
+            if (Math.abs(move.end.y - this.castleFlags[c][1]) <= 1)
+              this.castleFlags[c][1] = 8;
           }
         }
       }
     }
   }
 
-  unupdateVariables(move) {
-    super.unupdateVariables(move);
-    const c = move.vanish[0].c;
+  postUndo(move) {
+    super.postUndo(move);
+    const c = this.turn;
     const oppCol = V.GetOppCol(c);
-    if (
-      [this.kingPos[c][0], this.kingPos[oppCol][0]].some(e => {
-        return e < 0;
-      })
-    ) {
+    if ([this.kingPos[c][0], this.kingPos[oppCol][0]].some(e => e < 0)) {
       // There is a chance that last move blowed some king away..
       for (let psq of move.vanish) {
         if (psq.p == "k")
index 4a31fce..eb62146 100644 (file)
@@ -152,10 +152,10 @@ export const VariantRules = class BenedictRules extends ChessRules {
       castleSide < 2;
       castleSide++ //large, then small
     ) {
-      if (!this.castleFlags[c][castleSide]) continue;
+      if (this.castleFlags[c][castleSide] >= 8) continue;
       // If this code is reached, rooks and king are on initial position
 
-      const rookPos = this.INIT_COL_ROOK[c][castleSide];
+      const rookPos = this.castleFlags[c][castleSide];
       if (this.getColor(x, rookPos) != c)
         // Rook is here but changed color
         continue;
@@ -176,7 +176,7 @@ export const VariantRules = class BenedictRules extends ChessRules {
 
       // Nothing on the path to the rook?
       step = castleSide == 0 ? -1 : 1;
-      for (i = y + step; i != this.INIT_COL_ROOK[c][castleSide]; i += step) {
+      for (i = y + step; i != rookPos; i += step) {
         if (this.board[x][i] != V.EMPTY) continue castlingCheck;
       }
 
index a43f167..5b286e0 100644 (file)
@@ -5,7 +5,7 @@ import { randInt } from "@/utils/alea";
 export const VariantRules = class LosersRules extends ChessRules {
   // Trim all non-capturing moves
   static KeepCaptures(moves) {
-    return moves.filter(m => m.vanish.length == 2);
+    return moves.filter(m => m.vanish.length == 2 && m.appear.length == 1);
   }
 
        // Stop at the first capture found (if any)
index 1e42d99..0e089f3 100644 (file)
@@ -44,7 +44,7 @@ export const VariantRules = class CheckeredRules extends ChessRules {
     super.setOtherVariables(fen);
     // Local stack of non-capturing checkered moves:
     this.cmoves = [];
-    const cmove = fen.split(" ")[5];
+    const cmove = V.ParseFen(fen).cmove;
     if (cmove == "-") this.cmoves.push(null);
     else {
       this.cmoves.push({
@@ -65,7 +65,7 @@ export const VariantRules = class CheckeredRules extends ChessRules {
 
   static IsGoodFlags(flags) {
     // 4 for castle + 16 for pawns
-    return !!flags.match(/^[01]{20,20}$/);
+    return !!flags.match(/^[a-z]{4,4}[01]{16,16}$/);
   }
 
   setFlags(fenflags) {
@@ -74,7 +74,7 @@ export const VariantRules = class CheckeredRules extends ChessRules {
       w: [...Array(8).fill(true)], //pawns can move 2 squares?
       b: [...Array(8).fill(true)]
     };
-    const flags = fenflags.substr(4); //skip first 4 digits, for castle
+    const flags = fenflags.substr(4); //skip first 4 letters, for castle
     for (let c of ["w", "b"]) {
       for (let i = 0; i < 8; i++)
         this.pawnFlags[c][i] = flags.charAt((c == "w" ? 0 : 8) + i) == "1";
@@ -331,12 +331,17 @@ export const VariantRules = class CheckeredRules extends ChessRules {
     return res;
   }
 
-  updateVariables(move) {
-    super.updateVariables(move);
+  postPlay(move) {
+    super.postPlay(move);
     // Does this move turn off a 2-squares pawn flag?
-    const secondRank = [1, 6];
-    if (secondRank.includes(move.start.x) && move.vanish[0].p == V.PAWN)
+    if ([1, 6].includes(move.start.x) && move.vanish[0].p == V.PAWN)
       this.pawnFlags[move.start.x == 6 ? "w" : "b"][move.start.y] = false;
+    this.cmoves.push(this.getCmove(move));
+  }
+
+  postUndo(move) {
+    super.postUndo(move);
+    this.cmoves.pop();
   }
 
   getCurrentScore() {
@@ -374,9 +379,9 @@ export const VariantRules = class CheckeredRules extends ChessRules {
   }
 
   static GenRandInitFen(randomness) {
+    // Add 16 pawns flags + empty cmove:
     return ChessRules.GenRandInitFen(randomness)
-      // Add 16 pawns flags + empty cmove:
-      .replace(" w 0 1111", " w 0 11111111111111111111 -");
+      .slice(0, -2) + "1111111111111111 - -";
   }
 
   static ParseFen(fen) {
@@ -403,17 +408,6 @@ export const VariantRules = class CheckeredRules extends ChessRules {
     return fen;
   }
 
-  // TODO (design): this cmove update here or in (un)updateVariables ?
-  play(move) {
-    this.cmoves.push(this.getCmove(move));
-    super.play(move);
-  }
-
-  undo(move) {
-    this.cmoves.pop();
-    super.undo(move);
-  }
-
   static get SEARCH_DEPTH() {
     return 2;
   }
index a89712d..d6b431f 100644 (file)
@@ -3,6 +3,10 @@ import { ArrayFun } from "@/utils/array";
 import { randInt, shuffle } from "@/utils/alea";
 
 export const VariantRules = class CircularRules extends ChessRules {
+  static get HasCastle() {
+    return false;
+  }
+
   static get HasEnpassant() {
     return false;
   }
@@ -222,15 +226,11 @@ export const VariantRules = class CircularRules extends ChessRules {
     return flags;
   }
 
-  updateVariables(move) {
+  postPlay(move) {
+    super.postPlay(move);
     const c = move.vanish[0].c;
-    const secondRank = {"w":6, "b":2};
-    // Update king position + flags
-    if (move.vanish[0].p == V.KING && move.appear.length > 0) {
-      this.kingPos[c][0] = move.appear[0].x;
-      this.kingPos[c][1] = move.appear[0].y;
-    }
-    else if (move.vanish[0].p == V.PAWN && secondRank[c] == move.start.x)
+    const secondRank = { "w": 6, "b": 2 };
+    if (move.vanish[0].p == V.PAWN && secondRank[c] == move.start.x)
       // This move turns off a 2-squares pawn flag
       this.pawnFlags[c][move.start.y] = false;
   }
index 4f9ff63..fb8c744 100644 (file)
@@ -200,8 +200,8 @@ export const VariantRules = class CrazyhouseRules extends ChessRules {
     return true;
   }
 
-  updateVariables(move) {
-    super.updateVariables(move);
+  postPlay(move) {
+    super.postPlay(move);
     if (move.vanish.length == 2 && move.appear.length == 2) return; //skip castle
     const color = move.appear[0].c;
     if (move.vanish.length == 0) {
@@ -218,8 +218,8 @@ export const VariantRules = class CrazyhouseRules extends ChessRules {
     else if (move.vanish.length == 2) this.reserve[color][move.vanish[1].p]++;
   }
 
-  unupdateVariables(move) {
-    super.unupdateVariables(move);
+  postUndo(move) {
+    super.postUndo(move);
     if (move.vanish.length == 2 && move.appear.length == 2) return;
     const color = this.turn;
     if (move.vanish.length == 0) {
index 96163bf..a7edb91 100644 (file)
@@ -103,8 +103,8 @@ export const VariantRules = class DarkRules extends ChessRules {
     return [];
   }
 
-  updateVariables(move) {
-    super.updateVariables(move);
+  postPlay(move) {
+    super.postPlay(move);
     if (move.vanish.length >= 2 && move.vanish[1].p == V.KING)
       // We took opponent king (because if castle vanish[1] is a rook)
       this.kingPos[this.turn] = [-1, -1];
@@ -113,8 +113,8 @@ export const VariantRules = class DarkRules extends ChessRules {
     this.updateEnlightened();
   }
 
-  unupdateVariables(move) {
-    super.unupdateVariables(move);
+  postUndo(move) {
+    super.postUndo(move);
     const c = move.vanish[0].c;
     const oppCol = V.GetOppCol(c);
     if (this.kingPos[oppCol][0] < 0)
index c83cf5e..88c3739 100644 (file)
@@ -13,10 +13,6 @@ export const VariantRules = class EightpiecesRules extends ChessRules {
     return "l";
   }
 
-  static get PIECES() {
-    return ChessRules.PIECES.concat([V.JAILER, V.SENTRY, V.LANCER]);
-  }
-
   // Lancer directions *from white perspective*
   static get LANCER_DIRS() {
     return {
@@ -31,6 +27,12 @@ export const VariantRules = class EightpiecesRules extends ChessRules {
     };
   }
 
+  static get PIECES() {
+    return ChessRules.PIECES
+      .concat([V.JAILER, V.SENTRY])
+      .concat(Object.keys(V.LANCER_DIRS));
+  }
+
   getPiece(i, j) {
     const piece = this.board[i][j].charAt(1);
     // Special lancer case: 8 possible orientations
@@ -38,17 +40,66 @@ export const VariantRules = class EightpiecesRules extends ChessRules {
     return piece;
   }
 
-  getPpath(b) {
-    if ([V.JAILER, V.SENTRY].concat(Object.keys(V.LANCER_DIRS)).includes(b[1]))
-      return "Eightpieces/" + b;
+  getPpath(b, color, score, orientation) {
+    if ([V.JAILER, V.SENTRY].includes(b[1])) return "Eightpieces/" + b;
+    if (Object.keys(V.LANCER_DIRS).includes(b[1])) {
+      if (orientation == 'w') return "Eightpieces/" + b;
+      // Find opposite direction for adequate display:
+      let oppDir = '';
+      switch (b[1]) {
+        case 'c':
+          oppDir = 'g';
+          break;
+        case 'g':
+          oppDir = 'c';
+          break;
+        case 'd':
+          oppDir = 'h';
+          break;
+        case 'h':
+          oppDir = 'd';
+          break;
+        case 'e':
+          oppDir = 'm';
+          break;
+        case 'm':
+          oppDir = 'e';
+          break;
+        case 'f':
+          oppDir = 'o';
+          break;
+        case 'o':
+          oppDir = 'f';
+          break;
+      }
+      return "Eightpieces/" + b[0] + oppDir;
+    }
     return b;
   }
 
+  getPPpath(b, orientation) {
+    return this.getPpath(b, null, null, orientation);
+  }
+
   static ParseFen(fen) {
     const fenParts = fen.split(" ");
-    return Object.assign(ChessRules.ParseFen(fen), {
-      sentrypush: fenParts[5]
-    });
+    return Object.assign(
+      ChessRules.ParseFen(fen),
+      { sentrypush: fenParts[5] }
+    );
+  }
+
+  static IsGoodFen(fen) {
+    if (!ChessRules.IsGoodFen(fen)) return false;
+    const fenParsed = V.ParseFen(fen);
+    // 5) Check sentry push (if any)
+    if (
+      fenParsed.sentrypush != "-" &&
+      !fenParsed.sentrypush.match(/^([a-h][1-8],?)+$/)
+    ) {
+      return false;
+    }
+    return true;
   }
 
   getFen() {
@@ -89,9 +140,10 @@ export const VariantRules = class EightpiecesRules extends ChessRules {
   static GenRandInitFen(randomness) {
     if (randomness == 0)
       // Deterministic:
-      return "jsfqkbnr/pppppppp/8/8/8/8/PPPPPPPP/JSDQKBNR w 0 1111 - -";
+      return "jsfqkbnr/pppppppp/8/8/8/8/PPPPPPPP/JSDQKBNR w 0 ahah - -";
 
     let pieces = { w: new Array(8), b: new Array(8) };
+    let flags = "";
     // Shuffle pieces on first (and last rank if randomness == 2)
     for (let c of ["w", "b"]) {
       if (c == 'b' && randomness == 1) {
@@ -102,6 +154,7 @@ export const VariantRules = class EightpiecesRules extends ChessRules {
           pieces['w'].slice(0, lancerIdx)
           .concat(['g'])
           .concat(pieces['w'].slice(lancerIdx + 1));
+        flags += flags;
         break;
       }
 
@@ -140,6 +193,7 @@ export const VariantRules = class EightpiecesRules extends ChessRules {
       let rookPos = positions[0];
       let jailerPos = positions[2];
       const kingPos = positions[1];
+      flags += V.CoordToColumn(rookPos) + V.CoordToColumn(jailerPos);
       if (Math.random() < 0.5) [rookPos, jailerPos] = [jailerPos, rookPos];
 
       pieces[c][rookPos] = "r";
@@ -156,56 +210,10 @@ export const VariantRules = class EightpiecesRules extends ChessRules {
       pieces["b"].join("") +
       "/pppppppp/8/8/8/8/PPPPPPPP/" +
       pieces["w"].join("").toUpperCase() +
-      " w 0 1111 - -"
+      " w 0 " + flags + " - -"
     );
   }
 
-  // Scan kings, rooks and jailers
-  scanKingsRooks(fen) {
-    this.INIT_COL_KING = { w: -1, b: -1 };
-    this.INIT_COL_ROOK = { w: -1, b: -1 };
-    this.INIT_COL_JAILER = { w: -1, b: -1 };
-    this.kingPos = { w: [-1, -1], b: [-1, -1] };
-    const fenRows = V.ParseFen(fen).position.split("/");
-    const startRow = { 'w': V.size.x - 1, 'b': 0 };
-    for (let i = 0; i < fenRows.length; i++) {
-      let k = 0;
-      for (let j = 0; j < fenRows[i].length; j++) {
-        switch (fenRows[i].charAt(j)) {
-          case "k":
-            this.kingPos["b"] = [i, k];
-            this.INIT_COL_KING["b"] = k;
-            break;
-          case "K":
-            this.kingPos["w"] = [i, k];
-            this.INIT_COL_KING["w"] = k;
-            break;
-          case "r":
-            if (i == startRow['b'] && this.INIT_COL_ROOK["b"] < 0)
-              this.INIT_COL_ROOK["b"] = k;
-            break;
-          case "R":
-            if (i == startRow['w'] && this.INIT_COL_ROOK["w"] < 0)
-              this.INIT_COL_ROOK["w"] = k;
-            break;
-          case "j":
-            if (i == startRow['b'] && this.INIT_COL_JAILER["b"] < 0)
-              this.INIT_COL_JAILER["b"] = k;
-            break;
-          case "J":
-            if (i == startRow['w'] && this.INIT_COL_JAILER["w"] < 0)
-              this.INIT_COL_JAILER["w"] = k;
-            break;
-          default: {
-            const num = parseInt(fenRows[i].charAt(j));
-            if (!isNaN(num)) k += num - 1;
-          }
-        }
-        k++;
-      }
-    }
-  }
-
   // Is piece on square (x,y) immobilized?
   isImmobilized([x, y]) {
     const color = this.getColor(x, y);
@@ -267,9 +275,26 @@ export const VariantRules = class EightpiecesRules extends ChessRules {
     );
   }
 
-  getPotentialMovesFrom([x,y]) {
+  getPotentialMovesFrom([x, y]) {
     // At subTurn == 2, jailers aren't effective (Jeff K)
-    if (this.subTurn == 1 && !!this.isImmobilized([x, y])) return [];
+    if (this.subTurn == 1) {
+      const jsq = this.isImmobilized([x, y]);
+      if (!!jsq) {
+        let moves = [];
+        // Special pass move if king:
+        if (this.getPiece(x, y) == V.KING) {
+          moves.push(
+            new Move({
+              appear: [],
+              vanish: [],
+              start: { x: x, y: y },
+              end: { x: jsq[0], y: jsq[1] }
+            })
+          );
+        }
+        return moves;
+      }
+    }
     if (this.subTurn == 2) {
       // Temporarily change pushed piece color.
       // (Not using getPiece() because of lancers)
@@ -310,10 +335,11 @@ export const VariantRules = class EightpiecesRules extends ChessRules {
       // Don't forget to re-add the sentry on the board:
       // Also fix color of pushed piece afterward:
       moves.forEach(m => {
-        m.appear.push({x: x, y: y, p: V.SENTRY, c: color});
-        m.appear[0].c = oppCol;
+        m.appear.unshift({x: x, y: y, p: V.SENTRY, c: color});
+        m.appear[1].c = oppCol;
         m.vanish[0].c = oppCol;
       });
+      this.board[x][y] = saveXYstate;
     }
     return moves;
   }
@@ -372,7 +398,7 @@ export const VariantRules = class EightpiecesRules extends ChessRules {
 
     // En passant:
     const Lep = this.epSquares.length;
-    const epSquare = this.epSquares[Lep - 1]; //always at least one element
+    const epSquare = this.epSquares[Lep - 1];
     if (
       !!epSquare &&
       epSquare.x == x + shiftX &&
@@ -391,8 +417,7 @@ export const VariantRules = class EightpiecesRules extends ChessRules {
     return moves;
   }
 
-  // Obtain all lancer moves in "step" direction,
-  // without final re-orientation.
+  // Obtain all lancer moves in "step" direction
   getPotentialLancerMoves_aux([x, y], step, tr) {
     let moves = [];
     // Add all moves to vacant squares until opponent is met:
@@ -423,16 +448,35 @@ export const VariantRules = class EightpiecesRules extends ChessRules {
         this.sentryPush[L-1][pl-1].y == y
       ) {
         // I was pushed: allow all directions (for this move only), but
-        // do not change direction after moving.
+        // do not change direction after moving, *except* if I keep the
+        // same orientation in which I was pushed.
         const color = this.getColor(x, y);
+        const curDir = V.LANCER_DIRS[this.board[x][x].charAt(1)];
         Object.values(V.LANCER_DIRS).forEach(step => {
           const dirCode = Object.keys(V.LANCER_DIRS).find(k => {
-            return (V.LANCER_DIRS[k][0] == step[0] && V.LANCER_DIRS[k][1] == step[1]);
+            return (
+              V.LANCER_DIRS[k][0] == step[0] &&
+              V.LANCER_DIRS[k][1] == step[1]
+            );
           });
-          Array.prototype.push.apply(
-            moves,
-            this.getPotentialLancerMoves_aux([x, y], step, { p: dirCode, c: color })
-          );
+          const dirMoves =
+            this.getPotentialLancerMoves_aux(
+              [x, y],
+              step,
+              { p: dirCode, c: color }
+            );
+          if (curDir[0] == step[0] && curDir[1] == step[1]) {
+            // Keeping same orientation: can choose after
+            let chooseMoves = [];
+            dirMoves.forEach(m => {
+              Object.keys(V.LANCER_DIRS).forEach(k => {
+                let mk = JSON.parse(JSON.stringify(m));
+                mk.appear[0].p = k;
+                moves.push(mk);
+              });
+            });
+            Array.prototype.push.apply(moves, chooseMoves);
+          } else Array.prototype.push.apply(moves, dirMoves);
         });
         return moves;
       }
@@ -451,7 +495,24 @@ export const VariantRules = class EightpiecesRules extends ChessRules {
         });
       });
       return moves;
-    } else return monodirMoves;
+    } else {
+      // I'm pushed: add potential nudges
+      let potentialNudges = [];
+      for (let step of V.steps[V.ROOK].concat(V.steps[V.BISHOP])) {
+        if (
+          V.OnBoard(x + step[0], y + step[1]) &&
+          this.board[x + step[0]][y + step[1]] == V.EMPTY
+        ) {
+          potentialNudges.push(
+            this.getBasicMove(
+              [x, y],
+              [x + step[0], y + step[1]]
+            )
+          );
+        }
+      }
+      return monodirMoves.concat(potentialNudges);
+    }
   }
 
   getPotentialSentryMoves([x, y]) {
@@ -475,8 +536,22 @@ export const VariantRules = class EightpiecesRules extends ChessRules {
       if (m.appear.length == 0) {
         let res = false;
         this.play(m);
-        let moves2 = this.filterValid(
-          this.getPotentialMovesFrom([m.end.x, m.end.y]));
+        let potentialMoves = this.getPotentialMovesFrom([m.end.x, m.end.y]);
+        // Add nudges (if any a priori possible)
+        for (let step of V.steps[V.ROOK].concat(V.steps[V.BISHOP])) {
+          if (
+            V.OnBoard(m.end.x + step[0], m.end.y + step[1]) &&
+            this.board[m.end.x + step[0]][m.end.y + step[1]] == V.EMPTY
+          ) {
+            potentialMoves.push(
+              this.getBasicMove(
+                [m.end.x, m.end.y],
+                [m.end.x + step[0], m.end.y + step[1]]
+              )
+            );
+          }
+        }
+        let moves2 = this.filterValid(potentialMoves);
         for (let m2 of moves2) {
           this.play(m2);
           res = !this.underCheck(color);
@@ -498,23 +573,6 @@ export const VariantRules = class EightpiecesRules extends ChessRules {
     });
   }
 
-  getPotentialKingMoves([x, y]) {
-    let moves = super.getPotentialKingMoves([x, y]);
-    // Augment with pass move is the king is immobilized:
-    const jsq = this.isImmobilized([x, y]);
-    if (!!jsq) {
-      moves.push(
-        new Move({
-          appear: [],
-          vanish: [],
-          start: { x: x, y: y },
-          end: { x: jsq[0], y: jsq[1] }
-        })
-      );
-    }
-    return moves;
-  }
-
   // Adapted: castle with jailer possible
   getCastleMoves([x, y]) {
     const c = this.getColor(x, y);
@@ -535,9 +593,8 @@ export const VariantRules = class EightpiecesRules extends ChessRules {
       castleSide < 2;
       castleSide++
     ) {
-      if (!this.castleFlags[c][castleSide]) continue;
+      if (this.castleFlags[c][castleSide] >= 8) continue;
       // Rook (or jailer) and king are on initial position
-
       const finDist = finalSquares[castleSide][0] - y;
       let step = finDist / Math.max(1, Math.abs(finDist));
       i = y;
@@ -546,18 +603,14 @@ export const VariantRules = class EightpiecesRules extends ChessRules {
           this.isAttacked([x, i], [oppCol]) ||
           (this.board[x][i] != V.EMPTY &&
             (this.getColor(x, i) != c ||
-              ![V.KING, V.ROOK].includes(this.getPiece(x, i))))
+              ![V.KING, V.ROOK, V.JAILER].includes(this.getPiece(x, i))))
         ) {
           continue castlingCheck;
         }
         i += step;
       } while (i != finalSquares[castleSide][0]);
-
       step = castleSide == 0 ? -1 : 1;
-      const rookOrJailerPos =
-        castleSide == 0
-          ? Math.min(this.INIT_COL_ROOK[c], this.INIT_COL_JAILER[c])
-          : Math.max(this.INIT_COL_ROOK[c], this.INIT_COL_JAILER[c]);
+      const rookOrJailerPos = this.castleFlags[c][castleSide];
       for (i = y + step; i != rookOrJailerPos; i += step)
         if (this.board[x][i] != V.EMPTY) continue castlingCheck;
 
@@ -595,6 +648,12 @@ export const VariantRules = class EightpiecesRules extends ChessRules {
     return moves;
   }
 
+  atLeastOneMove() {
+    // If in second-half of a move, we already know that a move is possible
+    if (this.subTurn == 2) return true;
+    return super.atLeastOneMove();
+  }
+
   filterValid(moves) {
     // Disable check tests for sentry pushes,
     // because in this case the move isn't finished
@@ -604,6 +663,9 @@ export const VariantRules = class EightpiecesRules extends ChessRules {
       if (m.appear.length > 0) movesWithoutSentryPushes.push(m);
       else movesWithSentryPushes.push(m);
     });
+
+    // TODO: if after move a sentry can take king in 2 times?!
+
     const filteredMoves = super.filterValid(movesWithoutSentryPushes);
     // If at least one full move made, everything is allowed:
     if (this.movesCount >= 2)
@@ -626,97 +688,47 @@ export const VariantRules = class EightpiecesRules extends ChessRules {
     return this.filterValid(this.getPotentialMovesFrom(sentrySq));
   }
 
-  updateVariables(move) {
-    const c = this.turn;
-    const piece = move.vanish[0].p;
-    const firstRank = (c == "w" ? V.size.x - 1 : 0);
-
-    // Update king position + flags
-    if (piece == V.KING) {
-      this.kingPos[c][0] = move.appear[0].x;
-      this.kingPos[c][1] = move.appear[0].y;
-      this.castleFlags[c] = [false, false];
-      return;
-    }
-
-    // Update castling flags if rook or jailer moved (or is captured)
-    const oppCol = V.GetOppCol(c);
-    const oppFirstRank = V.size.x - 1 - firstRank;
-    let flagIdx = 0;
-    if (
-      // Our rook moves?
-      move.start.x == firstRank &&
-      this.INIT_COL_ROOK[c] == move.start.y
-    ) {
-      if (this.INIT_COL_ROOK[c] > this.INIT_COL_JAILER[c]) flagIdx++;
-      this.castleFlags[c][flagIdx] = false;
-    } else if (
-      // Our jailer moves?
-      move.start.x == firstRank &&
-      this.INIT_COL_JAILER[c] == move.start.y
-    ) {
-      if (this.INIT_COL_JAILER[c] > this.INIT_COL_ROOK[c]) flagIdx++;
-      this.castleFlags[c][flagIdx] = false;
-    } else if (
-      // We took opponent's rook?
-      move.end.x == oppFirstRank &&
-      this.INIT_COL_ROOK[oppCol] == move.end.y
-    ) {
-      if (this.INIT_COL_ROOK[oppCol] > this.INIT_COL_JAILER[oppCol]) flagIdx++;
-      this.castleFlags[oppCol][flagIdx] = false;
-    } else if (
-      // We took opponent's jailer?
-      move.end.x == oppFirstRank &&
-      this.INIT_COL_JAILER[oppCol] == move.end.y
-    ) {
-      if (this.INIT_COL_JAILER[oppCol] > this.INIT_COL_ROOK[oppCol]) flagIdx++;
-      this.castleFlags[oppCol][flagIdx] = false;
-    }
-
+  prePlay(move) {
     if (move.appear.length == 0 && move.vanish.length == 1) {
       // The sentry is about to push a piece: subTurn goes from 1 to 2
       this.sentryPos = { x: move.end.x, y: move.end.y };
-    } else if (this.subTurn == 2) {
+    } else if (this.subTurn == 2 && move.vanish[0].p != V.PAWN) {
       // A piece is pushed: forbid array of squares between start and end
       // of move, included (except if it's a pawn)
       let squares = [];
-      if (move.vanish[0].p != V.PAWN) {
-        if ([V.KNIGHT,V.KING].includes(move.vanish[0].p))
-          // short-range pieces: just forbid initial square
-          squares.push(move.start);
-        else {
-          const deltaX = move.end.x - move.start.x;
-          const deltaY = move.end.y - move.start.y;
-          const step = [
-            deltaX / Math.abs(deltaX) || 0,
-            deltaY / Math.abs(deltaY) || 0
-          ];
-          for (
-            let sq = {x: move.start.x, y: move.start.y};
-            sq.x != move.end.x && sq.y != move.end.y;
-            sq.x += step[0], sq.y += step[1]
-          ) {
-            squares.push(sq);
-          }
+      if ([V.KNIGHT,V.KING].includes(move.vanish[0].p))
+        // short-range pieces: just forbid initial square
+        squares.push({ x: move.start.x, y: move.start.y });
+      else {
+        const deltaX = move.end.x - move.start.x;
+        const deltaY = move.end.y - move.start.y;
+        const step = [
+          deltaX / Math.abs(deltaX) || 0,
+          deltaY / Math.abs(deltaY) || 0
+        ];
+        for (
+          let sq = {x: move.start.x, y: move.start.y};
+          sq.x != move.end.x && sq.y != move.end.y;
+          sq.x += step[0], sq.y += step[1]
+        ) {
+          squares.push({ x: sq.x, y: sq.y });
         }
-        // Add end square as well, to know if I was pushed (useful for lancers)
-        squares.push(move.end);
       }
+      // Add end square as well, to know if I was pushed (useful for lancers)
+      squares.push({ x: move.end.x, y: move.end.y });
       this.sentryPush.push(squares);
     } else this.sentryPush.push(null);
   }
 
-  // TODO: cleaner (global) update/unupdate variables logic, rename...
-  unupdateVariables(move) {
-    super.unupdateVariables(move);
-    this.sentryPush.pop();
-  }
-
   play(move) {
+//    if (!this.states) this.states = [];
+//    const stateFen = this.getBaseFen() + this.getTurnFen() + this.getFlagsFen();
+//    this.states.push(stateFen);
+
+    this.prePlay(move);
     move.flags = JSON.stringify(this.aggregateFlags());
     this.epSquares.push(this.getEpSquare(move));
     V.PlayOnBoard(this.board, move);
-    this.updateVariables(move);
     // Is it a sentry push? (useful for undo)
     move.sentryPush = (this.subTurn == 2);
     if (this.subTurn == 1) this.movesCount++;
@@ -726,19 +738,37 @@ export const VariantRules = class EightpiecesRules extends ChessRules {
       this.turn = V.GetOppCol(this.turn);
       this.subTurn = 1;
     }
+    this.postPlay(move);
+  }
+
+  postPlay(move) {
+    if (move.vanish.length == 0)
+      // Special pass move of the king: nothing to update!
+      return;
+    super.postPlay(move);
   }
 
   undo(move) {
     this.epSquares.pop();
     this.disaggregateFlags(JSON.parse(move.flags));
     V.UndoOnBoard(this.board, move);
-    const L = this.sentryPush.length;
     // Decrement movesCount except if the move is a sentry push
     if (!move.sentryPush) this.movesCount--;
-    // Turn changes only if not undoing second part of a sentry push
-    if (!move.sentryPush || this.subTurn == 1)
+    if (this.subTurn == 2) this.subTurn = 1;
+    else {
       this.turn = V.GetOppCol(this.turn);
-    this.unupdateVariables(move);
+      if (move.sentryPush) this.subTurn = 2;
+    }
+    this.postUndo(move);
+
+//    const stateFen = this.getBaseFen() + this.getTurnFen() + this.getFlagsFen();
+//    if (stateFen != this.states[this.states.length-1]) debugger;
+//    this.states.pop();
+  }
+
+  postUndo(move) {
+    super.postUndo(move);
+    this.sentryPush.pop();
   }
 
   isAttacked(sq, colors) {
@@ -763,7 +793,10 @@ export const VariantRules = class EightpiecesRules extends ChessRules {
           colors.includes(this.getColor(coord.x, coord.y))
         )
       ) {
-        lancerPos.push(coord);
+        if (this.getPiece(coord.x, coord.y) == V.LANCER)
+          lancerPos.push({x: coord.x, y: coord.y});
+        coord.x += step[0];
+        coord.y += step[1];
       }
       for (let xy of lancerPos) {
         const dir = V.LANCER_DIRS[this.board[xy.x][xy.y].charAt(1)];
@@ -780,16 +813,19 @@ export const VariantRules = class EightpiecesRules extends ChessRules {
       const deltaX = x2 - x1;
       const deltaY = y2 - y1;
       const step = [ deltaX / Math.abs(deltaX), deltaY / Math.abs(deltaY) ];
-      if (allowedStep.every(st => st[0] != step[0] || st[1] != step[1]))
+      if (allowedSteps.every(st => st[0] != step[0] || st[1] != step[1]))
         return false;
-      let sq = [ x1 = step[0], y1 + step[1] ];
+      let sq = [ x1 + step[0], y1 + step[1] ];
       while (sq[0] != x2 && sq[1] != y2) {
         if (
+          // NOTE: no need to check OnBoard in this special case
           (!lancer && this.board[sq[0]][sq[1]] != V.EMPTY) ||
           (!!lancer && this.getColor(sq[0], sq[1]) != color)
         ) {
           return false;
         }
+        sq[0] += step[0];
+        sq[1] += step[1];
       }
       return true;
     };
@@ -850,7 +886,7 @@ export const VariantRules = class EightpiecesRules extends ChessRules {
               V.OnBoard(sq[0], sq[1]) &&
               this.getColor(sq[0], sq[1]) == color
             ) {
-              candidates.push(sq);
+              candidates.push([ sq[0], sq[1] ]);
             }
           }
         }
@@ -879,14 +915,25 @@ export const VariantRules = class EightpiecesRules extends ChessRules {
       // TODO: this situation should not happen
       return null;
 
-    const setEval = (move) => {
+    const setEval = (move, next) => {
       const score = this.getCurrentScore();
+      const curEval = move.eval;
       if (score != "*") {
         move.eval =
           score == "1/2"
             ? 0
             : (score == "1-0" ? 1 : -1) * maxeval;
-      } else move[i].eval = this.evalPosition();
+      } else move.eval = this.evalPosition();
+      if (
+        // "next" is defined after sentry pushes
+        !!next && (
+          !curEval ||
+          color == 'w' && move.eval > curEval ||
+          color == 'b' && move.eval < curEval
+        )
+      ) {
+        move.second = next;
+      }
     };
 
     // Just search_depth == 1 (because of sentries. TODO: can do better...)
@@ -898,7 +945,7 @@ export const VariantRules = class EightpiecesRules extends ChessRules {
         const moves2 = this.getAllValidMoves();
         moves2.forEach(m2 => {
           this.play(m2);
-          setEval(m1);
+          setEval(m1, m2);
           this.undo(m2);
         });
       }
@@ -911,9 +958,12 @@ export const VariantRules = class EightpiecesRules extends ChessRules {
     let candidates = [0];
     for (let j = 1; j < moves1.length && moves1[j].eval == moves1[0].eval; j++)
       candidates.push(j);
-    return moves1[candidates[randInt(candidates.length)]];
+    const choice = moves1[candidates[randInt(candidates.length)]];
+    return (!choice.second ? choice : [choice, choice.second]);
   }
 
+  // TODO: if subTurn == 2, take some precautions, in particular pawn pushed on 1st rank.
+  // --> should indicate Sxb2,bxc1
   getNotation(move) {
     // Special case "king takes jailer" is a pass move
     if (move.appear.length == 0 && move.vanish.length == 0) return "pass";
index 45ace33..3fa5327 100644 (file)
@@ -93,8 +93,8 @@ export const VariantRules = class ExtinctionRules extends ChessRules {
     return [];
   }
 
-  updateVariables(move) {
-    super.updateVariables(move);
+  postPlay(move) {
+    super.postPlay(move);
     // Treat the promotion case: (not the capture part)
     if (move.appear[0].p != move.vanish[0].p) {
       this.material[move.appear[0].c][move.appear[0].p]++;
@@ -105,8 +105,8 @@ export const VariantRules = class ExtinctionRules extends ChessRules {
       this.material[move.vanish[1].c][move.vanish[1].p]--;
   }
 
-  unupdateVariables(move) {
-    super.unupdateVariables(move);
+  postUndo(move) {
+    super.postUndo(move);
     if (move.appear[0].p != move.vanish[0].p) {
       this.material[move.appear[0].c][move.appear[0].p]--;
       this.material[move.appear[0].c][V.PAWN]++;
index b8a0838..e6333cc 100644 (file)
@@ -289,8 +289,8 @@ export const VariantRules = class GrandRules extends ChessRules {
     );
   }
 
-  updateVariables(move) {
-    super.updateVariables(move);
+  postPlay(move) {
+    super.postPlay(move);
     if (move.vanish.length == 2 && move.appear.length == 1) {
       // Capture: update this.captured
       this.captured[move.vanish[1].c][move.vanish[1].p]++;
@@ -301,8 +301,8 @@ export const VariantRules = class GrandRules extends ChessRules {
     }
   }
 
-  unupdateVariables(move) {
-    super.unupdateVariables(move);
+  postUndo(move) {
+    super.postUndo(move);
     if (move.vanish.length == 2 && move.appear.length == 1)
       this.captured[move.vanish[1].c][move.vanish[1].p]--;
     if (move.vanish[0].p != move.appear[0].p)
@@ -324,7 +324,7 @@ export const VariantRules = class GrandRules extends ChessRules {
     if (randomness == 0) {
       // No castling in the official initial setup
       return "r8r/1nbqkmcbn1/pppppppppp/10/10/10/10/PPPPPPPPPP/1NBQKMCBN1/R8R " +
-        "w 0 0000 - 00000000000000";
+        "w 0 zzzz - 00000000000000";
     }
 
     let pieces = { w: new Array(10), b: new Array(10) };
index 416f632..82caa9f 100644 (file)
@@ -139,7 +139,7 @@ export const VariantRules = class GrasshopperRules extends ChessRules {
 
   static GenRandInitFen(randomness) {
     return ChessRules.GenRandInitFen(randomness)
-      .replace("w 0 1111 -", "w 0 1111")
+      .slice(0, -2)
       .replace(
         "/pppppppp/8/8/8/8/PPPPPPPP/",
         "/gggggggg/pppppppp/8/8/PPPPPPPP/GGGGGGGG/"
index 046fe99..79c3dfb 100644 (file)
@@ -74,7 +74,7 @@ export const VariantRules = class HiddenRules extends ChessRules {
   }
 
   // Scan board for kings positions (no castling)
-  scanKingsRooks(fen) {
+  scanKings(fen) {
     this.kingPos = { w: [-1, -1], b: [-1, -1] };
     const fenRows = V.ParseFen(fen).position.split("/");
     for (let i = 0; i < fenRows.length; i++) {
@@ -225,8 +225,8 @@ export const VariantRules = class HiddenRules extends ChessRules {
     return [];
   }
 
-  updateVariables(move) {
-    super.updateVariables(move);
+  postPlay(move) {
+    super.postPlay(move);
     if (
       move.vanish.length >= 2 &&
       [V.KING,V.HIDDEN_CODE[V.KING]].includes(move.vanish[1].p)
@@ -236,8 +236,8 @@ export const VariantRules = class HiddenRules extends ChessRules {
     }
   }
 
-  unupdateVariables(move) {
-    super.unupdateVariables(move);
+  postUndo(move) {
+    super.postUndo(move);
     const c = move.vanish[0].c;
     const oppCol = V.GetOppCol(c);
     if (this.kingPos[oppCol][0] < 0)
index 7048d41..15d9b1e 100644 (file)
@@ -42,36 +42,30 @@ export const VariantRules = class HiddenqueenRules extends ChessRules {
     const color = move.vanish[0].c;
     const pawnShift = color == "w" ? -1 : 1;
     const startRank = color == "w" ? V.size.x - 2 : 1;
-    const lastRank = color == "w" ? 0 : V.size.x - 1;
     return (
-      // The queen is discovered if she reaches the 8th rank,
-      // even if this would be a technically valid pawn move.
-      move.end.x != lastRank &&
       (
+        move.end.x - move.start.x == pawnShift &&
         (
-          move.end.x - move.start.x == pawnShift &&
           (
-            (
-              // Normal move
-              move.end.y == move.start.y &&
-              this.board[move.end.x][move.end.y] == V.EMPTY
-            )
-            ||
-            (
-              // Capture
-              Math.abs(move.end.y - move.start.y) == 1 &&
-              this.board[move.end.x][move.end.y] != V.EMPTY
-            )
+            // Normal move
+            move.end.y == move.start.y &&
+            this.board[move.end.x][move.end.y] == V.EMPTY
+          )
+          ||
+          (
+            // Capture
+            Math.abs(move.end.y - move.start.y) == 1 &&
+            this.board[move.end.x][move.end.y] != V.EMPTY
           )
         )
-        ||
-        (
-          // Two-spaces initial jump
-          move.start.x == startRank &&
-          move.end.y == move.start.y &&
-          move.end.x - move.start.x == 2 * pawnShift &&
-          this.board[move.end.x][move.end.y] == V.EMPTY
-        )
+      )
+      ||
+      (
+        // Two-spaces initial jump
+        move.start.x == startRank &&
+        move.end.y == move.start.y &&
+        move.end.x - move.start.x == 2 * pawnShift &&
+        this.board[move.end.x][move.end.y] == V.EMPTY
       )
     );
   }
@@ -109,7 +103,7 @@ export const VariantRules = class HiddenqueenRules extends ChessRules {
         ? piece == V.PAWN
           ? [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN]
           : [V.QUEEN] //hidden queen revealed
-        : piece;
+        : [piece]; //V.PAWN
     if (this.board[x + shiftX][y] == V.EMPTY) {
       // One square forward
       for (let p of finalPieces) {
@@ -147,24 +141,22 @@ export const VariantRules = class HiddenqueenRules extends ChessRules {
       }
     }
 
-    if (V.HasEnpassant) {
-      // En passant
-      const Lep = this.epSquares.length;
-      const epSquare = this.epSquares[Lep - 1]; //always at least one element
-      if (
-        !!epSquare &&
-        epSquare.x == x + shiftX &&
-        Math.abs(epSquare.y - y) == 1
-      ) {
-        let enpassantMove = this.getBasicMove([x, y], [epSquare.x, epSquare.y]);
-        enpassantMove.vanish.push({
-          x: x,
-          y: epSquare.y,
-          p: "p",
-          c: this.getColor(x, epSquare.y)
-        });
-        moves.push(enpassantMove);
-      }
+    // En passant
+    const Lep = this.epSquares.length;
+    const epSquare = this.epSquares[Lep - 1]; //always at least one element
+    if (
+      !!epSquare &&
+      epSquare.x == x + shiftX &&
+      Math.abs(epSquare.y - y) == 1
+    ) {
+      let enpassantMove = this.getBasicMove([x, y], [epSquare.x, epSquare.y]);
+      enpassantMove.vanish.push({
+        x: x,
+        y: epSquare.y,
+        p: "p",
+        c: this.getColor(x, epSquare.y)
+      });
+      moves.push(enpassantMove);
     }
 
     return moves;
@@ -189,19 +181,18 @@ export const VariantRules = class HiddenqueenRules extends ChessRules {
     return fen;
   }
 
-  updateVariables(move) {
-    super.updateVariables(move);
+  postPlay(move) {
+    super.postPlay(move);
     if (move.vanish.length == 2 && move.vanish[1].p == V.KING)
       // We took opponent king
       this.kingPos[this.turn] = [-1, -1];
   }
 
-  unupdateVariables(move) {
-    super.unupdateVariables(move);
-    const c = move.vanish[0].c;
-    const oppCol = V.GetOppCol(c);
+  preUndo(move) {
+    super.preUndo(move);
+    const oppCol = this.turn;
     if (this.kingPos[oppCol][0] < 0)
-      // Last move took opponent's king:
+      // Move takes opponent's king:
       this.kingPos[oppCol] = [move.vanish[1].x, move.vanish[1].y];
   }
 
index 4549e84..a59ce41 100644 (file)
@@ -145,34 +145,37 @@ export const VariantRules = class MagneticRules extends ChessRules {
     return [];
   }
 
-  updateVariables(move) {
-    super.updateVariables(move);
+  postPlay(move) {
+    super.postPlay(move);
     const c = move.vanish[0].c;
     if (move.vanish.length >= 2 && move.vanish[1].p == V.KING) {
       // We took opponent king !
       const oppCol = V.GetOppCol(c);
       this.kingPos[oppCol] = [-1, -1];
-      this.castleFlags[oppCol] = [false, false];
+      this.castleFlags[oppCol] = [8, 8];
     }
     // Did we magnetically move our (init) rooks or opponents' ones ?
     const firstRank = c == "w" ? 7 : 0;
     const oppFirstRank = 7 - firstRank;
     const oppCol = V.GetOppCol(c);
     move.vanish.forEach(psq => {
-      if (psq.x == firstRank && this.INIT_COL_ROOK[c].includes(psq.y))
-        this.castleFlags[c][psq.y == this.INIT_COL_ROOK[c][0] ? 0 : 1] = false;
+      if (
+        psq.x == firstRank &&
+        this.castleFlags[c].includes(psq.y)
+      ) {
+        this.castleFlags[c][psq.y == this.castleFlags[c][0] ? 0 : 1] = 8;
+      }
       else if (
         psq.x == oppFirstRank &&
-        this.INIT_COL_ROOK[oppCol].includes(psq.y)
-      )
-        this.castleFlags[oppCol][
-          psq.y == this.INIT_COL_ROOK[oppCol][0] ? 0 : 1
-        ] = false;
+        this.castleFlags[oppCol].includes(psq.y)
+      ) {
+        this.castleFlags[oppCol][psq.y == this.castleFlags[oppCol][0] ? 0 : 1] = 8;
+      }
     });
   }
 
-  unupdateVariables(move) {
-    super.unupdateVariables(move);
+  postUndo(move) {
+    super.postUndo(move);
     const c = move.vanish[0].c;
     const oppCol = V.GetOppCol(c);
     if (this.kingPos[oppCol][0] < 0) {
index 121bf01..c3bec32 100644 (file)
@@ -34,7 +34,7 @@ export const VariantRules = class MarseilleRules extends ChessRules {
       if (sq != "-") return V.SquareToCoords(sq);
       return undefined;
     })];
-    this.scanKingsRooks(fen);
+    this.scanKings(fen);
     // Extract subTurn from turn indicator: "w" (first move), or
     // "w1" or "w2" white subturn 1 or 2, and same for black
     const fullTurn = V.ParseFen(fen).turn;
@@ -158,7 +158,35 @@ export const VariantRules = class MarseilleRules extends ChessRules {
       }
       this.subTurn = 3 - this.subTurn;
     }
-    this.updateVariables(move);
+    this.postPlay(move);
+  }
+
+  postPlay(move) {
+    const c = move.turn.charAt(0);
+    const piece = move.vanish[0].p;
+    const firstRank = c == "w" ? V.size.x - 1 : 0;
+
+    if (piece == V.KING && move.appear.length > 0) {
+      this.kingPos[c][0] = move.appear[0].x;
+      this.kingPos[c][1] = move.appear[0].y;
+      if (V.HasCastle) this.castleFlags[c] = [V.size.y, V.size.y];
+      return;
+    }
+    const oppCol = V.GetOppCol(c);
+    const oppFirstRank = V.size.x - 1 - firstRank;
+    if (
+      move.start.x == firstRank && //our rook moves?
+      this.castleFlags[c].includes(move.start.y)
+    ) {
+      const flagIdx = (move.start.y == this.castleFlags[c][0] ? 0 : 1);
+      this.castleFlags[c][flagIdx] = V.size.y;
+    } else if (
+      move.end.x == oppFirstRank && //we took opponent rook?
+      this.castleFlags[oppCol].includes(move.end.y)
+    ) {
+      const flagIdx = (move.end.y == this.castleFlags[oppCol][0] ? 0 : 1);
+      this.castleFlags[oppCol][flagIdx] = V.size.y;
+    }
   }
 
   undo(move) {
@@ -176,7 +204,7 @@ export const VariantRules = class MarseilleRules extends ChessRules {
     }
     this.turn = move.turn[0];
     this.subTurn = parseInt(move.turn[1]);
-    this.unupdateVariables(move);
+    super.postUndo(move);
   }
 
   // NOTE:  GenRandInitFen() is OK,
index e31badb..d91e824 100644 (file)
@@ -223,31 +223,23 @@ export const VariantRules = class RecycleRules extends ChessRules {
     return this.getPiece(x2, y2) != V.KING;
   }
 
-  updateVariables(move) {
-    super.updateVariables(move);
+  postPlay(move) {
+    super.postPlay(move);
     if (move.vanish.length == 2 && move.appear.length == 2) return; //skip castle
-    const color = V.GetOppCol(this.turn);
-    if (move.vanish.length == 0) {
-      this.reserve[color][move.appear[0].p]--;
-      return;
-    }
-    else if (move.vanish.length == 2 && move.vanish[1].c == color) {
+    const color = move.appear[0].c;
+    if (move.vanish.length == 0) this.reserve[color][move.appear[0].p]--;
+    else if (move.vanish.length == 2 && move.vanish[1].c == color)
       // Self-capture
       this.reserve[color][move.vanish[1].p]++;
-    }
   }
 
-  unupdateVariables(move) {
-    super.unupdateVariables(move);
+  postUndo(move) {
+    super.postUndo(move);
     if (move.vanish.length == 2 && move.appear.length == 2) return;
     const color = this.turn;
-    if (move.vanish.length == 0) {
-      this.reserve[color][move.appear[0].p]++;
-      return;
-    }
-    else if (move.vanish.length == 2 && move.vanish[1].c == color) {
+    if (move.vanish.length == 0) this.reserve[color][move.appear[0].p]++;
+    else if (move.vanish.length == 2 && move.vanish[1].c == color)
       this.reserve[color][move.vanish[1].p]--;
-    }
   }
 
   static get SEARCH_DEPTH() {
index b45d915..c364b81 100644 (file)
@@ -19,7 +19,8 @@ export const VariantRules = class ShatranjRules extends ChessRules {
   }
 
   static GenRandInitFen(randomness) {
-    return ChessRules.GenRandInitFen(randomness).replace("w 1111 -", "w");
+    // Remove castle flags and en-passant indication
+    return ChessRules.GenRandInitFen(randomness).slice(0, -7);
   }
 
   getPotentialPawnMoves([x, y]) {
index 0fc88ce..9ac5b44 100644 (file)
@@ -1,11 +1,18 @@
 import { ChessRules, PiPo, Move } from "@/base_rules";
 
 export const VariantRules = class SuctionRules extends ChessRules {
+  static get HasFlags() {
+    return false;
+  }
+
   setOtherVariables(fen) {
+
+console.log(fen);
+
     super.setOtherVariables(fen);
-    // Local stack of captures
+    // Local stack of "captures"
     this.cmoves = [];
-    const cmove = fen.split(" ")[5];
+    const cmove = V.ParseFen(fen).cmove;
     if (cmove == "-") this.cmoves.push(null);
     else {
       this.cmoves.push({
@@ -15,6 +22,12 @@ export const VariantRules = class SuctionRules extends ChessRules {
     }
   }
 
+  static ParseFen(fen) {
+    return Object.assign({}, ChessRules.ParseFen(fen), {
+      cmove: fen.split(" ")[4]
+    });
+  }
+
   static IsGoodFen(fen) {
     if (!ChessRules.IsGoodFen(fen)) return false;
     const fenParts = fen.split(" ");
@@ -171,27 +184,9 @@ export const VariantRules = class SuctionRules extends ChessRules {
     });
   }
 
-  updateVariables(move) {
-    super.updateVariables(move);
-    if (move.vanish.length == 2) {
-      // Was opponent king swapped?
-      if (move.vanish[1].p == V.KING)
-        this.kingPos[this.turn] = [move.appear[1].x, move.appear[1].y];
-    }
-  }
-
-  unupdateVariables(move) {
-    super.unupdateVariables(move);
-    if (move.appear.length == 2) {
-      // Was opponent king swapped?
-      if (move.appear[1].p == V.KING)
-        this.kingPos[move.vanish[1].c] = [move.vanish[1].x,move.vanish[1].y];
-    }
-  }
-
   static GenRandInitFen(randomness) {
     // Add empty cmove:
-    return ChessRules.GenRandInitFen(randomness) + " -";
+    return ChessRules.GenRandInitFen(randomness).slice(0, -6) + "- -";
   }
 
   getFen() {
@@ -203,14 +198,23 @@ export const VariantRules = class SuctionRules extends ChessRules {
     return super.getFen() + " " + cmoveFen;
   }
 
-  play(move) {
+  postPlay(move) {
+    super.postPlay(move);
+    if (move.vanish.length == 2) {
+      // Was opponent king swapped?
+      if (move.vanish[1].p == V.KING)
+        this.kingPos[this.turn] = [move.appear[1].x, move.appear[1].y];
+    }
     this.cmoves.push(this.getCmove(move));
-    super.play(move);
   }
 
-  undo(move) {
+  postUndo(move) {
+    super.postUndo(move);
+    if (move.appear.length == 2) {
+      if (move.appear[1].p == V.KING)
+        this.kingPos[move.vanish[1].c] = [move.vanish[1].x, move.vanish[1].y];
+    }
     this.cmoves.pop();
-    super.undo(move);
   }
 
   atLeastOneMove() {
index f030b08..24e8ef2 100644 (file)
@@ -114,8 +114,10 @@ export const VariantRules = class SuicideRules extends ChessRules {
   }
 
   // No variables update because no royal king + no castling
-  updateVariables() {}
-  unupdateVariables() {}
+  prePlay() {}
+  postPlay() {}
+  preUndo() {}
+  postUndo() {}
 
   getCurrentScore() {
     if (this.atLeastOneMove()) return "*";
index f3b7725..3aa6db9 100644 (file)
@@ -32,8 +32,8 @@ export const VariantRules = class ThreechecksRules extends ChessRules {
     return b;
   }
 
-  updateVariables(move) {
-    super.updateVariables(move);
+  postPlay(move) {
+    super.postPlay(move);
     // Does this move give check?
     const oppCol = this.turn;
     if (this.underCheck(oppCol))
@@ -48,16 +48,14 @@ export const VariantRules = class ThreechecksRules extends ChessRules {
   }
 
   static GenRandInitFen(randomness) {
-    return ChessRules.GenRandInitFen(randomness)
-      // Add check flags (at 0)
-      .replace(" w 0 1111", " w 0 111100");
+    // Add check flags (at 0)
+    return ChessRules.GenRandInitFen(randomness).slice(0, -2) + "00";
   }
 
   getFlagsFen() {
     let fen = super.getFlagsFen();
     // Add check flags
-    for (let c of ["w", "b"])
-      fen += this.checkFlags[c];
+    for (let c of ["w", "b"]) fen += this.checkFlags[c];
     return fen;
   }
 
index 565f627..bf5c178 100644 (file)
@@ -247,12 +247,14 @@ export const VariantRules = class WildebeestRules extends ChessRules {
   static GenRandInitFen(randomness) {
     if (!randomness) randomness = 2;
     if (randomness == 0)
-      return "rnccwkqbbnr/ppppppppppp/11/11/11/11/11/11/PPPPPPPPPPP/RNBBQKWCCNR w 0 1111 -";
+      return "rnccwkqbbnr/ppppppppppp/11/11/11/11/11/11/PPPPPPPPPPP/RNBBQKWCCNR w 0 akak -";
 
     let pieces = { w: new Array(10), b: new Array(10) };
+    let flags = "";
     for (let c of ["w", "b"]) {
       if (c == 'b' && randomness == 1) {
         pieces['b'] = pieces['w'];
+        flags += flags;
         break;
       }
 
@@ -308,12 +310,13 @@ export const VariantRules = class WildebeestRules extends ChessRules {
       pieces[c][bishop2Pos] = "b";
       pieces[c][knight2Pos] = "n";
       pieces[c][rook2Pos] = "r";
+      flags += V.CoordToColumn(rook1Pos) + V.CoordToColumn(rook2Pos);
     }
     return (
       pieces["b"].join("") +
       "/ppppppppppp/11/11/11/11/11/11/PPPPPPPPPPP/" +
       pieces["w"].join("").toUpperCase() +
-      " w 0 1111 -"
+      " w 0 " + flags + " -"
     );
   }
 };
index 04699b2..462cb3b 100644 (file)
@@ -278,6 +278,10 @@ export const VariantRules = class WormholeRules extends ChessRules {
     return this.turn == "w" ? "0-1" : "1-0";
   }
 
+  static get SEARCH_DEPTH() {
+    return 2;
+  }
+
   evalPosition() {
     let evaluation = 0;
     for (let i = 0; i < V.size.x; i++) {
index e829ae4..f662d61 100644 (file)
@@ -96,11 +96,10 @@ export default {
     },
     adjustFenSize: function() {
       let fenInput = document.getElementById("fen");
-      fenInput.style.width = this.curFen.length + "ch";
+      fenInput.style.width = (this.curFen.length+1) + "ch";
     },
     tryGotoFen: function() {
-      if (V.IsGoodFen(this.curFen))
-      {
+      if (V.IsGoodFen(this.curFen)) {
         this.gameRef.fen = this.curFen;
         this.loadGame();
       }
index 18a2836..cb1b720 100644 (file)
@@ -938,14 +938,9 @@ export default {
           if (game.score == "*") {
             // Set clocks + initime
             game.initime = [0, 0];
-            if (L >= 1) {
-              const gameLastupdate = game.moves[L-1].played;
-              game.initime[L % 2] = gameLastupdate;
-              if (L >= 2) {
-                game.clocks[L % 2] =
-                  tc.mainTime - (Date.now() - gameLastupdate) / 1000;
-              }
-            }
+            if (L >= 1) game.initime[L % 2] = game.moves[L-1].played;
+            // NOTE: game.clocks shouldn't be computed right now:
+            // job will be done in re_setClocks() called soon below.
           }
           // Sort chat messages from newest to oldest
           game.chats.sort((c1, c2) => {