Several small improvements + integrate options + first working draft of Cwda
[vchess.git] / client / src / variants / Fullcavalry.js
index c25f340..07951f6 100644 (file)
@@ -76,9 +76,11 @@ export class FullcavalryRules extends ChessRules {
   }
 
   getPPpath(m, orientation) {
+    // If castle, show choices on m.appear[1]:
+    const index = (m.appear.length == 2 ? 1 : 0);
     return (
       this.getPpath(
-        m.appear[0].c + m.appear[0].p,
+        m.appear[index].c + m.appear[index].p,
         null,
         null,
         orientation
@@ -86,55 +88,30 @@ export class FullcavalryRules extends ChessRules {
     );
   }
 
-  static GenRandInitFen(randomness) {
-    if (randomness == 0)
+  static GenRandInitFen(options) {
+    if (options.randomness == 0)
       // Deterministic:
-      return "efbqkbnm/pppppppp/8/8/8/8/PPPPPPPP/EDBQKBNM w 0 ahah -";
+      return "enbqkbnm/pppppppp/8/8/8/8/PPPPPPPP/ENBQKBNM w 0 ahah -";
 
-    const baseFen = ChessRules.GenRandInitFen(randomness);
-    // Replace black rooks by lancers oriented south,
-    // and white rooks by lancers oriented north:
-    return baseFen.replace(/r/g, 'g').replace(/R/g, 'C');
-  }
-
-  // Because of the lancers, getPiece() could be wrong:
-  // use board[x][y][1] instead (always valid).
-  // TODO: base implementation now uses this too (no?)
-  getBasicMove([sx, sy], [ex, ey], tr) {
-    const initColor = this.getColor(sx, sy);
-    const initPiece = this.board[sx][sy].charAt(1);
-    let mv = new Move({
-      appear: [
-        new PiPo({
-          x: ex,
-          y: ey,
-          c: tr ? tr.c : initColor,
-          p: tr ? tr.p : initPiece
-        })
-      ],
-      vanish: [
-        new PiPo({
-          x: sx,
-          y: sy,
-          c: initColor,
-          p: initPiece
-        })
-      ]
-    });
-
-    // The opponent piece disappears if we take it
-    if (this.board[ex][ey] != V.EMPTY) {
-      mv.vanish.push(
-        new PiPo({
-          x: ex,
-          y: ey,
-          c: this.getColor(ex, ey),
-          p: this.board[ex][ey].charAt(1)
-        })
-      );
-    }
-
-    return mv;
+    const baseFen = ChessRules.GenRandInitFen(options);
+    // Replace rooks by lancers with expected orientation:
+    const firstBlackRook = baseFen.indexOf('r'),
+          lastBlackRook = baseFen.lastIndexOf('r'),
+          firstWhiteRook = baseFen.indexOf('R'),
+          lastWhiteRook = baseFen.lastIndexOf('R');
+    return (
+      baseFen.substring(0, firstBlackRook) +
+        (firstBlackRook <= 3 ? 'e' : 'm') +
+      baseFen.substring(firstBlackRook + 1, lastBlackRook) +
+        (lastBlackRook >= 5 ? 'm' : 'e') +
+      // Subtract 35 = total number of characters before last FEN row:
+      // 8x3 (full rows) + 4 (empty rows) + 7 (separators)
+      baseFen.substring(lastBlackRook + 1, firstWhiteRook) +
+        (firstWhiteRook - 35 <= 3 ? 'E' : 'M') +
+      baseFen.substring(firstWhiteRook + 1, lastWhiteRook) +
+        (lastWhiteRook - 35 >= 5 ? 'M' : 'E') +
+      baseFen.substring(lastWhiteRook + 1)
+    );
   }
 
   getPotentialMovesFrom([x, y]) {
@@ -143,6 +120,22 @@ export class FullcavalryRules extends ChessRules {
     return super.getPotentialMovesFrom([x, y]);
   }
 
+  getPotentialPawnMoves([x, y]) {
+    const color = this.getColor(x, y);
+    let shiftX = (color == "w" ? -1 : 1);
+    const lastRank = (color == "w" ? 0 : 7);
+    let finalPieces = [V.PAWN];
+    if (x + shiftX == lastRank) {
+      // Only allow direction facing inside board:
+      const allowedLancerDirs =
+        lastRank == 0
+          ? ['e', 'f', 'g', 'h', 'm']
+          : ['c', 'd', 'e', 'm', 'o'];
+      finalPieces = allowedLancerDirs.concat([V.KNIGHT, V.BISHOP, V.QUEEN]);
+    }
+    return super.getPotentialPawnMoves([x, y], finalPieces);
+  }
+
   // Obtain all lancer moves in "step" direction
   getPotentialLancerMoves_aux([x, y], step, tr) {
     let moves = [];
@@ -184,6 +177,100 @@ export class FullcavalryRules extends ChessRules {
     return moves;
   }
 
+  getCastleMoves([x, y]) {
+    const c = this.getColor(x, y);
+
+    // Castling ?
+    const oppCol = V.GetOppCol(c);
+    let moves = [];
+    let i = 0;
+    // King, then lancer:
+    const finalSquares = [ [2, 3], [V.size.y - 2, V.size.y - 3] ];
+    castlingCheck: for (
+      let castleSide = 0;
+      castleSide < 2;
+      castleSide++ //large, then small
+    ) {
+      if (this.castleFlags[c][castleSide] >= V.size.y) continue;
+      // If this code is reached, lancer and king are on initial position
+
+      const lancerPos = this.castleFlags[c][castleSide];
+      const castlingPiece = this.board[x][lancerPos].charAt(1);
+
+      // Nothing on the path of the king ? (and no checks)
+      const finDist = finalSquares[castleSide][0] - y;
+      let step = finDist / Math.max(1, Math.abs(finDist));
+      i = y;
+      do {
+        if (
+          (this.isAttacked([x, i], oppCol)) ||
+          (
+            this.board[x][i] != V.EMPTY &&
+            // NOTE: next check is enough, because of chessboard constraints
+            (this.getColor(x, i) != c || ![y, lancerPos].includes(i))
+          )
+        ) {
+          continue castlingCheck;
+        }
+        i += step;
+      } while (i != finalSquares[castleSide][0]);
+
+      // Nothing on final squares, except maybe king and castling lancer?
+      for (i = 0; i < 2; i++) {
+        if (
+          finalSquares[castleSide][i] != lancerPos &&
+          this.board[x][finalSquares[castleSide][i]] != V.EMPTY &&
+          (
+            finalSquares[castleSide][i] != y ||
+            this.getColor(x, finalSquares[castleSide][i]) != c
+          )
+        ) {
+          continue castlingCheck;
+        }
+      }
+
+      // If this code is reached, castle is valid
+      let allowedLancerDirs = [castlingPiece];
+      if (finalSquares[castleSide][1] != lancerPos) {
+        // It moved: allow reorientation
+        allowedLancerDirs =
+          x == 0
+            ? ['e', 'f', 'g', 'h', 'm']
+            : ['c', 'd', 'e', 'm', 'o'];
+      }
+      allowedLancerDirs.forEach(dir => {
+        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: dir,
+                c: c
+              })
+            ],
+            vanish: [
+              new PiPo({ x: x, y: y, p: V.KING, c: c }),
+              new PiPo({ x: x, y: lancerPos, p: castlingPiece, c: c })
+            ],
+            end:
+              Math.abs(y - lancerPos) <= 2
+                ? { x: x, y: lancerPos }
+                : { x: x, y: y + 2 * (castleSide == 0 ? -1 : 1) }
+          })
+        );
+      });
+    }
+
+    return moves;
+  }
+
   isAttacked(sq, color) {
     return (
       super.isAttacked(sq, color) ||
@@ -205,12 +292,8 @@ export class FullcavalryRules extends ChessRules {
           this.getColor(coord.x, coord.y) == color
         )
       ) {
-        if (
-          this.getPiece(coord.x, coord.y) == V.LANCER &&
-          !this.isImmobilized([coord.x, coord.y])
-        ) {
+        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];
       }
@@ -245,15 +328,22 @@ export class FullcavalryRules extends ChessRules {
 
   filterValid(moves) {
     // At move 1, forbid captures (in case of...):
-    if (this.movesCount >= 2) return moves;
+    if (this.movesCount >= 2) return super.filterValid(moves);
     return moves.filter(m => m.vanish.length == 1);
   }
 
+  static get SEARCH_DEPTH() {
+    return 2;
+  }
+
   getNotation(move) {
     let notation = super.getNotation(move);
     if (Object.keys(V.LANCER_DIRNAMES).includes(move.vanish[0].p))
       // Lancer: add direction info
       notation += "=" + V.LANCER_DIRNAMES[move.appear[0].p];
+    else if (move.appear.length == 2 && move.vanish[1].p != move.appear[1].p)
+      // Same after castle:
+      notation += "+L:" + V.LANCER_DIRNAMES[move.appear[1].p];
     else if (
       move.vanish[0].p == V.PAWN &&
       Object.keys(V.LANCER_DIRNAMES).includes(move.appear[0].p)