From 964eda04ad6415b4ec95387ea08b63a3d0f0f9cc Mon Sep 17 00:00:00 2001
From: Benjamin Auder <benjamin.auder@somewhere>
Date: Sun, 3 May 2020 23:00:21 +0200
Subject: [PATCH] Add Swap, Switching, pawns variants

---
 client/public/images/variants/README          |   2 -
 .../{images => }/variants/Orda/Archer.png     |   0
 .../{images => }/variants/Orda/Kheshig.png    |   0
 .../{images => }/variants/Orda/Lancer.png     |   0
 .../{images => }/variants/Orda/Orda.png       |   0
 .../{images => }/variants/Orda/Yurt.png       |   0
 client/public/variants/README                 |   1 +
 client/public/variants/Swap/game.pgn          |  30 ++
 client/src/base_rules.js                      |   6 +-
 client/src/translations/en.js                 |   3 +
 client/src/translations/es.js                 |   3 +
 client/src/translations/fr.js                 |   3 +
 .../src/translations/rules/Doublemove2/en.pug |   2 +-
 .../src/translations/rules/Doublemove2/es.pug |   2 +-
 .../src/translations/rules/Doublemove2/fr.pug |   2 +-
 client/src/translations/rules/Orda/en.pug     |  10 +-
 client/src/translations/rules/Orda/es.pug     |  10 +-
 client/src/translations/rules/Orda/fr.pug     |  10 +-
 client/src/translations/rules/Pawns/en.pug    |  20 +
 client/src/translations/rules/Pawns/es.pug    |  21 ++
 client/src/translations/rules/Pawns/fr.pug    |  21 ++
 client/src/translations/rules/Swap/en.pug     |  35 ++
 client/src/translations/rules/Swap/es.pug     |  40 ++
 client/src/translations/rules/Swap/fr.pug     |  37 ++
 .../src/translations/rules/Switching/en.pug   |  32 ++
 .../src/translations/rules/Switching/es.pug   |  35 ++
 .../src/translations/rules/Switching/fr.pug   |  34 ++
 client/src/variants/Doublemove1.js            |  18 +-
 client/src/variants/Doublemove2.js            |  14 +-
 client/src/variants/Monster.js                |  20 +-
 client/src/variants/Pawns.js                  |  39 ++
 client/src/variants/Suction.js                |   4 +-
 client/src/variants/Swap.js                   | 344 ++++++++++++++++++
 client/src/variants/Switching.js              | 125 +++++++
 client/src/views/Game.vue                     |   5 +-
 server/db/populate.sql                        |   3 +
 36 files changed, 886 insertions(+), 45 deletions(-)
 delete mode 100644 client/public/images/variants/README
 rename client/public/{images => }/variants/Orda/Archer.png (100%)
 rename client/public/{images => }/variants/Orda/Kheshig.png (100%)
 rename client/public/{images => }/variants/Orda/Lancer.png (100%)
 rename client/public/{images => }/variants/Orda/Orda.png (100%)
 rename client/public/{images => }/variants/Orda/Yurt.png (100%)
 create mode 100644 client/public/variants/README
 create mode 100644 client/public/variants/Swap/game.pgn
 create mode 100644 client/src/translations/rules/Pawns/en.pug
 create mode 100644 client/src/translations/rules/Pawns/es.pug
 create mode 100644 client/src/translations/rules/Pawns/fr.pug
 create mode 100644 client/src/translations/rules/Swap/en.pug
 create mode 100644 client/src/translations/rules/Swap/es.pug
 create mode 100644 client/src/translations/rules/Swap/fr.pug
 create mode 100644 client/src/translations/rules/Switching/en.pug
 create mode 100644 client/src/translations/rules/Switching/es.pug
 create mode 100644 client/src/translations/rules/Switching/fr.pug
 create mode 100644 client/src/variants/Pawns.js
 create mode 100644 client/src/variants/Swap.js
 create mode 100644 client/src/variants/Switching.js

diff --git a/client/public/images/variants/README b/client/public/images/variants/README
deleted file mode 100644
index c6a3a75f..00000000
--- a/client/public/images/variants/README
+++ /dev/null
@@ -1,2 +0,0 @@
-Here are some binary images used for some variants.
-Currently only Orda.
diff --git a/client/public/images/variants/Orda/Archer.png b/client/public/variants/Orda/Archer.png
similarity index 100%
rename from client/public/images/variants/Orda/Archer.png
rename to client/public/variants/Orda/Archer.png
diff --git a/client/public/images/variants/Orda/Kheshig.png b/client/public/variants/Orda/Kheshig.png
similarity index 100%
rename from client/public/images/variants/Orda/Kheshig.png
rename to client/public/variants/Orda/Kheshig.png
diff --git a/client/public/images/variants/Orda/Lancer.png b/client/public/variants/Orda/Lancer.png
similarity index 100%
rename from client/public/images/variants/Orda/Lancer.png
rename to client/public/variants/Orda/Lancer.png
diff --git a/client/public/images/variants/Orda/Orda.png b/client/public/variants/Orda/Orda.png
similarity index 100%
rename from client/public/images/variants/Orda/Orda.png
rename to client/public/variants/Orda/Orda.png
diff --git a/client/public/images/variants/Orda/Yurt.png b/client/public/variants/Orda/Yurt.png
similarity index 100%
rename from client/public/images/variants/Orda/Yurt.png
rename to client/public/variants/Orda/Yurt.png
diff --git a/client/public/variants/README b/client/public/variants/README
new file mode 100644
index 00000000..4753b6e1
--- /dev/null
+++ b/client/public/variants/README
@@ -0,0 +1 @@
+Here are some extra files used for some variants.
diff --git a/client/public/variants/Swap/game.pgn b/client/public/variants/Swap/game.pgn
new file mode 100644
index 00000000..cfacdd52
--- /dev/null
+++ b/client/public/variants/Swap/game.pgn
@@ -0,0 +1,30 @@
+[Site "vchess.club"]
+[Variant "Swap"]
+[Date "2004-01-01"]
+[White "Joao Neto"]
+[Black "Bill Taylor"]
+[Fen "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w 0 ahah - -"]
+[Result "0-1"]
+
+1.d4 Nc6,Bg7 2.Nf3,Bg2 Bxd4,Ba7 3.Nxd4,Ne2 f6,Qd7 4.Bxc6,Ra2 Qxc6,Qh1 5.Rxc7,Rc2 dxc7,Qh2 6.Rxa7,Ra1 Qxh1,Rh7 7.Bf4,Nd4 Bh3,Re7 8.Kd2,Nd2 Qxf1,Qf2 9.Nb5,Qd2 Qxf1,0-0 10.Be3,e3 Qxe2,Qb5
+
+1.w d2.d4 wpd4/wpd2
+1.b b8.c6 bnc6/bnb8,f8.g7 bbg7.bpf8/bbf8.bpg7
+2.w g1.f3 wnf3/wng1,f1.g2 wbg2.wpf1/wbf1.wpg2
+2.b g7.d4 bbd4/bbg7.wpd4,d4.a7 bba7.bpd4/bbd4.bpa7
+3.w f3.d4 wnd4/wnf3.bpd4,d4.e2 wne2.wpd4/wnd4.wpe2
+3.b f7.f6 bpf6/bpf7,d8.d7 bqd7.bpd8/bqd8.bpd7
+4.w g2.c6 wbc6/wbg2.bnc6,a1.a2 wra2.wpa1/wra1.wpa2
+4.b d7.c6 bqc6/bqd7.wbc6,c6.h1 bqh1.wrc6/bqc6.wrh1
+5.w c6.c7 wrc7/wrc6.bpc7,c7.c2 wrc2.wpc7/wrc7.wpc2
+5.b d8.c7 bpc7/bpd8.wpc7,h1.h2 bqh2.wph1/bqh1.wph2
+6.w a2.a7 wra7/wra2.bba7,a7.a1 wra1.wpa7/wra7.wpa1
+6.b h2.h1 bqh1/bqh2.wph1,h8.h7 brh7.bph8/brh8.bph7
+7.w c1.f4 wbf4/wbc1,e2.d4 wnd4.wpe2/wne2.wpd4
+7.b c8.h3 bbh3/bbc8,h7.e7 bre7.bph7/brh7.bpe7
+8.w e1.d2 wkd2/wke1,b1.d2 wnd2.wkb1/wnb1.wkd2
+8.b h1.f1 bqf1/bqh1.wpf1,f1.f2 bqf2.wpf1/bqf1.wpf2
+9.w d4.b5 wnb5/wnd4,d1.d2 wqd2.wnd1/wqd1.wnd2
+9.b f2.f1 bqf1/bqf2.wpf1,e8.f8 bkf8.bpe8/bke8.bpf8
+10.w f4.e3 wbe3/wbf4,e2.e3 wpe3.wbe2/wpe2.wbe3
+10.b f1.e2 bqe2/bqf1.wbe2,e2.b5 bqb5.wne2/bqe2.wnb5
diff --git a/client/src/base_rules.js b/client/src/base_rules.js
index e0353e8f..e70b9de8 100644
--- a/client/src/base_rules.js
+++ b/client/src/base_rules.js
@@ -1202,10 +1202,8 @@ export const ChessRules = class ChessRules {
       piece = move.appear[0].p;
 
     // Update king position + flags
-    if (piece == V.KING && move.appear.length > 0) {
-      this.kingPos[c][0] = move.appear[0].x;
-      this.kingPos[c][1] = move.appear[0].y;
-    }
+    if (piece == V.KING && move.appear.length > 0)
+      this.kingPos[c] = [move.appear[0].x, move.appear[0].y];
     if (V.HasCastle) this.updateCastleFlags(move, piece);
   }
 
diff --git a/client/src/translations/en.js b/client/src/translations/en.js
index 419cfc75..f853e52e 100644
--- a/client/src/translations/en.js
+++ b/client/src/translations/en.js
@@ -179,10 +179,12 @@ export const translations = {
   "Change colors": "Change colors",
   "Convert & support (v1)": "Convert & support (v1)",
   "Convert & support (v2)": "Convert & support (v2)",
+  "Dangerous captures": "Dangerous captures",
   "Dangerous collisions": "Dangerous collisions",
   "Double moves (v1)": "Double moves (v1)",
   "Double moves (v2)": "Double moves (v2)",
   "Each piece is unique": "Each piece is unique",
+  "Exchange pieces' positions": "Exchange pieces' positions",
   "Exotic captures": "Exotic captures",
   "Explosive captures": "Explosive captures",
   "Four new pieces": "Four new pieces",
@@ -226,6 +228,7 @@ export const translations = {
   "Protect your pawns": "Protect your pawns",
   "Push and pull": "Push and pull",
   "Queen disguised as a pawn": "Queen disguised as a pawn",
+  "Reach the last rank": "Reach the last rank",
   "Reposition pieces": "Reposition pieces",
   "Reuse pieces": "Reuse pieces",
   "Reverse captures": "Reverse captures",
diff --git a/client/src/translations/es.js b/client/src/translations/es.js
index 9aad9dbc..3243f92a 100644
--- a/client/src/translations/es.js
+++ b/client/src/translations/es.js
@@ -179,10 +179,12 @@ export const translations = {
   "Change colors": "Cambiar colores",
   "Convert & support (v1)": "Convertir & apoyar (v1)",
   "Convert & support (v2)": "Convertir & apoyar (v2)",
+  "Dangerous captures": "Capturas peligrosas",
   "Dangerous collisions": "Colisiones peligrosas",
   "Double moves (v1)": "Jugadas doble (v1)",
   "Double moves (v2)": "Jugadas doble (v2)",
   "Each piece is unique": "Cada pieza es única",
+  "Exchange pieces' positions": "Intercambiar posiciones de piezas",
   "Exotic captures": "Capturas exóticas",
   "Explosive captures": "Capturas explosivas",
   "Four new pieces": "Quatro nuevas piezas",
@@ -226,6 +228,7 @@ export const translations = {
   "Protect your pawns": "Protege tus peones",
   "Push and pull": "Empujar y tirar",
   "Queen disguised as a pawn": "Reina disfrazada de peón",
+  "Reach the last rank": "Llegar a la última fila",
   "Reposition pieces": "Reposicionar las piezas",
   "Reuse pieces": "Reutilizar piezas",
   "Reverse captures": "Capturas invertidas",
diff --git a/client/src/translations/fr.js b/client/src/translations/fr.js
index aa885df2..483f34c3 100644
--- a/client/src/translations/fr.js
+++ b/client/src/translations/fr.js
@@ -179,10 +179,12 @@ export const translations = {
   "Change colors": "Changer les couleurs",
   "Convert & support (v1)": "Convertir & soutenir (v1)",
   "Convert & support (v2)": "Convertir & soutenir (v2)",
+  "Dangerous captures": "Captures dangeureuses",
   "Dangerous collisions": "Collisions dangeureuses",
   "Double moves (v1)": "Coups doubles (v1)",
   "Double moves (v2)": "Coups doubles (v2)",
   "Each piece is unique": "Chaque pièce est unique",
+  "Exchange pieces' positions": "Échangez les positions des pièces",
   "Exotic captures": "Captures exotiques",
   "Explosive captures": "Captures explosives",
   "Four new pieces": "Quatre nouvelles pièces",
@@ -226,6 +228,7 @@ export const translations = {
   "Protect your pawns": "Protégez vos pions",
   "Push and pull": "Pousser et tirer",
   "Queen disguised as a pawn": "Reine déguisée en pion",
+  "Reach the last rank": "Atteignez la dernière rangée",
   "Reposition pieces": "Replacer les pièces",
   "Reuse pieces": "Réutiliser les pièces",
   "Reverse captures": "Captures inversées",
diff --git a/client/src/translations/rules/Doublemove2/en.pug b/client/src/translations/rules/Doublemove2/en.pug
index ca24491f..a350ebae 100644
--- a/client/src/translations/rules/Doublemove2/en.pug
+++ b/client/src/translations/rules/Doublemove2/en.pug
@@ -8,7 +8,7 @@ p.
 p.
   At the very first move of the game, white make only one move - as usual.
   However, after that and for all the game each side must play twice at
-  every turn (even if the first move captures the enemy king).
+  every turn (except if the first move captures the enemy king).
 
 figure.diagram-container
   .diagram.diag12
diff --git a/client/src/translations/rules/Doublemove2/es.pug b/client/src/translations/rules/Doublemove2/es.pug
index 179025b3..8fcc4d07 100644
--- a/client/src/translations/rules/Doublemove2/es.pug
+++ b/client/src/translations/rules/Doublemove2/es.pug
@@ -9,7 +9,7 @@ p.
   Al comienzo del juego, las blancas solo juegan un movimiento, como
   en general. Sin embargo, después de eso y por el resto del juego,
   cada lado debe jugar dos jugadas cada turno
-  (incluso si la primera captura al rey contrario).
+  (a menos que la primera captura al rey contrario).
 
 figure.diagram-container
   .diagram.diag12
diff --git a/client/src/translations/rules/Doublemove2/fr.pug b/client/src/translations/rules/Doublemove2/fr.pug
index 0dff04f9..213a8c4d 100644
--- a/client/src/translations/rules/Doublemove2/fr.pug
+++ b/client/src/translations/rules/Doublemove2/fr.pug
@@ -9,7 +9,7 @@ p.
   Au tout début de la partie les blancs ne jouent qu'un seul coup, comme
   d'habitude. Cependant, après cela et ce pour tout le reste de la partie
   chaque camp doit jouer deux coups à chaque tour
-  (même si le premier capture le roi adverse).
+  (sauf si le premier capture le roi adverse).
 
 figure.diagram-container
   .diagram.diag12
diff --git a/client/src/translations/rules/Orda/en.pug b/client/src/translations/rules/Orda/en.pug
index 244baf11..494db502 100644
--- a/client/src/translations/rules/Orda/en.pug
+++ b/client/src/translations/rules/Orda/en.pug
@@ -2,7 +2,7 @@ p.boxed
   | The player in second has a different set of pieces,
   | moving roughly like "augmented knights".
 
-img.img-center(src="/images/variants/Orda/Orda.png")
+img.img-center(src="/variants/Orda/Orda.png")
 
 p
   | Orda Chess is a chess variant designed in 2020 by Couch Tomato. 
@@ -97,7 +97,7 @@ p.
 
 h4 Yurt (Y)
 
-img.img-center(src="/images/variants/Orda/Yurt.png")
+img.img-center(src="/variants/Orda/Yurt.png")
 
 p.
   The Yurt moves and captures one space diagonally or one space forward.
@@ -117,7 +117,7 @@ p.
 
 h4 Kheshig (H)
 
-img.img-center(src="/images/variants/Orda/Kheshig.png")
+img.img-center(src="/variants/Orda/Kheshig.png")
 
 p.
   The Kheshig is a hybrid piece that moves and captures as a knight and
@@ -135,7 +135,7 @@ p.
 
 h4 Horse Archer (A)
 
-img.img-center(src="/images/variants/Orda/Archer.png")
+img.img-center(src="/variants/Orda/Archer.png")
 
 p.
   The Horse Archer, or simply abbreviated Archer, is a unique "pseudohybrid"
@@ -152,7 +152,7 @@ p.
 
 h4 Lancer (L)
 
-img.img-center(src="/images/variants/Orda/Lancer.png")
+img.img-center(src="/variants/Orda/Lancer.png")
 
 p.
   The Lancer is a unique "pseudohybrid" piece that moves and attacks
diff --git a/client/src/translations/rules/Orda/es.pug b/client/src/translations/rules/Orda/es.pug
index c62594d6..b90d7947 100644
--- a/client/src/translations/rules/Orda/es.pug
+++ b/client/src/translations/rules/Orda/es.pug
@@ -2,7 +2,7 @@ p.boxed
   | El segundo jugador tiene un conjunto diferente de piezas,
   | moviéndose aproximadamente como "caballos aumentados".
 
-img.img-center(src="/images/variantes/Orda/Orda.png")
+img.img-center(src="/variants/Orda/Orda.png")
 
 p
   | Orda Chess es una variante inventada en 2020 por Couch Tomato 
@@ -99,7 +99,7 @@ p.
 
 h4 Yurta (Y)
 
-img.img-center(src="/images/variantes/Orda/Yurt.png")
+img.img-center(src="/variants/Orda/Yurt.png")
 
 p.
   La yurta se mueve y captura una casilla diagonal o un cuadrado hacia
@@ -119,7 +119,7 @@ p.
 
 h4 Kheshig (H)
 
-img.img-center(src="/images/variantes/Orda/Kheshig.png")
+img.img-center(src="/variants/Orda/Kheshig.png")
 
 p.
   El kheshig es una pieza híbrida que se mueve y captura como un rey y un
@@ -138,7 +138,7 @@ p.
 
 h4 Arquero a caballo (A)
 
-img.img-center(src="/images/variantes/Orda/Archer.png")
+img.img-center(src="/variants/Orda/Archer.png")
 
 p.
   El arquero (a caballo) es una pieza única "pseudo-híbrida" que se mueve
@@ -155,7 +155,7 @@ p.
 
 h4 Lancero (L)
 
-img.img-center(src="/images/variantes/Orda/Lancer.png")
+img.img-center(src="/variants/Orda/Lancer.png")
 
 p.
   El lancero es una pieza única "pseudo-híbrida" que se mueve y ataca
diff --git a/client/src/translations/rules/Orda/fr.pug b/client/src/translations/rules/Orda/fr.pug
index 7b10a474..ba7675ad 100644
--- a/client/src/translations/rules/Orda/fr.pug
+++ b/client/src/translations/rules/Orda/fr.pug
@@ -2,7 +2,7 @@ p.boxed
   | Le joueur en second dispose d'un jeu de pièces différent,
   | se déplaçant en gros comme des "cavaliers augmentés".
 
-img.img-center(src="/images/variants/Orda/Orda.png")
+img.img-center(src="/variants/Orda/Orda.png")
 
 p
   | Orda Chess est une variante inventée en 2020 par Couch Tomato 
@@ -101,7 +101,7 @@ p.
 
 h4 Yourte (Y)
 
-img.img-center(src="/images/variants/Orda/Yurt.png")
+img.img-center(src="/variants/Orda/Yurt.png")
 
 p.
   La yourte se déplace et capture d'une case en diagonale ou une case vers
@@ -121,7 +121,7 @@ p.
 
 h4 Kheshig (H)
 
-img.img-center(src="/images/variants/Orda/Kheshig.png")
+img.img-center(src="/variants/Orda/Kheshig.png")
 
 p.
   Le kheshig est une pièce hybride qui bouge et capture comme roi et cavalier
@@ -140,7 +140,7 @@ p.
 
 h4 Archer à cheval (A)
 
-img.img-center(src="/images/variants/Orda/Archer.png")
+img.img-center(src="/variants/Orda/Archer.png")
 
 p.
   L'archer (à cheval) est une pièce unique "pseudo-hybride" qui se déplace
@@ -157,7 +157,7 @@ p.
 
 h4 Lancier (L)
 
-img.img-center(src="/images/variants/Orda/Lancer.png")
+img.img-center(src="/variants/Orda/Lancer.png")
 
 p.
   Le lancier est une pièce unique "pseudo-hybride" qui se déplace et attaque
diff --git a/client/src/translations/rules/Pawns/en.pug b/client/src/translations/rules/Pawns/en.pug
new file mode 100644
index 00000000..ab79370b
--- /dev/null
+++ b/client/src/translations/rules/Pawns/en.pug
@@ -0,0 +1,20 @@
+p.boxed
+  | Only pawns. Standard rules. The first to promote wins.
+
+p.
+  This game may appear simple, but since you cannot play any waiting move,
+  beware the Zugzwang ;)
+
+figure.diagram-container
+  .diagram
+    | fen:8/5p1p/2p1p1p1/1p1p2P1/p2P1P1P/P1P1P3/1P6/8:
+  figcaption The side to play will lose.
+
+p.
+  Also, space is important. On the following diagram any move of a white pawn
+  which is not on the edge wins.
+
+figure.diagram-container
+  .diagram
+    | fen:8/8/pppppppp/8/PPPPPPPP/8/8/8:
+  figcaption Any central advance wins.
diff --git a/client/src/translations/rules/Pawns/es.pug b/client/src/translations/rules/Pawns/es.pug
new file mode 100644
index 00000000..d3293a31
--- /dev/null
+++ b/client/src/translations/rules/Pawns/es.pug
@@ -0,0 +1,21 @@
+p.boxed
+  | Solo peones. Regla estándar.
+  | El primero que realiza una promoción gana.
+
+p.
+  Este juego puede sonar simple, pero como no tienes movimientos de espera,
+  ten cuidado con Zugzwang;)
+
+figure.diagram-container
+  .diagram
+    | fen:8/5p1p/2p1p1p1/1p1p2P1/p2P1P1P/P1P1P3/1P6/8:
+  figcaption El campamento al turno perderá.
+
+p.
+  El espacio también es importante. En el siguiente diagrama cualquier
+  movimiento de peón blanco no en el borde gana.
+
+figure.diagram-container
+  .diagram
+    | fen:8/8/pppppppp/8/PPPPPPPP/8/8/8:
+  figcaption Cualquier avance central gana.
diff --git a/client/src/translations/rules/Pawns/fr.pug b/client/src/translations/rules/Pawns/fr.pug
new file mode 100644
index 00000000..569a1245
--- /dev/null
+++ b/client/src/translations/rules/Pawns/fr.pug
@@ -0,0 +1,21 @@
+p.boxed
+  | Seulement des pions. Règle standard.
+  | Le premier à effectuer une promotion gagne.
+
+p.
+  Ce jeu peut paraître simple, mais puisque vous ne disposez d'aucun coup
+  d'attente, faites attention au Zugzwang ;)
+
+figure.diagram-container
+  .diagram
+    | fen:8/5p1p/2p1p1p1/1p1p2P1/p2P1P1P/P1P1P3/1P6/8:
+  figcaption Le camp au trait perdra.
+
+p.
+  L'espace est important aussi. Sur le diagramme suivant n'importe quel coup
+  de pion blanc ne se trouvant pas sur le bord gagne.
+
+figure.diagram-container
+  .diagram
+    | fen:8/8/pppppppp/8/PPPPPPPP/8/8/8:
+  figcaption N'importe quelle avancée centrale gagne.
diff --git a/client/src/translations/rules/Swap/en.pug b/client/src/translations/rules/Swap/en.pug
new file mode 100644
index 00000000..149658bb
--- /dev/null
+++ b/client/src/translations/rules/Swap/en.pug
@@ -0,0 +1,35 @@
+p.boxed.
+  At each turn play first a normal move, and then exchange two pieces'
+  positions by making a capture.
+
+ol
+  li.
+    Each turn is made of a standard move and a swap move (see 2).
+    At the first white move, only the regular move is done.
+  li.
+    A swap move is a two piece swap of positions. One piece is a player's
+    piece, and the other is on a square where the first piece could move
+    (if the square was empty) or take (if the square had an opponent piece).
+  li If a player cannot make the regular move or the swap, he loses.
+
+p.
+  On the diagram the queen first takes the g7 pawn, and then swap its
+  position with the g8 knight: checkmate.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:r1pqk1n1/pn1pppp1/2p5/8/8/2PPPK2/BP4Q1/N1R2P2:
+  .diagram.diag22
+    | fen:r1pqk1Q1/pn1pppn1/2p5/8/8/2PPPK2/BP6/N1R2P2:
+  figcaption Before and after 1.Qxg7,Sg7g8#
+
+h3 Source
+
+p
+  a(href="https://www.chessvariants.com/diffmove.dir/balancedswap.html")
+    | Balanced Swap Chess
+  | &nbsp;on chessvariants.com. Here is an 
+  a(href="/variants/Swap/game.pgn") example game
+  | &nbsp;played by the variant's creator.
+
+p Inventor: Joao P. Neto (1998)
diff --git a/client/src/translations/rules/Swap/es.pug b/client/src/translations/rules/Swap/es.pug
new file mode 100644
index 00000000..2f822c03
--- /dev/null
+++ b/client/src/translations/rules/Swap/es.pug
@@ -0,0 +1,40 @@
+p.boxed.
+  En cada turno, primero haga un movimiento normal, luego cambie las posiciones de
+  dos piezas por capturar.
+
+ol
+  li.
+    Cada ronda consta de un movimiento estándar seguido de un movimiento de
+    intercambio (ver 2). Durante el primer movimiento blanco, solo se realiza
+    el movimiento normal.
+  li.
+    Un intercambio es un intercambio de posiciones de dos piezas. Una de las
+    dos es la pieza del jugador, y la otra está en una casilla donde la
+    primera podría ir (si la casilla estaba vacía) o capturar (si la
+    casilla estaba ocupada por el oponente).
+  li.
+    Si un jugador no puede hacer un movimiento estándar o un movimiento de
+    intercambio, pierde.
+
+p.
+  En el siguiente diagrama, la dama primero toma el peón g7, luego
+  intercambia su posición con el caballo g8: jaque mate.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:r1pqk1n1/pn1pppp1/2p5/8/8/2PPPK2/BP4Q1/N1R2P2:
+  .diagram.diag22
+    | fen:r1pqk1Q1/pn1pppn1/2p5/8/8/2PPPK2/BP6/N1R2P2:
+  figcaption Antes y después 1.Qxg7,Sg7g8#
+
+h3 Fuente
+
+p
+  | La 
+  a(href="https://www.chessvariants.com/diffmove.dir/balancedswap.html")
+    | variante Balanced Swap
+  | &nbsp;en chessvariants.com. Aquí hay una 
+  a(href="/variants/Swap/game.pgn") partida ejemplo
+  | &nbsp;jugada por el creador de la variante.
+
+p Inventor: Joao P. Neto (1998)
diff --git a/client/src/translations/rules/Swap/fr.pug b/client/src/translations/rules/Swap/fr.pug
new file mode 100644
index 00000000..56960659
--- /dev/null
+++ b/client/src/translations/rules/Swap/fr.pug
@@ -0,0 +1,37 @@
+p.boxed.
+  À chaque tour jouez d'abord un coup normal, puis échangez les positions de
+  deux pièces en effectuant une capture.
+
+ol
+  li.
+    Chaque tour consiste en un coup standard suivi d'un coup d'échange
+    (voir 2). Lors du premier coup blanc, seul le coup normal est joué.
+  li.
+    Un coup d'échange est un échange des positions de deux pièces. Une des
+    deux est une pièce du joueur, et l'autre est sur une case où la première
+    pourrait aller (si la case était vide) ou capturer (si la case était
+    occupée par l'adversaire).
+  li Si un joueur ne peut effectuer de coup standard ou d'échange, il perd.
+
+p.
+  Sur le diagramme suivant la dame prend d'abord le pion g7, puis échange sa
+  position avec le cavalier g8 : échec et mat.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:r1pqk1n1/pn1pppp1/2p5/8/8/2PPPK2/BP4Q1/N1R2P2:
+  .diagram.diag22
+    | fen:r1pqk1Q1/pn1pppn1/2p5/8/8/2PPPK2/BP6/N1R2P2:
+  figcaption Avant et après 1.Qxg7,Sg7g8#
+
+h3 Source
+
+p
+  | La 
+  a(href="https://www.chessvariants.com/diffmove.dir/balancedswap.html")
+    | variante Balanced Swap
+  | &nbsp;sur chessvariants.com. Voici une 
+  a(href="/variants/Swap/game.pgn") partie exemple
+  | &nbsp;jouée par le créateur de la variante.
+
+p Inventeur : Joao P. Neto (1998)
diff --git a/client/src/translations/rules/Switching/en.pug b/client/src/translations/rules/Switching/en.pug
new file mode 100644
index 00000000..b51aa86a
--- /dev/null
+++ b/client/src/translations/rules/Switching/en.pug
@@ -0,0 +1,32 @@
+p.boxed
+  | In addition to standard moves, a piece can be exchanged
+  | with an adjacent friendly unit.
+
+p.
+  Instead of a normal move, a piece may be exchanged with an adjacent
+  friendly one. Switching can move pawns until rank 1 or final rank.
+  In the first case a 2-squares jump is possible, and in the second a
+  promotion occurs.
+  Switching must involves two different units.
+  Switching while the king is under check is not allowed.
+
+p.
+  Notation: a switch move is marked by the capital letter 'S' followed by
+  the exchanged squares. Example in diagram: Sb8c8.
+
+figure.diagram-container
+  .diagram
+    | fen:1RB3k1/5ppp/8/8/8/8/8/K7:
+  figcaption White can mate in 1 by switching b8-c8
+
+p.
+  Note: to trigger a switch move involving the king, select the non-royal
+  piece first (to not interfere with castling).
+
+h3 Source
+
+p
+  a(href="https://www.chessvariants.com/diffmove.dir/switching.html") Switching chess 
+    | on chessvariants.com.
+
+p Inventor: Tony Quintanilla (2004)
diff --git a/client/src/translations/rules/Switching/es.pug b/client/src/translations/rules/Switching/es.pug
new file mode 100644
index 00000000..710004e6
--- /dev/null
+++ b/client/src/translations/rules/Switching/es.pug
@@ -0,0 +1,35 @@
+p.boxed
+  | Además de los movimientos habituales,
+  | se puede cambiar una pieza con una vecina.
+
+p.
+  En lugar de hacer un movimiento normal, se puede cambiar una pieza con una
+  pieza amiga contigua.
+  El intercambio puede mover peones en la primera o última fila:
+  en el primer caso es posible un salto de dos casillas, y en el
+  segundo se lleva a cabo una promoción.
+  El intercambio debe involucrar dos piezas de diferentes naturalezas.
+  El intercambio está prohibido cuando el rey está en jaque.
+
+p.
+  Notación: un movimiento de cambio se indica con la letra mayúscula 'S'
+  seguido por las casillas intercambiadas. Por ejemplo en el diagrama: Sb8c8.
+
+figure.diagram-container
+  .diagram
+    | fen:1RB3k1/5ppp/8/8/8/8/8/K7:
+  figcaption Las blancas pueden dar jaque mate en 1 por intercambio b8-c8.
+
+p.
+  Nota: para activar un intercambio que involucre al rey, seleccione la pieza
+  no real primero (para no interferir con el enroque).
+
+h3 Fuente
+
+p
+  | La 
+  a(href="https://www.chessvariants.com/diffmove.dir/switching.html")
+    | variante Switching
+  | &nbsp;en chessvariants.com.
+
+p Inventor: Tony Quintanilla (2004)
diff --git a/client/src/translations/rules/Switching/fr.pug b/client/src/translations/rules/Switching/fr.pug
new file mode 100644
index 00000000..e6c7a444
--- /dev/null
+++ b/client/src/translations/rules/Switching/fr.pug
@@ -0,0 +1,34 @@
+p.boxed
+  | En plus des coups habituels, une pièce peut être échangée avec une voisine.
+
+p.
+  Au lieu d'effectuer un coup normal, une pièce peut être échangée avec une
+  pièce amie adjacente.
+  L'échange peut déplacer des pions sur la première ou dernière rangée :
+  dans le premier cas un saut de deux cases est alors possible, et dans le
+  second une promotion s'effectue.
+  L'échange doit impliquer deux pièces de différentes natures.
+  L'échange est interdit quand le roi est en échec.
+
+p.
+  Notation : un coup d'échange est indiqué par la lettre majuscule 'S'
+  suivie des cases échangées. Par exemple sur le diagramme : Sb8c8.
+
+figure.diagram-container
+  .diagram
+    | fen:1RB3k1/5ppp/8/8/8/8/8/K7:
+  figcaption Les blancs peuvent mater en 1 par l'échange b8-c8
+
+p.
+  Note : pour déclencher un échange impliquant le roi, sélectionnez la pièce
+  non royale d'abord (pour ne pas interférer avec le roque).
+
+h3 Source
+
+p
+  | La 
+  a(href="https://www.chessvariants.com/diffmove.dir/switching.html")
+    | variante Switching
+  | &nbsp;sur chessvariants.com.
+
+p Inventeur : Tony Quintanilla (2004)
diff --git a/client/src/variants/Doublemove1.js b/client/src/variants/Doublemove1.js
index c30ad0cf..f51dd7ac 100644
--- a/client/src/variants/Doublemove1.js
+++ b/client/src/variants/Doublemove1.js
@@ -79,7 +79,7 @@ export class Doublemove1Rules extends ChessRules {
 
   play(move) {
     move.flags = JSON.stringify(this.aggregateFlags());
-    move.turn = this.turn + this.subTurn;
+    move.turn = [this.turn, this.subTurn];
     V.PlayOnBoard(this.board, move);
     const epSq = this.getEpSquare(move);
     if (this.movesCount == 0) {
@@ -94,12 +94,14 @@ export class Doublemove1Rules extends ChessRules {
       this.epSquares.push([epSq]);
       move.checkOnSubturn1 = true;
       this.movesCount++;
-    } else {
+    }
+    else {
       if (this.subTurn == 2) {
         this.turn = V.GetOppCol(this.turn);
         let lastEpsq = this.epSquares[this.epSquares.length - 1];
         lastEpsq.push(epSq);
-      } else {
+      }
+      else {
         this.epSquares.push([epSq]);
         this.movesCount++;
       }
@@ -109,7 +111,7 @@ export class Doublemove1Rules extends ChessRules {
   }
 
   postPlay(move) {
-    const c = move.turn.charAt(0);
+    const c = move.turn[0];
     const piece = move.vanish[0].p;
     const firstRank = c == "w" ? V.size.x - 1 : 0;
 
@@ -127,7 +129,8 @@ export class Doublemove1Rules extends ChessRules {
     ) {
       const flagIdx = (move.start.y == this.castleFlags[c][0] ? 0 : 1);
       this.castleFlags[c][flagIdx] = V.size.y;
-    } else if (
+    }
+    else if (
       move.end.x == oppFirstRank && //we took opponent rook?
       this.castleFlags[oppCol].includes(move.end.y)
     ) {
@@ -144,13 +147,14 @@ export class Doublemove1Rules extends ChessRules {
       this.epSquares.pop();
       // Moves counter was just incremented:
       this.movesCount--;
-    } else {
+    }
+    else {
       // Undo the second half of a move
       let lastEpsq = this.epSquares[this.epSquares.length - 1];
       lastEpsq.pop();
     }
     this.turn = move.turn[0];
-    this.subTurn = parseInt(move.turn[1]);
+    this.subTurn = move.turn[1];
     super.postUndo(move);
   }
 
diff --git a/client/src/variants/Doublemove2.js b/client/src/variants/Doublemove2.js
index 7f3f6cec..4801d367 100644
--- a/client/src/variants/Doublemove2.js
+++ b/client/src/variants/Doublemove2.js
@@ -110,7 +110,13 @@ export class Doublemove2Rules extends ChessRules {
     else {
       this.epSquares.push([epSq]);
       this.movesCount++;
-      if (this.movesCount == 1) this.turn = "b";
+      if (
+        this.movesCount == 1 ||
+        // King is captured at subTurn 1?
+        (move.vanish.length == 2 && move.vanish[1].p == V.KING)
+      ) {
+        this.turn = "b";
+      }
     }
     if (this.movesCount > 1) this.subTurn = 3 - this.subTurn;
     this.postPlay(move);
@@ -127,9 +133,11 @@ export class Doublemove2Rules extends ChessRules {
       return;
     }
     const oppCol = V.GetOppCol(c);
-    if (move.vanish.length == 2 && move.vanish[1].p == V.KING)
+    if (move.vanish.length == 2 && move.vanish[1].p == V.KING) {
       // Opponent's king is captured, game over
       this.kingPos[oppCol] = [-1, -1];
+      move.captureKing = true; //for undo
+    }
     const oppFirstRank = V.size.x - 1 - firstRank;
     if (
       move.start.x == firstRank && //our rook moves?
@@ -150,7 +158,7 @@ export class Doublemove2Rules extends ChessRules {
   undo(move) {
     this.disaggregateFlags(JSON.parse(move.flags));
     V.UndoOnBoard(this.board, move);
-    if (this.subTurn == 2 || this.movesCount == 1) {
+    if (this.subTurn == 2 || this.movesCount == 1 || !!move.captureKing) {
       this.epSquares.pop();
       this.movesCount--;
       if (this.movesCount == 0) this.turn = "w";
diff --git a/client/src/variants/Monster.js b/client/src/variants/Monster.js
index 399bc112..6e3e10c5 100644
--- a/client/src/variants/Monster.js
+++ b/client/src/variants/Monster.js
@@ -72,9 +72,17 @@ export class MonsterRules extends ChessRules {
     V.PlayOnBoard(this.board, move);
     if (this.turn == 'w') {
       if (this.subTurn == 1) this.movesCount++;
-      else this.turn = 'b';
-      this.subTurn = 3 - this.subTurn;
-    } else {
+      if (
+        this.subTurn == 2 ||
+        // King captured
+        (move.vanish.length == 2 && move.vanish[1].p == V.KING)
+      ) {
+        this.turn = 'b';
+        this.subTurn = 1;
+      }
+      else this.subTurn = 2;
+    }
+    else {
       this.turn = 'w';
       this.movesCount++;
     }
@@ -108,9 +116,11 @@ export class MonsterRules extends ChessRules {
     const piece = move.vanish[0].p;
     if (piece == V.KING)
       this.kingPos[c] = [move.appear[0].x, move.appear[0].y];
-    if (move.vanish.length == 2 && move.vanish[1].p == V.KING)
+    if (move.vanish.length == 2 && move.vanish[1].p == V.KING) {
       // Opponent's king is captured, game over
       this.kingPos[move.vanish[1].c] = [-1, -1];
+      move.captureKing = true; //for undo
+    }
     this.updateCastleFlags(move, piece);
   }
 
@@ -125,7 +135,7 @@ export class MonsterRules extends ChessRules {
     }
     else {
       this.turn = 'w';
-      this.subTurn = 2;
+      this.subTurn = (!move.captureKing ? 2 : 1);
     }
     this.postUndo(move);
   }
diff --git a/client/src/variants/Pawns.js b/client/src/variants/Pawns.js
new file mode 100644
index 00000000..83ccca70
--- /dev/null
+++ b/client/src/variants/Pawns.js
@@ -0,0 +1,39 @@
+import { ChessRules } from "@/base_rules";
+
+export class PawnsRules extends ChessRules {
+  static get PawnSpecs() {
+    return Object.assign(
+      {},
+      ChessRules.PawnSpecs,
+      // The promotion piece doesn't matter, the game is won
+      { promotions: [V.QUEEN] }
+    );
+  }
+
+  static get HasFlags() {
+    return false;
+  }
+
+  static GenRandInitFen() {
+    return "8/pppppppp/8/8/8/8/PPPPPPPP/8 w 0 -";
+  }
+
+  filterValid(moves) {
+    return moves;
+  }
+
+  getCheckSquares() {
+    return [];
+  }
+
+  getCurrentScore() {
+    const oppCol = V.GetOppCol(this.turn);
+    if (this.board.some(b =>
+      b.some(cell => cell[0] == oppCol && cell[1] != V.PAWN))
+    ) {
+      return (oppCol == 'w' ? "1-0" : "0-1");
+    }
+    if (!this.atLeastOneMove()) return "1/2";
+    return "*";
+  }
+};
diff --git a/client/src/variants/Suction.js b/client/src/variants/Suction.js
index c3277e9c..7fff939e 100644
--- a/client/src/variants/Suction.js
+++ b/client/src/variants/Suction.js
@@ -1,4 +1,5 @@
 import { ChessRules, PiPo, Move } from "@/base_rules";
+import { SuicideRules } from "@/variants/Suicide";
 
 export class SuctionRules extends ChessRules {
   static get PawnSpecs() {
@@ -141,7 +142,6 @@ export class SuctionRules extends ChessRules {
 
   filterValid(moves) {
     if (moves.length == 0) return [];
-    const color = this.turn;
     return moves.filter(m => {
       const L = this.cmoves.length; //at least 1: init from FEN
       return !this.oppositeMoves(this.cmoves[L - 1], m);
@@ -150,7 +150,7 @@ export class SuctionRules extends ChessRules {
 
   static GenRandInitFen(randomness) {
     // Add empty cmove:
-    return ChessRules.GenRandInitFen(randomness).slice(0, -6) + "- -";
+    return SuicideRules.GenRandInitFen(randomness) + " -";
   }
 
   getCmoveFen() {
diff --git a/client/src/variants/Swap.js b/client/src/variants/Swap.js
new file mode 100644
index 00000000..bf4e7871
--- /dev/null
+++ b/client/src/variants/Swap.js
@@ -0,0 +1,344 @@
+import { ChessRules, PiPo } from "@/base_rules";
+import { randInt } from "@/utils/alea";
+
+export class SwapRules extends ChessRules {
+  setOtherVariables(fen) {
+    super.setOtherVariables(fen);
+    // Local stack of swaps
+    this.swaps = [];
+    const smove = V.ParseFen(fen).smove;
+    if (smove == "-") this.swaps.push(null);
+    else {
+      this.swaps.push({
+        start: ChessRules.SquareToCoords(smove.substr(0, 2)),
+        end: ChessRules.SquareToCoords(smove.substr(2))
+      });
+    }
+    this.subTurn = 1;
+  }
+
+  static ParseFen(fen) {
+    return Object.assign(
+      ChessRules.ParseFen(fen),
+      { smove: fen.split(" ")[5] }
+    );
+  }
+
+  static IsGoodFen(fen) {
+    if (!ChessRules.IsGoodFen(fen)) return false;
+    const fenParts = fen.split(" ");
+    if (fenParts.length != 6) return false;
+    if (fenParts[5] != "-" && !fenParts[5].match(/^([a-h][1-8]){2}$/))
+      return false;
+    return true;
+  }
+
+  getPPpath(m) {
+    if (m.vanish.length == 1) return super.getPPpath(m);
+    // Swap promotion:
+    return m.appear[1].c + m.appear[1].p;
+  }
+
+  getSwapMoves([x1, y1], [x2, y2]) {
+    let move = super.getBasicMove([x1, y1], [x2, y2]);
+    move.appear.push(
+      new PiPo({
+        x: x1,
+        y: y1,
+        c: this.getColor(x2, y2),
+        p: this.getPiece(x2, y2)
+      })
+    );
+    const lastRank = (move.appear[1].c == 'w' ? 0 : 7);
+    if (move.appear[1].p == V.PAWN && move.appear[1].x == lastRank) {
+      // Promotion:
+      move.appear[1].p = V.ROOK;
+      let moves = [move];
+      [V.KNIGHT, V.BISHOP, V.QUEEN].forEach(p => {
+        let m = JSON.parse(JSON.stringify(move));
+        m.appear[1].p = p;
+        moves.push(m);
+      });
+      return moves;
+    }
+    return [move];
+  }
+
+  getPotentialMovesFrom([x, y]) {
+    if (this.subTurn == 1) return super.getPotentialMovesFrom([x, y]);
+    // SubTurn == 2: only swaps
+    let moves = [];
+    const color = this.turn;
+    const piece = this.getPiece(x, y);
+    const addSmoves = (i, j) => {
+      if (this.getPiece(i, j) != piece)
+        Array.prototype.push.apply(moves, this.getSwapMoves([x, y], [i, j]));
+    };
+    switch (piece) {
+      case V.PAWN: {
+        const forward = (color == 'w' ? -1 : 1);
+        const startRank = (color == 'w' ? [6, 7] : [0, 1]);
+        if (
+          x == startRank &&
+          this.board[x + forward][y] == V.EMPTY &&
+          this.board[x + 2 * forward][y] != V.EMPTY
+        ) {
+          // Swap using 2 squares move
+          addSmoves(x + 2 * forward, y);
+        }
+        for (let shift of [-1, 0, 1]) {
+          const [i, j] = [x + forward, y + shift];
+          if (V.OnBoard(i, j) && this.board[i][j] != V.EMPTY) addSmoves(i, j);
+        }
+        break;
+      }
+      case V.KNIGHT:
+        V.steps[V.KNIGHT].forEach(s => {
+          const [i, j] = [x + s[0], y + s[1]];
+          if (V.OnBoard(i, j) && this.board[i][j] != V.EMPTY) addSmoves(i, j);
+        });
+        break;
+      case V.KING:
+        V.steps[V.ROOK].concat(V.steps[V.BISHOP]).forEach(s => {
+          const [i, j] = [x + s[0], y + s[1]];
+          if (V.OnBoard(i, j) && this.board[i][j] != V.EMPTY) addSmoves(i, j);
+        });
+        break;
+      case V.ROOK:
+      case V.BISHOP:
+      case V.QUEEN: {
+        const steps =
+          piece != V.QUEEN
+            ? V.steps[piece]
+            : V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
+        steps.forEach(s => {
+          let [i, j] = [x + s[0], y + s[1]];
+          while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
+            i += s[0];
+            j += s[1];
+          }
+          if (V.OnBoard(i, j) && this.board[i][j] != V.EMPTY) addSmoves(i, j);
+        });
+        break;
+      }
+    }
+    return moves;
+  }
+
+  // Does m2 un-do m1 ? (to disallow undoing swaps)
+  oppositeSwaps(m1, m2) {
+    return (
+      !!m1 &&
+      m1.start.x == m2.start.x &&
+      m1.end.x == m2.end.x &&
+      m1.start.y == m2.start.y &&
+      m1.end.y == m2.end.y
+    );
+  }
+
+  filterValid(moves) {
+    const fmoves = super.filterValid(moves);
+    if (this.subTurn == 1) return fmoves;
+    return fmoves.filter(m => {
+      const L = this.swaps.length; //at least 1: init from FEN
+      return !this.oppositeSwaps(this.swaps[L - 1], m);
+    });
+  }
+
+  static GenRandInitFen(randomness) {
+    // Add empty smove:
+    return ChessRules.GenRandInitFen(randomness) + " -";
+  }
+
+  getSmoveFen() {
+    const L = this.swaps.length;
+    return (
+      !this.swaps[L - 1]
+        ? "-"
+        : ChessRules.CoordsToSquare(this.swaps[L - 1].start) +
+          ChessRules.CoordsToSquare(this.swaps[L - 1].end)
+    );
+  }
+
+  getFen() {
+    return super.getFen() + " " + this.getSmoveFen();
+  }
+
+  getFenForRepeat() {
+    return super.getFenForRepeat() + "_" + this.getSmoveFen();
+  }
+
+  getCurrentScore() {
+    const L = this.swaps.length;
+    if (this.movesCount >= 2 && !this.swaps[L-1])
+      // Opponent had no swap moves: I win
+      return (this.turn == "w" ? "1-0" : "0-1");
+    if (this.atLeastOneMove()) return "*";
+    // No valid move: I lose
+    return (this.turn == "w" ? "0-1" : "1-0");
+  }
+
+  noSwapAfter(move) {
+    this.subTurn = 2;
+    const res = !this.atLeastOneMove();
+    this.subTurn = 1;
+    return res;
+  }
+
+  play(move) {
+    move.flags = JSON.stringify(this.aggregateFlags());
+    move.turn = [this.turn, this.subTurn];
+    V.PlayOnBoard(this.board, move);
+    let epSq = undefined;
+    if (this.subTurn == 1) epSq = this.getEpSquare(move);
+    if (this.movesCount == 0) {
+      // First move in game
+      this.turn = "b";
+      this.epSquares.push(epSq);
+      this.movesCount = 1;
+    }
+    // Any swap available after move? If no, skip subturn 2
+    else if (this.subTurn == 1 && this.noSwapAfter(move)) {
+      this.turn = V.GetOppCol(this.turn);
+      this.epSquares.push(epSq);
+      move.noSwap = true;
+      this.movesCount++;
+    }
+    else {
+      if (this.subTurn == 2) {
+        this.turn = V.GetOppCol(this.turn);
+        this.swaps.push({ start: move.start, end: move.end });
+      }
+      else {
+        this.epSquares.push(epSq);
+        this.movesCount++;
+      }
+      this.subTurn = 3 - this.subTurn;
+    }
+    this.postPlay(move);
+  }
+
+  postPlay(move) {
+    const firstRank = { 7: 'w', 0: 'b' };
+    // Did some king move?
+    move.appear.forEach(a => {
+      if (a.p == V.KING) {
+        this.kingPos[a.c] = [a.x, a.y];
+        this.castleFlags[a.c] = [V.size.y, V.size.y];
+      }
+    });
+    for (let coords of [move.start, move.end]) {
+      if (
+        Object.keys(firstRank).includes(coords.x) &&
+        this.castleFlags[firstRank[coords.x]].includes(coords.y)
+      ) {
+        const c = firstRank[coords.x];
+        const flagIdx = (coords.y == this.castleFlags[c][0] ? 0 : 1);
+        this.castleFlags[c][flagIdx] = V.size.y;
+      }
+    }
+  }
+
+  undo(move) {
+    this.disaggregateFlags(JSON.parse(move.flags));
+    V.UndoOnBoard(this.board, move);
+    if (move.turn[1] == 1) {
+      // The move may not be full, but is fully undone:
+      this.epSquares.pop();
+      // Moves counter was just incremented:
+      this.movesCount--;
+    }
+    else
+      // Undo the second half of a move
+      this.swaps.pop();
+    this.turn = move.turn[0];
+    this.subTurn = move.turn[1];
+    this.postUndo(move);
+  }
+
+  postUndo(move) {
+    // Did some king move?
+    move.vanish.forEach(v => {
+      if (v.p == V.KING) this.kingPos[v.c] = [v.x, v.y];
+    });
+  }
+
+  // No alpha-beta here, just adapted min-max at depth 2(+1)
+  getComputerMove() {
+    const maxeval = V.INFINITY;
+    const color = this.turn;
+    const oppCol = V.GetOppCol(this.turn);
+
+    // Search best (half) move for opponent turn (TODO: a bit too slow)
+//    const getBestMoveEval = () => {
+//      let score = this.getCurrentScore();
+//      if (score != "*") return maxeval * (score == "1-0" ? 1 : -1);
+//      let moves = this.getAllValidMoves();
+//      let res = (oppCol == "w" ? -maxeval : maxeval);
+//      for (let m of moves) {
+//        this.play(m);
+//        score = this.getCurrentScore();
+//        // Now turn is oppCol,2 if m allow a swap and movesCount >= 2
+//        // Otherwise it's color,1. In both cases the next test makes sense
+//        if (score != "*") {
+//          // Game over
+//          this.undo(m);
+//          return maxeval * (score == "1-0" ? 1 : -1);
+//        }
+//        const evalPos = this.evalPosition();
+//        res = oppCol == "w" ? Math.max(res, evalPos) : Math.min(res, evalPos);
+//        this.undo(m);
+//      }
+//      return res;
+//    };
+
+    const moves11 = this.getAllValidMoves();
+    if (this.movesCount == 0)
+      // Just play first move at random:
+      return moves11[randInt(moves11.length)];
+    let bestMove = undefined;
+    // Rank moves using a min-max at depth 2
+    for (let i = 0; i < moves11.length; i++) {
+      this.play(moves11[i]);
+      if (!!moves11[i].noSwap) {
+        // I lose
+        if (!bestMove) bestMove = {
+          moves: moves11[i],
+          eval: (color == 'w' ? -maxeval : maxeval)
+        };
+      }
+      else {
+        let moves12 = this.getAllValidMoves();
+        for (let j = 0; j < moves12.length; j++) {
+          this.play(moves12[j]);
+//          const evalMove = getBestMoveEval() + 0.05 - Math.random() / 10;
+          const evalMove = this.evalPosition() + 0.05 - Math.random() / 10;
+          if (
+            !bestMove ||
+            (color == 'w' && evalMove > bestMove.eval) ||
+            (color == 'b' && evalMove < bestMove.eval)
+          ) {
+            bestMove = {
+              moves: [moves11[i], moves12[j]],
+              eval: evalMove
+            };
+          }
+          this.undo(moves12[j]);
+        }
+      }
+      this.undo(moves11[i]);
+    }
+    return bestMove.moves;
+  }
+
+  getNotation(move) {
+    if (move.appear.length == 1)
+      // Normal move
+      return super.getNotation(move);
+    if (move.appear[0].p == V.KING && move.appear[1].p == V.ROOK)
+      // Castle
+      return (move.end.y < move.start.y ? "0-0-0" : "0-0");
+    // Swap
+    return "S" + V.CoordsToSquare(move.start) + V.CoordsToSquare(move.end);
+  }
+};
diff --git a/client/src/variants/Switching.js b/client/src/variants/Switching.js
new file mode 100644
index 00000000..229c7b35
--- /dev/null
+++ b/client/src/variants/Switching.js
@@ -0,0 +1,125 @@
+import { ChessRules, Move, PiPo } from "@/base_rules";
+
+export class SwitchingRules extends ChessRules {
+  // Build switch move between squares x1,y1 and x2,y2
+	getSwitchMove_s([x1, y1], [x2, y2]) {
+		const c = this.getColor(x1, y1); //same as color at square 2
+		const p1 = this.getPiece(x1, y1);
+		const p2 = this.getPiece(x2, y2);
+		let move = new Move({
+			appear: [
+				new PiPo({ x: x2, y: y2, c: c, p: p1 }),
+				new PiPo({ x: x1, y: y1, c: c, p: p2 })
+			],
+			vanish: [
+				new PiPo({ x: x1, y: y1, c: c, p: p1 }),
+				new PiPo({ x: x2, y: y2, c: c, p: p2 })
+			]
+		});
+		// Move completion: promote switched pawns (as in Magnetic)
+		const lastRank = (c == "w" ? 0 : V.size.x - 1);
+		let moves = [];
+		if ((p1 == V.PAWN && x2 == lastRank) || (p2 == V.PAWN && x1 == lastRank)) {
+			const idx = (p1 == V.PAWN ? 0 : 1);
+			move.appear[idx].p = V.ROOK;
+			moves.push(move);
+			for (let piece of [V.KNIGHT, V.BISHOP, V.QUEEN]) {
+				let cmove = JSON.parse(JSON.stringify(move));
+				cmove.appear[idx].p = piece;
+				moves.push(cmove);
+			}
+			if (idx == 1) {
+				// Swap moves[i].appear[0] and [1] for moves presentation [TODO...]
+				moves.forEach(m => {
+					let tmp = m.appear[0];
+					m.appear[0] = m.appear[1];
+					m.appear[1] = tmp;
+				});
+			}
+		}
+		else
+      // Other cases
+			moves.push(move);
+		return moves;
+	}
+
+	getPotentialMovesFrom([x,y]) {
+		let moves = super.getPotentialMovesFrom([x,y]);
+		const piece = this.getPiece(x,y);
+    const color = this.turn;
+    const oppCol = V.GetOppCol(color);
+    const kp = this.kingPos[color];
+		// Add switches (if not under check, from anything but the king)
+		if (piece != V.KING && !this.isAttacked(kp, oppCol)) {
+      const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
+      for (let step of steps) {
+        const [i, j] = [x+step[0], y+step[1]];
+        if (
+          V.OnBoard(i, j) &&
+          this.board[i][j] != V.EMPTY &&
+          this.getColor(i,j) == color &&
+          this.getPiece(i,j) != piece
+        ) {
+          const switchMove_s = this.getSwitchMove_s([x,y], [i,j]);
+          Array.prototype.push.apply(moves, switchMove_s);
+        }
+      }
+    }
+		return moves;
+	}
+
+	postPlay(move) {
+    // Did some king move?
+    move.appear.forEach(a => {
+      if (a.p == V.KING) {
+        this.kingPos[a.c] = [a.x, a.y];
+        this.castleFlags[a.c] = [V.size.y, V.size.y];
+      }
+    });
+    for (let coords of [move.start, move.end]) {
+      if (
+        Object.keys(firstRank).includes(coords.x) &&
+        this.castleFlags[firstRank[coords.x]].includes(coords.y)
+      ) {
+        const c = firstRank[coords.x];
+        const flagIdx = (coords.y == this.castleFlags[c][0] ? 0 : 1);
+        this.castleFlags[c][flagIdx] = V.size.y;
+      }
+    }
+	}
+
+	postUndo(move) {
+    // Did some king move?
+    move.vanish.forEach(v => {
+      if (v.p == V.KING) this.kingPos[v.c] = [v.x, v.y];
+    });
+	}
+
+	static get SEARCH_DEPTH() {
+    // Branching factor is quite high
+    return 2;
+  }
+
+  getAllPotentialMoves() {
+    // Since this function is used only for computer play,
+    // remove duplicate switches:
+    return super.getAllPotentialMoves().filter(m => {
+      return (
+        m.appear.length == 1 ||
+        (move.appear[0].p == V.KING && move.appear[1].p == V.ROOK) ||
+        (m.appear[1].x <= m.vanish[1].x && m.appear[1].y <= m.vanish[1].y)
+      );
+    });
+  }
+
+  getNotation(move) {
+    if (move.appear.length == 1)
+      // Normal move
+      return super.getNotation(move);
+    if (move.appear[0].p == V.KING && move.appear[1].p == V.ROOK)
+      // Castle
+      return (move.end.y < move.start.y ? "0-0-0" : "0-0");
+    // Switch
+    return "S" + V.CoordsToSquare(move.start) + V.CoordsToSquare(move.end);
+  }
+}
diff --git a/client/src/views/Game.vue b/client/src/views/Game.vue
index 171dc6c6..075919db 100644
--- a/client/src/views/Game.vue
+++ b/client/src/views/Game.vue
@@ -46,7 +46,7 @@ main
         span {{ st.tr["Participant(s):"] }} 
         span(
           v-for="p in Object.values(people)"
-          v-if="participateInChat(p)"
+          v-if="!!p.name"
         )
           | {{ p.name }} 
         span.anonymous(v-if="someAnonymousPresent()") + @nonymous
@@ -321,9 +321,6 @@ export default {
         )
       );
     },
-    participateInChat: function(p) {
-      return Object.keys(p.tmpIds).some(x => p.tmpIds[x].focus) && !!p.name;
-    },
     someAnonymousPresent: function() {
       return (
         Object.values(this.people).some(p =>
diff --git a/server/db/populate.sql b/server/db/populate.sql
index df100229..9b55c863 100644
--- a/server/db/populate.sql
+++ b/server/db/populate.sql
@@ -70,6 +70,7 @@ insert or ignore into Variants (name, description) values
   ('Pacifist1', 'Convert & support (v1)'),
   ('Pacifist2', 'Convert & support (v2)'),
   ('Parachute', 'Landing on the board'),
+  ('Pawns', 'Reach the last rank'),
   ('Perfect', 'Powerful pieces'),
   ('Racingkings', 'Kings cross the 8x8 board'),
   ('Rampage', 'Move under cover'),
@@ -85,6 +86,8 @@ insert or ignore into Variants (name, description) values
   ('Sittuyin', 'Burmese chess'),
   ('Suicide', 'Lose all pieces'),
   ('Suction', 'Attract opposite king'),
+  ('Swap', 'Dangerous captures'),
+  ('Switching', 'Exchange pieces'' positions'),
   ('Takenmake', 'Prolongated captures'),
   ('Teleport', 'Reposition pieces'),
   ('Tencubed', 'Four new pieces'),
-- 
2.44.0