From dbc79ee67847c36aad6b640b15d25d6fb7f361e5 Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Fri, 1 Jan 2021 02:26:17 +0100 Subject: [PATCH] Add Makpong, Hoppelpoppel, and Jangqi (rules unwritten yet) --- TODO | 8 +- client/public/images/pieces/Jiangqi/ba.svg | 1 + client/public/images/pieces/Jiangqi/bc.svg | 1 + client/public/images/pieces/Jiangqi/be.svg | 1 + client/public/images/pieces/Jiangqi/bk.svg | 1 + client/public/images/pieces/Jiangqi/bn.svg | 1 + client/public/images/pieces/Jiangqi/bp.svg | 1 + client/public/images/pieces/Jiangqi/br.svg | 1 + client/public/images/pieces/Jiangqi/wa.svg | 1 + client/public/images/pieces/Jiangqi/wc.svg | 1 + client/public/images/pieces/Jiangqi/we.svg | 1 + client/public/images/pieces/Jiangqi/wk.svg | 1 + client/public/images/pieces/Jiangqi/wn.svg | 1 + client/public/images/pieces/Jiangqi/wp.svg | 1 + client/public/images/pieces/Jiangqi/wr.svg | 1 + client/src/base_rules.js | 18 +- client/src/translations/en.js | 5 +- client/src/translations/es.js | 5 +- client/src/translations/fr.js | 2 + .../translations/rules/Hoppelpoppel/en.pug | 20 + .../translations/rules/Hoppelpoppel/es.pug | 20 + .../translations/rules/Hoppelpoppel/fr.pug | 20 + client/src/translations/rules/Jangqi/en.pug | 6 + client/src/translations/rules/Jangqi/es.pug | 1 + client/src/translations/rules/Jangqi/fr.pug | 1 + client/src/translations/rules/Makpong/en.pug | 23 + client/src/translations/rules/Makpong/es.pug | 24 + client/src/translations/rules/Makpong/fr.pug | 24 + .../src/translations/rules/Synochess/en.pug | 6 +- .../src/translations/rules/Synochess/es.pug | 8 +- .../src/translations/rules/Synochess/fr.pug | 6 +- client/src/translations/variants/en.pug | 3 + client/src/translations/variants/es.pug | 3 + client/src/translations/variants/fr.pug | 3 + client/src/variants/Hoppelpoppel.js | 64 ++ client/src/variants/Jangqi.js | 590 ++++++++++++++++++ client/src/variants/Makpong.js | 14 + client/src/variants/Synochess.js | 2 +- client/src/variants/Xiangqi.js | 66 +- server/db/populate.sql | 5 +- 40 files changed, 910 insertions(+), 51 deletions(-) create mode 120000 client/public/images/pieces/Jiangqi/ba.svg create mode 120000 client/public/images/pieces/Jiangqi/bc.svg create mode 120000 client/public/images/pieces/Jiangqi/be.svg create mode 120000 client/public/images/pieces/Jiangqi/bk.svg create mode 120000 client/public/images/pieces/Jiangqi/bn.svg create mode 120000 client/public/images/pieces/Jiangqi/bp.svg create mode 120000 client/public/images/pieces/Jiangqi/br.svg create mode 120000 client/public/images/pieces/Jiangqi/wa.svg create mode 120000 client/public/images/pieces/Jiangqi/wc.svg create mode 120000 client/public/images/pieces/Jiangqi/we.svg create mode 120000 client/public/images/pieces/Jiangqi/wk.svg create mode 120000 client/public/images/pieces/Jiangqi/wn.svg create mode 120000 client/public/images/pieces/Jiangqi/wp.svg create mode 120000 client/public/images/pieces/Jiangqi/wr.svg create mode 100644 client/src/translations/rules/Hoppelpoppel/en.pug create mode 100644 client/src/translations/rules/Hoppelpoppel/es.pug create mode 100644 client/src/translations/rules/Hoppelpoppel/fr.pug create mode 100644 client/src/translations/rules/Jangqi/en.pug create mode 100644 client/src/translations/rules/Jangqi/es.pug create mode 100644 client/src/translations/rules/Jangqi/fr.pug create mode 100644 client/src/translations/rules/Makpong/en.pug create mode 100644 client/src/translations/rules/Makpong/es.pug create mode 100644 client/src/translations/rules/Makpong/fr.pug create mode 100644 client/src/variants/Hoppelpoppel.js create mode 100644 client/src/variants/Jangqi.js create mode 100644 client/src/variants/Makpong.js diff --git a/TODO b/TODO index 227683d7..3790c55d 100644 --- a/TODO +++ b/TODO @@ -1,16 +1,10 @@ +PROBABLY WON'T FIX: Embedded rules language not updated when language is set (in Analyse, Game and Problems) If new live game starts in background, "new game" notify OK but not first move (not too serious however) -On smartphone for Teleport, Chakart, Weiqi and some others: option "confirm moves on touch screen" -(=> comme pour corr) + option "confirm moves in corr games"? NEW VARIANTS: https://www.pychess.org/variant/manchu https://www.pychess.org/variant/dobutsu -https://www.pychess.org/variant/cambodian -https://www.pychess.org/variant/makpong -https://www.pychess.org/variant/janggi -https://www.pychess.org/variant/kyotoshogi -https://www.pychess.org/variant/hoppelpoppel https://musketeerchess.net/games/musketeer/index.php Attention règle de promotion + SVG / PNG (https://www.pychess.org/variant/shogun) Isardam (type B) : https://echekk.fr/spip.php?page=article&id_article=280 diff --git a/client/public/images/pieces/Jiangqi/ba.svg b/client/public/images/pieces/Jiangqi/ba.svg new file mode 120000 index 00000000..b727f3e8 --- /dev/null +++ b/client/public/images/pieces/Jiangqi/ba.svg @@ -0,0 +1 @@ +../Xiangqi/ba.svg \ No newline at end of file diff --git a/client/public/images/pieces/Jiangqi/bc.svg b/client/public/images/pieces/Jiangqi/bc.svg new file mode 120000 index 00000000..c7797929 --- /dev/null +++ b/client/public/images/pieces/Jiangqi/bc.svg @@ -0,0 +1 @@ +../Xiangqi/bc.svg \ No newline at end of file diff --git a/client/public/images/pieces/Jiangqi/be.svg b/client/public/images/pieces/Jiangqi/be.svg new file mode 120000 index 00000000..65da9376 --- /dev/null +++ b/client/public/images/pieces/Jiangqi/be.svg @@ -0,0 +1 @@ +../Xiangqi/be.svg \ No newline at end of file diff --git a/client/public/images/pieces/Jiangqi/bk.svg b/client/public/images/pieces/Jiangqi/bk.svg new file mode 120000 index 00000000..d88bff5a --- /dev/null +++ b/client/public/images/pieces/Jiangqi/bk.svg @@ -0,0 +1 @@ +../Xiangqi/bk.svg \ No newline at end of file diff --git a/client/public/images/pieces/Jiangqi/bn.svg b/client/public/images/pieces/Jiangqi/bn.svg new file mode 120000 index 00000000..31d112e0 --- /dev/null +++ b/client/public/images/pieces/Jiangqi/bn.svg @@ -0,0 +1 @@ +../Xiangqi/bn.svg \ No newline at end of file diff --git a/client/public/images/pieces/Jiangqi/bp.svg b/client/public/images/pieces/Jiangqi/bp.svg new file mode 120000 index 00000000..6cb84a94 --- /dev/null +++ b/client/public/images/pieces/Jiangqi/bp.svg @@ -0,0 +1 @@ +../Xiangqi/bp.svg \ No newline at end of file diff --git a/client/public/images/pieces/Jiangqi/br.svg b/client/public/images/pieces/Jiangqi/br.svg new file mode 120000 index 00000000..d7ce7a3c --- /dev/null +++ b/client/public/images/pieces/Jiangqi/br.svg @@ -0,0 +1 @@ +../Xiangqi/br.svg \ No newline at end of file diff --git a/client/public/images/pieces/Jiangqi/wa.svg b/client/public/images/pieces/Jiangqi/wa.svg new file mode 120000 index 00000000..fcf50d06 --- /dev/null +++ b/client/public/images/pieces/Jiangqi/wa.svg @@ -0,0 +1 @@ +../Xiangqi/wa.svg \ No newline at end of file diff --git a/client/public/images/pieces/Jiangqi/wc.svg b/client/public/images/pieces/Jiangqi/wc.svg new file mode 120000 index 00000000..d1634561 --- /dev/null +++ b/client/public/images/pieces/Jiangqi/wc.svg @@ -0,0 +1 @@ +../Xiangqi/wc.svg \ No newline at end of file diff --git a/client/public/images/pieces/Jiangqi/we.svg b/client/public/images/pieces/Jiangqi/we.svg new file mode 120000 index 00000000..ecaa730a --- /dev/null +++ b/client/public/images/pieces/Jiangqi/we.svg @@ -0,0 +1 @@ +../Xiangqi/we.svg \ No newline at end of file diff --git a/client/public/images/pieces/Jiangqi/wk.svg b/client/public/images/pieces/Jiangqi/wk.svg new file mode 120000 index 00000000..ef1cfacb --- /dev/null +++ b/client/public/images/pieces/Jiangqi/wk.svg @@ -0,0 +1 @@ +../Xiangqi/wk.svg \ No newline at end of file diff --git a/client/public/images/pieces/Jiangqi/wn.svg b/client/public/images/pieces/Jiangqi/wn.svg new file mode 120000 index 00000000..9cef643a --- /dev/null +++ b/client/public/images/pieces/Jiangqi/wn.svg @@ -0,0 +1 @@ +../Xiangqi/wn.svg \ No newline at end of file diff --git a/client/public/images/pieces/Jiangqi/wp.svg b/client/public/images/pieces/Jiangqi/wp.svg new file mode 120000 index 00000000..c9d9dfcb --- /dev/null +++ b/client/public/images/pieces/Jiangqi/wp.svg @@ -0,0 +1 @@ +../Xiangqi/wp.svg \ No newline at end of file diff --git a/client/public/images/pieces/Jiangqi/wr.svg b/client/public/images/pieces/Jiangqi/wr.svg new file mode 120000 index 00000000..db0b4e16 --- /dev/null +++ b/client/public/images/pieces/Jiangqi/wr.svg @@ -0,0 +1 @@ +../Xiangqi/wr.svg \ No newline at end of file diff --git a/client/src/base_rules.js b/client/src/base_rules.js index aa49f91f..293e933d 100644 --- a/client/src/base_rules.js +++ b/client/src/base_rules.js @@ -655,18 +655,12 @@ export const ChessRules = class ChessRules { // All possible moves from selected square getPotentialMovesFrom([x, y]) { switch (this.getPiece(x, y)) { - case V.PAWN: - return this.getPotentialPawnMoves([x, y]); - case V.ROOK: - return this.getPotentialRookMoves([x, y]); - case V.KNIGHT: - return this.getPotentialKnightMoves([x, y]); - case V.BISHOP: - return this.getPotentialBishopMoves([x, y]); - case V.QUEEN: - return this.getPotentialQueenMoves([x, y]); - case V.KING: - return this.getPotentialKingMoves([x, y]); + case V.PAWN: return this.getPotentialPawnMoves([x, y]); + case V.ROOK: return this.getPotentialRookMoves([x, y]); + case V.KNIGHT: return this.getPotentialKnightMoves([x, y]); + case V.BISHOP: return this.getPotentialBishopMoves([x, y]); + case V.QUEEN: return this.getPotentialQueenMoves([x, y]); + case V.KING: return this.getPotentialKingMoves([x, y]); } return []; //never reached } diff --git a/client/src/translations/en.js b/client/src/translations/en.js index fbcad341..c01b09ae 100644 --- a/client/src/translations/en.js +++ b/client/src/translations/en.js @@ -216,8 +216,10 @@ export const translations = { "King of the Hill": "King of the Hill", "Kings cross the 8x8 board": "Kings cross the 8x8 board", "Kings cross the 11x11 board": "Kings cross the 11x11 board", + "Knibis and Bisknis": "Knibis and Bisknis", "Knight in pocket": "Knight in pocket", "Knight versus pawns": "Knight versus pawns", + "Korean Chess": "Korean Chess", "Lancers everywhere": "Lancers everywhere", "Landing on the board": "Landing on the board", "Laws of attraction": "Laws of attraction", @@ -273,7 +275,8 @@ export const translations = { "Squares disappear": "Squares disappear", "Standard rules": "Standard rules", "Stun & kick pieces": "Stun & kick pieces", - "Thai Chess": "Thai Chess", + "Thai Chess (v1)": "Thai Chess (v1)", + "Thai Chess (v2)": "Thai Chess (v2)", "The colorbound clobberers": "The colorbound clobberers", "The end of the world": "The end of the world", "Transform an essay": "Transform an essay", diff --git a/client/src/translations/es.js b/client/src/translations/es.js index 22b20a91..629c6c00 100644 --- a/client/src/translations/es.js +++ b/client/src/translations/es.js @@ -216,8 +216,10 @@ export const translations = { "King of the Hill": "Rey de la Colina", "Kings cross the 8x8 board": "Los reyes cruzan el 8x8 tablero", "Kings cross the 11x11 board": "Los reyes cruzan el 11x11 tablero", + "Knibis and Bisknis": "Knibis y Bisknis", "Knight in pocket": "Caballo en bolsillo", "Knight versus pawns": "Caballo contra peones", + "Korean Chess": "Ajedrez coreano", "Lancers everywhere": "Lanceros por todas partes", "Landing on the board": "Aterrizando en el tablero", "Laws of attraction": "Las leyes de las atracciones", @@ -273,7 +275,8 @@ export const translations = { "Squares disappear": "Las casillas desaparecen", "Standard rules": "Reglas estandar", "Stun & kick pieces": "Aturdir & patear piezas", - "Thai Chess": "Ajedrez tailandés", + "Thai Chess (v1)": "Ajedrez tailandés (v1)", + "Thai Chess (v2)": "Ajedrez tailandés (v2)", "The colorbound clobberers": "Los batidores unicolor", "The end of the world": "El fin del mundo", "Transform an essay": "Transformar un ensayo", diff --git a/client/src/translations/fr.js b/client/src/translations/fr.js index e3e53795..1d18d17c 100644 --- a/client/src/translations/fr.js +++ b/client/src/translations/fr.js @@ -216,8 +216,10 @@ export const translations = { "King of the Hill": "Roi de la Colline", "Kings cross the 8x8 board": "Les rois traversent l'échiquier 8x8", "Kings cross the 11x11 board": "Les rois traversent l'échiquier 11x11", + "Knibis and Bisknis": "Knibis et Bisknis", "Knight in pocket": "Cavalier en poche", "Knight versus pawns": "Cavalier contre pions", + "Korean Chess": "Échecs coréens", "Lancers everywhere": "Lanciers à tous les coins", "Landing on the board": "Débarquement sur l'échiquier", "Laws of attraction": "Les lois de l'attraction", diff --git a/client/src/translations/rules/Hoppelpoppel/en.pug b/client/src/translations/rules/Hoppelpoppel/en.pug new file mode 100644 index 00000000..6939a91e --- /dev/null +++ b/client/src/translations/rules/Hoppelpoppel/en.pug @@ -0,0 +1,20 @@ +p.boxed + | Knights capture as bishops, and bishops capture as knights. + +figure.diagram-container + .diagram.diag12 + | fen:r1bqkbnr/pppp1ppp/2n5/4p3/2B1P3/8/PPPP1PPP/RNBQK1NR: + .diagram.diag22 + | fen:r1bqkbnr/pppp1ppp/8/4B3/4n3/8/PPPP1PPP/RNBQK1NR: + figcaption Left: 1.e4 e5 2.Bc4 Nc6. Right: after 3.Bxe5 Nxe4 + +p The minor pieces are thus, in a way, more balanced. + +h3 Source + +p + | The variant is reported by Jörg Knappen in 2002, but is probably anterior + | according to the + a(href="https://www.chessvariants.com/diffmove.dir/hoppel-poppel.html") + | chessvariants page + | . diff --git a/client/src/translations/rules/Hoppelpoppel/es.pug b/client/src/translations/rules/Hoppelpoppel/es.pug new file mode 100644 index 00000000..cdaccc2f --- /dev/null +++ b/client/src/translations/rules/Hoppelpoppel/es.pug @@ -0,0 +1,20 @@ +p.boxed + | Los caballos capturan como alfiles y los alfiles como caballos. + +figure.diagram-container + .diagram.diag12 + | fen:r1bqkbnr/pppp1ppp/2n5/4p3/2B1P3/8/PPPP1PPP/RNBQK1NR: + .diagram.diag22 + | fen:r1bqkbnr/pppp1ppp/8/4B3/4n3/8/PPPP1PPP/RNBQK1NR: + figcaption Izquierda: 1.e4 e5 2.Bc4 Nc6. Derecha: después de 3.Bxe5 Nxe4 + +p Por tanto, las piezas menores son, en cierto modo, más equilibradas. + +h3 Fuente + +p + | La variante es descrita por Jörg Knappen en 2002, pero se podría decir que + | arriba según la + a(href="https://www.chessvariants.com/diffmove.dir/hoppel-poppel.html") + | página chessvariants + | . diff --git a/client/src/translations/rules/Hoppelpoppel/fr.pug b/client/src/translations/rules/Hoppelpoppel/fr.pug new file mode 100644 index 00000000..d3cb7cc8 --- /dev/null +++ b/client/src/translations/rules/Hoppelpoppel/fr.pug @@ -0,0 +1,20 @@ +p.boxed + | Les cavaliers capturent comme des fous, et les fous comme des cavaliers. + +figure.diagram-container + .diagram.diag12 + | fen:r1bqkbnr/pppp1ppp/2n5/4p3/2B1P3/8/PPPP1PPP/RNBQK1NR: + .diagram.diag22 + | fen:r1bqkbnr/pppp1ppp/8/4B3/4n3/8/PPPP1PPP/RNBQK1NR: + figcaption Gauche : 1.e4 e5 2.Bc4 Nc6. Droite : après 3.Bxe5 Nxe4 + +p Les pièces mineures sont ainsi, d'une certaine manière, plus équilibrées. + +h3 Source + +p + | La variante est décrite par Jörg Knappen en 2002, mais est sans doute + | antérieure d'après la + a(href="https://www.chessvariants.com/diffmove.dir/hoppel-poppel.html") + | page chessvariants + | . diff --git a/client/src/translations/rules/Jangqi/en.pug b/client/src/translations/rules/Jangqi/en.pug new file mode 100644 index 00000000..5c3d09c6 --- /dev/null +++ b/client/src/translations/rules/Jangqi/en.pug @@ -0,0 +1,6 @@ +p.boxed + | TODO: Korean Chess + +p https://www.pychess.org/variant/janggi + +p https://fr.wikipedia.org/wiki/Janggi diff --git a/client/src/translations/rules/Jangqi/es.pug b/client/src/translations/rules/Jangqi/es.pug new file mode 100644 index 00000000..21203baa --- /dev/null +++ b/client/src/translations/rules/Jangqi/es.pug @@ -0,0 +1 @@ +p.boxed TODO diff --git a/client/src/translations/rules/Jangqi/fr.pug b/client/src/translations/rules/Jangqi/fr.pug new file mode 100644 index 00000000..21203baa --- /dev/null +++ b/client/src/translations/rules/Jangqi/fr.pug @@ -0,0 +1 @@ +p.boxed TODO diff --git a/client/src/translations/rules/Makpong/en.pug b/client/src/translations/rules/Makpong/en.pug new file mode 100644 index 00000000..522cb58b --- /dev/null +++ b/client/src/translations/rules/Makpong/en.pug @@ -0,0 +1,23 @@ +p.boxed + | Makruk, with kings immobilized by checks. + +p + | Everything goes as in the variant + a(href="/#/variants/Makruk") Makruk + | , but the king isn't allowed to move when he's under check: + | one has to capture the attacker, or intercept the attack if possible. + +figure.diagram-container + .diagram + | fen:rnbqkb1r/8/pppNpppp/3n4/8/PPPPPPPP/8/R1BKQBNR: + figcaption Checkmate: the king cannot move. + +p. + This variant is designed to reduce draws. + It is played in Makruk single elimination tournaments in Thailand to + decide a winner after a certain number of games have been drawn. + +p + | I found the variant on + a(href="https://www.pychess.org/variant/makpong") pychess-variants + | , where you can also play it. diff --git a/client/src/translations/rules/Makpong/es.pug b/client/src/translations/rules/Makpong/es.pug new file mode 100644 index 00000000..d3e2cf7a --- /dev/null +++ b/client/src/translations/rules/Makpong/es.pug @@ -0,0 +1,24 @@ +p.boxed + | Makruk, con reyes inmovilizados por los jaques. + +p + | Todo va como en la variante + a(href="/#/variants/Makruk") Makruk + | , pero el rey no puede moverse cuando está en jaque: + | debemos capturar al agresor, o interponer una pieza si es posible. + +figure.diagram-container + .diagram + | fen:rnbqkb1r/8/pppNpppp/3n4/8/PPPPPPPP/8/R1BKQBNR: + figcaption Jaque mate: el rey no puede moverse. + +p. + Esta variante está diseñada para minimizar las tablas. + Se juega en los torneos eliminatorios de Makruk en + Tailandia, para decidir un ganador después + una serie de partes no decisivas. + +p + | Encontré esta variante en + a(href="https://www.pychess.org/variant/makpong") pychess-variants + | , donde también puedes jugarlo. diff --git a/client/src/translations/rules/Makpong/fr.pug b/client/src/translations/rules/Makpong/fr.pug new file mode 100644 index 00000000..dd24d4af --- /dev/null +++ b/client/src/translations/rules/Makpong/fr.pug @@ -0,0 +1,24 @@ +p.boxed + | Makruk, avec rois immobilisés par les échecs. + +p + | Tout se déroule comme dans la variante + a(href="/#/variants/Makruk") Makruk + | , mais le roi n'a pas le droit de bouger lorsqu'il est en échec : + | il faut capturer l'assaillant, ou interposer une pièce si possible. + +figure.diagram-container + .diagram + | fen:rnbqkb1r/8/pppNpppp/3n4/8/PPPPPPPP/8/R1BKQBNR: + figcaption Échec et mat : le roi ne peut pas bouger. + +p. + Cette variante est conçue pour minimiser les nulles. + Elle est jouée dans les tournois de Makruk à élimination direct en + Thaïlande, pour décider d'un vainqueur après + un certain nombre de parties non décisives. + +p + | J'ai trouvé cette variante sur + a(href="https://www.pychess.org/variant/makpong") pychess-variants + | , où vous pouvez aussi y jouer. diff --git a/client/src/translations/rules/Synochess/en.pug b/client/src/translations/rules/Synochess/en.pug index 8d200c4b..5350d690 100644 --- a/client/src/translations/rules/Synochess/en.pug +++ b/client/src/translations/rules/Synochess/en.pug @@ -55,8 +55,8 @@ ul li The Advisor (A) moves and captures exactly like a king. li. The Cannon (C) moves like a rook, but needs an - intervening piece in-between to achieve a capture. - It cannot hop over another cannon, however. + intervening piece in-between before it can land or capture something. + It cannot hop over another cannon. li. The Elephant (E) is a leaping piece that moves diagonally one or two spaces. @@ -65,7 +65,7 @@ figure.diagram-container .diagram.diag12 | fen:8/6P1/5P2/4e3/3P1p2/8/8/8 c3,c7,d4,d6,g3,f6,g7: .diagram.diag22 - | fen:3B4/3P4/8/1P1c1p1P/8/3c4/8/3N4 d8,d6,c5,e5,h5,d4: + | fen:3B4/3P4/8/1P1cp2P/8/3c4/8/3N4 d8,a5,f5,g5,h5: figcaption Elephant & Cannon movements. h3 Piece valuation diff --git a/client/src/translations/rules/Synochess/es.pug b/client/src/translations/rules/Synochess/es.pug index 33c2ffef..1f2bdf0b 100644 --- a/client/src/translations/rules/Synochess/es.pug +++ b/client/src/translations/rules/Synochess/es.pug @@ -57,8 +57,8 @@ ul li El Consejero (A) se mueve y captura como un rey. li. El cañón (C) se mueve como una torre, pero necesita una pieza - intermediario para realizar una captura. - Sin embargo, no puede saltar sobre otro cañón. + intermediario antes de aterrizar o de realizar una captura. + No puede saltar sobre otro cañón. li. El elefante (E) avanza en diagonal uno o dos cuadrados, posiblemente saltando sobre una pieza. @@ -67,8 +67,8 @@ figure.diagram-container .diagram.diag12 | fen:8/6P1/5P2/4e3/3P1p2/8/8/8 c3,c7,d4,d6,g3,f6,g7: .diagram.diag22 - | fen:3B4/3P4/8/1P1c1p1P/8/3c4/8/3N4 d8,d6,c5,e5,h5,d4: - figcaption Movimientos del elefante y el cañón. + | fen:3B4/3P4/8/1P1cp2P/8/3c4/8/3N4 d8,a5,f5,g5,h5: + figcaption Movimientos del elefante y del cañón. h3 Valores de las piezas diff --git a/client/src/translations/rules/Synochess/fr.pug b/client/src/translations/rules/Synochess/fr.pug index d439dd4a..7d5692db 100644 --- a/client/src/translations/rules/Synochess/fr.pug +++ b/client/src/translations/rules/Synochess/fr.pug @@ -55,8 +55,8 @@ ul li Le Conseiller (A) se déplace et capture exactement comme un roi. li. Le Canon (C) se déplace comme une tour, mais a besoin d'une pièce - intermédiaire pour effectuer une capture. - Cependant, il ne peut pas sauter par dessus un autre canon. + intermédiaire avant d'atterrir ou d'effectuer une capture. + Il ne peut pas sauter par dessus un autre canon. li. L'Éléphant (E) avance en diagonale d'une case ou deux, éventuellement en sautant par dessus une pièce. @@ -65,7 +65,7 @@ figure.diagram-container .diagram.diag12 | fen:8/6P1/5P2/4e3/3P1p2/8/8/8 c3,c7,d4,d6,g3,f6,g7: .diagram.diag22 - | fen:3B4/3P4/8/1P1c1p1P/8/3c4/8/3N4 d8,d6,c5,e5,h5,d4: + | fen:3B4/3P4/8/1P1cp2P/8/3c4/8/3N4 d8,a5,f5,g5,h5: figcaption Déplacements de l'Éléphant et du Canon. h3 Valeurs des pièces diff --git a/client/src/translations/variants/en.pug b/client/src/translations/variants/en.pug index 156b4b55..fb55730f 100644 --- a/client/src/translations/variants/en.pug +++ b/client/src/translations/variants/en.pug @@ -118,6 +118,7 @@ p. "Fullcavalry", "Grand", "Grasshopper", + "Hoppelpoppel", "Omega", "Ordamirror", "Perfect", @@ -313,6 +314,8 @@ h3 Regional and historical variants p (Partial) Game evolution in time and space. - var varlist = [ + "Jangqi", + "Makpong", "Makruk", "Minishogi", "Minixiangqi", diff --git a/client/src/translations/variants/es.pug b/client/src/translations/variants/es.pug index 890c081b..19a416d0 100644 --- a/client/src/translations/variants/es.pug +++ b/client/src/translations/variants/es.pug @@ -125,6 +125,7 @@ p. "Fullcavalry", "Grand", "Grasshopper", + "Hoppelpoppel", "Omega", "Ordamirror", "Perfect", @@ -324,6 +325,8 @@ h3 Variantes regionales e históricas p Evolución (parcial) del juego en espacio y tiempo. - var varlist = [ + "Jangqi", + "Makpong", "Makruk", "Minishogi", "Minixiangqi", diff --git a/client/src/translations/variants/fr.pug b/client/src/translations/variants/fr.pug index 59ba797f..12c6853a 100644 --- a/client/src/translations/variants/fr.pug +++ b/client/src/translations/variants/fr.pug @@ -124,6 +124,7 @@ p. "Fullcavalry", "Grand", "Grasshopper", + "Hoppelpoppel", "Omega", "Ordamirror", "Perfect", @@ -323,6 +324,8 @@ h3 Variantes régionales et historiques p Évolution (partielle) du jeu dans l'espace et le temps. - var varlist = [ + "Jangqi", + "Makpong", "Makruk", "Minishogi", "Minixiangqi", diff --git a/client/src/variants/Hoppelpoppel.js b/client/src/variants/Hoppelpoppel.js new file mode 100644 index 00000000..c3504662 --- /dev/null +++ b/client/src/variants/Hoppelpoppel.js @@ -0,0 +1,64 @@ +import { ChessRules } from "@/base_rules"; + +export class HoppelpoppelRules extends ChessRules { + + getSlideNJumpMoves_([x, y], steps, oneStep, options) { + options = options || {}; + 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 (!options.onlyTake) moves.push(this.getBasicMove([x, y], [i, j])); + if (!!oneStep) continue outerLoop; + i += step[0]; + j += step[1]; + } + if (V.OnBoard(i, j) && this.canTake([x, y], [i, j]) && !options.onlyMove) + moves.push(this.getBasicMove([x, y], [i, j])); + } + return moves; + } + + getPotentialKnightMoves(sq) { + // The knight captures like a bishop + return ( + this.getSlideNJumpMoves_( + sq, ChessRules.steps[V.KNIGHT], "oneStep", { onlyMove: true }) + .concat( + this.getSlideNJumpMoves_( + sq, ChessRules.steps[V.BISHOP], null, { onlyTake: true })) + ); + } + + getPotentialBishopMoves(sq) { + // The bishop captures like a knight + return ( + this.getSlideNJumpMoves_( + sq, ChessRules.steps[V.BISHOP], null, { onlyMove: true }) + .concat( + this.getSlideNJumpMoves_( + sq, ChessRules.steps[V.KNIGHT], "oneStep", { onlyTake: true })) + ); + } + + isAttackedByKnight([x, y], color) { + return super.isAttackedBySlideNJump( + [x, y], + color, + V.KNIGHT, + V.steps[V.BISHOP] + ); + } + + isAttackedByAntiking([x, y], color) { + return super.isAttackedBySlideNJump( + [x, y], + color, + V.BISHOP, + V.steps[V.KNIGHT], + "oneStep" + ); + } + +}; diff --git a/client/src/variants/Jangqi.js b/client/src/variants/Jangqi.js new file mode 100644 index 00000000..154fa325 --- /dev/null +++ b/client/src/variants/Jangqi.js @@ -0,0 +1,590 @@ +import { ChessRules, Move, PiPo } from "@/base_rules"; + +export class JangqiRules extends ChessRules { + + static get Monochrome() { + return true; + } + + static get Notoodark() { + return true; + } + + static get Lines() { + let lines = []; + // Draw all inter-squares lines, shifted: + for (let i = 0; i < V.size.x; i++) + lines.push([[i+0.5, 0.5], [i+0.5, V.size.y-0.5]]); + for (let j = 0; j < V.size.y; j++) + lines.push([[0.5, j+0.5], [V.size.x-0.5, j+0.5]]); + // Add palaces: + lines.push([[0.5, 3.5], [2.5, 5.5]]); + lines.push([[0.5, 5.5], [2.5, 3.5]]); + lines.push([[9.5, 3.5], [7.5, 5.5]]); + lines.push([[9.5, 5.5], [7.5, 3.5]]); + return lines; + } + + // No castle, but flag: bikjang + static get HasCastle() { + return false; + } + + static get HasEnpassant() { + return false; + } + + static get LoseOnRepetition() { + return true; + } + + static get ELEPHANT() { + return "e"; + } + + static get CANNON() { + return "c"; + } + + static get ADVISOR() { + return "a"; + } + + static get PIECES() { + return [V.PAWN, V.ROOK, V.KNIGHT, V.ELEPHANT, V.ADVISOR, V.KING, V.CANNON]; + } + + getPpath(b) { + return "Jiangqi/" + b; + } + + static get size() { + return { x: 10, y: 9}; + } + + getPotentialMovesFrom(sq) { + switch (this.getPiece(sq[0], sq[1])) { + case V.PAWN: return this.getPotentialPawnMoves(sq); + case V.ROOK: return this.getPotentialRookMoves(sq); + case V.KNIGHT: return this.getPotentialKnightMoves(sq); + case V.ELEPHANT: return this.getPotentialElephantMoves(sq); + case V.ADVISOR: return this.getPotentialAdvisorMoves(sq); + case V.KING: return this.getPotentialKingMoves(sq); + case V.CANNON: return this.getPotentialCannonMoves(sq); + } + return []; //never reached + } + + static IsGoodFlags(flags) { + // bikjang status of last move + pass + return !!flags.match(/^[0-2]{2,2}$/); + } + + aggregateFlags() { + return [this.bikjangFlag, this.passFlag]; + } + + disaggregateFlags(flags) { + this.bikjangFlag = flags[0]; + this.passFlag = flags[1]; + } + + getFlagsFen() { + return this.bikjangFlag.toString() + this.passFlag.toString() + } + + setFlags(fenflags) { + this.bikjangFlag = parseInt(fenflags.charAt(0), 10); + this.passFlag = parseInt(fenflags.charAt(1), 10); + } + + setOtherVariables(fen) { + super.setOtherVariables(fen); + // Sub-turn is useful only at first move... + this.subTurn = 1; + } + + getPotentialMovesFrom([x, y]) { + let moves = []; + const c = this.getColor(x, y); + const oppCol = V.GetOppCol(c); + if (this.kingPos[c][0] == x && this.kingPos[c][1] == y) { + // Add pass move (might be impossible if undercheck) + moves.push( + new Move({ + appear: [], + vanish: [], + start: { x: this.kingPos[c][0], y: this.kingPos[c][1] }, + end: { x: this.kingPos[oppCol][0], y: this.kingPos[oppCol][1] } + }) + ); + } + // TODO: next "if" is mutually exclusive with the block above + if (this.movesCount <= 1) { + const firstRank = (this.movesCount == 0 ? 9 : 0); + const [initFile, destFile] = (this.subTurn == 1 ? [1, 2] : [7, 6]); + // Only option is knight / elephant swap: + if (x == firstRank && y == initFile) { + moves.push( + new Move({ + appear: [ + new PiPo({ + x: x, + y: destFile, + c: c, + p: V.KNIGHT + }), + new PiPo({ + x: x, + y: y, + c: c, + p: V.ELEPHANT + }) + ], + vanish: [ + new PiPo({ + x: x, + y: y, + c: c, + p: V.KNIGHT + }), + new PiPo({ + x: x, + y: destFile, + c: c, + p: V.ELEPHANT + }) + ], + start: { x: x, y: y }, + end: { x: x, y: destFile } + }) + ); + } + } + else + Array.prototype.push.apply(moves, super.getPotentialMovesFrom([x, y])); + return moves; + } + + getPotentialPawnMoves([x, y]) { + const c = this.getColor(x, y); + const oppCol = V.GetOppCol(c); + const shiftX = (c == 'w' ? -1 : 1); + const rank23 = (oppCol == 'w' ? [8, 7] : [1, 2]); + let steps = [[shiftX, 0], [0, -1], [0, 1]]; + // Diagonal moves inside enemy palace: + if (y == 4 && x == rank23[0]) + Array.prototype.push.apply(steps, [[shiftX, 1], [shiftX, -1]]); + else if (x == rank23[1]) { + if (y == 3) steps.push([shiftX, 1]); + else if (y == 5) steps.push([shiftX, -1]); + } + return super.getSlideNJumpMoves([x, y], steps, "oneStep"); + } + + knightStepsFromRookStep(step) { + if (step[0] == 0) return [ [1, 2*step[1]], [-1, 2*step[1]] ]; + return [ [2*step[0], 1], [2*step[0], -1] ]; + } + + getPotentialKnightMoves([x, y]) { + let steps = []; + for (let rookStep of ChessRules.steps[V.ROOK]) { + const [i, j] = [x + rookStep[0], y + rookStep[1]]; + if (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) { + Array.prototype.push.apply(steps, + // These moves might be impossible, but need to be checked: + this.knightStepsFromRookStep(rookStep)); + } + } + return super.getSlideNJumpMoves([x, y], steps, "oneStep"); + } + + elephantStepsFromRookStep(step) { + if (step[0] == 0) return [ [2, 3*step[1]], [-2, 3*step[1]] ]; + return [ [3*step[0], 2], [3*step[0], -2] ]; + } + + getPotentialElephantMoves([x, y]) { + let steps = []; + for (let rookStep of ChessRules.steps[V.ROOK]) { + const eSteps = this.elephantStepsFromRookStep(rookStep); + const [i, j] = [x + rookStep[0], y + rookStep[1]]; + if (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) { + // Check second crossing: + const knightSteps = this.knightStepsFromRookStep(rookStep); + for (let k of [0, 1]) { + const [ii, jj] = [x + knightSteps[k][0], y + knightSteps[k][1]]; + if (V.OnBoard(ii, jj) && this.board[ii][jj] == V.EMPTY) + steps.push(eSteps[k]); //ok: same ordering + } + } + } + return super.getSlideNJumpMoves([x, y], steps, "oneStep"); + } + + palacePeopleMoves([x, y]) { + const c = this.getColor(x, y); + let steps = []; + // Orthogonal steps: + if (x < (c == 'w' ? 9 : 2)) steps.push([1, 0]); + if (x > (c == 'w' ? 7 : 0)) steps.push([-1, 0]); + if (y > 3) steps.push([0, -1]); + if (y < 5) steps.push([0, 1]); + // Diagonal steps, if in the middle or corner: + if ( + y != 4 && + ( + (c == 'w' && x != 8) || + (c == 'b' && x != 1) + ) + ) { + // In a corner: maximum one diagonal step available + let step = null; + const direction = (c == 'w' ? -1 : 1); + if ((c == 'w' && x == 9) || (c == 'b' && x == 0)) { + // On first line + if (y == 3) step = [direction, 1]; + else step = [direction, -1]; + } + else if ((c == 'w' && x == 7) || (c == 'b' && x == 2)) { + // On third line + if (y == 3) step = [-direction, 1]; + else step = [-direction, -1]; + } + steps.push(step); + } + else if ( + y == 4 && + ( + (c == 'w' && x == 8) || + (c == 'b' && x == 1) + ) + ) { + // At the middle: all directions available + Array.prototype.push.apply(steps, ChessRules.steps[V.BISHOP]); + } + return super.getSlideNJumpMoves([x, y], steps, "oneStep"); + } + + getPotentialAdvisorMoves(sq) { + return this.palacePeopleMoves(sq); + } + + getPotentialKingMoves(sq) { + return this.palacePeopleMoves(sq); + } + + getPotentialRookMoves([x, y]) { + let moves = super.getPotentialRookMoves([x, y]); + if ([3, 5].includes(y) && [0, 2, 7, 9].includes(x)) { + // In a corner of a palace: move along diagonal + const step = [[0, 7].includes(x) ? 1 : -1, 4 - y]; + const oppCol = V.GetOppCol(this.getColor(x, y)); + for (let i of [1, 2]) { + const [xx, yy] = [x + i * step[0], y + i * step[1]]; + if (this.board[xx][yy] == V.EMPTY) + moves.push(this.getBasicMove([x, y], [xx, yy])); + else { + if (this.getColor(xx, yy) == oppCol) + moves.push(this.getBasicMove([x, y], [xx, yy])); + break; + } + } + } + else if (y == 4 && [1, 8].includes(x)) { + // In the middle of a palace: 4 one-diagonal-step to check + Array.prototype.push.apply( + moves, + super.getSlideNJumpMoves([x, y], + ChessRules.steps[V.BISHOP], + "oneStep") + ); + } + return moves; + } + + // NOTE: (mostly) duplicated from Shako (TODO?) + getPotentialCannonMoves([x, y]) { + const oppCol = V.GetOppCol(this.turn); + let moves = []; + // Look in every direction until an obstacle (to jump) is met + for (const step of V.steps[V.ROOK]) { + let i = x + step[0]; + let j = y + step[1]; + while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) { + i += step[0]; + j += step[1]; + } + // Then, search for an enemy (if jumped piece isn't a cannon) + if (V.OnBoard(i, j) && this.getPiece(i, j) != V.CANNON) { + i += step[0]; + j += step[1]; + while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) { + moves.push(this.getBasicMove([x, y], [i, j])); + i += step[0]; + j += step[1]; + } + if ( + V.OnBoard(i, j) && + this.getColor(i, j) == oppCol && + this.getPiece(i, j) != V.CANNON + ) { + moves.push(this.getBasicMove([x, y], [i, j])); + } + } + } + if ([3, 5].includes(y) && [0, 2, 7, 9].includes(x)) { + // In a corner of a palace: hop over next obstacle if possible + const step = [[0, 7].includes(x) ? 1 : -1, 4 - y]; + const [x1, y1] = [x + step[0], y + step[1]]; + const [x2, y2] = [x + 2 * step[0], y + 2 * step[1]]; + if ( + this.board[x1][y1] != V.EMPTY && + this.getPiece(x1, y1) != V.CANNON && + ( + this.board[x2][y2] == V.EMPTY || + ( + this.getColor(x2, y2) == oppCol && + this.getPiece(x2, y2) != V.CANNON + ) + ) + ) { + moves.push(this.getBasicMove([x, y], [x2, y2])); + } + } + return moves; + } + + // (King) Never attacked by advisor, since it stays in the palace + isAttacked(sq, color) { + return ( + this.isAttackedByPawn(sq, color) || + this.isAttackedByRook(sq, color) || + this.isAttackedByKnight(sq, color) || + this.isAttackedByElephant(sq, color) || + this.isAttackedByCannon(sq, color) + ); + } + + onPalaceDiagonal([x, y]) { + return ( + (y == 4 && [1, 8].includes(x)) || + ([3, 5].includes(y) && [0, 2, 7, 9].includes(x)) + ); + } + + isAttackedByPawn([x, y], color) { + const shiftX = (color == 'w' ? 1 : -1); //shift from king + if (super.isAttackedBySlideNJump( + [x, y], color, V.PAWN, [[shiftX, 0], [0, 1], [0, -1]], "oneStep") + ) { + return true; + } + if (this.onPalaceDiagonal([x, y])) { + for (let yStep of [-1, 1]) { + const [xx, yy] = [x + shiftX, y + yStep]; + if ( + this.onPalaceDiagonal([xx,yy]) && + this.board[xx][yy] != V.EMPTY && + this.getColor(xx, yy) == color && + this.getPiece(xx, yy) == V.PAWN + ) { + return true; + } + } + } + return false; + } + + knightStepsFromBishopStep(step) { + return [ [2*step[0], step[1]], [step[0], 2*step[1]] ]; + } + + isAttackedByKnight([x, y], color) { + // Check bishop steps: if empty, look continuation knight step + let steps = []; + for (let s of ChessRules.steps[V.BISHOP]) { + const [i, j] = [x + s[0], y + s[1]]; + if ( + V.OnBoard(i, j) && + this.board[i][j] == V.EMPTY + ) { + Array.prototype.push.apply(steps, this.knightStepsFromBishopStep(s)); + } + } + return ( + super.isAttackedBySlideNJump([x, y], color, V.KNIGHT, steps, "oneStep") + ); + } + + elephantStepsFromBishopStep(step) { + return [ [3*step[0], 2*step[1]], [2*step[0], 3*step[1]] ]; + } + + isAttackedByElephant([x, y], color) { + // Check bishop steps: if empty, look continuation elephant step + let steps = []; + for (let s of ChessRules.steps[V.BISHOP]) { + const [i1, j1] = [x + s[0], y + s[1]]; + const [i2, j2] = [x + 2*s[0], y + 2*s[1]]; + if ( + V.OnBoard(i2, j2) && this.board[i2][j2] == V.EMPTY && + V.OnBoard(i1, j1) && this.board[i1][j1] == V.EMPTY + ) { + Array.prototype.push.apply(steps, this.elephantStepsFromBishopStep(s)); + } + } + return ( + super.isAttackedBySlideNJump([x, y], color, V.ELEPHANT, steps, "oneStep") + ); + } + + isAttackedByRook([x, y], color) { + if (super.isAttackedByRook([x, y], color)) return true; + // Also check diagonals, if inside palace + if (this.onPalaceDiagonal([x, y])) { + // TODO: next scan is clearly suboptimal + for (let s of ChessRules.steps[V.BISHOP]) { + for (let i of [1, 2]) { + const [xx, yy] = [x + i * s[0], y + i * s[1]]; + if ( + V.OnBoard(xx, yy) && + this.onPalaceDiagonal([xx, yy]) + ) { + if (this.board[xx][yy] != V.EMPTY) { + if ( + this.getColor(xx, yy) == color && + this.getPiece(xx, yy) == V.ROOK + ) { + return true; + } + break; + } + } + else continue; + } + } + } + return false; + } + + // NOTE: (mostly) duplicated from Shako (TODO?) + isAttackedByCannon([x, y], color) { + // Reversed process: is there an obstacle in line, + // and a cannon next in the same line? + for (const step of V.steps[V.ROOK]) { + let [i, j] = [x+step[0], y+step[1]]; + while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) { + i += step[0]; + j += step[1]; + } + if (V.OnBoard(i, j) && this.getPiece(i, j) != V.CANNON) { + // Keep looking in this direction + i += step[0]; + j += step[1]; + while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) { + i += step[0]; + j += step[1]; + } + if ( + V.OnBoard(i, j) && + this.getPiece(i, j) == V.CANNON && + this.getColor(i, j) == color + ) { + return true; + } + } + } + return false; + } + + getCurrentScore() { + if ([this.bikjangFlag, this.passFlag].includes(2)) return "1/2"; + const color = this.turn; + // super.atLeastOneMove() does not consider passing (OK) + if (this.underCheck(color) && !super.atLeastOneMove()) + return (color == "w" ? "0-1" : "1-0"); + return "*"; + } + + static get VALUES() { + return { + p: 2, + r: 13, + n: 5, + e: 3, + a: 3, + c: 7, + k: 1000 + }; + } + + static get SEARCH_DEPTH() { + return 2; + } + + static GenRandInitFen() { + // No randomization here (but initial setup choice) + return ( + "rnea1aenr/4k4/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/4K4/RNEA1AENR w 0 00" + ); + } + + play(move) { + move.subTurn = this.subTurn; //much easier + if (this.movesCount >= 2 || this.subTurn == 2 || move.vanish.length == 0) { + this.turn = V.GetOppCol(this.turn); + this.subTurn = 1; + this.movesCount++; + } + else this.subTurn = 2; + move.flags = JSON.stringify(this.aggregateFlags()); + V.PlayOnBoard(this.board, move); + this.postPlay(move); + } + + postPlay(move) { + if (move.vanish.length > 0) super.postPlay(move); + else if (this.movesCount > 2) this.passFlag++; + // Update bikjang flag + if (this.kingPos['w'][1] == this.kingPos['b'][1]) { + const y = this.kingPos['w'][1]; + let bikjang = true; + for (let x = this.kingPos['b'][0] + 1; x < this.kingPos['w'][0]; x++) { + if (this.board[x][y] != V.EMPTY) { + bikjang = false; + break; + } + } + if (bikjang) this.bikjangFlag++; + else this.bikjangFlag = 0; + } + else this.bikjangFlag = 0; + } + + undo(move) { + this.disaggregateFlags(JSON.parse(move.flags)); + V.UndoOnBoard(this.board, move); + this.postUndo(move); + if (this.movesCount >= 2 || this.subTurn == 1 || move.vanish.length == 0) { + this.turn = V.GetOppCol(this.turn); + this.movesCount--; + } + this.subTurn = move.subTurn; + } + + postUndo(move) { + if (move.vanish.length > 0) super.postUndo(move); + } + + getNotation(move) { + if (move.vanish.length == 0) return "pass"; + if (move.appear.length == 2) return "S"; //"swap" + let notation = super.getNotation(move); + if (move.vanish.length == 2 && move.vanish[0].p == V.PAWN) + notation = "P" + notation.substr(1); + return notation; + } + +}; diff --git a/client/src/variants/Makpong.js b/client/src/variants/Makpong.js new file mode 100644 index 00000000..05766651 --- /dev/null +++ b/client/src/variants/Makpong.js @@ -0,0 +1,14 @@ +import { MakrukRules } from "@/variants/Makruk"; + +export class MakpongRules extends MakrukRules { + + filterValid(moves) { + const color = this.turn; + if (this.underCheck(color)) { + // Filter out all moves involving king + return super.filterValid(moves.filter(m => m.vanish[0].p != V.KING)); + } + return super.filterValid(moves); + } + +}; diff --git a/client/src/variants/Synochess.js b/client/src/variants/Synochess.js index e5c3213b..52915220 100644 --- a/client/src/variants/Synochess.js +++ b/client/src/variants/Synochess.js @@ -344,7 +344,6 @@ export class SynochessRules extends ChessRules { 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])); i += step[0]; j += step[1]; } @@ -353,6 +352,7 @@ export class SynochessRules extends ChessRules { i += step[0]; j += step[1]; while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) { + moves.push(this.getBasicMove([x, y], [i, j])); i += step[0]; j += step[1]; } diff --git a/client/src/variants/Xiangqi.js b/client/src/variants/Xiangqi.js index 5deed643..b3024332 100644 --- a/client/src/variants/Xiangqi.js +++ b/client/src/variants/Xiangqi.js @@ -2,6 +2,8 @@ import { ChessRules } from "@/base_rules"; export class XiangqiRules extends ChessRules { + // NOTE (TODO?) scanKings() could be more efficient (in Jangqi too) + static get Monochrome() { return true; } @@ -189,34 +191,55 @@ export class XiangqiRules extends ChessRules { return super.getSlideNJumpMoves([x, y], steps, "oneStep"); } - insidePalace(x, y, c) { - return ( - (y >= 3 && y <= 5) && - ( - (c == 'w' && x >= 7) || - (c == 'b' && x <= 2) - ) - ); - } - getPotentialAdvisorMoves([x, y]) { // Diagonal steps inside palace - let steps = []; const c = this.getColor(x, y); - for (let s of ChessRules.steps[V.BISHOP]) { - if (this.insidePalace(x + s[0], y + s[1], c)) steps.push(s); + if ( + y != 4 || + (c == 'w' && x != V.size.x - 2) || + (c == 'b' && x != 1) + ) { + // In a corner: only one step available + let step = null; + const direction = (c == 'w' ? -1 : 1); + if ((c == 'w' && x == V.size.x - 1) || (c == 'b' && x == 0)) { + // On first line + if (y == 3) step = [direction, 1]; + else step = [direction, -1]; + } + else { + // On third line + if (y == 3) step = [-direction, 1]; + else step = [-direction, -1]; + } + return super.getSlideNJumpMoves([x, y], [step], "oneStep"); } - return super.getSlideNJumpMoves([x, y], steps, "oneStep"); + // In the middle of the palace: + return ( + super.getSlideNJumpMoves([x, y], ChessRules.steps[V.BISHOP], "oneStep") + ); } getPotentialKingMoves([x, y]) { // Orthogonal steps inside palace - let steps = []; const c = this.getColor(x, y); - for (let s of ChessRules.steps[V.ROOK]) { - if (this.insidePalace(x + s[0], y + s[1], c)) steps.push(s); + if ( + y != 4 || + (c == 'w' && x != V.size.x - 2) || + (c == 'b' && x != 1) + ) { + // On the edge: only two steps available + let steps = []; + if (x < (c == 'w' ? V.size.x - 1 : 2)) steps.push([1, 0]); + if (x > (c == 'w' ? V.size.x - 3 : 0)) steps.push([-1, 0]); + if (y > 3) steps.push([0, -1]); + if (y < 5) steps.push([0, 1]); + return super.getSlideNJumpMoves([x, y], steps, "oneStep"); } - return super.getSlideNJumpMoves([x, y], steps, "oneStep"); + // In the middle of the palace: + return ( + super.getSlideNJumpMoves([x, y], ChessRules.steps[V.ROOK], "oneStep") + ); } // NOTE: duplicated from Shako (TODO?) @@ -369,4 +392,11 @@ export class XiangqiRules extends ChessRules { return "rneakaenr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNEAKAENR w 0"; } + getNotation(move) { + let notation = super.getNotation(move); + if (move.vanish.length == 2 && move.vanish[0].p == V.PAWN) + notation = "P" + substr(notation, 1); + return notation; + } + }; diff --git a/server/db/populate.sql b/server/db/populate.sql index 47a6f3cf..56a0430c 100644 --- a/server/db/populate.sql +++ b/server/db/populate.sql @@ -61,8 +61,10 @@ insert or ignore into Variants (name, description) values ('Grasshopper', 'Long jumps over pieces'), ('Gridolina', 'Jump the borders'), ('Hamilton', 'Walk on a graph'), + ('Hoppelpoppel', 'Knibis and Bisknis'), ('Horde', 'A pawns cloud'), ('Interweave', 'Interweaved colorbound teams'), + ('Jangqi', 'Korean Chess'), ('Kinglet', 'Protect your pawns'), ('Knightmate', 'Mate the knight'), ('Knightpawns', 'Knight versus pawns'), @@ -74,7 +76,8 @@ insert or ignore into Variants (name, description) values ('Madhouse', 'Rearrange enemy pieces'), ('Madrasi', 'Paralyzed pieces'), ('Magnetic', 'Laws of attraction'), - ('Makruk', 'Thai Chess'), + ('Makpong', 'Thai Chess (v2)'), + ('Makruk', 'Thai Chess (v1)'), ('Maxima', 'Occupy the enemy palace'), ('Minishogi', 'Shogi 5 x 5'), ('Minixiangqi', 'Xiangqi 7 x 7'), -- 2.44.0