Add Pacifist1 & 2. Missing french + spanish translations for now
authorBenjamin Auder <benjamin.auder@somewhere>
Sat, 2 May 2020 17:58:25 +0000 (19:58 +0200)
committerBenjamin Auder <benjamin.auder@somewhere>
Sat, 2 May 2020 17:58:25 +0000 (19:58 +0200)
client/src/translations/rules/Pacifist1/en.pug [new file with mode: 0644]
client/src/translations/rules/Pacifist1/es.pug [new file with mode: 0644]
client/src/translations/rules/Pacifist1/fr.pug [new file with mode: 0644]
client/src/translations/rules/Pacifist2/en.pug [new file with mode: 0644]
client/src/translations/rules/Pacifist2/es.pug [new file with mode: 0644]
client/src/translations/rules/Pacifist2/fr.pug [new file with mode: 0644]
client/src/variants/Benedict.js
client/src/variants/Pacifist1.js [new file with mode: 0644]
client/src/variants/Pacifist2.js [new file with mode: 0644]

diff --git a/client/src/translations/rules/Pacifist1/en.pug b/client/src/translations/rules/Pacifist1/en.pug
new file mode 100644 (file)
index 0000000..ca2f8fb
--- /dev/null
@@ -0,0 +1,67 @@
+p.boxed
+  | No captures. Pieces under attack insufficiently defended change color.
+
+p.
+  Conversion occurs when the amount of persuasion exceeds the
+  amount of support. Pieces persuade and support according to
+  their standard attack moves. Opposing pieces persuade, friendly support.
+
+p.
+  For example on the following diagram, 1.Nf5 attacks g7.
+  Since the bishop is undefended, it turns white. Same for the pawn e7.
+  Then, the rook f8 is converted. This allows to convert both the knight
+  on d8 and the bishop on g8. Finally, the pawns f7 and h7 are under attack
+  and thus also change color.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:rkqn1rb1/ppppppbp/P7/1n4p1/8/4N3/1PPPPPPP/RNK1BBQR:
+  .diagram.diag22
+    | fen:rkqN1RB1/ppppPPBP/P7/1n3Np1/8/8/1PPPPPPP/RNK1BBQR:
+  figcaption Before and after 1.Nf5
+
+p.
+  The game ends when a king is converted, and the side cannot convert a king
+  at the next turn. Note that kings may be exchanged, as on the following
+  diagram: 1...g6 would convert back the original black king on e7, but
+  1...Nb6 is also possible, turning the white one on a4 into black.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:nbr1b1qr/ppppKppp/7n/4pN2/K3P3/P7/QPPP1PPP/B1R1RBN1:
+  .diagram.diag22
+    | fen:1br1b1qr/ppppKppp/1n5n/4pN2/k3P3/P7/QPPP1PPP/B1R1RBN1:
+  figcaption before and after 1...Nb6
+
+p.
+  On this final illustration, the king cannot be converted at next turn:
+  white wins.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:1rknn1br/p1pp1p1p/3bp1p1/1p1q2n1/5p2/2P4B/pP1PP1PP/B1RKR1QN:
+  .diagram.diag22
+    | fen:1RKNN1br/P1PP1p1p/1Q1Bp1p1/1P1q2n1/5P2/2P4B/pP1PP1PP/B1RKR2N:
+  figcaption Before and after 1.Qb6#
+
+p Notes:
+ul
+  li.
+    It is possible for pieces to persuade or support through other
+    friendly pieces. For example, rooks and queens in the same orthogonal
+    or bishops and queens in the same diagonal.
+  li.
+    The Pawn is forbidden to move through a cell which is overly persuaded
+    during its initial two-step move.
+  li.
+    A King is allowed to move to cells which are being persuaded as
+    long as the level of persuasion is not greater than its support.
+
+h3 Source
+
+p
+  | Adapted from 
+  a(href="https://www.chessvariants.com/rules/pacifist-chess") Pacifist Chess
+  | &nbsp;description on chessvariants.com.
+
+p Inventor: Larry L. Smith (2007)
diff --git a/client/src/translations/rules/Pacifist1/es.pug b/client/src/translations/rules/Pacifist1/es.pug
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/client/src/translations/rules/Pacifist1/fr.pug b/client/src/translations/rules/Pacifist1/fr.pug
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/client/src/translations/rules/Pacifist2/en.pug b/client/src/translations/rules/Pacifist2/en.pug
new file mode 100644 (file)
index 0000000..d454362
--- /dev/null
@@ -0,0 +1,33 @@
+p.boxed
+  | Pas de captures.
+  | Les pièces attaquées insuffisamment défendues changent de couleur.
+
+p
+  | Same as 
+  a(href="/#/variants/Pacifist1") Pacifist1
+  | , but persuasion and support are determined by the sum of pieces values
+  | attacking a square. For example on the following diagram,
+  | 1...Qa7 converts many white pieces because the rook is worth more than the
+  | knight defending e1, so the king turns black. The king then turns all
+  | surrounding pieces black, then c2 changes color because it is attacked by
+  | the bishop on d1. Finally the pawn on c2 attacks the rook, which converts
+  | the bishop on a1 and then the b2 pawn with the help of the newly converted
+  | bishop.
+  | In Pacifist1, only the rook would change color.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:qrbnkb1n/1pppp1rp/p7/5Pp1/5PPP/3N3Q/PPPPP3/BR1BK1RN:
+  .diagram.diag22
+    | fen:1rbnkb1n/qpppp1rp/p7/5Pp1/5PPP/3N3Q/Ppppp3/br1bk1rN:
+  figcaption Before and after 1...Qa7
+
+p The pieces values are taken as follows:
+ul
+  li Pawn: 1
+  li Rook: 5
+  li Knight: 3
+  li Bishop: 3
+  li Queen: 9
+  li King: 1000
+p So the king converts anything next to him, unless the other king is around.
diff --git a/client/src/translations/rules/Pacifist2/es.pug b/client/src/translations/rules/Pacifist2/es.pug
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/client/src/translations/rules/Pacifist2/fr.pug b/client/src/translations/rules/Pacifist2/fr.pug
new file mode 100644 (file)
index 0000000..e69de29
index 4ca5292..039aaea 100644 (file)
@@ -135,13 +135,10 @@ export class BenedictRules extends ChessRules {
   // Stop at the first move found
   atLeastOneMove() {
     const color = this.turn;
-    const oppCol = V.GetOppCol(color);
     for (let i = 0; i < V.size.x; i++) {
       for (let j = 0; j < V.size.y; j++) {
-        if (this.board[i][j] != V.EMPTY && this.getColor(i, j) != oppCol) {
-          const moves = this.getPotentialMovesFrom([i, j]);
-          if (moves.length > 0)
-            return true;
+        if (this.board[i][j] != V.EMPTY && this.getColor(i, j) == color) {
+          if (this.getPotentialMovesFrom([i, j]).length > 0) return true;
         }
       }
     }
diff --git a/client/src/variants/Pacifist1.js b/client/src/variants/Pacifist1.js
new file mode 100644 (file)
index 0000000..234b02a
--- /dev/null
@@ -0,0 +1,263 @@
+import { ChessRules } from "@/base_rules";
+
+export class Pacifist1Rules extends ChessRules {
+  static get PawnSpecs() {
+    return Object.assign(
+      {},
+      ChessRules.PawnSpecs,
+      { canCapture: false }
+    );
+  }
+
+  static get HasEnpassant() {
+    return false;
+  }
+
+  static IsGoodPosition(position) {
+    if (position.length == 0) return false;
+    const rows = position.split("/");
+    if (rows.length != V.size.x) return false;
+    let kingsCount = 0;
+    for (let row of rows) {
+      let sumElts = 0;
+      for (let i = 0; i < row.length; i++) {
+        if (['K','k'].includes(row[i])) kingsCount++;
+        if (V.PIECES.includes(row[i].toLowerCase())) sumElts++;
+        else {
+          const num = parseInt(row[i]);
+          if (isNaN(num)) return false;
+          sumElts += num;
+        }
+      }
+      if (sumElts != V.size.y) return false;
+    }
+    // Both kings should be on board. May be of the same color.
+    if (kingsCount != 2) return false;
+    return true;
+  }
+
+  scanKings(fen) {
+    // Kings may be swapped, so they are not tracked (no kingPos)
+    this.INIT_COL_KING = { w: -1, b: -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; //column index on board
+      for (let j = 0; j < fenRows[i].length; j++) {
+        switch (fenRows[i].charAt(j)) {
+          case "k":
+            this.INIT_COL_KING["b"] = k;
+            break;
+          case "K":
+            this.INIT_COL_KING["w"] = k;
+            break;
+          default: {
+            const num = parseInt(fenRows[i].charAt(j));
+            if (!isNaN(num)) k += num - 1;
+          }
+        }
+        k++;
+      }
+    }
+  }
+
+  // Sum white pieces attacking a square, and remove black pieces count.
+  sumAttacks([x, y]) {
+    const getSign = (color) => {
+      return (color == 'w' ? 1 : -1);
+    };
+    let res = 0;
+    // Knights:
+    V.steps[V.KNIGHT].forEach(s => {
+      const [i, j] = [x + s[0], y + s[1]];
+      if (V.OnBoard(i, j) && this.getPiece(i, j) == V.KNIGHT)
+        res += getSign(this.getColor(i, j));
+    });
+    // Kings:
+    V.steps[V.ROOK].concat(V.steps[V.BISHOP]).forEach(s => {
+      const [i, j] = [x + s[0], y + s[1]];
+      if (V.OnBoard(i, j) && this.getPiece(i, j) == V.KING)
+        res += getSign(this.getColor(i, j));
+    });
+    // Pawns:
+    for (let c of ['w', 'b']) {
+      for (let shift of [-1, 1]) {
+        const sign = getSign(c);
+        const [i, j] = [x + sign, y + shift];
+        if (
+          V.OnBoard(i, j) &&
+          this.getPiece(i, j) == V.PAWN &&
+          this.getColor(i, j) == c
+        ) {
+          res += sign;
+        }
+      }
+    }
+    // Other pieces (sliders):
+    V.steps[V.ROOK].concat(V.steps[V.BISHOP]).forEach(s => {
+      let [i, j] = [x + s[0], y + s[1]];
+      let compatible = [V.QUEEN];
+      compatible.push(s[0] == 0 || s[1] == 0 ? V.ROOK : V.BISHOP);
+      let firstCol = undefined;
+      while (V.OnBoard(i, j)) {
+        if (this.board[i][j] != V.EMPTY) {
+          if (!(compatible.includes(this.getPiece(i, j)))) break;
+          const colIJ = this.getColor(i, j);
+          if (!firstCol) firstCol = colIJ;
+          if (colIJ == firstCol) res += getSign(colIJ);
+          else break;
+        }
+        i += s[0];
+        j += s[1];
+      }
+    });
+    return res;
+  }
+
+  getPotentialMovesFrom([x, y]) {
+    let moves = super.getPotentialMovesFrom([x, y]);
+    const color = this.turn;
+    const oppCol = V.GetOppCol(color);
+    if (this.getPiece(x, y) == V.PAWN) {
+      // Pawns cannot move 2 squares if the intermediate is overly persuaded
+      moves = moves.filter(m => {
+        if (Math.abs(m.end.x - m.start.x) == 2) {
+          const [i, j] = [(m.start.x + m.end.x) / 2, y];
+          const persuasion = this.sumAttacks([i, j]);
+          return (
+            color == 'w' && persuasion >= 0 ||
+            color == 'b' && persuasion <= 0
+          );
+        }
+        return true;
+      });
+    }
+    // Potentially flipped (opp) pieces
+    let targets = [];
+    for (let i=0; i<8; i++) {
+      for (let j=0; j<8; j++) {
+        if (this.board[i][j] != V.EMPTY && this.getColor(i, j) == oppCol)
+          targets.push([i, j]);
+      }
+    }
+    moves.forEach(m => {
+      // Start persuading other pieces: loop until nothing changes
+      V.PlayOnBoard(this.board, m);
+      while (true) {
+        let persuaded = [];
+        targets.forEach(t => {
+          if (this.getColor(t[0], t[1]) == oppCol) {
+            const sqAttacks = this.sumAttacks([t[0], t[1]]);
+            if (
+              (oppCol == 'w' && sqAttacks < 0) ||
+              (oppCol == 'b' && sqAttacks > 0)
+            ) {
+              persuaded.push(t);
+            }
+          }
+        });
+        if (persuaded.length == 0) break;
+        persuaded.forEach(p => {
+          this.board[p[0]][p[1]] = color + this.getPiece(p[0], p[1]);
+        });
+      }
+      V.UndoOnBoard(this.board, m);
+      // Reset pieces colors + adjust move (flipped pieces)
+      targets.forEach(t => {
+        if (this.getColor(t[0], t[1]) == color) {
+          const piece = this.getPiece(t[0], t[1]);
+          m.appear.push({ x: t[0], y: t[1], c: color, p: piece });
+          m.vanish.push({ x: t[0], y: t[1], c: oppCol, p: piece });
+          this.board[t[0]][t[1]] = oppCol + piece;
+        }
+      });
+    });
+    return moves;
+  }
+
+  getSlideNJumpMoves([x, y], steps, oneStep) {
+    let moves = [];
+    outerLoop: for (let loop = 0; loop < steps.length; loop++) {
+      const step = steps[loop];
+      let i = x + step[0];
+      let j = y + step[1];
+      while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
+        moves.push(this.getBasicMove([x, y], [i, j]));
+        if (oneStep) continue outerLoop;
+        i += step[0];
+        j += step[1];
+      }
+      // No captures
+    }
+    return moves;
+  }
+
+  underCheck(color) {
+    // Find the king(s) and determine if it (both) is persuaded.
+    // If yes, "under check"
+    let kingPos = [];
+    for (let i=0; i<8; i++) {
+      for (let j=0; j<8; j++) {
+        if (this.getPiece(i, j) == V.KING && this.getColor(i, j) == color)
+          kingPos.push([i, j]);
+      }
+    }
+    return kingPos.every(kp => {
+      const persuasion = this.sumAttacks(kp);
+      return (
+        (color == 'w' && persuasion < 0) ||
+        (color == 'b' && persuasion > 0)
+      );
+    });
+  }
+
+  filterValid(moves) {
+    const fmoves = super.filterValid(moves);
+    const color = this.turn;
+    // If the king isn't here, only moves persuading a king are valid
+    const kingHere = this.board.some(b =>
+      b.some(cell => cell[0] == color && cell[1] == V.KING)
+    );
+    if (!kingHere) {
+      return (
+        fmoves.filter(m => m.appear.some(a => a.c == color && a.p == V.KING))
+      );
+    }
+    return fmoves;
+  }
+
+  getCheckSquares() {
+    // There are not really "checks": just color change
+    return [];
+  }
+
+  getCurrentScore() {
+    const color = this.turn;
+    // TODO: if no king of turn color, and no move to get one, then it's lost
+    // otherwise 1/2 if no moves, or "*"
+    const kingHere = this.board.some(b =>
+      b.some(cell => cell[0] == color && cell[1] == V.KING)
+    );
+    if (kingHere) {
+      if (this.atLeastOneMove()) return "*";
+      return "1/2";
+    }
+    // No king was found: try to convert one
+    const moves = this.getAllValidMoves();
+    return (
+      moves.some(m => m.appear.some(a => a.c == color && a.p == V.KING))
+        ? "*"
+        : (color == 'w' ? "0-1" : "1-0")
+    );
+  }
+
+  postPlay(move) {
+    this.updateCastleFlags(move, move.vanish[0].p);
+  }
+
+  postUndo() {}
+
+  static get SEARCH_DEPTH() {
+    return 1;
+  }
+};
diff --git a/client/src/variants/Pacifist2.js b/client/src/variants/Pacifist2.js
new file mode 100644 (file)
index 0000000..e812802
--- /dev/null
@@ -0,0 +1,58 @@
+import { Pacifist1Rules } from "@/variants/Pacifist1";
+
+export class Pacifist2Rules extends Pacifist1Rules {
+  // Sum values of white pieces attacking a square,
+  // and remove (sum of) black pieces values.
+  sumAttacks([x, y]) {
+    const getSign = (color) => {
+      return (color == 'w' ? 1 : -1);
+    };
+    let res = 0;
+    // Knights:
+    V.steps[V.KNIGHT].forEach(s => {
+      const [i, j] = [x + s[0], y + s[1]];
+      if (V.OnBoard(i, j) && this.getPiece(i, j) == V.KNIGHT)
+        res += getSign(this.getColor(i, j)) * V.VALUES[V.KNIGHT];
+    });
+    // Kings:
+    V.steps[V.ROOK].concat(V.steps[V.BISHOP]).forEach(s => {
+      const [i, j] = [x + s[0], y + s[1]];
+      if (V.OnBoard(i, j) && this.getPiece(i, j) == V.KING)
+        res += getSign(this.getColor(i, j)) * V.VALUES[V.KING];
+    });
+    // Pawns:
+    for (let c of ['w', 'b']) {
+      for (let shift of [-1, 1]) {
+        const sign = getSign(c);
+        const [i, j] = [x + sign, y + shift];
+        if (
+          V.OnBoard(i, j) &&
+          this.getPiece(i, j) == V.PAWN &&
+          this.getColor(i, j) == c
+        ) {
+          res += sign * V.VALUES[V.PAWN];
+        }
+      }
+    }
+    // Other pieces (sliders):
+    V.steps[V.ROOK].concat(V.steps[V.BISHOP]).forEach(s => {
+      let [i, j] = [x + s[0], y + s[1]];
+      let compatible = [V.QUEEN];
+      compatible.push(s[0] == 0 || s[1] == 0 ? V.ROOK : V.BISHOP);
+      let firstCol = undefined;
+      while (V.OnBoard(i, j)) {
+        if (this.board[i][j] != V.EMPTY) {
+          const pieceIJ = this.getPiece(i, j);
+          if (!(compatible.includes(pieceIJ))) break;
+          const colIJ = this.getColor(i, j);
+          if (!firstCol) firstCol = colIJ;
+          if (colIJ == firstCol) res += getSign(colIJ) * V.VALUES[pieceIJ];
+          else break;
+        }
+        i += s[0];
+        j += s[1];
+      }
+    });
+    return res;
+  }
+};