From: Benjamin Auder <benjamin.auder@somewhere>
Date: Wed, 15 Apr 2020 16:22:14 +0000 (+0200)
Subject: Add Minishogi
X-Git-Url: https://git.auder.net/variants/current/css/img/pieces/doc/%24%7BgetWhatsApp%28link%29%7D?a=commitdiff_plain;h=e2f204eda8630746cb951889da01241b1e5f5733;p=vchess.git

Add Minishogi
---

diff --git a/client/src/translations/en.js b/client/src/translations/en.js
index 3d83513a..e9bd52c1 100644
--- a/client/src/translations/en.js
+++ b/client/src/translations/en.js
@@ -222,6 +222,7 @@ export const translations = {
   "Seirawan-Harper Chess": "Seirawan-Harper Chess",
   "Shared pieces (v1)": "Shared pieces (v1)",
   "Shared pieces (v2)": "Shared pieces (v2)",
+  "Shogi 5 x 5": "Shogi 5 x 5",
   "Shoot pieces": "Shoot pieces",
   "Squares disappear": "Squares disappear",
   "Standard rules": "Standard rules",
diff --git a/client/src/translations/es.js b/client/src/translations/es.js
index 335d1e04..27ad31c8 100644
--- a/client/src/translations/es.js
+++ b/client/src/translations/es.js
@@ -222,6 +222,7 @@ export const translations = {
   "Seirawan-Harper Chess": "Ajedrez Seirawan-Harper",
   "Shared pieces (v1)": "Piezas compartidas (v1)",
   "Shared pieces (v2)": "Piezas compartidas (v2)",
+  "Shogi 5 x 5": "Shogi 5 x 5",
   "Shoot pieces": "Tirar de las piezas",
   "Squares disappear": "Las casillas desaparecen",
   "Standard rules": "Reglas estandar",
diff --git a/client/src/translations/fr.js b/client/src/translations/fr.js
index e9e983e0..5d2ef3be 100644
--- a/client/src/translations/fr.js
+++ b/client/src/translations/fr.js
@@ -222,6 +222,7 @@ export const translations = {
   "Seirawan-Harper Chess": "Échecs Seirawan-Harper",
   "Shared pieces (v1)": "Pièces partagées (v1)",
   "Shared pieces (v2)": "Pièces partagées (v2)",
+  "Shogi 5 x 5": "Shogi 5 x 5",
   "Shoot pieces": "Tirez sur les pièces",
   "Squares disappear": "Les cases disparaissent",
   "Standard rules": "Règles usuelles",
diff --git a/client/src/translations/rules/Minishogi/en.pug b/client/src/translations/rules/Minishogi/en.pug
new file mode 100644
index 00000000..18f5e363
--- /dev/null
+++ b/client/src/translations/rules/Minishogi/en.pug
@@ -0,0 +1,21 @@
+p.boxed Shogi on a 5 x 5 board.
+
+figure.diagram-container
+  .diagram
+    | fen:rbsgk/4p/5/P4/KGSBR:
+  figcaption Initial position.
+
+p.
+  Minishogi is essentially shogi on a 5x5 board. The game was invented
+  (or rediscovered) around 1970 by Shigenobu Kusumoto of Osaka, Japan.
+
+p
+  | The rules are the same as in 
+  a(href="/variants/Shogi") Shogi
+  | . Unlike standard shogi, there is no knight or lance.
+  | Promotion is only done in the last rank.
+
+p
+  | See also 
+  a(href="https://www.pychess.org/variant/minishogi") Minishogi
+  | &nbsp;on pychess-variants.
diff --git a/client/src/translations/rules/Minishogi/es.pug b/client/src/translations/rules/Minishogi/es.pug
new file mode 100644
index 00000000..77a06d04
--- /dev/null
+++ b/client/src/translations/rules/Minishogi/es.pug
@@ -0,0 +1,22 @@
+p.boxed Shogi en un tablero 5 x 5.
+
+figure.diagram-container
+  .diagram
+    | fen:rbsgk/4p/5/P4/KGSBR:
+  figcaption Posición inicial.
+
+p.
+  Esta variante corresponde al Shogi en un tablero de ajedrez de 5x5.
+  Este juego fue inventado (o redescubierto) en 1970 por Shigenobu Kusumoto
+  de Osaka, Japón.
+
+p
+  | Las reglas son las mismas que en 
+  a(href="/variants/Shogi") Shogi
+  | . A diferencia del Shogi estándar, no hay lanza ni caballo.
+  | Las promociones solo se realizan en la última fila.
+
+p
+  | Ver también 
+  a(href="https://www.pychess.org/variant/minishogi") Minishogi
+  | & nbsp;en pychess-variants.
diff --git a/client/src/translations/rules/Minishogi/fr.pug b/client/src/translations/rules/Minishogi/fr.pug
new file mode 100644
index 00000000..470d7b89
--- /dev/null
+++ b/client/src/translations/rules/Minishogi/fr.pug
@@ -0,0 +1,22 @@
+p.boxed Shogi sur un plateau 5 x 5.
+
+figure.diagram-container
+  .diagram
+    | fen:rbsgk/4p/5/P4/KGSBR:
+  figcaption Position initiale.
+
+p.
+  Cette variante correspond au Shogi sur un échiquier 5x5.
+  Ce jeu a été inventé (ou redécouvert) en 1970 par Shigenobu Kusumoto
+  d'Osaka, Japon.
+
+p
+  | les règles sont les mêmes qu'au 
+  a(href="/variants/Shogi") Shogi
+  | . Contrairement au Shogi standard, il n'y a ni lance ni cavalier.
+  | Les promotion ne s'effectuent que sur la dernière rangée.
+
+p
+  | Voir aussi 
+  a(href="https://www.pychess.org/variant/minishogi") Minishogi
+  | &nbsp;sur pychess-variants.
diff --git a/client/src/variants/Minishogi.js b/client/src/variants/Minishogi.js
new file mode 100644
index 00000000..ae5876fe
--- /dev/null
+++ b/client/src/variants/Minishogi.js
@@ -0,0 +1,170 @@
+import { ChessRules } from "@/base_rules";
+import { ShogiRules } from "@/variants/Shogi";
+
+export class MinishogiRules extends ShogiRules {
+  static IsGoodFen(fen) {
+    if (!ChessRules.IsGoodFen(fen)) return false;
+    const fenParsed = V.ParseFen(fen);
+    // 3) Check reserves
+    if (!fenParsed.reserve || !fenParsed.reserve.match(/^[0-9]{10,10}$/))
+      return false;
+    return true;
+  }
+
+  // No knight or lance
+  static get PIECES() {
+    return [
+      ChessRules.PAWN,
+      ChessRules.ROOK,
+      ChessRules.BISHOP,
+      ChessRules.KING,
+      V.GOLD_G,
+      V.SILVER_G,
+      V.P_PAWN,
+      V.P_SILVER,
+      V.P_ROOK,
+      V.P_BISHOP
+    ];
+  }
+
+  static GenRandInitFen() {
+    return "rbsgk/4p/5/P4/KGSBR w 0 0000000000";
+  }
+
+  getReserveFen() {
+    let counts = new Array(10);
+    for (let i = 0; i < V.RESERVE_PIECES.length; i++) {
+      counts[i] = this.reserve["w"][V.RESERVE_PIECES[i]];
+      counts[5 + i] = this.reserve["b"][V.RESERVE_PIECES[i]];
+    }
+    return counts.join("");
+  }
+
+  setOtherVariables(fen) {
+    super.setOtherVariables(fen);
+    const fenParsed = V.ParseFen(fen);
+    // Also init reserves (used by the interface to show landable pieces)
+    this.reserve = {
+      w: {
+        [V.PAWN]: parseInt(fenParsed.reserve[0]),
+        [V.ROOK]: parseInt(fenParsed.reserve[1]),
+        [V.BISHOP]: parseInt(fenParsed.reserve[2]),
+        [V.GOLD_G]: parseInt(fenParsed.reserve[3]),
+        [V.SILVER_G]: parseInt(fenParsed.reserve[4])
+      },
+      b: {
+        [V.PAWN]: parseInt(fenParsed.reserve[5]),
+        [V.ROOK]: parseInt(fenParsed.reserve[6]),
+        [V.BISHOP]: parseInt(fenParsed.reserve[7]),
+        [V.GOLD_G]: parseInt(fenParsed.reserve[8]),
+        [V.SILVER_G]: parseInt(fenParsed.reserve[9])
+      }
+    };
+  }
+
+  static get size() {
+    return { x: 5, y: 5 };
+  }
+
+  static get RESERVE_PIECES() {
+    return (
+      [V.PAWN, V.ROOK, V.BISHOP, V.GOLD_G, V.SILVER_G]
+    );
+  }
+
+  getReserveMoves([x, y]) {
+    const color = this.turn;
+    const p = V.RESERVE_PIECES[y];
+    if (p == V.PAWN) {
+      var oppCol = V.GetOppCol(color);
+      var allowedFiles =
+        [...Array(5).keys()].filter(j =>
+          [...Array(5).keys()].every(i => {
+            return (
+              this.board[i][j] == V.EMPTY ||
+              this.getColor(i, j) != color ||
+              this.getPiece(i, j) != V.PAWN
+            );
+          })
+        )
+    }
+    if (this.reserve[color][p] == 0) return [];
+    let moves = [];
+    const forward = color == 'w' ? -1 : 1;
+    const lastRank = color == 'w' ? 0 : 4;
+    for (let i = 0; i < V.size.x; i++) {
+      if (p == V.PAWN && i == lastRank) continue;
+      for (let j = 0; j < V.size.y; j++) {
+        if (
+          this.board[i][j] == V.EMPTY &&
+          (p != V.PAWN || allowedFiles.includes(j))
+        ) {
+          let mv = new Move({
+            appear: [
+              new PiPo({
+                x: i,
+                y: j,
+                c: color,
+                p: p
+              })
+            ],
+            vanish: [],
+            start: { x: x, y: y }, //a bit artificial...
+            end: { x: i, y: j }
+          });
+          if (p == V.PAWN) {
+            // Do not drop on checkmate:
+            this.play(mv);
+            const res = (this.underCheck(oppCol) && !this.atLeastOneMove());
+            this.undo(mv);
+            if (res) continue;
+          }
+          moves.push(mv);
+        }
+      }
+    }
+    return moves;
+  }
+
+  getSlideNJumpMoves([x, y], steps, options) {
+    options = options || {};
+    const color = this.turn;
+    const oneStep = options.oneStep;
+    const forcePromoteOnLastRank = options.force;
+    const promoteInto = options.promote;
+    const lastRank = (color == 'w' ? 0 : 4);
+    let moves = [];
+    outerLoop: for (let step of steps) {
+      let i = x + step[0];
+      let j = y + step[1];
+      while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
+        if (i != lastRank || !forcePromoteOnLastRank)
+          moves.push(this.getBasicMove([x, y], [i, j]));
+        if (i == lastRank && !!promoteInto) {
+          moves.push(
+            this.getBasicMove(
+              [x, y], [i, j], { c: color, p: promoteInto })
+          );
+        }
+        if (oneStep) continue outerLoop;
+        i += step[0];
+        j += step[1];
+      }
+      if (V.OnBoard(i, j) && this.canTake([x, y], [i, j])) {
+        if (i != lastRank || !forcePromoteOnLastRank)
+          moves.push(this.getBasicMove([x, y], [i, j]));
+        if (i == lastRank && !!promoteInto) {
+          moves.push(
+            this.getBasicMove(
+              [x, y], [i, j], { c: color, p: promoteInto })
+          );
+        }
+      }
+    }
+    return moves;
+  }
+
+  static get SEARCH_DEPTH() {
+    return 3;
+  }
+};
diff --git a/client/src/variants/Shogi.js b/client/src/variants/Shogi.js
index bbd3af5b..082d9391 100644
--- a/client/src/variants/Shogi.js
+++ b/client/src/variants/Shogi.js
@@ -35,7 +35,7 @@ export class ShogiRules extends ChessRules {
   static get SILVER_G() {
     return "s";
   }
-  static get LANCER() {
+  static get LANCE() {
     return "l";
   }
 
@@ -49,7 +49,7 @@ export class ShogiRules extends ChessRules {
   static get P_SILVER() {
     return 't';
   }
-  static get P_LANCER() {
+  static get P_LANCE() {
     return 'm';
   }
   static get P_ROOK() {
@@ -68,11 +68,11 @@ export class ShogiRules extends ChessRules {
       ChessRules.KING,
       V.GOLD_G,
       V.SILVER_G,
-      V.LANCER,
+      V.LANCE,
       V.P_PAWN,
       V.P_KNIGHT,
       V.P_SILVER,
-      V.P_LANCER,
+      V.P_LANCE,
       V.P_ROOK,
       V.P_BISHOP
     ];
@@ -149,7 +149,7 @@ export class ShogiRules extends ChessRules {
         [V.GOLD_G]: parseInt(fenParsed.reserve[3]),
         [V.SILVER_G]: parseInt(fenParsed.reserve[4]),
         [V.KNIGHT]: parseInt(fenParsed.reserve[5]),
-        [V.LANCER]: parseInt(fenParsed.reserve[6])
+        [V.LANCE]: parseInt(fenParsed.reserve[6])
       },
       b: {
         [V.PAWN]: parseInt(fenParsed.reserve[7]),
@@ -158,7 +158,7 @@ export class ShogiRules extends ChessRules {
         [V.GOLD_G]: parseInt(fenParsed.reserve[10]),
         [V.SILVER_G]: parseInt(fenParsed.reserve[11]),
         [V.KNIGHT]: parseInt(fenParsed.reserve[12]),
-        [V.LANCER]: parseInt(fenParsed.reserve[13])
+        [V.LANCE]: parseInt(fenParsed.reserve[13])
       }
     };
   }
@@ -187,7 +187,7 @@ export class ShogiRules extends ChessRules {
   // Ordering on reserve pieces
   static get RESERVE_PIECES() {
     return (
-      [V.PAWN, V.ROOK, V.BISHOP, V.GOLD_G, V.SILVER_G, V.KNIGHT, V.LANCER]
+      [V.PAWN, V.ROOK, V.BISHOP, V.GOLD_G, V.SILVER_G, V.KNIGHT, V.LANCE]
     );
   }
 
@@ -213,7 +213,7 @@ export class ShogiRules extends ChessRules {
     const lastRanks = color == 'w' ? [0, 1] : [8, 7];
     for (let i = 0; i < V.size.x; i++) {
       if (
-        (i == lastRanks[0] && [V.PAWN, V.KNIGHT, V.LANCER].includes(p)) ||
+        (i == lastRanks[0] && [V.PAWN, V.KNIGHT, V.LANCE].includes(p)) ||
         (i == lastRanks[1] && p == V.KNIGHT)
       ) {
         continue;
@@ -266,8 +266,8 @@ export class ShogiRules extends ChessRules {
         return this.getPotentialBishopMoves([x, y]);
       case V.SILVER_G:
         return this.getPotentialSilverMoves([x, y]);
-      case V.LANCER:
-        return this.getPotentialLancerMoves([x, y]);
+      case V.LANCE:
+        return this.getPotentialLanceMoves([x, y]);
       case V.KING:
         return this.getPotentialKingMoves([x, y]);
       case V.P_ROOK:
@@ -278,7 +278,7 @@ export class ShogiRules extends ChessRules {
       case V.P_PAWN:
       case V.P_SILVER:
       case V.P_KNIGHT:
-      case V.P_LANCER:
+      case V.P_LANCE:
         return this.getPotentialGoldMoves([x, y]);
     }
     return []; //never reached
@@ -382,10 +382,10 @@ export class ShogiRules extends ChessRules {
       sq, V.steps[V.BISHOP], { promote: V.P_BISHOP });
   }
 
-  getPotentialLancerMoves(sq) {
+  getPotentialLanceMoves(sq) {
     const forward = (this.turn == 'w' ? -1 : 1);
     return this.getSlideNJumpMoves(
-      sq, [[forward, 0]], { promote: V.P_LANCER });
+      sq, [[forward, 0]], { promote: V.P_LANCE });
   }
 
   getPotentialDragonMoves(sq) {
@@ -418,7 +418,7 @@ export class ShogiRules extends ChessRules {
       this.isAttackedByKnight(sq, color) ||
       this.isAttackedByBishop(sq, color) ||
       this.isAttackedByHorse(sq, color) ||
-      this.isAttackedByLancer(sq, color) ||
+      this.isAttackedByLance(sq, color) ||
       this.isAttackedBySilver(sq, color) ||
       this.isAttackedByGold(sq, color) ||
       this.isAttackedByKing(sq, color)
@@ -433,7 +433,7 @@ export class ShogiRules extends ChessRules {
         V.OnBoard(i, j) &&
         this.board[i][j] != V.EMPTY &&
         this.getColor(i, j) == color &&
-        [V.GOLD_G, V.P_PAWN, V.P_SILVER, V.P_KNIGHT, V.P_LANCER]
+        [V.GOLD_G, V.P_PAWN, V.P_SILVER, V.P_KNIGHT, V.P_LANCE]
           .includes(this.getPiece(i, j))
       ) {
         return true;
@@ -475,9 +475,9 @@ export class ShogiRules extends ChessRules {
       sq, color, V.KNIGHT, [[forward, 1], [forward, -1]], "oneStep");
   }
 
-  isAttackedByLancer(sq, color) {
+  isAttackedByLance(sq, color) {
     const forward = (color == 'w' ? 1 : -1);
-    return this.isAttackedBySlideNJump(sq, color, V.LANCER, [[forward, 0]]);
+    return this.isAttackedBySlideNJump(sq, color, V.LANCE, [[forward, 0]]);
   }
 
   isAttackedByDragon(sq, color) {
diff --git a/server/db/populate.sql b/server/db/populate.sql
index 91e92543..a3479206 100644
--- a/server/db/populate.sql
+++ b/server/db/populate.sql
@@ -50,6 +50,7 @@ insert or ignore into Variants (name, description) values
   ('Magnetic', 'Laws of attraction'),
   ('Makruk', 'Thai Chess'),
   ('Maxima', 'Occupy the enemy palace'),
+  ('Minishogi', 'Shogi 5 x 5'),
   ('Monochrome', 'All of the same color'),
   ('Monster', 'White move twice'),
   ('Omega', 'A wizard in the corner'),