Add Makpong, Hoppelpoppel, and Jangqi (rules unwritten yet)
authorBenjamin Auder <benjamin.auder@somewhere>
Fri, 1 Jan 2021 01:26:17 +0000 (02:26 +0100)
committerBenjamin Auder <benjamin.auder@somewhere>
Fri, 1 Jan 2021 01:26:17 +0000 (02:26 +0100)
40 files changed:
TODO
client/public/images/pieces/Jiangqi/ba.svg [new symlink]
client/public/images/pieces/Jiangqi/bc.svg [new symlink]
client/public/images/pieces/Jiangqi/be.svg [new symlink]
client/public/images/pieces/Jiangqi/bk.svg [new symlink]
client/public/images/pieces/Jiangqi/bn.svg [new symlink]
client/public/images/pieces/Jiangqi/bp.svg [new symlink]
client/public/images/pieces/Jiangqi/br.svg [new symlink]
client/public/images/pieces/Jiangqi/wa.svg [new symlink]
client/public/images/pieces/Jiangqi/wc.svg [new symlink]
client/public/images/pieces/Jiangqi/we.svg [new symlink]
client/public/images/pieces/Jiangqi/wk.svg [new symlink]
client/public/images/pieces/Jiangqi/wn.svg [new symlink]
client/public/images/pieces/Jiangqi/wp.svg [new symlink]
client/public/images/pieces/Jiangqi/wr.svg [new symlink]
client/src/base_rules.js
client/src/translations/en.js
client/src/translations/es.js
client/src/translations/fr.js
client/src/translations/rules/Hoppelpoppel/en.pug [new file with mode: 0644]
client/src/translations/rules/Hoppelpoppel/es.pug [new file with mode: 0644]
client/src/translations/rules/Hoppelpoppel/fr.pug [new file with mode: 0644]
client/src/translations/rules/Jangqi/en.pug [new file with mode: 0644]
client/src/translations/rules/Jangqi/es.pug [new file with mode: 0644]
client/src/translations/rules/Jangqi/fr.pug [new file with mode: 0644]
client/src/translations/rules/Makpong/en.pug [new file with mode: 0644]
client/src/translations/rules/Makpong/es.pug [new file with mode: 0644]
client/src/translations/rules/Makpong/fr.pug [new file with mode: 0644]
client/src/translations/rules/Synochess/en.pug
client/src/translations/rules/Synochess/es.pug
client/src/translations/rules/Synochess/fr.pug
client/src/translations/variants/en.pug
client/src/translations/variants/es.pug
client/src/translations/variants/fr.pug
client/src/variants/Hoppelpoppel.js [new file with mode: 0644]
client/src/variants/Jangqi.js [new file with mode: 0644]
client/src/variants/Makpong.js [new file with mode: 0644]
client/src/variants/Synochess.js
client/src/variants/Xiangqi.js
server/db/populate.sql

diff --git a/TODO b/TODO
index 227683d..3790c55 100644 (file)
--- 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 (symlink)
index 0000000..b727f3e
--- /dev/null
@@ -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 (symlink)
index 0000000..c779792
--- /dev/null
@@ -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 (symlink)
index 0000000..65da937
--- /dev/null
@@ -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 (symlink)
index 0000000..d88bff5
--- /dev/null
@@ -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 (symlink)
index 0000000..31d112e
--- /dev/null
@@ -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 (symlink)
index 0000000..6cb84a9
--- /dev/null
@@ -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 (symlink)
index 0000000..d7ce7a3
--- /dev/null
@@ -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 (symlink)
index 0000000..fcf50d0
--- /dev/null
@@ -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 (symlink)
index 0000000..d163456
--- /dev/null
@@ -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 (symlink)
index 0000000..ecaa730
--- /dev/null
@@ -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 (symlink)
index 0000000..ef1cfac
--- /dev/null
@@ -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 (symlink)
index 0000000..9cef643
--- /dev/null
@@ -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 (symlink)
index 0000000..c9d9dfc
--- /dev/null
@@ -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 (symlink)
index 0000000..db0b4e1
--- /dev/null
@@ -0,0 +1 @@
+../Xiangqi/wr.svg
\ No newline at end of file
index aa49f91..293e933 100644 (file)
@@ -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
   }
index fbcad34..c01b09a 100644 (file)
@@ -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",
index 22b20a9..629c6c0 100644 (file)
@@ -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",
index e3e5379..1d18d17 100644 (file)
@@ -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 (file)
index 0000000..6939a91
--- /dev/null
@@ -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 (file)
index 0000000..cdaccc2
--- /dev/null
@@ -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 (file)
index 0000000..d3cb7cc
--- /dev/null
@@ -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 (file)
index 0000000..5c3d09c
--- /dev/null
@@ -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 (file)
index 0000000..21203ba
--- /dev/null
@@ -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 (file)
index 0000000..21203ba
--- /dev/null
@@ -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 (file)
index 0000000..522cb58
--- /dev/null
@@ -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 (file)
index 0000000..d3e2cf7
--- /dev/null
@@ -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 (file)
index 0000000..dd24d4a
--- /dev/null
@@ -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.
index 8d200c4..5350d69 100644 (file)
@@ -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
index 33c2ffe..1f2bdf0 100644 (file)
@@ -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
 
index d439dd4..7d5692d 100644 (file)
@@ -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
index 156b4b5..fb55730 100644 (file)
@@ -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",
index 890c081..19a416d 100644 (file)
@@ -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",
index 59ba797..12c6853 100644 (file)
@@ -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 (file)
index 0000000..c350466
--- /dev/null
@@ -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 (file)
index 0000000..154fa32
--- /dev/null
@@ -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 (file)
index 0000000..0576665
--- /dev/null
@@ -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);
+  }
+
+};
index e5c3213..5291522 100644 (file)
@@ -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];
         }
index 5deed64..b302433 100644 (file)
@@ -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;
+  }
+
 };
index 47a6f3c..56a0430 100644 (file)
@@ -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'),