A few small fixes + add Monster variant
[vchess.git] / client / src / base_rules.js
index e142839..22f57d5 100644 (file)
@@ -27,7 +27,8 @@ export const Move = class Move {
   }
 };
 
-// NOTE: x coords = top to bottom; y = left to right (from white player perspective)
+// NOTE: x coords = top to bottom; y = left to right
+// (from white player perspective)
 export const ChessRules = class ChessRules {
   //////////////
   // MISC UTILS
@@ -85,6 +86,11 @@ export const ChessRules = class ChessRules {
     return V.CanFlip;
   }
 
+  // Some variants use click infos:
+  doClick() {
+    return null;
+  }
+
   static get IMAGE_EXTENSION() {
     // All pieces should be in the SVG format
     return ".svg";
@@ -197,8 +203,8 @@ export const ChessRules = class ChessRules {
   }
 
   // Path to promotion pieces (usually the same)
-  getPPpath(b) {
-    return this.getPpath(b);
+  getPPpath(m) {
+    return this.getPpath(m.appear[0].c + m.appear[0].p);
   }
 
   // Aggregates flags into one object
@@ -224,9 +230,11 @@ export const ChessRules = class ChessRules {
     const s = move.start,
           e = move.end;
     if (
-      Math.abs(s.x - e.x) == 2 &&
       s.y == e.y &&
-      (move.appear.length > 0 && move.appear[0].p == V.PAWN)
+      Math.abs(s.x - e.x) == 2 &&
+      // Next conditions for variants like Atomic or Rifle, Recycle...
+      (move.appear.length > 0 && move.appear[0].p == V.PAWN) &&
+      (move.vanish.length > 0 && move.vanish[0].p == V.PAWN)
     ) {
       return {
         x: (s.x + e.x) / 2,
@@ -255,7 +263,8 @@ export const ChessRules = class ChessRules {
   getCheckSquares(color) {
     return (
       this.underCheck(color)
-        ? [JSON.parse(JSON.stringify(this.kingPos[color]))] //need to duplicate!
+        // kingPos must be duplicated, because it may change:
+        ? [JSON.parse(JSON.stringify(this.kingPos[color]))]
         : []
     );
   }
@@ -464,7 +473,8 @@ export const ChessRules = class ChessRules {
   // Scan board for kings positions
   scanKings(fen) {
     this.INIT_COL_KING = { w: -1, b: -1 };
-    this.kingPos = { w: [-1, -1], b: [-1, -1] }; //squares of white and black king
+    // Squares of white and black king:
+    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++) {
@@ -839,20 +849,22 @@ export const ChessRules = class ChessRules {
       if (this.castleFlags[c][castleSide] >= V.size.y) continue;
       // If this code is reached, rook and king are on initial position
 
-      // NOTE: in some variants this is not a rook, but let's keep variable name
+      // NOTE: in some variants this is not a rook
       const rookPos = this.castleFlags[c][castleSide];
-      const castlingPiece = this.getPiece(x, rookPos);
-      if (this.getColor(x, rookPos) != c)
-        // Rook is here but changed color (see Benedict)
+      if (this.board[x][rookPos] == V.EMPTY || this.getColor(x, rookPos) != c)
+        // Rook is not here, or changed color (see Benedict)
         continue;
 
       // Nothing on the path of the king ? (and no checks)
+      const castlingPiece = this.getPiece(x, rookPos);
       const finDist = finalSquares[castleSide][0] - y;
       let step = finDist / Math.max(1, Math.abs(finDist));
       i = y;
       do {
         if (
-          (!castleInCheck && this.isAttacked([x, i], oppCol)) ||
+          // NOTE: "castling" arg is used by some variants (Monster),
+          // where "isAttacked" is overloaded in an infinite-recursive way.
+          (!castleInCheck && this.isAttacked([x, i], oppCol, "castling")) ||
           (this.board[x][i] != V.EMPTY &&
             // NOTE: next check is enough, because of chessboard constraints
             (this.getColor(x, i) != c ||
@@ -872,9 +884,12 @@ export const ChessRules = class ChessRules {
       // Nothing on final squares, except maybe king and castling rook?
       for (i = 0; i < 2; i++) {
         if (
+          finalSquares[castleSide][i] != rookPos &&
           this.board[x][finalSquares[castleSide][i]] != V.EMPTY &&
-          this.getPiece(x, finalSquares[castleSide][i]) != V.KING &&
-          finalSquares[castleSide][i] != rookPos
+          (
+            this.getPiece(x, finalSquares[castleSide][i]) != V.KING ||
+            this.getColor(x, finalSquares[castleSide][i]) != c
+          )
         ) {
           continue castlingCheck;
         }
@@ -884,8 +899,18 @@ export const ChessRules = class ChessRules {
       moves.push(
         new Move({
           appear: [
-            new PiPo({ x: x, y: finalSquares[castleSide][0], p: V.KING, c: c }),
-            new PiPo({ x: x, y: finalSquares[castleSide][1], p: castlingPiece, c: c })
+            new PiPo({
+              x: x,
+              y: finalSquares[castleSide][0],
+              p: V.KING,
+              c: c
+            }),
+            new PiPo({
+              x: x,
+              y: finalSquares[castleSide][1],
+              p: castlingPiece,
+              c: c
+            })
           ],
           vanish: [
             new PiPo({ x: x, y: y, p: V.KING, c: c }),
@@ -922,9 +947,7 @@ export const ChessRules = class ChessRules {
     });
   }
 
-  // Search for all valid moves considering current turn
-  // (for engine and game end)
-  getAllValidMoves() {
+  getAllPotentialMoves() {
     const color = this.turn;
     let potentialMoves = [];
     for (let i = 0; i < V.size.x; i++) {
@@ -937,10 +960,17 @@ export const ChessRules = class ChessRules {
         }
       }
     }
-    return this.filterValid(potentialMoves);
+    return potentialMoves;
+  }
+
+  // Search for all valid moves considering current turn
+  // (for engine and game end)
+  getAllValidMoves() {
+    return this.filterValid(this.getAllPotentialMoves());
   }
 
   // Stop at the first move found
+  // TODO: not really, it explores all moves from a square (one is enough).
   atLeastOneMove() {
     const color = this.turn;
     for (let i = 0; i < V.size.x; i++) {
@@ -1079,7 +1109,8 @@ export const ChessRules = class ChessRules {
 //    this.states.push(stateFen);
 
     this.prePlay(move);
-    if (V.HasFlags) move.flags = JSON.stringify(this.aggregateFlags()); //save flags (for undo)
+    // Save flags (for undo)
+    if (V.HasFlags) move.flags = JSON.stringify(this.aggregateFlags());
     if (V.HasEnpassant) this.epSquares.push(this.getEpSquare(move));
     V.PlayOnBoard(this.board, move);
     this.turn = V.GetOppCol(this.turn);
@@ -1091,7 +1122,7 @@ export const ChessRules = class ChessRules {
     const c = V.GetOppCol(this.turn);
     const firstRank = (c == "w" ? V.size.x - 1 : 0);
     // Update castling flags if rooks are moved
-    const oppCol = V.GetOppCol(c);
+    const oppCol = this.turn;
     const oppFirstRank = V.size.x - 1 - firstRank;
     if (piece == V.KING && move.appear.length > 0)
       this.castleFlags[c] = [V.size.y, V.size.y];
@@ -1197,7 +1228,7 @@ export const ChessRules = class ChessRules {
     return V.INFINITY;
   }
 
-  // Search depth: 1,2 for high branching factor, 4 for small (Loser chess, eg.)
+  // Search depth: 1,2 for e.g. higher branching factor, 4 for smaller
   static get SEARCH_DEPTH() {
     return 3;
   }
@@ -1370,4 +1401,28 @@ export const ChessRules = class ChessRules {
       finalSquare
     );
   }
+
+  static GetUnambiguousNotation(move) {
+    // Machine-readable format with all the informations about the move
+    return (
+      (!!move.start && V.OnBoard(move.start.x, move.start.y)
+        ? V.CoordsToSquare(move.start)
+        : "-"
+      ) + "." +
+      (!!move.end && V.OnBoard(move.end.x, move.end.y)
+        ? V.CoordsToSquare(move.end)
+        : "-"
+      ) + " " +
+      (!!move.appear && move.appear.length > 0
+        ? move.appear.map(a =>
+          a.c + a.p + V.CoordsToSquare({ x: a.x, y: a.y })).join(".")
+        : "-"
+      ) + "/" +
+      (!!move.vanish && move.vanish.length > 0
+        ? move.vanish.map(a =>
+          a.c + a.p + V.CoordsToSquare({ x: a.x, y: a.y })).join(".")
+        : "-"
+      )
+    );
+  }
 };