Several small improvements + integrate options + first working draft of Cwda
[vchess.git] / client / src / variants / Eightpieces.js
index 6efb182..927530c 100644 (file)
@@ -1,8 +1,8 @@
-import { ArrayFun } from "@/utils/array";
 import { randInt } from "@/utils/alea";
 import { ChessRules, PiPo, Move } from "@/base_rules";
 
 export class EightpiecesRules extends ChessRules {
+
   static get JAILER() {
     return "j";
   }
@@ -167,81 +167,58 @@ export class EightpiecesRules extends ChessRules {
     }
   }
 
-  static GenRandInitFen(randomness) {
-    if (randomness == 0)
-      // Deterministic:
+  static GenRandInitFen(options) {
+    if (options.randomness == 0)
       return "jfsqkbnr/pppppppp/8/8/8/8/PPPPPPPP/JDSQKBNR 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) {
-        const lancerIdx = pieces['w'].findIndex(p => {
-          return Object.keys(V.LANCER_DIRS).includes(p);
-        });
-        pieces['b'] =
-          pieces['w'].slice(0, lancerIdx)
-          .concat(['g'])
-          .concat(pieces['w'].slice(lancerIdx + 1));
-        flags += flags;
-        break;
-      }
+    const baseFen = ChessRules.GenRandInitFen(options);
+    const fenParts = baseFen.split(' ');
+    const posParts = fenParts[0].split('/');
 
-      let positions = ArrayFun.range(8);
+    // Replace one bishop by sentry, so that sentries on different colors
+    // Also replace one random rook by jailer,
+    // and one random knight by lancer (facing north/south)
 
-      // Get random squares for bishop and sentry
-      let randIndex = 2 * randInt(4);
-      let bishopPos = positions[randIndex];
-      // The sentry must be on a square of different color
-      let randIndex_tmp = 2 * randInt(4) + 1;
-      let sentryPos = positions[randIndex_tmp];
-      if (c == 'b') {
-        // Check if white sentry is on the same color as ours.
-        // If yes: swap bishop and sentry positions.
-        // NOTE: test % 2 == 1 because there are 7 slashes.
-        if ((pieces['w'].indexOf('s') - sentryPos) % 2 == 1)
-          [bishopPos, sentryPos] = [sentryPos, bishopPos];
+    // "replaced" array contains -2 initially, then either -1 if skipped,
+    // or (eventually) the index of replacement:
+    let newPos = { 0: "", 7: "" };
+    let sentryOddity = -1;
+    for (let rank of [0, 7]) {
+      let replaced = { 'b': -2, 'n': -2, 'r': -2 };
+      for (let i = 0; i < 8; i++) {
+        const curChar = posParts[rank].charAt(i).toLowerCase();
+        if (['b', 'n', 'r'].includes(curChar)) {
+          if (
+            replaced[curChar] == -1 ||
+            (curChar == 'b' && rank == 7 && i % 2 == sentryOddity) ||
+            (
+              (curChar != 'b' || rank == 0) &&
+              replaced[curChar] == -2 &&
+              randInt(2) == 0
+            )
+          ) {
+            replaced[curChar] = i;
+            if (curChar == 'b') {
+              if (sentryOddity < 0) sentryOddity = i % 2;
+              newPos[rank] += 's';
+            }
+            else if (curChar == 'r') newPos[rank] += 'j';
+            else
+              // Lancer: orientation depends on side
+              newPos[rank] += (rank == 0 ? 'g' : 'c');
+          }
+          else {
+            if (replaced[curChar] == -2) replaced[curChar]++;
+            newPos[rank] += curChar;
+          }
+        }
+        else newPos[rank] += curChar;
       }
-      positions.splice(Math.max(randIndex, randIndex_tmp), 1);
-      positions.splice(Math.min(randIndex, randIndex_tmp), 1);
-
-      // Get random squares for knight and lancer
-      randIndex = randInt(6);
-      const knightPos = positions[randIndex];
-      positions.splice(randIndex, 1);
-      randIndex = randInt(5);
-      const lancerPos = positions[randIndex];
-      positions.splice(randIndex, 1);
-
-      // Get random square for queen
-      randIndex = randInt(4);
-      const queenPos = positions[randIndex];
-      positions.splice(randIndex, 1);
-
-      // Rook, jailer and king positions are now almost fixed,
-      // only the ordering rook->jailer or jailer->rook must be decided.
-      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";
-      pieces[c][knightPos] = "n";
-      pieces[c][bishopPos] = "b";
-      pieces[c][queenPos] = "q";
-      pieces[c][kingPos] = "k";
-      pieces[c][sentryPos] = "s";
-      // Lancer faces north for white, and south for black:
-      pieces[c][lancerPos] = c == 'w' ? 'c' : 'g';
-      pieces[c][jailerPos] = "j";
     }
+
     return (
-      pieces["b"].join("") +
-      "/pppppppp/8/8/8/8/PPPPPPPP/" +
-      pieces["w"].join("").toUpperCase() +
-      " w 0 " + flags + " - -"
+      newPos[0] + "/" + posParts.slice(1, 7).join('/') + "/" +
+      newPos[7].toUpperCase() + " " + fenParts.slice(1, 5).join(' ') + " -"
     );
   }
 
@@ -269,45 +246,6 @@ export class EightpiecesRules extends ChessRules {
     return null;
   }
 
-  // Because of the lancers, getPiece() could be wrong:
-  // use board[x][y][1] instead (always valid).
-  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;
-  }
-
   canIplay(side, [x, y]) {
     return (
       (this.subTurn == 1 && this.turn == side && this.getColor(x, y) == side)
@@ -383,7 +321,8 @@ export class EightpiecesRules extends ChessRules {
         }
         return true;
       });
-    } else if (this.subTurn == 2) {
+    }
+    else if (this.subTurn == 2) {
       // Put back the sentinel on board:
       const color = this.turn;
       moves.forEach(m => {
@@ -406,12 +345,18 @@ export class EightpiecesRules extends ChessRules {
     // Pawns might be pushed on 1st rank and attempt to move again:
     if (!V.OnBoard(x + shiftX, y)) return [];
 
-    const finalPieces =
-      // A push cannot put a pawn on last rank (it goes backward)
-      x + shiftX == lastRank
-        ? Object.keys(V.LANCER_DIRS).concat(
-          [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN, V.SENTRY, V.JAILER])
-        : [V.PAWN];
+    // A push cannot put a pawn on last rank (it goes backward)
+    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.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN, V.SENTRY, V.JAILER]);
+    }
     if (this.board[x + shiftX][y] == V.EMPTY) {
       // One square forward
       for (let piece of finalPieces) {
@@ -557,6 +502,7 @@ export class EightpiecesRules extends ChessRules {
     const L = this.sentryPush.length;
     const color = this.getColor(x, y);
     const dirCode = this.board[x][y][1];
+    const curDir = V.LANCER_DIRS[dirCode];
     if (!!this.sentryPush[L-1]) {
       // Maybe I was pushed
       const pl = this.sentryPush[L-1].length;
@@ -567,7 +513,6 @@ export class EightpiecesRules extends ChessRules {
         // I was pushed: allow all directions (for this move only), but
         // do not change direction after moving, *except* if I keep the
         // same orientation in which I was pushed.
-        const curDir = V.LANCER_DIRS[dirCode];
         // Also allow simple reorientation ("capturing king"):
         if (!V.OnBoard(x + curDir[0], y + curDir[1])) {
           const kp = this.kingPos[color];
@@ -622,13 +567,18 @@ export class EightpiecesRules extends ChessRules {
             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);
+                const newDir = V.LANCER_DIRS[k];
+                // Prevent orientations toward outer board:
+                if (V.OnBoard(m.end.x + newDir[0], m.end.y + newDir[1])) {
+                  let mk = JSON.parse(JSON.stringify(m));
+                  mk.appear[0].p = k;
+                  chooseMoves.push(mk);
+                }
               });
             });
             Array.prototype.push.apply(moves, chooseMoves);
-          } else Array.prototype.push.apply(moves, dirMoves);
+          }
+          else Array.prototype.push.apply(moves, dirMoves);
         });
         return moves;
       }
@@ -640,17 +590,23 @@ export class EightpiecesRules extends ChessRules {
     if (this.subTurn == 1) {
       monodirMoves.forEach(m => {
         Object.keys(V.LANCER_DIRS).forEach(k => {
-          let mk = JSON.parse(JSON.stringify(m));
-          mk.appear[0].p = k;
-          moves.push(mk);
+          const newDir = V.LANCER_DIRS[k];
+          // Prevent orientations toward outer board:
+          if (V.OnBoard(m.end.x + newDir[0], m.end.y + newDir[1])) {
+            let mk = JSON.parse(JSON.stringify(m));
+            mk.appear[0].p = k;
+            moves.push(mk);
+          }
         });
       });
       return moves;
-    } else {
-      // I'm pushed: add potential nudges
+    }
+    else {
+      // I'm pushed: add potential nudges, except for current orientation
       let potentialNudges = [];
       for (let step of V.steps[V.ROOK].concat(V.steps[V.BISHOP])) {
         if (
+          (step[0] != curDir[0] || step[1] != curDir[1]) &&
           V.OnBoard(x + step[0], y + step[1]) &&
           this.board[x + step[0]][y + step[1]] == V.EMPTY
         ) {
@@ -715,10 +671,7 @@ export class EightpiecesRules extends ChessRules {
 
   getPotentialKingMoves(sq) {
     const moves = this.getSlideNJumpMoves(
-      sq,
-      V.steps[V.ROOK].concat(V.steps[V.BISHOP]),
-      "oneStep"
-    );
+      sq, V.steps[V.ROOK].concat(V.steps[V.BISHOP]), 1);
     return (
       this.subTurn == 1
         ? moves.concat(this.getCastleMoves(sq))
@@ -842,9 +795,21 @@ export class EightpiecesRules extends ChessRules {
         coord.x += step[0];
         coord.y += step[1];
       }
+      const L = this.sentryPush.length;
+      const pl = (!!this.sentryPush[L-1] ? this.sentryPush[L-1].length : 0);
       for (let xy of lancerPos) {
         const dir = V.LANCER_DIRS[this.board[xy.x][xy.y].charAt(1)];
-        if (dir[0] == -step[0] && dir[1] == -step[1]) return true;
+        if (
+          (dir[0] == -step[0] && dir[1] == -step[1]) ||
+          // If the lancer was just pushed, this is an attack too:
+          (
+            !!this.sentryPush[L-1] &&
+            this.sentryPush[L-1][pl-1].x == xy.x &&
+            this.sentryPush[L-1][pl-1].y == xy.y
+          )
+        ) {
+          return true;
+        }
       }
     }
     return false;
@@ -1165,4 +1130,5 @@ export class EightpiecesRules extends ChessRules {
     }
     return notation;
   }
+
 };