From e2f204eda8630746cb951889da01241b1e5f5733 Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Wed, 15 Apr 2020 18:22:14 +0200 Subject: [PATCH] Add Minishogi --- client/src/translations/en.js | 1 + client/src/translations/es.js | 1 + client/src/translations/fr.js | 1 + .../src/translations/rules/Minishogi/en.pug | 21 +++ .../src/translations/rules/Minishogi/es.pug | 22 +++ .../src/translations/rules/Minishogi/fr.pug | 22 +++ client/src/variants/Minishogi.js | 170 ++++++++++++++++++ client/src/variants/Shogi.js | 34 ++-- server/db/populate.sql | 1 + 9 files changed, 256 insertions(+), 17 deletions(-) create mode 100644 client/src/translations/rules/Minishogi/en.pug create mode 100644 client/src/translations/rules/Minishogi/es.pug create mode 100644 client/src/translations/rules/Minishogi/fr.pug create mode 100644 client/src/variants/Minishogi.js 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 + |  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 + |  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'), -- 2.44.0