From 7d7e947f74907222f1d98e17a0042fdac08c6769 Mon Sep 17 00:00:00 2001
From: Benjamin Auder <benjamin.auder@somewhere>
Date: Thu, 25 Feb 2021 11:47:01 +0100
Subject: [PATCH] Add 'Connect' variant

---
 client/src/translations/en.js                |   1 +
 client/src/translations/es.js                |   1 +
 client/src/translations/fr.js                |   1 +
 client/src/translations/rules/Connect/en.pug |  10 +
 client/src/translations/rules/Connect/es.pug |  11 +
 client/src/translations/rules/Connect/fr.pug |  11 +
 client/src/translations/variants/en.pug      |   1 +
 client/src/translations/variants/es.pug      |   1 +
 client/src/translations/variants/fr.pug      |   1 +
 client/src/variants/Connect.js               | 201 +++++++++++++++++++
 server/db/populate.sql                       |   1 +
 11 files changed, 240 insertions(+)
 create mode 100644 client/src/translations/rules/Connect/en.pug
 create mode 100644 client/src/translations/rules/Connect/es.pug
 create mode 100644 client/src/translations/rules/Connect/fr.pug
 create mode 100644 client/src/variants/Connect.js

diff --git a/client/src/translations/en.js b/client/src/translations/en.js
index 4d93adc7..acd3fdbc 100644
--- a/client/src/translations/en.js
+++ b/client/src/translations/en.js
@@ -172,6 +172,7 @@ export const translations = {
   "Absorb powers": "Absorb powers",
   "African Draughts": "African Draughts",
   "Align five stones": "Align five stones",
+  "Align four pawns": "Align four pawns",
   "All of the same color": "All of the same color",
   "Ancient rules": "Ancient rules",
   "As in the movie": "As in the movie",
diff --git a/client/src/translations/es.js b/client/src/translations/es.js
index a07a5c12..a648854a 100644
--- a/client/src/translations/es.js
+++ b/client/src/translations/es.js
@@ -171,6 +171,7 @@ export const translations = {
   "A wizard in the corner": "Un mago en la esquina",
   "Absorb powers": "Absorber poderes",
   "African Draughts": "Damas africanas",
+  "Align four pawns": "Alinea cuatro peones",
   "Align five stones": "Alinea cinco piedras",
   "All of the same color": "Todo el mismo color",
   "Ancient rules": "Viejas reglas",
diff --git a/client/src/translations/fr.js b/client/src/translations/fr.js
index 247b46f0..282117af 100644
--- a/client/src/translations/fr.js
+++ b/client/src/translations/fr.js
@@ -171,6 +171,7 @@ export const translations = {
   "A wizard in the corner": "Un sorcier dans le coin",
   "Absorb powers": "Absorber les pouvoirs",
   "African Draughts": "Dames africaines",
+  "Align four pawns": "Alignez quatre pions",
   "Align five stones": "Alignez cinq pierres",
   "All of the same color": "Tout de la même couleur",
   "Ancient rules": "Règles anciennes",
diff --git a/client/src/translations/rules/Connect/en.pug b/client/src/translations/rules/Connect/en.pug
new file mode 100644
index 00000000..c583d9b2
--- /dev/null
+++ b/client/src/translations/rules/Connect/en.pug
@@ -0,0 +1,10 @@
+p.boxed
+  | Black wins by lining up 4 pawns. Infinite pawns supply.
+
+p.
+  Black initially has only the king, and can only land pawns on the board
+  (or play a regular move); if four are aligned in any direction, it's a win.
+  Of course black could also win by checkmate, but it's rather unlikely.
+  White wins by checkmating the enemy king.
+
+p This variant was invented by madi (2021) on vchess Discord.
diff --git a/client/src/translations/rules/Connect/es.pug b/client/src/translations/rules/Connect/es.pug
new file mode 100644
index 00000000..697a0df9
--- /dev/null
+++ b/client/src/translations/rules/Connect/es.pug
@@ -0,0 +1,11 @@
+p.boxed
+  | Las negras ganan alineando 4 peones. Fondo de peones infinito.
+
+p.
+  Las negras eran solo su rey, y solo podían lanzarse en paracaídas
+  peones en el tablero de ajedrez (o jugar un movimiento normal); si cuatro
+  están alineados en cualquier dirección, se gana. Por supuesto que las negras
+  podrían también gana por jaque mate, pero eso es bastante improbable.
+  Las blancas ganan por matar al rey contrario.
+
+P Esta variante fue inventada por madi (2021) en el Discord vchess.
diff --git a/client/src/translations/rules/Connect/fr.pug b/client/src/translations/rules/Connect/fr.pug
new file mode 100644
index 00000000..e16615a7
--- /dev/null
+++ b/client/src/translations/rules/Connect/fr.pug
@@ -0,0 +1,11 @@
+p.boxed
+  | Les noirs gagnent en alignant 4 pions. Réserve de pions infinie.
+
+p.
+  Les noirs n'ont initialement que leur roi, et ne peuvent parachuter que des
+  pions sur l'échiquier (ou jouer un coup normal) ; si quatre sont alignés
+  dans n'importe quelle direction, c'est gagné. Bien sûr les noirs pourraient
+  aussi gagner par échec et mat, mais c'est assez improbable.
+  Les blancs gagnent en matant le roi adverse.
+
+p Cette variante a été inventée par madi (2021) sur le Discord vchess.
diff --git a/client/src/translations/variants/en.pug b/client/src/translations/variants/en.pug
index 966f96f5..672e5a53 100644
--- a/client/src/translations/variants/en.pug
+++ b/client/src/translations/variants/en.pug
@@ -462,6 +462,7 @@ p.
     "Ambiguous",
     "Bario",
     "Bicolour",
+    "Connect",
     "Convert",
     "Evolution",
     "Forward",
diff --git a/client/src/translations/variants/es.pug b/client/src/translations/variants/es.pug
index 90196f68..2435c567 100644
--- a/client/src/translations/variants/es.pug
+++ b/client/src/translations/variants/es.pug
@@ -472,6 +472,7 @@ p.
     "Ambiguous",
     "Bario",
     "Bicolour",
+    "Connect",
     "Convert",
     "Evolution",
     "Forward",
diff --git a/client/src/translations/variants/fr.pug b/client/src/translations/variants/fr.pug
index 7729aaf0..6eb7776e 100644
--- a/client/src/translations/variants/fr.pug
+++ b/client/src/translations/variants/fr.pug
@@ -470,6 +470,7 @@ p.
     "Ambiguous",
     "Bario",
     "Bicolour",
+    "Connect",
     "Convert",
     "Evolution",
     "Forward",
diff --git a/client/src/variants/Connect.js b/client/src/variants/Connect.js
new file mode 100644
index 00000000..0e205c8e
--- /dev/null
+++ b/client/src/variants/Connect.js
@@ -0,0 +1,201 @@
+import { ChessRules, Move, PiPo } from "@/base_rules";
+
+export class ConnectRules extends ChessRules {
+
+  static GenRandInitFen(randomness) {
+    const baseFen = ChessRules.GenRandInitFen(Math.min(randomness, 1));
+    return "4k3/8" + baseFen.substring(17, 50) + " -";
+  }
+
+  getReservePpath() {
+    return "bp";
+  }
+
+  static get RESERVE_PIECES() {
+    return [V.PAWN]; //only black pawns
+  }
+
+  getColor(i, j) {
+    if (i >= V.size.x) return "b";
+    return this.board[i][j].charAt(0);
+  }
+
+  getPiece(i, j) {
+    if (i >= V.size.x) return V.PAWN;
+    return this.board[i][j].charAt(1);
+  }
+
+  static IsGoodFlags(flags) {
+    // Only white can castle
+    return !!flags.match(/^[a-z]{2,2}$/);
+  }
+
+  getFlagsFen() {
+    return this.castleFlags['w'].map(V.CoordToColumn).join("");
+  }
+
+  setFlags(fenflags) {
+    this.castleFlags = { 'w': [-1, -1] };
+    for (let i = 0; i < 2; i++)
+      this.castleFlags['w'][i] = V.ColumnToCoord(fenflags.charAt(i));
+  }
+
+  setOtherVariables(fen) {
+    super.setOtherVariables(fen);
+    this.reserve = { b: { [V.PAWN]: 1 } };
+  }
+
+  getReserveMoves() {
+    if (this.turn != 'b') return [];
+    let moves = [];
+    for (let i = 1; i <= 6; i++) {
+      for (let j = 0; j < V.size.y; j++) {
+        if (this.board[i][j] == V.EMPTY) {
+          let mv = new Move({
+            appear: [
+              new PiPo({
+                x: i,
+                y: j,
+                c: 'b',
+                p: 'p'
+              })
+            ],
+            vanish: [],
+            start: { x: 9, y: 0 },
+            end: { x: i, y: j }
+          });
+          moves.push(mv);
+        }
+      }
+    }
+    return moves;
+  }
+
+  getPotentialMovesFrom(sq) {
+    if (sq[0] >= V.size.x) return this.getReserveMoves();
+    return super.getPotentialMovesFrom(sq);
+  }
+
+  getPotentialKingMoves([x, y]) {
+    if (this.getColor(x, y) == 'w') return super.getPotentialKingMoves([x, y]);
+    // Black doesn't castle:
+    return super.getSlideNJumpMoves(
+      [x, y],
+      V.steps[V.ROOK].concat(V.steps[V.BISHOP]),
+      "oneStep"
+    );
+  }
+
+  getAllValidMoves() {
+    return (
+      super.getAllValidMoves().concat(
+        super.filterValid(this.getReserveMoves()))
+    );
+  }
+
+  atLeastOneMove() {
+    if (super.atLeastOneMove()) return true;
+    // Search one reserve move
+    if (this.filterValid(this.getReserveMoves()).length > 0) return true;
+    return false;
+  }
+
+  updateCastleFlags(move, piece) {
+    // Only white can castle:
+    const firstRank = 7;
+    if (piece == V.KING && move.appear[0].c == 'w')
+      this.castleFlags['w'] = [8, 8];
+    else if (
+      move.start.x == firstRank &&
+      this.castleFlags['w'].includes(move.start.y)
+    ) {
+      const flagIdx = (move.start.y == this.castleFlags['w'][0] ? 0 : 1);
+      this.castleFlags['w'][flagIdx] = 8;
+    }
+    else if (
+      move.end.x == firstRank &&
+      this.castleFlags['w'].includes(move.end.y)
+    ) {
+      const flagIdx = (move.end.y == this.castleFlags['w'][0] ? 0 : 1);
+      this.castleFlags['w'][flagIdx] = 8;
+    }
+  }
+
+  getCurrentScore() {
+    const score = super.getCurrentScore();
+    if (score != "*") return score;
+    // Check pawns connection:
+    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) == 'b' &&
+          this.getPiece(i, j) == V.PAWN
+        ) {
+          // Exploration "rightward + downward" is enough
+          for (let step of [[1, 0], [0, 1], [1, 1], [-1, 1]]) {
+            let [ii, jj] = [i + step[0], j + step[1]];
+            let kounter = 1;
+            while (
+              V.OnBoard(ii, jj) &&
+              this.board[ii][jj] != V.EMPTY &&
+              this.getColor(ii, jj) == 'b' &&
+              this.getPiece(ii, jj) != V.KING
+            ) {
+              kounter++;
+              ii += step[0];
+              jj += step[1];
+            }
+            if (kounter == 4) return "0-1";
+          }
+        }
+      }
+    }
+    return "*";
+  }
+
+  evalPosition() {
+    let evaluation = 0;
+    // Count white material + check pawns alignments
+    let maxAlign = 0;
+    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) {
+          const piece = this.getPiece(i, j);
+          if (piece != V.KING) {
+            const color = this.getColor(i, j);
+            if (color == 'w') evaluation += V.VALUES[piece];
+            else {
+              // Exploration "rightward + downward" is enough
+              for (let step of [[1, 0], [0, 1], [1, 1], [-1, 1]]) {
+                let [ii, jj] = [i + step[0], j + step[1]];
+                let kounter = 1;
+                while (
+                  V.OnBoard(ii, jj) &&
+                  this.board[ii][jj] != V.EMPTY &&
+                  this.getColor(ii, jj) == 'b' &&
+                  this.getPiece(ii, jj) != V.KING
+                ) {
+                  kounter++;
+                  ii += step[0];
+                  jj += step[1];
+                }
+                if (kounter > maxAlign) maxAlign = kounter;
+              }
+            }
+          }
+        }
+      }
+    }
+    // -1 for two aligned pawns, -3 for 3 aligned pawns.
+    if ([1, 2].includes(maxAlign)) maxAlign--;
+    return evaluation - maxAlign;
+  }
+
+  getNotation(move) {
+    if (move.vanish.length == 0) return "@" + V.CoordsToSquare(move.end);
+    return super.getNotation(move);
+  }
+
+};
+
diff --git a/server/db/populate.sql b/server/db/populate.sql
index c08dc9e3..3208b594 100644
--- a/server/db/populate.sql
+++ b/server/db/populate.sql
@@ -47,6 +47,7 @@ insert or ignore into Variants (name, description) values
   ('Circular', 'Run forward'),
   ('Clorange', 'A Clockwork Orange'),
   ('Colorbound', 'The colorbound clobberers'),
+  ('Connect', 'Align four pawns'),
   ('Convert', 'Convert enemy pieces'),
   ('Coregal', 'Two royal pieces'),
   ('Coronation', 'Long live the Queen'),
-- 
2.44.0