From d2af3400944331ffd0c770f83857257c2f48e487 Mon Sep 17 00:00:00 2001
From: Benjamin Auder <benjamin.auder@somewhere>
Date: Sun, 17 Jan 2021 18:17:51 +0100
Subject: [PATCH] Add Konane, (very) early drafts of
 Emergo/Fanorona/Yote/Gomoku, fix repetitions detection in Otage/Pacosako

---
 client/public/images/pieces/Konane/SOURCE     |   3 +
 client/public/images/pieces/Konane/bp.svg     |   9 +
 client/public/images/pieces/Konane/wp.svg     |   9 +
 client/src/base_rules.js                      |   5 +
 client/src/components/Board.vue               |   5 +-
 client/src/translations/en.js                 |   7 +-
 client/src/translations/es.js                 |   7 +-
 client/src/translations/fr.js                 |   7 +-
 client/src/translations/rules/Emergo/en.pug   |   2 +
 client/src/translations/rules/Emergo/es.pug   |   2 +
 client/src/translations/rules/Emergo/fr.pug   |   2 +
 client/src/translations/rules/Fanorona/en.pug |   2 +
 client/src/translations/rules/Fanorona/es.pug |   2 +
 client/src/translations/rules/Fanorona/fr.pug |   2 +
 client/src/translations/rules/Gomoku/en.pug   |   2 +
 client/src/translations/rules/Gomoku/es.pug   |   2 +
 client/src/translations/rules/Gomoku/fr.pug   |   2 +
 client/src/translations/rules/Konane/en.pug   |  49 +++++
 client/src/translations/rules/Konane/es.pug   |  49 +++++
 client/src/translations/rules/Konane/fr.pug   |  49 +++++
 client/src/translations/rules/Yote/en.pug     |   2 +
 client/src/translations/rules/Yote/es.pug     |   2 +
 client/src/translations/rules/Yote/fr.pug     |   2 +
 client/src/translations/variants/en.pug       |  15 ++
 client/src/translations/variants/es.pug       |  15 ++
 client/src/translations/variants/fr.pug       |  15 ++
 client/src/variants/Bario.js                  | 105 ++++-----
 client/src/variants/Emergo.js                 |   7 +
 client/src/variants/Fanorona.js               |   7 +
 client/src/variants/Gomoku.js                 |   7 +
 client/src/variants/Konane.js                 | 206 ++++++++++++++++++
 client/src/variants/Otage.js                  |  11 +-
 client/src/variants/Pacosako.js               |  11 +-
 client/src/variants/Selfabsorption.js         |  10 +-
 client/src/variants/Yote.js                   |   7 +
 server/db/populate.sql                        |   5 +
 36 files changed, 584 insertions(+), 60 deletions(-)
 create mode 100644 client/public/images/pieces/Konane/SOURCE
 create mode 100644 client/public/images/pieces/Konane/bp.svg
 create mode 100644 client/public/images/pieces/Konane/wp.svg
 create mode 100644 client/src/translations/rules/Emergo/en.pug
 create mode 100644 client/src/translations/rules/Emergo/es.pug
 create mode 100644 client/src/translations/rules/Emergo/fr.pug
 create mode 100644 client/src/translations/rules/Fanorona/en.pug
 create mode 100644 client/src/translations/rules/Fanorona/es.pug
 create mode 100644 client/src/translations/rules/Fanorona/fr.pug
 create mode 100644 client/src/translations/rules/Gomoku/en.pug
 create mode 100644 client/src/translations/rules/Gomoku/es.pug
 create mode 100644 client/src/translations/rules/Gomoku/fr.pug
 create mode 100644 client/src/translations/rules/Konane/en.pug
 create mode 100644 client/src/translations/rules/Konane/es.pug
 create mode 100644 client/src/translations/rules/Konane/fr.pug
 create mode 100644 client/src/translations/rules/Yote/en.pug
 create mode 100644 client/src/translations/rules/Yote/es.pug
 create mode 100644 client/src/translations/rules/Yote/fr.pug
 create mode 100644 client/src/variants/Emergo.js
 create mode 100644 client/src/variants/Fanorona.js
 create mode 100644 client/src/variants/Gomoku.js
 create mode 100644 client/src/variants/Konane.js
 create mode 100644 client/src/variants/Yote.js

diff --git a/client/public/images/pieces/Konane/SOURCE b/client/public/images/pieces/Konane/SOURCE
new file mode 100644
index 00000000..df8ca5f5
--- /dev/null
+++ b/client/public/images/pieces/Konane/SOURCE
@@ -0,0 +1,3 @@
+https://commons.wikimedia.org/wiki/File:Go_w.svg
+https://commons.wikimedia.org/wiki/File:Go_b.svg
+(EDITED: remove yellow background)
diff --git a/client/public/images/pieces/Konane/bp.svg b/client/public/images/pieces/Konane/bp.svg
new file mode 100644
index 00000000..75e907e0
--- /dev/null
+++ b/client/public/images/pieces/Konane/bp.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500">
+<defs><radialGradient id="rg" cx=".47" cy=".49" r=".48">
+<stop offset=".7" stop-color="#FFF"/>
+<stop offset=".9" stop-color="#DDD"/>
+<stop offset="1" stop-color="#777"/>
+</radialGradient></defs>
+<circle cx="250" cy="250" r="235" fill="url(#rg)"/>
+</svg>
diff --git a/client/public/images/pieces/Konane/wp.svg b/client/public/images/pieces/Konane/wp.svg
new file mode 100644
index 00000000..357079eb
--- /dev/null
+++ b/client/public/images/pieces/Konane/wp.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500">
+<defs><radialGradient id="rg" cx=".3" cy=".3" r=".8">
+<stop offset="0" stop-color="#777"/>
+<stop offset=".3" stop-color="#222"/>
+<stop offset="1" stop-color="#000"/>
+</radialGradient></defs>
+<circle cx="250" cy="250" r="235" fill="url(#rg)"/>
+</svg>
diff --git a/client/src/base_rules.js b/client/src/base_rules.js
index a9b6a6a1..05491398 100644
--- a/client/src/base_rules.js
+++ b/client/src/base_rules.js
@@ -136,6 +136,11 @@ export const ChessRules = class ChessRules {
     return false;
   }
 
+  // At some stages, some games could wait clicks only:
+  onlyClick() {
+    return false;
+  }
+
   // Some variants use click infos:
   doClick() {
     return null;
diff --git a/client/src/components/Board.vue b/client/src/components/Board.vue
index b8d0fadd..cc3f6fd7 100644
--- a/client/src/components/Board.vue
+++ b/client/src/components/Board.vue
@@ -659,8 +659,9 @@ export default {
             // Emit the click event which could be used by some variants
             const targetId =
               (withPiece ? e.target.parentNode.id : e.target.id);
-            this.$emit("click-square", getSquareFromId(targetId));
-            if (withPiece) {
+            const sq = getSquareFromId(targetId);
+            this.$emit("click-square", sq);
+            if (withPiece && !this.vr.onlyClick(sq)) {
               this.possibleMoves = this.vr.getPossibleMovesFrom(startSquare);
               // For potential drag'n drop, remember start coordinates
               // (to center the piece on mouse cursor)
diff --git a/client/src/translations/en.js b/client/src/translations/en.js
index 90a4fa15..06fadc9c 100644
--- a/client/src/translations/en.js
+++ b/client/src/translations/en.js
@@ -170,6 +170,8 @@ export const translations = {
   "A quantum story": "A quantum story",
   "A wizard in the corner": "A wizard in the corner",
   "Absorb powers": "Absorb powers",
+  "African Draughts": "African Draughts",
+  "Align five stones": "Align five stones",
   "All of the same color": "All of the same color",
   "Ancient rules": "Ancient rules",
   "As in the movie": "As in the movie",
@@ -224,6 +226,7 @@ export const translations = {
   "Get strong at self-mate": "Get strong at self-mate",
   "Give three checks": "Give three checks",
   "Harassed kings": "Harassed kings",
+  "Hawaiian Checkers": "Hawaiian Checkers",
   "Japanese Chess": "Japanese Chess",
   "Jump the borders": "Jump the borders",
   "Keep antiking in check (v1)": "Keep antiking in check (v1)",
@@ -242,7 +245,7 @@ export const translations = {
   "Long jumps over pieces": "Long jumps over pieces",
   "Long live the Queen": "Long live the Queen",
   "Lose all pieces": "Lose all pieces",
-  "Rearrange enemy pieces": "Rearrange enemy pieces",
+  "Malagasy Draughts": "Malagasy Draughts",
   "Mandatory captures": "Mandatory captures",
   "Mate any piece (v1)": "Mate any piece (v1)",
   "Mate any piece (v2)": "Mate any piece (v2)",
@@ -282,6 +285,7 @@ export const translations = {
   "Queen versus pawns": "Queen versus pawns",
   "Reach the last rank (v1)": "Reach the last rank (v1)",
   "Reach the last rank (v2)": "Reach the last rank (v2)",
+  "Rearrange enemy pieces": "Rearrange enemy pieces",
   "Replace pieces": "Replace pieces",
   "Reposition pieces": "Reposition pieces",
   "Reuse pieces": "Reuse pieces",
@@ -300,6 +304,7 @@ export const translations = {
   "Squares disappear": "Squares disappear",
   "Squat last rank (v1)": "Squat last rank (v1)",
   "Squat last rank (v2)": "Squat last rank (v2)",
+  "Stacking Checkers variant": "Stacking Checkers variant",
   "Standard rules": "Standard rules",
   "Stun & kick pieces": "Stun & kick pieces",
   "Thai Chess (v1)": "Thai Chess (v1)",
diff --git a/client/src/translations/es.js b/client/src/translations/es.js
index 650aea92..cab69cf2 100644
--- a/client/src/translations/es.js
+++ b/client/src/translations/es.js
@@ -170,6 +170,8 @@ export const translations = {
   "A quantum story": "Una historia cuántica",
   "A wizard in the corner": "Un mago en la esquina",
   "Absorb powers": "Absorber poderes",
+  "African Draughts": "Damas africanas",
+  "Align five stones": "Alinea cinco piedras",
   "All of the same color": "Todo el mismo color",
   "Ancient rules": "Viejas reglas",
   "As in the movie": "Como en la pelicula",
@@ -224,6 +226,7 @@ export const translations = {
   "Get strong at self-mate": "Progreso en mates asistidos",
   "Give three checks": "Dar tres jaques",
   "Harassed kings": "Reyes acosados",
+  "Hawaiian Checkers": "Damas hawaianas",
   "Japanese Chess": "Ajedrez japonés",
   "Jump the borders": "Saltar las fronteras",
   "Keep antiking in check (v1)": "Mantener el antirey en jaque (v1)",
@@ -242,7 +245,7 @@ export const translations = {
   "Long jumps over pieces": "Saltos largos sobre las piezas",
   "Long live the Queen": "Larga vida a la reina",
   "Lose all pieces": "Perder todas las piezas",
-  "Rearrange enemy pieces": "Reorganizar piezas opuestas",
+  "Malagasy Draughts": "Damas malgaches",
   "Mandatory captures": "Capturas obligatorias",
   "Mate any piece (v1)": "Matar cualquier pieza (v1)",
   "Mate any piece (v2)": "Matar cualquier pieza (v2)",
@@ -282,6 +285,7 @@ export const translations = {
   "Queen versus pawns": "Dama contra peones",
   "Reach the last rank (v1)": "Llegar a la última fila (v1)",
   "Reach the last rank (v2)": "Llegar a la última fila (v2)",
+  "Rearrange enemy pieces": "Reorganizar piezas opuestas",
   "Replace pieces": "Reemplazar piezas",
   "Reposition pieces": "Reposicionar las piezas",
   "Reuse pieces": "Reutilizar piezas",
@@ -300,6 +304,7 @@ export const translations = {
   "Squares disappear": "Las casillas desaparecen",
   "Squat last rank (v1)": "Ocupa la última fila (v1)",
   "Squat last rank (v2)": "Ocupa la última fila (v2)",
+  "Stacking Checkers variant": "Variante de damas con pilas",
   "Standard rules": "Reglas estandar",
   "Stun & kick pieces": "Aturdir & patear piezas",
   "Thai Chess (v1)": "Ajedrez tailandés (v1)",
diff --git a/client/src/translations/fr.js b/client/src/translations/fr.js
index 5a04fab5..ba812a46 100644
--- a/client/src/translations/fr.js
+++ b/client/src/translations/fr.js
@@ -170,6 +170,8 @@ export const translations = {
   "A quantum story": "Une histoire quantique",
   "A wizard in the corner": "Un sorcier dans le coin",
   "Absorb powers": "Absorber les pouvoirs",
+  "African Draughts": "Dames africaines",
+  "Align five stones": "Alignez cinq pierres",
   "All of the same color": "Tout de la même couleur",
   "Ancient rules": "Règles anciennes",
   "As in the movie": "Comme dans le film",
@@ -224,6 +226,7 @@ export const translations = {
   "Get strong at self-mate": "Progressez en mats aidés",
   "Give three checks": "Donnez trois échecs",
   "Harassed kings": "Rois harcelés",
+  "Hawaiian Checkers": "Dames hawaïennes",
   "Japanese Chess": "Échecs japonais",
   "Jump the borders": "Sauter les frontières",
   "Keep antiking in check (v1)": "Gardez l'antiroi en échec (v1)",
@@ -242,7 +245,7 @@ export const translations = {
   "Long jumps over pieces": "Sauts longs par dessus les pièces",
   "Long live the Queen": "Long vie à la Reine",
   "Lose all pieces": "Perdez toutes les pièces",
-  "Rearrange enemy pieces": "Réorganisez les pièces adverses",
+  "Malagasy Draughts": "Dames malgaches",
   "Mandatory captures": "Captures obligatoires",
   "Mate any piece (v1)": "Matez n'importe quelle pièce (v1)",
   "Mate any piece (v2)": "Matez n'importe quelle pièce (v2)",
@@ -282,6 +285,7 @@ export const translations = {
   "Queen versus pawns": "Dame contre pions",
   "Reach the last rank (v1)": "Atteignez la dernière rangée (v1)",
   "Reach the last rank (v2)": "Atteignez la dernière rangée (v2)",
+  "Rearrange enemy pieces": "Réorganisez les pièces adverses",
   "Replace pieces": "Remplacer les pièces",
   "Reposition pieces": "Replacer les pièces",
   "Reuse pieces": "Réutiliser les pièces",
@@ -300,6 +304,7 @@ export const translations = {
   "Squares disappear": "Les cases disparaissent",
   "Squat last rank (v1)": "Occupez la dernière rangée (v1)",
   "Squat last rank (v2)": "Occupez la dernière rangée (v2)",
+  "Stacking Checkers variant": "Variante des Dames avec empilements",
   "Standard rules": "Règles usuelles",
   "Stun & kick pieces": "Étourdissez & frappez les pièces",
   "Thai Chess (v1)": "Échecs thai (v1)",
diff --git a/client/src/translations/rules/Emergo/en.pug b/client/src/translations/rules/Emergo/en.pug
new file mode 100644
index 00000000..3a33838b
--- /dev/null
+++ b/client/src/translations/rules/Emergo/en.pug
@@ -0,0 +1,2 @@
+p.boxed
+  | TODO
diff --git a/client/src/translations/rules/Emergo/es.pug b/client/src/translations/rules/Emergo/es.pug
new file mode 100644
index 00000000..3a33838b
--- /dev/null
+++ b/client/src/translations/rules/Emergo/es.pug
@@ -0,0 +1,2 @@
+p.boxed
+  | TODO
diff --git a/client/src/translations/rules/Emergo/fr.pug b/client/src/translations/rules/Emergo/fr.pug
new file mode 100644
index 00000000..3a33838b
--- /dev/null
+++ b/client/src/translations/rules/Emergo/fr.pug
@@ -0,0 +1,2 @@
+p.boxed
+  | TODO
diff --git a/client/src/translations/rules/Fanorona/en.pug b/client/src/translations/rules/Fanorona/en.pug
new file mode 100644
index 00000000..3a33838b
--- /dev/null
+++ b/client/src/translations/rules/Fanorona/en.pug
@@ -0,0 +1,2 @@
+p.boxed
+  | TODO
diff --git a/client/src/translations/rules/Fanorona/es.pug b/client/src/translations/rules/Fanorona/es.pug
new file mode 100644
index 00000000..3a33838b
--- /dev/null
+++ b/client/src/translations/rules/Fanorona/es.pug
@@ -0,0 +1,2 @@
+p.boxed
+  | TODO
diff --git a/client/src/translations/rules/Fanorona/fr.pug b/client/src/translations/rules/Fanorona/fr.pug
new file mode 100644
index 00000000..3a33838b
--- /dev/null
+++ b/client/src/translations/rules/Fanorona/fr.pug
@@ -0,0 +1,2 @@
+p.boxed
+  | TODO
diff --git a/client/src/translations/rules/Gomoku/en.pug b/client/src/translations/rules/Gomoku/en.pug
new file mode 100644
index 00000000..3a33838b
--- /dev/null
+++ b/client/src/translations/rules/Gomoku/en.pug
@@ -0,0 +1,2 @@
+p.boxed
+  | TODO
diff --git a/client/src/translations/rules/Gomoku/es.pug b/client/src/translations/rules/Gomoku/es.pug
new file mode 100644
index 00000000..3a33838b
--- /dev/null
+++ b/client/src/translations/rules/Gomoku/es.pug
@@ -0,0 +1,2 @@
+p.boxed
+  | TODO
diff --git a/client/src/translations/rules/Gomoku/fr.pug b/client/src/translations/rules/Gomoku/fr.pug
new file mode 100644
index 00000000..3a33838b
--- /dev/null
+++ b/client/src/translations/rules/Gomoku/fr.pug
@@ -0,0 +1,2 @@
+p.boxed
+  | TODO
diff --git a/client/src/translations/rules/Konane/en.pug b/client/src/translations/rules/Konane/en.pug
new file mode 100644
index 00000000..9a15f67a
--- /dev/null
+++ b/client/src/translations/rules/Konane/en.pug
@@ -0,0 +1,49 @@
+p.boxed
+  | Capture orthogonally at each turn, "as in Draughts".
+  | If you cannot capture, you lose.
+
+p.
+  To initiate the game, the first player (black) must remove one of his stones
+  either in the upper left or lower right corner, or in the center,
+  as marked on the illustration.
+  The second player (white) then removes a stone orthogonally adjacent to
+  the first removed one, and the game starts.
+  To remove a stone at this stage, click it.
+
+figure.diagram-container
+  .diagram
+    | fen:PpPpPpPp/pPpPpPpP/PpPpPpPp/pPpPpPpP/PpPpPpPp/pPpPpPpP/PpPpPpPp/pPpPpPpP a8,d5,e4,h1:
+  figcaption Allowed first "moves" (stone removal).
+
+p.
+  Every move then is necessary a capture of at least one enemy piece.
+  Capture by jumping orthogonally over an adjacent stone to land right after
+  on an empty square. The intermediate piece is thus removed.
+  You may continue capturing, but only in the same direction,
+  with the same stone.
+  To stop a chain of captures (while more are available), play a move from the
+  current capturer until the location of the last captured stone:
+  g4 on this example.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:PpPpPpPp/pPpPpPpP/PpPpP2p/pPpPp1pP/PpP1Pp1p/pPpPpP1P/PpPpPpPp/pPpPpPpP:
+  .diagram.diag22
+    | fen:PpPpPp1p/pPpPpP1P/PpPpP1Pp/pPpPp1pP/PpP1Pp1p/pPpPpP1P/PpPpPpPp/pPpPpPpP g4,g7:
+  figcaption.
+    Before and after a first capture g8xg7.
+    The available continuations are indicated.
+
+p If a player has no possible capture, he loses.
+
+h3 More information
+
+p
+  | See for example 
+  a(href="https://hawaiiancheckers.com/") hawaiiancheckers.com
+  | , and an 
+  a(href="https://www.youtube.com/watch?app=desktop&v=-_y-B2wAwyw")
+    | example game
+  | . Konane is also playable on 
+  a(href="https://brainking.com/en/GameRules?tp=94") brainking.com
+  | .
diff --git a/client/src/translations/rules/Konane/es.pug b/client/src/translations/rules/Konane/es.pug
new file mode 100644
index 00000000..7cb7f73f
--- /dev/null
+++ b/client/src/translations/rules/Konane/es.pug
@@ -0,0 +1,49 @@
+p.boxed
+  | Captura ortogonalmente en cada turno, "como a las Damas".
+  | Si no es posible la captura, ha perdido.
+
+p.
+  Para iniciar el juego, el primer jugador (negras) debe eliminar uno de
+  sus piedras en la esquina superior izquierda o inferior derecha,
+  ya sea en el centro, como se ilustra.
+  El segundo jugador (blancas) luego quita una piedra ortogonalmente
+  adyacente al primero eliminado, y comienza el juego.
+  Para quitar una piedra, haga clic en ella.
+
+figure.diagram-container
+  .diagram
+    | fen:PpPpPpPp/pPpPpPpP/PpPpPpPp/pPpPpPpP/PpPpPpPp/pPpPpPpP/PpPpPpPp/pPpPpPpP a8,d5,e4,h1:
+  figcaption Los primeros "movimientos" autorizado (eliminación de piedras).
+
+p.
+  Cada movimiento implica necesariamente la captura de al menos una pieza
+  enemiga. Captura saltando ortogonalmente sobre un obstáculo para aterrizar
+  en un cuadrado vacío justo detrás. Se quita así la pieza intermedia.
+  Tienes derecho a seguir capturando, pero solo en el mismo
+  dirección, con la misma piedra.
+  Para detener una cadena de capturas (mientras que otras son posibles),
+  hacer un movimiento de la captura actual a la ubicación de la última
+  pieza capturada: g4 en el ejemplo.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:PpPpPpPp/pPpPpPpP/PpPpP2p/pPpPp1pP/PpP1Pp1p/pPpPpP1P/PpPpPpPp/pPpPpPpP:
+  .diagram.diag22
+    | fen:PpPpPp1p/pPpPpP1P/PpPpP1Pp/pPpPp1pP/PpP1Pp1p/pPpPpP1P/PpPpPpPp/pPpPpPpP g4,g7:
+  figcaption.
+    Antes y después de una primera captura g8xg7.
+    Se indican las continuaciones disponibles.
+
+p Si un jugador no tiene más capturas disponibles, pierde.
+
+h3 Más información
+
+p
+  | Ver por ejemplo 
+  a(href="https://hawaiiancheckers.com/") hawaiiancheckers.com
+  | , y una 
+  a(href="https://www.youtube.com/watch?app=desktop&v=-_y-B2wAwyw")
+    | partida ejemplo
+  | . Konane también se puede jugar en 
+  a(href="https://brainking.com/en/GameRules?tp=94") brainking.com
+  | .
diff --git a/client/src/translations/rules/Konane/fr.pug b/client/src/translations/rules/Konane/fr.pug
new file mode 100644
index 00000000..23297370
--- /dev/null
+++ b/client/src/translations/rules/Konane/fr.pug
@@ -0,0 +1,49 @@
+p.boxed
+  | Capturez orthogonalement à chaque tour, "comme aux Dames".
+  | Si aucune capture n'est possible, vous avez perdu.
+
+p.
+  Pour initialiser la partie, le premier joueur (noirs) doit retirer une de
+  ses pierres soit dans le coin supérieur gauche ou inférieur droit,
+  soit au centre, comme illustré.
+  Le second joueur (blancs) retire ensuite une pierre orthogonalement
+  adjacente à la première supprimée, et la partie commence.
+  Pour enlever une pierre, cliquez dessus.
+
+figure.diagram-container
+  .diagram
+    | fen:PpPpPpPp/pPpPpPpP/PpPpPpPp/pPpPpPpP/PpPpPpPp/pPpPpPpP/PpPpPpPp/pPpPpPpP a8,d5,e4,h1:
+  figcaption Premiers "coups" autorisés (suppression de pierre).
+
+p.
+  Chaque coup comporte nécessairement au moins une capture de pièce ennemie.
+  Capturez en sautant orthogonalement par dessus un obstacle pour atterrir
+  sur une case vide juste derrière. La pièce intermédiaire est ainsi retirée.
+  Vous avez le droit de continuer de capturer, mais seulement dans la même
+  direction, avec la même pierre.
+  Pour arrêter une chaîne de captures (alors que d'autres sont possibles),
+  jouez un coup depuis le capturant actuel vers l'emplacement de la dernière
+  pièce capturée : g4 sur l'exemple.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:PpPpPpPp/pPpPpPpP/PpPpP2p/pPpPp1pP/PpP1Pp1p/pPpPpP1P/PpPpPpPp/pPpPpPpP:
+  .diagram.diag22
+    | fen:PpPpPp1p/pPpPpP1P/PpPpP1Pp/pPpPp1pP/PpP1Pp1p/pPpPpP1P/PpPpPpPp/pPpPpPpP g4,g7:
+  figcaption.
+    Avant et après une première capture g8xg7.
+    Les continuations disponibles sont indiquées.
+
+p Si un joueur n'a plus de captures à disposition, il perd.
+
+h3 Plus d'informations
+
+p
+  | Voir par exemple 
+  a(href="https://hawaiiancheckers.com/") hawaiiancheckers.com
+  | , et une 
+  a(href="https://www.youtube.com/watch?app=desktop&v=-_y-B2wAwyw")
+    | partie exemple
+  | . Konane est aussi jouable sur 
+  a(href="https://brainking.com/en/GameRules?tp=94") brainking.com
+  | .
diff --git a/client/src/translations/rules/Yote/en.pug b/client/src/translations/rules/Yote/en.pug
new file mode 100644
index 00000000..3a33838b
--- /dev/null
+++ b/client/src/translations/rules/Yote/en.pug
@@ -0,0 +1,2 @@
+p.boxed
+  | TODO
diff --git a/client/src/translations/rules/Yote/es.pug b/client/src/translations/rules/Yote/es.pug
new file mode 100644
index 00000000..3a33838b
--- /dev/null
+++ b/client/src/translations/rules/Yote/es.pug
@@ -0,0 +1,2 @@
+p.boxed
+  | TODO
diff --git a/client/src/translations/rules/Yote/fr.pug b/client/src/translations/rules/Yote/fr.pug
new file mode 100644
index 00000000..3a33838b
--- /dev/null
+++ b/client/src/translations/rules/Yote/fr.pug
@@ -0,0 +1,2 @@
+p.boxed
+  | TODO
diff --git a/client/src/translations/variants/en.pug b/client/src/translations/variants/en.pug
index 542c2d48..552652ff 100644
--- a/client/src/translations/variants/en.pug
+++ b/client/src/translations/variants/en.pug
@@ -433,6 +433,21 @@ ul
   for v in varlist
     li #[a(href="/#/variants/"+v) #{v}]
 
+h3 Non-chess
+
+p Some games not chess related.
+-
+  var varlist = [
+    "Emergo",
+    "Fanorona",
+    "Gomoku",
+    "Konane",
+    "Yote"
+  ]
+ul
+  for v in varlist
+    li #[a(href="/#/variants/"+v) #{v}]
+
 h3 Miscelleanous
 
 p.
diff --git a/client/src/translations/variants/es.pug b/client/src/translations/variants/es.pug
index 09fd1169..57ba50e8 100644
--- a/client/src/translations/variants/es.pug
+++ b/client/src/translations/variants/es.pug
@@ -443,6 +443,21 @@ ul
   for v in varlist
     li #[a(href="/#/variants/"+v) #{v}]
 
+h3 Aparte del Ajedrez
+
+p Algunos juegos no están relacionados con el ajedrez.
+-
+  var varlist = [
+    "Emergo",
+    "Fanorona",
+    "Gomoku",
+    "Konane",
+    "Yote"
+  ]
+ul
+  for v in varlist
+    li #[a(href="/#/variants/"+v) #{v}]
+
 h3 Varios
 
 p.
diff --git a/client/src/translations/variants/fr.pug b/client/src/translations/variants/fr.pug
index 144ad2df..cebc6221 100644
--- a/client/src/translations/variants/fr.pug
+++ b/client/src/translations/variants/fr.pug
@@ -441,6 +441,21 @@ ul
   for v in varlist
     li #[a(href="/#/variants/"+v) #{v}]
 
+h3 Hors Échecs
+
+p Quelques jeux non connectés aux échecs.
+-
+  var varlist = [
+    "Emergo",
+    "Fanorona",
+    "Gomoku",
+    "Konane",
+    "Yote"
+  ]
+ul
+  for v in varlist
+    li #[a(href="/#/variants/"+v) #{v}]
+
 h3 Divers
 
 p.
diff --git a/client/src/variants/Bario.js b/client/src/variants/Bario.js
index e895aeef..21965e16 100644
--- a/client/src/variants/Bario.js
+++ b/client/src/variants/Bario.js
@@ -46,6 +46,14 @@ export class BarioRules extends ChessRules {
     );
   }
 
+  onlyClick([x, y]) {
+    return (
+      this.movesCount <= 1 ||
+      // TODO: next line theoretically shouldn't be required...
+      (this.movesCount == 2 && this.getColor(x, y) != this.turn)
+    );
+  }
+
   // Initiate the game by choosing a square for the king:
   doClick(square) {
     const c = this.turn;
@@ -62,7 +70,9 @@ export class BarioRules extends ChessRules {
       appear: [
         new PiPo({ x: square[0], y: square[1], c: c, p: V.KING })
       ],
-      vanish: [],
+      vanish: [
+        new PiPo({ x: square[0], y: square[1], c: c, p: V.UNDEFINED })
+      ],
       start: { x: -1, y: -1 },
     });
   }
@@ -138,7 +148,7 @@ export class BarioRules extends ChessRules {
   }
 
   static GenRandInitFen() {
-    return "8/pppppppp/8/8/8/8/PPPPPPPP/8 w 0 - 22212221 -";
+    return "uuuuuuuu/pppppppp/8/8/8/8/PPPPPPPP/UUUUUUUU w 0 - 22212221 -";
   }
 
   setOtherVariables(fen) {
@@ -259,7 +269,9 @@ export class BarioRules extends ChessRules {
           appear: [
             new PiPo({ x: firstRank, y: j, c: color, p: V.KING })
           ],
-          vanish: [],
+          vanish: [
+            new PiPo({ x: firstRank, y: j, c: color, p: V.UNDEFINED })
+          ],
           start: { x: -1, y: -1 }
         });
       });
@@ -379,6 +391,11 @@ export class BarioRules extends ChessRules {
     return false;
   }
 
+  getCheckSquares() {
+    if (this.movesCount <= 2) return [];
+    return super.getCheckSquares();
+  }
+
   play(move) {
     move.turn = [this.turn, this.subTurn]; //easier undo (TODO?)
     const toNextPlayer = () => {
@@ -389,40 +406,33 @@ export class BarioRules extends ChessRules {
       this.movesCount++;
       this.postPlay(move);
     };
-    if (move.vanish.length == 0) {
-      if (move.appear.length == 1) toNextPlayer();
-      else {
-        // Removal (subTurn == 0 --> 1)
-        this.reserve[this.turn][move.start.p]--;
-        this.subTurn++;
-      }
-      return;
-    }
-    const start = { x: move.vanish[0].x, y: move.vanish[0].y };
-    const end = { x: move.appear[0].x, y: move.appear[0].y };
-    if (start.x == end.x && start.y == end.y) {
-      // Specialisation (subTurn == 1 before 2)
-      this.reserve[this.turn][move.appear[0].p]--;
-      V.PlayOnBoard(this.board, move);
-      this.definitions.push(move.end);
+    if (this.movesCount <= 1) toNextPlayer();
+    else if (move.vanish.length == 0) {
+      // Removal (subTurn == 0 --> 1)
+      this.reserve[this.turn][move.start.p]--;
       this.subTurn++;
     }
     else {
-      // Normal move (subTurn 1 or 2: change turn)
-      this.epSquares.push(this.getEpSquare(move));
-      toNextPlayer();
+      const start = { x: move.vanish[0].x, y: move.vanish[0].y };
+      const end = { x: move.appear[0].x, y: move.appear[0].y };
+      if (start.x == end.x && start.y == end.y) {
+        // Specialisation (subTurn == 1 before 2)
+        this.reserve[this.turn][move.appear[0].p]--;
+        V.PlayOnBoard(this.board, move);
+        this.definitions.push(move.end);
+        this.subTurn++;
+      }
+      else {
+        // Normal move (subTurn 1 or 2: change turn)
+        this.epSquares.push(this.getEpSquare(move));
+        toNextPlayer();
+      }
     }
   }
 
   postPlay(move) {
     const color = V.GetOppCol(this.turn);
-    if (move.vanish.length == 0) {
-      this.kingPos[color] = [move.end.x, move.end.y];
-      const firstRank = (color == 'w' ? 7 : 0);
-      for (let j = 0; j < 8; j++) {
-        if (j != move.end.y) this.board[firstRank][j] = color + V.UNDEFINED;
-      }
-    }
+    if (this.movesCount <= 2) this.kingPos[color] = [move.end.x, move.end.y];
     else {
       if (move.vanish.length == 2 && move.vanish[1].p == V.UNDEFINED)
         this.captureUndefined.push(move.end);
@@ -509,35 +519,30 @@ export class BarioRules extends ChessRules {
       this.movesCount--;
       this.postUndo(move);
     };
-    if (move.vanish.length == 0) {
-      if (move.appear.length == 1) toPrevPlayer();
-      else {
-        this.reserve[this.turn][move.start.p]++;
-        this.subTurn = move.turn[1];
-      }
-      return;
-    }
-    const start = { x: move.vanish[0].x, y: move.vanish[0].y };
-    const end = { x: move.appear[0].x, y: move.appear[0].y };
-    if (start.x == end.x && start.y == end.y) {
-      this.reserve[this.turn][move.appear[0].p]++;
-      V.UndoOnBoard(this.board, move);
-      this.definitions.pop();
+    if (this.movesCount <= 2) toPrevPlayer();
+    else if (move.vanish.length == 0) {
+      this.reserve[this.turn][move.start.p]++;
       this.subTurn = move.turn[1];
     }
     else {
-      this.epSquares.pop();
-      toPrevPlayer();
+      const start = { x: move.vanish[0].x, y: move.vanish[0].y };
+      const end = { x: move.appear[0].x, y: move.appear[0].y };
+      if (start.x == end.x && start.y == end.y) {
+        this.reserve[this.turn][move.appear[0].p]++;
+        V.UndoOnBoard(this.board, move);
+        this.definitions.pop();
+        this.subTurn = move.turn[1];
+      }
+      else {
+        this.epSquares.pop();
+        toPrevPlayer();
+      }
     }
   }
 
   postUndo(move) {
     const color = this.turn;
-    if (move.vanish.length == 0) {
-      this.kingPos[color] = [-1, -1];
-      const firstRank = (color == 'w' ? 7 : 0);
-      for (let j = 0; j < 8; j++) this.board[firstRank][j] = "";
-    }
+    if (this.movesCount <= 1) this.kingPos[color] = [-1, -1];
     else {
       this.captureUndefined.pop();
       if (move.appear[0].p == V.KING) super.postUndo(move);
diff --git a/client/src/variants/Emergo.js b/client/src/variants/Emergo.js
new file mode 100644
index 00000000..0d81759c
--- /dev/null
+++ b/client/src/variants/Emergo.js
@@ -0,0 +1,7 @@
+import { ChessRules } from "@/base_rules";
+
+export class YoteRules extends ChessRules {
+
+  // TODO
+
+};
diff --git a/client/src/variants/Fanorona.js b/client/src/variants/Fanorona.js
new file mode 100644
index 00000000..9f1db3a1
--- /dev/null
+++ b/client/src/variants/Fanorona.js
@@ -0,0 +1,7 @@
+import { ChessRules } from "@/base_rules";
+
+export class FanoronaRules extends ChessRules {
+
+  // TODO
+
+};
diff --git a/client/src/variants/Gomoku.js b/client/src/variants/Gomoku.js
new file mode 100644
index 00000000..5bf971a8
--- /dev/null
+++ b/client/src/variants/Gomoku.js
@@ -0,0 +1,7 @@
+import { ChessRules } from "@/base_rules";
+
+export class GomokuRules extends ChessRules {
+
+  // TODO
+
+};
diff --git a/client/src/variants/Konane.js b/client/src/variants/Konane.js
new file mode 100644
index 00000000..8f285efc
--- /dev/null
+++ b/client/src/variants/Konane.js
@@ -0,0 +1,206 @@
+import { ChessRules, Move, PiPo } from "@/base_rules";
+
+// TODO: Maybe more flexible end of game messages (V.ColorsReversed ?!)
+
+export class KonaneRules extends ChessRules {
+
+  static get HasFlags() {
+    return false;
+  }
+
+  static get HasEnpassant() {
+    return false;
+  }
+
+  static get PIECES() {
+    return V.PAWN;
+  }
+
+  getPpath(b) {
+    return "Konane/" + b;
+  }
+
+  static IsGoodPosition(position) {
+    if (position.length == 0) return false;
+    const rows = position.split("/");
+    if (rows.length != V.size.x) return false;
+    for (let row of rows) {
+      let sumElts = 0;
+      for (let i = 0; i < row.length; i++) {
+        if (V.PIECES.includes(row[i].toLowerCase())) sumElts++;
+        else {
+          const num = parseInt(row[i], 10);
+          if (isNaN(num) || num <= 0) return false;
+          sumElts += num;
+        }
+      }
+      if (sumElts != V.size.y) return false;
+    }
+    return true;
+  }
+
+  static GenRandInitFen() {
+    return (
+      "PpPpPpPp/pPpPpPpP/PpPpPpPp/pPpPpPpP/" +
+      "PpPpPpPp/pPpPpPpP/PpPpPpPp/pPpPpPpP w 0"
+    );
+  }
+
+  setOtherVariables(fen) {
+    this.captures = []; //reinit for each move
+  }
+
+  hoverHighlight(x, y) {
+    if (this.movesCount >= 2) return false;
+    const c = this.turn;
+    if (c == 'w') return (x == y && [0, 3, 4, 7].includes(x));
+    // "Black": search for empty square and allow nearby
+    for (let i of [0, 3, 4, 7]) {
+      if (this.board[i][i] == V.EMPTY)
+        return (Math.abs(x - i) + Math.abs(y - i) == 1)
+    }
+  }
+
+  onlyClick([x, y]) {
+    return (
+      this.movesCount <= 1 ||
+      // TODO: next line theoretically shouldn't be required...
+      (this.movesCount == 2 && this.getColor(x, y) != this.turn)
+    );
+  }
+
+  doClick([x, y]) {
+    if (this.movesCount >= 2) return null;
+    const color = this.turn;
+    if (color == 'w') {
+      if (x != y || ![0, 3, 4, 7].includes(x)) return null;
+      return new Move({
+        appear: [],
+        vanish: [ new PiPo({ x: x, y: y, c: color, p: V.PAWN }) ],
+        end: { x: x, y: y }
+      });
+    }
+    // "Black": search for empty square and allow nearby
+    for (let i of [0, 3, 4, 7]) {
+      if (this.board[i][i] == V.EMPTY) {
+        if (Math.abs(x - i) + Math.abs(y - i) != 1) return null;
+        return new Move({
+          appear: [],
+          vanish: [ new PiPo({ x: x, y: y, c: color, p: V.PAWN }) ],
+          end: { x: x, y: y }
+        });
+      }
+    }
+  }
+
+  getPotentialMovesFrom([x, y]) {
+    if (this.movesCount <= 1) {
+      const mv = this.doClick([x, y]);
+      return (!!mv ? [mv] : []);
+    }
+    const L = this.captures.length;
+    const c = (L > 0 ? this.captures[L-1] : null);
+    const color = this.turn;
+    const oppCol = V.GetOppCol(color);
+    let step = null;
+    let moves = [];
+    if (!!c) {
+      if (x != c.end.x || y != c.end.y) return [];
+      step = [(c.end.x - c.start.x) / 2, (c.end.y - c.start.y) / 2];
+      // Add move to adjacent empty square to mark "end of capture"
+      moves.push(
+        new Move({
+          appear: [],
+          vanish: [],
+          start: { x: x, y: y },
+          end: { x: x - step[0], y: y - step[1] }
+        })
+      );
+    }
+    // Examine captures from here
+    for (let s of (!!step ? [step] : V.steps[V.ROOK])) {
+      let [i, j] = [x + 2*s[0], y + 2*s[1]];
+      if (
+        !!c || //avoid redundant checks if continuation
+        (
+          V.OnBoard(i, j) &&
+          this.board[i][j] == V.EMPTY &&
+          this.board[i - s[0]][j - s[1]] != V.EMPTY &&
+          this.getColor(i - s[0], j - s[1]) == oppCol
+        )
+      ) {
+        let mv = new Move({
+          appear: [
+            new PiPo({ x: i, y: j, c: color, p: V.PAWN })
+          ],
+          vanish: [
+            new PiPo({ x: x, y: y, c: color, p: V.PAWN }),
+            new PiPo({ x: i - s[0], y: j - s[1], c: oppCol, p: V.PAWN })
+          ]
+        });
+        // Is there another capture possible then?
+        [i, j] = [i + 2*s[0], j + 2*s[1]];
+        if (
+          V.OnBoard(i, j) &&
+          this.board[i][j] == V.EMPTY &&
+          this.board[i - s[0]][j - s[1]] != V.EMPTY &&
+          this.getColor(i - s[0], j - s[1]) == oppCol
+        ) {
+          mv.end.moreCapture = true;
+        }
+        moves.push(mv);
+      }
+    }
+    return moves;
+  }
+
+  filterValid(moves) {
+    return moves;
+  }
+
+  getCheckSquares() {
+    return [];
+  }
+
+  getCurrentScore() {
+    if (this.atLeastOneMove()) return "*";
+    return (this.turn == "w" ? "0-1" : "1-0");
+  }
+
+  play(move) {
+    V.PlayOnBoard(this.board, move);
+    if (!move.end.moreCapture) {
+      this.turn = V.GetOppCol(this.turn);
+      this.movesCount++;
+      this.captures = [];
+    }
+    else {
+      this.captures.push(
+        {
+          start: move.start,
+          end: { x: move.end.x, y: move.end.y }
+        }
+      );
+    }
+  }
+
+  undo(move) {
+    V.UndoOnBoard(this.board, move);
+    if (!move.end.moreCapture) {
+      this.turn = V.GetOppCol(this.turn);
+      this.movesCount--;
+    }
+    else this.captures.pop();
+  }
+
+  static get SEARCH_DEPTH() {
+    return 4;
+  }
+
+  getNotation(move) {
+    if (this.movesCount <= 1) return V.CoordsToSquare(move.start) + "X";
+    if (move.vanish.length == 0) return "end";
+    return V.CoordsToSquare(move.start) + "x" + V.CoordsToSquare(move.end);
+  }
+
+};
diff --git a/client/src/variants/Otage.js b/client/src/variants/Otage.js
index 6f408bb1..742c6ee1 100644
--- a/client/src/variants/Otage.js
+++ b/client/src/variants/Otage.js
@@ -646,11 +646,19 @@ export class OtageRules extends ChessRules {
       if (!m.end.released) return true;
       // Check for repetitions:
       V.PlayOnBoard(this.board, m);
-      const newState = { piece: m.end.released, position: this.getBaseFen() };
+      const newState = {
+        piece: m.end.released,
+        square: { x: m.end.x, y: m.end.y },
+        position: this.getBaseFen()
+      };
       const repet =
         this.repetitions.some(r => {
           return (
             r.piece == newState.piece &&
+            (
+              r.square.x == newState.square.x &&
+              r.square.y == newState.square.y &&
+            ) &&
             r.position == newState.position
           );
         });
@@ -724,6 +732,7 @@ export class OtageRules extends ChessRules {
       this.repetitions.push(
         {
           piece: move.end.released,
+          square: { x: move.end.x, y: move.end.y },
           position: this.getBaseFen()
         }
       );
diff --git a/client/src/variants/Pacosako.js b/client/src/variants/Pacosako.js
index d4908a5a..d219916d 100644
--- a/client/src/variants/Pacosako.js
+++ b/client/src/variants/Pacosako.js
@@ -716,11 +716,19 @@ export class PacosakoRules extends ChessRules {
       if (!m.end.released) return true;
       // Check for repetitions:
       V.PlayOnBoard(this.board, m);
-      const newState = { piece: m.end.released, position: this.getBaseFen() };
+      const newState = {
+        piece: m.end.released,
+        square: { x: m.end.x, y: m.end.y },
+        position: this.getBaseFen()
+      };
       const repet =
         this.repetitions.some(r => {
           return (
             r.piece == newState.piece &&
+            (
+              r.square.x == newState.square.x &&
+              r.square.y == newState.square.y &&
+            ) &&
             r.position == newState.position
           );
         });
@@ -801,6 +809,7 @@ export class PacosakoRules extends ChessRules {
       this.repetitions.push(
         {
           piece: move.end.released,
+          square: { x: move.end.x, y: move.end.y },
           position: this.getBaseFen()
         }
       );
diff --git a/client/src/variants/Selfabsorption.js b/client/src/variants/Selfabsorption.js
index e7d3c071..6f15d48c 100644
--- a/client/src/variants/Selfabsorption.js
+++ b/client/src/variants/Selfabsorption.js
@@ -9,10 +9,14 @@ export class SelfabsorptionRules extends AbsorptionRules {
     const p2 = this.getPiece(x2, y2);
     return (
       p1 != p2 &&
-      [V.QUEEN, V.ROOK, V.KNIGHT, V.BISHOP].includes(p1) &&
-      [V.QUEEN, V.ROOK, V.KNIGHT, V.BISHOP].includes(p2) &&
+      [V.QUEEN, V.RN, V.BN, V.ROOK, V.KNIGHT, V.BISHOP].includes(p1) &&
+      [V.QUEEN, V.RN, V.BN, V.ROOK, V.KNIGHT, V.BISHOP].includes(p2) &&
       (p1 != V.QUEEN || p2 == V.KNIGHT) &&
-      (p2 != V.QUEEN || p1 == V.KNIGHT)
+      (p2 != V.QUEEN || p1 == V.KNIGHT) &&
+      (p1 != V.RN || p2 == V.BISHOP) &&
+      (p2 != V.RN || p1 == V.BISHOP) &&
+      (p1 != V.BN || p2 == V.ROOK) &&
+      (p2 != V.BN || p1 == V.ROOK)
     );
   }
 
diff --git a/client/src/variants/Yote.js b/client/src/variants/Yote.js
new file mode 100644
index 00000000..0d81759c
--- /dev/null
+++ b/client/src/variants/Yote.js
@@ -0,0 +1,7 @@
+import { ChessRules } from "@/base_rules";
+
+export class YoteRules extends ChessRules {
+
+  // TODO
+
+};
diff --git a/server/db/populate.sql b/server/db/populate.sql
index 5322ce99..e419e902 100644
--- a/server/db/populate.sql
+++ b/server/db/populate.sql
@@ -57,16 +57,19 @@ insert or ignore into Variants (name, description) values
   ('Doublemove2', 'Double moves (v2)'),
   ('Dynamo', 'Push and pull'),
   ('Eightpieces', 'Each piece is unique'),
+  ('Emergo', 'Stacking Checkers variant'),
   ('Empire', 'Empire versus Kingdom'),
   ('Enpassant', 'Capture en passant'),
   ('Evolution', 'Faster development'),
   ('Extinction', 'Capture all of a kind'),
+  ('Fanorona', 'Malagasy Draughts'),
   ('Football', 'Score a goal'),
   ('Forward', 'Moving forward'),
   ('Freecapture', 'Capture both colors'),
   ('Fugue', 'Baroque Music'),
   ('Fullcavalry', 'Lancers everywhere'),
   ('Fusion', 'Fusion pieces (v1)'),
+  ('Gomoku', 'Align five stones'),
   ('Grand', 'Big board'),
   ('Grasshopper', 'Long jumps over pieces'),
   ('Gridolina', 'Jump the borders'),
@@ -84,6 +87,7 @@ insert or ignore into Variants (name, description) values
   ('Knightpawns', 'Knight versus pawns'),
   ('Knightrelay1', 'Move like a knight (v1)'),
   ('Knightrelay2', 'Move like a knight (v2)'),
+  ('Konane', 'Hawaiian Checkers'),
   ('Koopa', 'Stun & kick pieces'),
   ('Koth', 'King of the Hill'),
   ('Losers', 'Get strong at self-mate'),
@@ -154,4 +158,5 @@ insert or ignore into Variants (name, description) values
   ('Wildebeest', 'Balanced sliders & leapers'),
   ('Wormhole', 'Squares disappear'),
   ('Xiangqi', 'Chinese Chess'),
+  ('Yote', 'African Draughts'),
   ('Zen', 'Reverse captures');
-- 
2.44.0