Add Minishogi
authorBenjamin Auder <benjamin.auder@somewhere>
Wed, 15 Apr 2020 16:22:14 +0000 (18:22 +0200)
committerBenjamin Auder <benjamin.auder@somewhere>
Wed, 15 Apr 2020 16:22:14 +0000 (18:22 +0200)
client/src/translations/en.js
client/src/translations/es.js
client/src/translations/fr.js
client/src/translations/rules/Minishogi/en.pug [new file with mode: 0644]
client/src/translations/rules/Minishogi/es.pug [new file with mode: 0644]
client/src/translations/rules/Minishogi/fr.pug [new file with mode: 0644]
client/src/variants/Minishogi.js [new file with mode: 0644]
client/src/variants/Shogi.js
server/db/populate.sql

index 3d83513..e9bd52c 100644 (file)
@@ -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)",
   "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",
   "Shoot pieces": "Shoot pieces",
   "Squares disappear": "Squares disappear",
   "Standard rules": "Standard rules",
index 335d1e0..27ad31c 100644 (file)
@@ -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)",
   "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",
   "Shoot pieces": "Tirar de las piezas",
   "Squares disappear": "Las casillas desaparecen",
   "Standard rules": "Reglas estandar",
index e9e983e..5d2ef3b 100644 (file)
@@ -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)",
   "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",
   "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 (file)
index 0000000..18f5e36
--- /dev/null
@@ -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 (file)
index 0000000..77a06d0
--- /dev/null
@@ -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 (file)
index 0000000..470d7b8
--- /dev/null
@@ -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 (file)
index 0000000..ae5876f
--- /dev/null
@@ -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;
+  }
+};
index bbd3af5..082d939 100644 (file)
@@ -35,7 +35,7 @@ export class ShogiRules extends ChessRules {
   static get SILVER_G() {
     return "s";
   }
   static get SILVER_G() {
     return "s";
   }
-  static get LANCER() {
+  static get LANCE() {
     return "l";
   }
 
     return "l";
   }
 
@@ -49,7 +49,7 @@ export class ShogiRules extends ChessRules {
   static get P_SILVER() {
     return 't';
   }
   static get P_SILVER() {
     return 't';
   }
-  static get P_LANCER() {
+  static get P_LANCE() {
     return 'm';
   }
   static get P_ROOK() {
     return 'm';
   }
   static get P_ROOK() {
@@ -68,11 +68,11 @@ export class ShogiRules extends ChessRules {
       ChessRules.KING,
       V.GOLD_G,
       V.SILVER_G,
       ChessRules.KING,
       V.GOLD_G,
       V.SILVER_G,
-      V.LANCER,
+      V.LANCE,
       V.P_PAWN,
       V.P_KNIGHT,
       V.P_SILVER,
       V.P_PAWN,
       V.P_KNIGHT,
       V.P_SILVER,
-      V.P_LANCER,
+      V.P_LANCE,
       V.P_ROOK,
       V.P_BISHOP
     ];
       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.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]),
       },
       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.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 (
   // 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 (
     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;
         (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]);
         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:
       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_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
         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 });
   }
 
       sq, V.steps[V.BISHOP], { promote: V.P_BISHOP });
   }
 
-  getPotentialLancerMoves(sq) {
+  getPotentialLanceMoves(sq) {
     const forward = (this.turn == 'w' ? -1 : 1);
     return this.getSlideNJumpMoves(
     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) {
   }
 
   getPotentialDragonMoves(sq) {
@@ -418,7 +418,7 @@ export class ShogiRules extends ChessRules {
       this.isAttackedByKnight(sq, color) ||
       this.isAttackedByBishop(sq, color) ||
       this.isAttackedByHorse(sq, color) ||
       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)
       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.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;
           .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");
   }
 
       sq, color, V.KNIGHT, [[forward, 1], [forward, -1]], "oneStep");
   }
 
-  isAttackedByLancer(sq, color) {
+  isAttackedByLance(sq, color) {
     const forward = (color == 'w' ? 1 : -1);
     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) {
   }
 
   isAttackedByDragon(sq, color) {
index 91e9254..a347920 100644 (file)
@@ -50,6 +50,7 @@ insert or ignore into Variants (name, description) values
   ('Magnetic', 'Laws of attraction'),
   ('Makruk', 'Thai Chess'),
   ('Maxima', 'Occupy the enemy palace'),
   ('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'),
   ('Monochrome', 'All of the same color'),
   ('Monster', 'White move twice'),
   ('Omega', 'A wizard in the corner'),