From dac395887d96e2d642b209c6db6aaacc3ffacb34 Mon Sep 17 00:00:00 2001
From: Benjamin Auder <benjamin.auder@somewhere>
Date: Tue, 4 Feb 2020 23:38:53 +0100
Subject: [PATCH] Convert all remaining tabs by 2spaces

---
 TODO                                    |    1 -
 client/public/index.html                |   10 +-
 client/src/components/ChallengeList.vue |    2 +-
 client/src/components/ContactForm.vue   |    4 +-
 client/src/components/GameList.vue      |    4 +-
 client/src/components/Language.vue      |    2 +-
 client/src/components/MoveList.vue      |  126 +--
 client/src/components/Settings.vue      |    4 +-
 client/src/components/UpsertUser.vue    |   28 +-
 client/src/data/challengeCheck.js       |   18 +-
 client/src/data/userCheck.js            |   28 +-
 client/src/parameters.js.dist           |    8 +-
 client/src/router.js                    |    6 +-
 client/src/utils/ajax.js                |   78 +-
 client/src/utils/datetime.js            |    8 +-
 client/src/utils/printDiagram.js        |  184 ++--
 client/src/utils/squareId.js            |    8 +-
 client/src/variants/Alice.js            |  644 ++++++------
 client/src/variants/Antiking.js         |  392 ++++----
 client/src/variants/Atomic.js           |  260 ++---
 client/src/variants/Baroque.js          | 1226 +++++++++++------------
 client/src/variants/Berolina.js         |  252 ++---
 client/src/variants/Checkered.js        |  582 +++++------
 client/src/variants/Chess960.js         |    2 +-
 client/src/variants/Crazyhouse.js       |  520 +++++-----
 client/src/variants/Dark.js             |  524 +++++-----
 client/src/variants/Extinction.js       |  230 ++---
 client/src/variants/Grand.js            |  768 +++++++-------
 client/src/variants/Losers.js           |  356 +++----
 client/src/variants/Magnetic.js         |  388 +++----
 client/src/variants/Marseille.js        |  550 +++++-----
 client/src/variants/Upsidedown.js       |  114 +--
 client/src/variants/Wildebeest.js       |  508 +++++-----
 client/src/variants/Zen.js              |  448 ++++-----
 client/src/views/Hall.vue               |    2 +-
 client/src/views/MyGames.vue            |    2 +-
 server/app.js                           |   36 +-
 server/bin/www                          |    8 +-
 server/config/parameters.js.dist        |   54 +-
 server/db/populate.sql                  |   36 +-
 server/gulpfile.js                      |   18 +-
 server/models/Game.js                   |  188 ++--
 server/models/User.js                   |  186 ++--
 server/models/Variant.js                |   20 +-
 server/routes/games.js                  |   42 +-
 server/routes/messages.js               |   22 +-
 server/routes/users.js                  |  146 +--
 server/routes/variants.js               |   10 +-
 server/utils/access.js                  |  114 +--
 server/utils/database.js                |    2 +-
 server/utils/mailer.js                  |   60 +-
 server/utils/tokenGenerator.js          |   12 +-
 52 files changed, 4620 insertions(+), 4621 deletions(-)

diff --git a/TODO b/TODO
index 53eab354..d8f28b4e 100644
--- a/TODO
+++ b/TODO
@@ -1,2 +1 @@
-MarseilleChess: revise bot and test
 Translations (including About page), refresh rules
diff --git a/client/public/index.html b/client/public/index.html
index 0ab70d5d..12eb803b 100644
--- a/client/public/index.html
+++ b/client/public/index.html
@@ -2,14 +2,14 @@
 <html>
   <head>
     <meta charset="utf-8">
-		<title>vchess - club</title>
-		<meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <title>vchess - club</title>
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
     <meta name="viewport" content="width=device-width,initial-scale=1.0">
     <link rel="icon" href="<%= BASE_URL %>favicon.ico">
-		<link rel="stylesheet"
+    <link rel="stylesheet"
       href="//cdnjs.cloudflare.com/ajax/libs/mini.css/3.0.1/mini-default.min.css">
-		<link rel="stylesheet"
-			href="//fonts.googleapis.com/css?family=Open+Sans:400,700">
+    <link rel="stylesheet"
+      href="//fonts.googleapis.com/css?family=Open+Sans:400,700">
     <style>
       body {
         --fore-color: #2c3e50;
diff --git a/client/src/components/ChallengeList.vue b/client/src/components/ChallengeList.vue
index 890ec1d9..c57073c0 100644
--- a/client/src/components/ChallengeList.vue
+++ b/client/src/components/ChallengeList.vue
@@ -20,7 +20,7 @@ import { store } from "@/store";
 
 export default {
   name: "my-challenge-list",
-	props: ["challenges"],
+  props: ["challenges"],
   data: function() {
     return {
       st: store.state,
diff --git a/client/src/components/ContactForm.vue b/client/src/components/ContactForm.vue
index 734d7f19..45b7c011 100644
--- a/client/src/components/ContactForm.vue
+++ b/client/src/components/ContactForm.vue
@@ -34,7 +34,7 @@ export default {
     };
   },
   methods: {
-		// Note: not using Vue here, but would be possible
+    // Note: not using Vue here, but would be possible
     trySendMessage: function() {
       let email = document.getElementById("userEmail");
       let subject = document.getElementById("mailSubject");
@@ -65,7 +65,7 @@ export default {
         }
       );
     },
-	},
+  },
 };
 </script>
 
diff --git a/client/src/components/GameList.vue b/client/src/components/GameList.vue
index 5ab61cc3..0d2fbf46 100644
--- a/client/src/components/GameList.vue
+++ b/client/src/components/GameList.vue
@@ -23,14 +23,14 @@ import { store } from "@/store";
 
 export default {
   name: "my-game-list",
-	props: ["games"],
+  props: ["games"],
   data: function() {
     return {
       st: store.state,
       showResult: false,
     };
   },
-	computed: {
+  computed: {
     sortedGames: function() {
       // Show in order: games where it's my turn, my running games, my games, other games
       this.showResult = this.games.some(g => g.score != "*");
diff --git a/client/src/components/Language.vue b/client/src/components/Language.vue
index c701e590..d35d8e21 100644
--- a/client/src/components/Language.vue
+++ b/client/src/components/Language.vue
@@ -40,6 +40,6 @@ export default {
       localStorage["lang"] = e.target.value;
       store.setLanguage(e.target.value);
     },
-	},
+  },
 };
 </script>
diff --git a/client/src/components/MoveList.vue b/client/src/components/MoveList.vue
index e50047d9..90b76b43 100644
--- a/client/src/components/MoveList.vue
+++ b/client/src/components/MoveList.vue
@@ -2,7 +2,7 @@
 // Component for moves list on the right
 export default {
   name: 'my-move-list',
-	props: ["moves","cursor","score","message","firstNum"],
+  props: ["moves","cursor","score","message","firstNum"],
   watch: {
     cursor: function(newCursor) {
       if (window.innerWidth <= 767)
@@ -32,75 +32,75 @@ export default {
       });
     },
   },
-	render(h) {
-		if (this.moves.length == 0)
-			return;
-		let tableContent = [];
-		let moveCounter = 0;
-		let tableRow = undefined;
-		let moveCells = undefined;
-		let curCellContent = "";
-		let firstIndex = 0;
+  render(h) {
+    if (this.moves.length == 0)
+      return;
+    let tableContent = [];
+    let moveCounter = 0;
+    let tableRow = undefined;
+    let moveCells = undefined;
+    let curCellContent = "";
+    let firstIndex = 0;
     for (let i=0; i<this.moves.length; i++)
-		{
-			if (this.moves[i].color == "w")
-			{
-				if (i == 0 || i>0 && this.moves[i-1].color=="b")
-				{
-					if (!!tableRow)
-					{
-						tableRow.children = moveCells;
-						tableContent.push(tableRow);
-					}
-					moveCells = [
-						h(
-							"td",
-							{ domProps: { innerHTML: (++moveCounter) + "." } }
-						)
-					];
-					tableRow = h(
-						"tr",
-						{ }
-					);
-					curCellContent = "";
+    {
+      if (this.moves[i].color == "w")
+      {
+        if (i == 0 || i>0 && this.moves[i-1].color=="b")
+        {
+          if (!!tableRow)
+          {
+            tableRow.children = moveCells;
+            tableContent.push(tableRow);
+          }
+          moveCells = [
+            h(
+              "td",
+              { domProps: { innerHTML: (++moveCounter) + "." } }
+            )
+          ];
+          tableRow = h(
+            "tr",
+            { }
+          );
+          curCellContent = "";
           firstIndex = i;
-				}
-			}
+        }
+      }
       // Next condition is fine because even if the first move is black,
       // there will be the "..." which count as white move.
       else if (this.moves[i].color == "b" && this.moves[i-1].color == "w")
         firstIndex = i;
-			curCellContent += this.moves[i].notation;
-			if (i < this.moves.length-1 && this.moves[i+1].color == this.moves[i].color)
-				curCellContent += ",";
-			else //color change
-			{
-				moveCells.push(
-					h(
-						"td",
-						{
-							domProps: { innerHTML: curCellContent },
-							on: { click: () => this.gotoMove(i) },
+      curCellContent += this.moves[i].notation;
+      if (i < this.moves.length-1 && this.moves[i+1].color == this.moves[i].color)
+        curCellContent += ",";
+      else //color change
+      {
+        moveCells.push(
+          h(
+            "td",
+            {
+              domProps: { innerHTML: curCellContent },
+              on: { click: () => this.gotoMove(i) },
               "class": { "highlight-lm":
                 this.cursor >= firstIndex && this.cursor <= i },
-						}
-					)
-				);
-				curCellContent = "";
-			}
-		}
-		// Complete last row, which might not be full:
-		if (moveCells.length-1 == 1)
-		{
+            }
+          )
+        );
+        curCellContent = "";
+      }
+    }
+    // Complete last row, which might not be full:
+    if (moveCells.length-1 == 1)
+    {
       moveCells.push(
         h(
           "td",
           { domProps: { innerHTML: "" } }
         )
       );
-		}
-		tableRow.children = moveCells;
-		tableContent.push(tableRow);
+    }
+    tableRow.children = moveCells;
+    tableContent.push(tableRow);
     let rootElements = [];
     if (this.score != "*")
     {
@@ -127,18 +127,18 @@ export default {
           },
         },
         tableContent
-		  )
+      )
     );
-		return h(
+    return h(
       "div",
       { },
       rootElements);
-	},
+  },
   methods: {
-		gotoMove: function(index) {
-			this.$emit("goto-move", index);
-		},
-	},
+    gotoMove: function(index) {
+      this.$emit("goto-move", index);
+    },
+  },
 };
 </script>
 
diff --git a/client/src/components/Settings.vue b/client/src/components/Settings.vue
index 92b463ef..1cfbdc8f 100644
--- a/client/src/components/Settings.vue
+++ b/client/src/components/Settings.vue
@@ -67,7 +67,7 @@ export default {
       }
     });
   },
-	methods: {
+  methods: {
     updateSettings: function(event) {
       const propName =
         event.target.id.substr(3).replace(/^\w/, c => c.toLowerCase())
@@ -90,6 +90,6 @@ export default {
       document.getElementById("gameContainer").style.width =
         (boardSize + movesWidth) + "px";
     },
-	},
+  },
 };
 </script>
diff --git a/client/src/components/UpsertUser.vue b/client/src/components/UpsertUser.vue
index 51edba33..ecd51fa9 100644
--- a/client/src/components/UpsertUser.vue
+++ b/client/src/components/UpsertUser.vue
@@ -46,20 +46,20 @@ export default {
       enterTime: Number.MAX_SAFE_INTEGER, //for a basic anti-bot strategy
     };
   },
-	watch: {
-		nameOrEmail: function(newValue) {
-			if (newValue.indexOf('@') >= 0)
-			{
-				this.user.email = newValue;
-				this.user.name = "";
-			}
-			else
-			{
-				this.user.name = newValue;
-				this.user.email = "";
-			}
-		},
-	},
+  watch: {
+    nameOrEmail: function(newValue) {
+      if (newValue.indexOf('@') >= 0)
+      {
+        this.user.email = newValue;
+        this.user.name = "";
+      }
+      else
+      {
+        this.user.name = newValue;
+        this.user.email = "";
+      }
+    },
+  },
   computed: {
     submitMessage: function() {
       switch (this.stage)
diff --git a/client/src/data/challengeCheck.js b/client/src/data/challengeCheck.js
index c2700008..1a8b9fee 100644
--- a/client/src/data/challengeCheck.js
+++ b/client/src/data/challengeCheck.js
@@ -2,21 +2,21 @@ import { extractTime } from "@/utils/timeControl";
 
 export function checkChallenge(c)
 {
-	const vid = parseInt(c.vid);
-	if (isNaN(vid) || vid <= 0)
-		return "Please select a variant";
+  const vid = parseInt(c.vid);
+  if (isNaN(vid) || vid <= 0)
+    return "Please select a variant";
 
   const tc = extractTime(c.timeControl);
   if (!tc)
     return "Wrong time control";
 
-	// Basic alphanumeric check for opponent name
-	if (!!c.to)
-	{
+  // Basic alphanumeric check for opponent name
+  if (!!c.to)
+  {
      // TODO: slightly redundant (see data/userCheck.js)
-		if (!c.to.match(/^[\w]+$/))
-			return "Wrong characters in opponent name";
-	}
+    if (!c.to.match(/^[\w]+$/))
+      return "Wrong characters in opponent name";
+  }
 
   // Allow custom FEN (and check it) only for individual challenges
   if (c.fen.length > 0 && !!c.to)
diff --git a/client/src/data/userCheck.js b/client/src/data/userCheck.js
index 4c714a68..9eb85622 100644
--- a/client/src/data/userCheck.js
+++ b/client/src/data/userCheck.js
@@ -1,17 +1,17 @@
 export function checkNameEmail(o)
 {
-	if (typeof o.name === "string")
-	{
-		if (o.name.length == 0)
-			return "Empty name";
-		if (!o.name.match(/^[\w]+$/))
-			return "Bad characters in name";
-	}
-	if (typeof o.email === "string")
-	{
-		if (o.email.length == 0)
-			return "Empty email";
-		if (!o.email.match(/^[\w.+-]+@[\w.+-]+$/))
-			return "Bad characters in email";
-	}
+  if (typeof o.name === "string")
+  {
+    if (o.name.length == 0)
+      return "Empty name";
+    if (!o.name.match(/^[\w]+$/))
+      return "Bad characters in name";
+  }
+  if (typeof o.email === "string")
+  {
+    if (o.email.length == 0)
+      return "Empty email";
+    if (!o.email.match(/^[\w.+-]+@[\w.+-]+$/))
+      return "Bad characters in email";
+  }
 }
diff --git a/client/src/parameters.js.dist b/client/src/parameters.js.dist
index 5710bac0..edad3bbd 100644
--- a/client/src/parameters.js.dist
+++ b/client/src/parameters.js.dist
@@ -1,10 +1,10 @@
 const Parameters =
 {
-	// URL of your socket server
-	socketUrl: "ws://localhost:3000",
+  // URL of your socket server
+  socketUrl: "ws://localhost:3000",
 
-	// URL of the server (leave blank for 1-server case)
-	serverUrl: "http://localhost:3000",
+  // URL of the server (leave blank for 1-server case)
+  serverUrl: "http://localhost:3000",
 
   // true if the server is at a different address
   cors: true,
diff --git a/client/src/router.js b/client/src/router.js
index 1eb88f91..599cfcb3 100644
--- a/client/src/router.js
+++ b/client/src/router.js
@@ -5,7 +5,7 @@ import Hall from "./views/Hall.vue";
 Vue.use(Router);
 
 function loadView(view) {
-	return () => import(/* webpackChunkName: "view-[request]" */ `@/views/${view}.vue`)
+  return () => import(/* webpackChunkName: "view-[request]" */ `@/views/${view}.vue`)
 }
 
 import { ajax } from "@/utils/ajax";
@@ -46,8 +46,8 @@ const router = new Router({
               localStorage["myname"] = res.name;
               localStorage["myid"] = res.id;
             }
-						// TODO: I don't like these 2 lines, "next('/')" should be enough
-						window.location = "/";
+            // TODO: I don't like these 2 lines, "next('/')" should be enough
+            window.location = "/";
             next();
           }
         );
diff --git a/client/src/utils/ajax.js b/client/src/utils/ajax.js
index eb30330a..adfba467 100644
--- a/client/src/utils/ajax.js
+++ b/client/src/utils/ajax.js
@@ -7,64 +7,64 @@ import params from "../parameters"; //for server URL
 // From JSON (encoded string values!) to "arg1=...&arg2=..."
 function toQueryString(data)
 {
-	let data_str = "";
-	Object.keys(data).forEach(k => {
-		data_str += k + "=" + encodeURIComponent(data[k]) + "&";
-	});
-	return data_str.slice(0, -1); //remove last "&"
+  let data_str = "";
+  Object.keys(data).forEach(k => {
+    data_str += k + "=" + encodeURIComponent(data[k]) + "&";
+  });
+  return data_str.slice(0, -1); //remove last "&"
 }
 
 // data, error: optional
 export function ajax(url, method, data, success, error)
 {
-	let xhr = new XMLHttpRequest();
-	if (data === undefined || typeof(data) === "function") //no data
-	{
-		error = success;
-		success = data;
-		data = {};
-	}
+  let xhr = new XMLHttpRequest();
+  if (data === undefined || typeof(data) === "function") //no data
+  {
+    error = success;
+    success = data;
+    data = {};
+  }
   if (!success)
     success = () => {}; //by default, do nothing
-	if (!error)
-		error = errmsg => { alert(errmsg); };
-	xhr.onreadystatechange = function() {
-		if (this.readyState == 4 && this.status == 200)
-		{
+  if (!error)
+    error = errmsg => { alert(errmsg); };
+  xhr.onreadystatechange = function() {
+    if (this.readyState == 4 && this.status == 200)
+    {
       let res_json = "";
-			try {
-				res_json = JSON.parse(xhr.responseText);
+      try {
+        res_json = JSON.parse(xhr.responseText);
       } catch (e) {
-				// Plain text (e.g. for rules retrieval)
-				return success(xhr.responseText);
+        // Plain text (e.g. for rules retrieval)
+        return success(xhr.responseText);
       }
       if (!res_json.errmsg && !res_json.errno)
         success(res_json);
-			else
+      else
       {
         if (!!res_json.errmsg)
-				  error(res_json.errmsg);
+          error(res_json.errmsg);
         else
           error(res_json.code + ". errno = " + res_json.errno);
       }
-		}
-	};
+    }
+  };
 
-	if (["GET","DELETE"].includes(method) && !!data)
-	{
-		// Append query params to URL
-		url += "/?" + toQueryString(data);
-	}
-	xhr.open(method, params.serverUrl + url, true);
-	xhr.setRequestHeader('X-Requested-With', "XMLHttpRequest");
-	// Next line to allow cross-domain cookies in dev mode (TODO: if...)
+  if (["GET","DELETE"].includes(method) && !!data)
+  {
+    // Append query params to URL
+    url += "/?" + toQueryString(data);
+  }
+  xhr.open(method, params.serverUrl + url, true);
+  xhr.setRequestHeader('X-Requested-With', "XMLHttpRequest");
+  // Next line to allow cross-domain cookies in dev mode (TODO: if...)
   if (params.cors)
     xhr.withCredentials = true;
   if (["POST","PUT"].includes(method))
-	{
-		xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
-		xhr.send(JSON.stringify(data));
-	}
-	else
-		xhr.send();
+  {
+    xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
+    xhr.send(JSON.stringify(data));
+  }
+  else
+    xhr.send();
 }
diff --git a/client/src/utils/datetime.js b/client/src/utils/datetime.js
index 49b70caf..5addd255 100644
--- a/client/src/utils/datetime.js
+++ b/client/src/utils/datetime.js
@@ -1,17 +1,17 @@
 function zeroPad(x)
 {
-	return (x<10 ? "0" : "") + x;
+  return (x<10 ? "0" : "") + x;
 }
 
 export function getDate(d)
 {
-	return d.getFullYear() + '-' + zeroPad(d.getMonth()+1) + '-' + zeroPad(d.getDate());
+  return d.getFullYear() + '-' + zeroPad(d.getMonth()+1) + '-' + zeroPad(d.getDate());
 }
 
 export function getTime(d)
 {
-	return zeroPad(d.getHours()) + ":" + zeroPad(d.getMinutes()) + ":" +
-		zeroPad(d.getSeconds());
+  return zeroPad(d.getHours()) + ":" + zeroPad(d.getMinutes()) + ":" +
+    zeroPad(d.getSeconds());
 }
 
 function padDigits(x)
diff --git a/client/src/utils/printDiagram.js b/client/src/utils/printDiagram.js
index 0d8d9ae3..bf078cee 100644
--- a/client/src/utils/printDiagram.js
+++ b/client/src/utils/printDiagram.js
@@ -3,109 +3,109 @@ import { ArrayFun } from "@/utils/array";
 // Turn (human) marks into coordinates
 function getMarkArray(marks)
 {
-	if (!marks || marks == "-")
-		return [];
-	let markArray = ArrayFun.init(V.size.x, V.size.y, false);
-	const squares = marks.split(",");
-	for (let i=0; i<squares.length; i++)
-	{
-		const coords = V.SquareToCoords(squares[i]);
-		markArray[coords.x][coords.y] = true;
-	}
-	return markArray;
+  if (!marks || marks == "-")
+    return [];
+  let markArray = ArrayFun.init(V.size.x, V.size.y, false);
+  const squares = marks.split(",");
+  for (let i=0; i<squares.length; i++)
+  {
+    const coords = V.SquareToCoords(squares[i]);
+    markArray[coords.x][coords.y] = true;
+  }
+  return markArray;
 }
 
 // Turn (human) shadow indications into coordinates
 function getShadowArray(shadow)
 {
-	if (!shadow || shadow == "-")
-		return [];
-	let shadowArray = ArrayFun.init(V.size.x, V.size.y, false);
-	const squares = shadow.split(",");
-	for (let i=0; i<squares.length; i++)
-	{
-		const rownum = V.size.x - parseInt(squares[i]);
-		if (!isNaN(rownum))
-		{
-			// Shadow a full row
-			for (let i=0; i<V.size.y; i++)
-				shadowArray[rownum][i] = true;
-			continue;
-		}
-		if (squares[i].length == 1)
-		{
-			// Shadow a full column
-			const colnum = V.ColumnToCoord(squares[i]);
-			for (let i=0; i<V.size.x; i++)
-				shadowArray[i][colnum] = true;
-			continue;
-		}
-		if (squares[i].indexOf("-") >= 0)
-		{
-			// Shadow a range of squares, horizontally or vertically
-			const firstLastSq = squares[i].split("-");
-			const range =
-			[
-				V.SquareToCoords(firstLastSq[0]),
-				V.SquareToCoords(firstLastSq[1])
-			];
-			const step =
-			[
-				range[1].x == range[0].x
-					? 0
-					: (range[1].x - range[0].x) / Math.abs(range[1].x - range[0].x),
-				range[1].y == range[0].y
-					? 0
-					: (range[1].y - range[0].y) / Math.abs(range[1].y - range[0].y)
-			];
-			// Convention: range always from smaller to larger number
-			for (let x=range[0].x, y=range[0].y; x <= range[1].x && y <= range[1].y;
-				x += step[0], y += step[1])
-			{
-				shadowArray[x][y] = true;
-			}
-			continue;
-		}
-		// Shadow just one square:
-		const coords = V.SquareToCoords(squares[i]);
-		shadowArray[coords.x][coords.y] = true;
-	}
-	return shadowArray;
+  if (!shadow || shadow == "-")
+    return [];
+  let shadowArray = ArrayFun.init(V.size.x, V.size.y, false);
+  const squares = shadow.split(",");
+  for (let i=0; i<squares.length; i++)
+  {
+    const rownum = V.size.x - parseInt(squares[i]);
+    if (!isNaN(rownum))
+    {
+      // Shadow a full row
+      for (let i=0; i<V.size.y; i++)
+        shadowArray[rownum][i] = true;
+      continue;
+    }
+    if (squares[i].length == 1)
+    {
+      // Shadow a full column
+      const colnum = V.ColumnToCoord(squares[i]);
+      for (let i=0; i<V.size.x; i++)
+        shadowArray[i][colnum] = true;
+      continue;
+    }
+    if (squares[i].indexOf("-") >= 0)
+    {
+      // Shadow a range of squares, horizontally or vertically
+      const firstLastSq = squares[i].split("-");
+      const range =
+      [
+        V.SquareToCoords(firstLastSq[0]),
+        V.SquareToCoords(firstLastSq[1])
+      ];
+      const step =
+      [
+        range[1].x == range[0].x
+          ? 0
+          : (range[1].x - range[0].x) / Math.abs(range[1].x - range[0].x),
+        range[1].y == range[0].y
+          ? 0
+          : (range[1].y - range[0].y) / Math.abs(range[1].y - range[0].y)
+      ];
+      // Convention: range always from smaller to larger number
+      for (let x=range[0].x, y=range[0].y; x <= range[1].x && y <= range[1].y;
+        x += step[0], y += step[1])
+      {
+        shadowArray[x][y] = true;
+      }
+      continue;
+    }
+    // Shadow just one square:
+    const coords = V.SquareToCoords(squares[i]);
+    shadowArray[coords.x][coords.y] = true;
+  }
+  return shadowArray;
 }
 
 // args: object with position (mandatory), and
 // orientation, marks, shadow (optional)
 export function getDiagram(args)
 {
-	// Obtain the array of pieces images names:
-	const board = V.GetBoard(args.position);
-	const orientation = args.orientation || "w";
-	const markArray = getMarkArray(args.marks);
-	const shadowArray = getShadowArray(args.shadow);
-	let boardDiv = "";
-	const [startX,startY,inc] = orientation == 'w'
-		? [0, 0, 1]
-		: [V.size.x-1, V.size.y-1, -1];
-	for (let i=startX; i>=0 && i<V.size.x; i+=inc)
-	{
-		boardDiv += "<div class='row'>";
-		for (let j=startY; j>=0 && j<V.size.y; j+=inc)
-		{
-			boardDiv += "<div class='board board" + V.size.y + " " +
-				((i+j)%2==0 ? "light-square-diag" : "dark-square-diag") +
-				(shadowArray.length > 0 && shadowArray[i][j] ? " in-shadow" : "") +
-				"'>";
-			if (board[i][j] != V.EMPTY)
-			{
-				boardDiv += "<img " +
+  // Obtain the array of pieces images names:
+  const board = V.GetBoard(args.position);
+  const orientation = args.orientation || "w";
+  const markArray = getMarkArray(args.marks);
+  const shadowArray = getShadowArray(args.shadow);
+  let boardDiv = "";
+  const [startX,startY,inc] = orientation == 'w'
+    ? [0, 0, 1]
+    : [V.size.x-1, V.size.y-1, -1];
+  for (let i=startX; i>=0 && i<V.size.x; i+=inc)
+  {
+    boardDiv += "<div class='row'>";
+    for (let j=startY; j>=0 && j<V.size.y; j+=inc)
+    {
+      boardDiv += "<div class='board board" + V.size.y + " " +
+        ((i+j)%2==0 ? "light-square-diag" : "dark-square-diag") +
+        (shadowArray.length > 0 && shadowArray[i][j] ? " in-shadow" : "") +
+        "'>";
+      if (board[i][j] != V.EMPTY)
+      {
+        boardDiv += "<img " +
           "src='/images/pieces/" + V.getPpath(board[i][j]) + ".svg' " +
           "class='piece'/>";
-			}
-			if (markArray.length > 0 && markArray[i][j])
-				boardDiv += "<img src='/images/mark.svg' class='mark-square'/>";
-			boardDiv += "</div>";
-		}
-		boardDiv += "</div>";
-	}
-	return boardDiv;
+      }
+      if (markArray.length > 0 && markArray[i][j])
+        boardDiv += "<img src='/images/mark.svg' class='mark-square'/>";
+      boardDiv += "</div>";
+    }
+    boardDiv += "</div>";
+  }
+  return boardDiv;
 }
diff --git a/client/src/utils/squareId.js b/client/src/utils/squareId.js
index a68b51f9..6469bc3f 100644
--- a/client/src/utils/squareId.js
+++ b/client/src/utils/squareId.js
@@ -1,13 +1,13 @@
 // Get the identifier of a HTML square from its numeric coordinates o.x,o.y.
 export function getSquareId(o)
 {
-	// NOTE: a separator is required to allow any size of board
-	return  "sq-" + o.x + "-" + o.y;
+  // NOTE: a separator is required to allow any size of board
+  return  "sq-" + o.x + "-" + o.y;
 }
 
 // Inverse function
 export function getSquareFromId(id)
 {
-	const idParts = id.split('-');
-	return [parseInt(idParts[1]), parseInt(idParts[2])];
+  const idParts = id.split('-');
+  return [parseInt(idParts[1]), parseInt(idParts[2])];
 }
diff --git a/client/src/variants/Alice.js b/client/src/variants/Alice.js
index e81d3fc8..d7e775cf 100644
--- a/client/src/variants/Alice.js
+++ b/client/src/variants/Alice.js
@@ -5,355 +5,355 @@ import { ArrayFun} from "@/utils/array";
 // TODO? atLeastOneMove() would be more efficient if rewritten here (less sideBoard computations)
 export const VariantRules = class AliceRules extends ChessRules
 {
-	static get ALICE_PIECES()
-	{
-		return {
-			's': 'p',
-			't': 'q',
-			'u': 'r',
-			'c': 'b',
-			'o': 'n',
-			'l': 'k',
-		};
-	}
-	static get ALICE_CODES()
-	{
-		return {
-			'p': 's',
-			'q': 't',
-			'r': 'u',
-			'b': 'c',
-			'n': 'o',
-			'k': 'l',
-		};
-	}
+  static get ALICE_PIECES()
+  {
+    return {
+      's': 'p',
+      't': 'q',
+      'u': 'r',
+      'c': 'b',
+      'o': 'n',
+      'l': 'k',
+    };
+  }
+  static get ALICE_CODES()
+  {
+    return {
+      'p': 's',
+      'q': 't',
+      'r': 'u',
+      'b': 'c',
+      'n': 'o',
+      'k': 'l',
+    };
+  }
 
-	static getPpath(b)
-	{
-		return (Object.keys(this.ALICE_PIECES).includes(b[1]) ? "Alice/" : "") + b;
-	}
+  static getPpath(b)
+  {
+    return (Object.keys(this.ALICE_PIECES).includes(b[1]) ? "Alice/" : "") + b;
+  }
 
-	static get PIECES()
-	{
-		return ChessRules.PIECES.concat(Object.keys(V.ALICE_PIECES));
-	}
+  static get PIECES()
+  {
+    return ChessRules.PIECES.concat(Object.keys(V.ALICE_PIECES));
+  }
 
-	setOtherVariables(fen)
-	{
-		super.setOtherVariables(fen);
-		const rows = V.ParseFen(fen).position.split("/");
-		if (this.kingPos["w"][0] < 0 || this.kingPos["b"][0] < 0)
-		{
-			// INIT_COL_XXX won't be required if Alice kings are found (means 'king moved')
-			for (let i=0; i<rows.length; i++)
-			{
-				let k = 0; //column index on board
-				for (let j=0; j<rows[i].length; j++)
-				{
-					switch (rows[i].charAt(j))
-					{
-						case 'l':
-							this.kingPos['b'] = [i,k];
-							break;
-						case 'L':
-							this.kingPos['w'] = [i,k];
-							break;
-						default:
-							const num = parseInt(rows[i].charAt(j));
-							if (!isNaN(num))
-								k += (num-1);
-					}
-					k++;
-				}
-			}
-		}
-	}
+  setOtherVariables(fen)
+  {
+    super.setOtherVariables(fen);
+    const rows = V.ParseFen(fen).position.split("/");
+    if (this.kingPos["w"][0] < 0 || this.kingPos["b"][0] < 0)
+    {
+      // INIT_COL_XXX won't be required if Alice kings are found (means 'king moved')
+      for (let i=0; i<rows.length; i++)
+      {
+        let k = 0; //column index on board
+        for (let j=0; j<rows[i].length; j++)
+        {
+          switch (rows[i].charAt(j))
+          {
+            case 'l':
+              this.kingPos['b'] = [i,k];
+              break;
+            case 'L':
+              this.kingPos['w'] = [i,k];
+              break;
+            default:
+              const num = parseInt(rows[i].charAt(j));
+              if (!isNaN(num))
+                k += (num-1);
+          }
+          k++;
+        }
+      }
+    }
+  }
 
-	// Return the (standard) color+piece notation at a square for a board
-	getSquareOccupation(i, j, mirrorSide)
-	{
-		const piece = this.getPiece(i,j);
-		if (mirrorSide==1 && Object.keys(V.ALICE_CODES).includes(piece))
-			return this.board[i][j];
-		else if (mirrorSide==2 && Object.keys(V.ALICE_PIECES).includes(piece))
-			return this.getColor(i,j) + V.ALICE_PIECES[piece];
-		return "";
-	}
+  // Return the (standard) color+piece notation at a square for a board
+  getSquareOccupation(i, j, mirrorSide)
+  {
+    const piece = this.getPiece(i,j);
+    if (mirrorSide==1 && Object.keys(V.ALICE_CODES).includes(piece))
+      return this.board[i][j];
+    else if (mirrorSide==2 && Object.keys(V.ALICE_PIECES).includes(piece))
+      return this.getColor(i,j) + V.ALICE_PIECES[piece];
+    return "";
+  }
 
-	// Build board of the given (mirror)side
-	getSideBoard(mirrorSide)
-	{
-		// Build corresponding board from complete board
-		let sideBoard = ArrayFun.init(V.size.x, V.size.y, "");
-		for (let i=0; i<V.size.x; i++)
-		{
-			for (let j=0; j<V.size.y; j++)
-				sideBoard[i][j] = this.getSquareOccupation(i, j, mirrorSide);
-		}
-		return sideBoard;
-	}
+  // Build board of the given (mirror)side
+  getSideBoard(mirrorSide)
+  {
+    // Build corresponding board from complete board
+    let sideBoard = ArrayFun.init(V.size.x, V.size.y, "");
+    for (let i=0; i<V.size.x; i++)
+    {
+      for (let j=0; j<V.size.y; j++)
+        sideBoard[i][j] = this.getSquareOccupation(i, j, mirrorSide);
+    }
+    return sideBoard;
+  }
 
-	// NOTE: castle & enPassant https://www.chessvariants.com/other.dir/alice.html
-	getPotentialMovesFrom([x,y], sideBoard)
-	{
-		const pieces = Object.keys(V.ALICE_CODES);
-		const codes = Object.keys(V.ALICE_PIECES);
-		const mirrorSide = (pieces.includes(this.getPiece(x,y)) ? 1 : 2);
+  // NOTE: castle & enPassant https://www.chessvariants.com/other.dir/alice.html
+  getPotentialMovesFrom([x,y], sideBoard)
+  {
+    const pieces = Object.keys(V.ALICE_CODES);
+    const codes = Object.keys(V.ALICE_PIECES);
+    const mirrorSide = (pieces.includes(this.getPiece(x,y)) ? 1 : 2);
     if (!sideBoard)
-	    sideBoard = [this.getSideBoard(1), this.getSideBoard(2)];
-		const color = this.getColor(x,y);
+      sideBoard = [this.getSideBoard(1), this.getSideBoard(2)];
+    const color = this.getColor(x,y);
 
-		// Search valid moves on sideBoard
-		const saveBoard = this.board;
-		this.board = sideBoard[mirrorSide-1];
-		const moves = super.getPotentialMovesFrom([x,y])
-			.filter(m => {
-				// Filter out king moves which result in under-check position on
-				// current board (before mirror traversing)
-				let aprioriValid = true;
-				if (m.appear[0].p == V.KING)
-				{
-					this.play(m);
-					if (this.underCheck(color, sideBoard))
-						aprioriValid = false;
-					this.undo(m);
-				}
-				return aprioriValid;
-			});
-		this.board = saveBoard;
+    // Search valid moves on sideBoard
+    const saveBoard = this.board;
+    this.board = sideBoard[mirrorSide-1];
+    const moves = super.getPotentialMovesFrom([x,y])
+      .filter(m => {
+        // Filter out king moves which result in under-check position on
+        // current board (before mirror traversing)
+        let aprioriValid = true;
+        if (m.appear[0].p == V.KING)
+        {
+          this.play(m);
+          if (this.underCheck(color, sideBoard))
+            aprioriValid = false;
+          this.undo(m);
+        }
+        return aprioriValid;
+      });
+    this.board = saveBoard;
 
-		// Finally filter impossible moves
-		const res = moves.filter(m => {
-			if (m.appear.length == 2) //castle
-			{
-				// appear[i] must be an empty square on the other board
-				for (let psq of m.appear)
-				{
-					if (this.getSquareOccupation(psq.x,psq.y,3-mirrorSide) != V.EMPTY)
-						return false;
-				}
-			}
-			else if (this.board[m.end.x][m.end.y] != V.EMPTY)
-			{
-				// Attempt to capture
-				const piece = this.getPiece(m.end.x,m.end.y);
-				if ((mirrorSide==1 && codes.includes(piece))
-					|| (mirrorSide==2 && pieces.includes(piece)))
-				{
-					return false;
-				}
-			}
-			// If the move is computed on board1, m.appear change for Alice pieces.
-			if (mirrorSide==1)
-			{
-				m.appear.forEach(psq => { //forEach: castling taken into account
-					psq.p = V.ALICE_CODES[psq.p]; //goto board2
-				});
-			}
-			else //move on board2: mark vanishing pieces as Alice
-			{
-				m.vanish.forEach(psq => {
-					psq.p = V.ALICE_CODES[psq.p];
-				});
-			}
-			// Fix en-passant captures
-			if (m.vanish[0].p == V.PAWN && m.vanish.length == 2
-				&& this.board[m.end.x][m.end.y] == V.EMPTY)
-			{
-				m.vanish[1].c = V.GetOppCol(this.getColor(x,y));
-				// In the special case of en-passant, if
-				//  - board1 takes board2 : vanish[1] --> Alice
-				//  - board2 takes board1 : vanish[1] --> normal
-				let van = m.vanish[1];
-				if (mirrorSide==1 && codes.includes(this.getPiece(van.x,van.y)))
-					van.p = V.ALICE_CODES[van.p];
-				else if (mirrorSide==2 && pieces.includes(this.getPiece(van.x,van.y)))
-					van.p = V.ALICE_PIECES[van.p];
-			}
-			return true;
-		});
-		return res;
-	}
+    // Finally filter impossible moves
+    const res = moves.filter(m => {
+      if (m.appear.length == 2) //castle
+      {
+        // appear[i] must be an empty square on the other board
+        for (let psq of m.appear)
+        {
+          if (this.getSquareOccupation(psq.x,psq.y,3-mirrorSide) != V.EMPTY)
+            return false;
+        }
+      }
+      else if (this.board[m.end.x][m.end.y] != V.EMPTY)
+      {
+        // Attempt to capture
+        const piece = this.getPiece(m.end.x,m.end.y);
+        if ((mirrorSide==1 && codes.includes(piece))
+          || (mirrorSide==2 && pieces.includes(piece)))
+        {
+          return false;
+        }
+      }
+      // If the move is computed on board1, m.appear change for Alice pieces.
+      if (mirrorSide==1)
+      {
+        m.appear.forEach(psq => { //forEach: castling taken into account
+          psq.p = V.ALICE_CODES[psq.p]; //goto board2
+        });
+      }
+      else //move on board2: mark vanishing pieces as Alice
+      {
+        m.vanish.forEach(psq => {
+          psq.p = V.ALICE_CODES[psq.p];
+        });
+      }
+      // Fix en-passant captures
+      if (m.vanish[0].p == V.PAWN && m.vanish.length == 2
+        && this.board[m.end.x][m.end.y] == V.EMPTY)
+      {
+        m.vanish[1].c = V.GetOppCol(this.getColor(x,y));
+        // In the special case of en-passant, if
+        //  - board1 takes board2 : vanish[1] --> Alice
+        //  - board2 takes board1 : vanish[1] --> normal
+        let van = m.vanish[1];
+        if (mirrorSide==1 && codes.includes(this.getPiece(van.x,van.y)))
+          van.p = V.ALICE_CODES[van.p];
+        else if (mirrorSide==2 && pieces.includes(this.getPiece(van.x,van.y)))
+          van.p = V.ALICE_PIECES[van.p];
+      }
+      return true;
+    });
+    return res;
+  }
 
-	filterValid(moves, sideBoard)
-	{
-		if (moves.length == 0)
-			return [];
-		if (!sideBoard)
+  filterValid(moves, sideBoard)
+  {
+    if (moves.length == 0)
+      return [];
+    if (!sideBoard)
       sideBoard = [this.getSideBoard(1), this.getSideBoard(2)];
-		const color = this.turn;
-		return moves.filter(m => {
-			this.playSide(m, sideBoard); //no need to track flags
-			const res = !this.underCheck(color, sideBoard);
-			this.undoSide(m, sideBoard);
-			return res;
-		});
-	}
+    const color = this.turn;
+    return moves.filter(m => {
+      this.playSide(m, sideBoard); //no need to track flags
+      const res = !this.underCheck(color, sideBoard);
+      this.undoSide(m, sideBoard);
+      return res;
+    });
+  }
 
-	getAllValidMoves()
-	{
-		const color = this.turn;
-		const oppCol = V.GetOppCol(color);
-		let potentialMoves = [];
-		const sideBoard = [this.getSideBoard(1), this.getSideBoard(2)];
-		for (var i=0; i<V.size.x; i++)
-		{
-			for (var j=0; j<V.size.y; j++)
-			{
-				if (this.board[i][j] != V.EMPTY && this.getColor(i,j) == color)
-				{
-					Array.prototype.push.apply(potentialMoves,
-						this.getPotentialMovesFrom([i,j], sideBoard));
-				}
-			}
-		}
-		return this.filterValid(potentialMoves, sideBoard);
-	}
+  getAllValidMoves()
+  {
+    const color = this.turn;
+    const oppCol = V.GetOppCol(color);
+    let potentialMoves = [];
+    const sideBoard = [this.getSideBoard(1), this.getSideBoard(2)];
+    for (var i=0; i<V.size.x; i++)
+    {
+      for (var j=0; j<V.size.y; j++)
+      {
+        if (this.board[i][j] != V.EMPTY && this.getColor(i,j) == color)
+        {
+          Array.prototype.push.apply(potentialMoves,
+            this.getPotentialMovesFrom([i,j], sideBoard));
+        }
+      }
+    }
+    return this.filterValid(potentialMoves, sideBoard);
+  }
 
-	// Play on sideboards [TODO: only one sideBoard required]
-	playSide(move, sideBoard)
-	{
-		const pieces = Object.keys(V.ALICE_CODES);
-		move.vanish.forEach(psq => {
-			const mirrorSide = (pieces.includes(psq.p) ? 1 : 2);
-			sideBoard[mirrorSide-1][psq.x][psq.y] = V.EMPTY;
-		});
-		move.appear.forEach(psq => {
-			const mirrorSide = (pieces.includes(psq.p) ? 1 : 2);
-			const piece = (mirrorSide == 1 ? psq.p : V.ALICE_PIECES[psq.p]);
-			sideBoard[mirrorSide-1][psq.x][psq.y] = psq.c + piece;
-			if (piece == V.KING)
-				this.kingPos[psq.c] = [psq.x,psq.y];
-		});
-	}
+  // Play on sideboards [TODO: only one sideBoard required]
+  playSide(move, sideBoard)
+  {
+    const pieces = Object.keys(V.ALICE_CODES);
+    move.vanish.forEach(psq => {
+      const mirrorSide = (pieces.includes(psq.p) ? 1 : 2);
+      sideBoard[mirrorSide-1][psq.x][psq.y] = V.EMPTY;
+    });
+    move.appear.forEach(psq => {
+      const mirrorSide = (pieces.includes(psq.p) ? 1 : 2);
+      const piece = (mirrorSide == 1 ? psq.p : V.ALICE_PIECES[psq.p]);
+      sideBoard[mirrorSide-1][psq.x][psq.y] = psq.c + piece;
+      if (piece == V.KING)
+        this.kingPos[psq.c] = [psq.x,psq.y];
+    });
+  }
 
-	// Undo on sideboards
-	undoSide(move, sideBoard)
-	{
-		const pieces = Object.keys(V.ALICE_CODES);
-		move.appear.forEach(psq => {
-			const mirrorSide = (pieces.includes(psq.p) ? 1 : 2);
-			sideBoard[mirrorSide-1][psq.x][psq.y] = V.EMPTY;
-		});
-		move.vanish.forEach(psq => {
-			const mirrorSide = (pieces.includes(psq.p) ? 1 : 2);
-			const piece = (mirrorSide == 1 ? psq.p : V.ALICE_PIECES[psq.p]);
-			sideBoard[mirrorSide-1][psq.x][psq.y] = psq.c + piece;
-			if (piece == V.KING)
-				this.kingPos[psq.c] = [psq.x,psq.y];
-		});
-	}
+  // Undo on sideboards
+  undoSide(move, sideBoard)
+  {
+    const pieces = Object.keys(V.ALICE_CODES);
+    move.appear.forEach(psq => {
+      const mirrorSide = (pieces.includes(psq.p) ? 1 : 2);
+      sideBoard[mirrorSide-1][psq.x][psq.y] = V.EMPTY;
+    });
+    move.vanish.forEach(psq => {
+      const mirrorSide = (pieces.includes(psq.p) ? 1 : 2);
+      const piece = (mirrorSide == 1 ? psq.p : V.ALICE_PIECES[psq.p]);
+      sideBoard[mirrorSide-1][psq.x][psq.y] = psq.c + piece;
+      if (piece == V.KING)
+        this.kingPos[psq.c] = [psq.x,psq.y];
+    });
+  }
 
   // sideBoard: arg containing both boards (see getAllValidMoves())
-	underCheck(color, sideBoard)
-	{
-		const kp = this.kingPos[color];
-		const mirrorSide = (sideBoard[0][kp[0]][kp[1]] != V.EMPTY ? 1 : 2);
-		let saveBoard = this.board;
-		this.board = sideBoard[mirrorSide-1];
-		let res = this.isAttacked(kp, [V.GetOppCol(color)]);
-		this.board = saveBoard;
-		return res;
-	}
+  underCheck(color, sideBoard)
+  {
+    const kp = this.kingPos[color];
+    const mirrorSide = (sideBoard[0][kp[0]][kp[1]] != V.EMPTY ? 1 : 2);
+    let saveBoard = this.board;
+    this.board = sideBoard[mirrorSide-1];
+    let res = this.isAttacked(kp, [V.GetOppCol(color)]);
+    this.board = saveBoard;
+    return res;
+  }
 
-	getCheckSquares(color)
-	{
-		const pieces = Object.keys(V.ALICE_CODES);
-		const kp = this.kingPos[color];
-		const mirrorSide = (pieces.includes(this.getPiece(kp[0],kp[1])) ? 1 : 2);
-		let sideBoard = this.getSideBoard(mirrorSide);
-		let saveBoard = this.board;
-		this.board = sideBoard;
-		let res = this.isAttacked(this.kingPos[color], [V.GetOppCol(color)])
-			? [ JSON.parse(JSON.stringify(this.kingPos[color])) ]
-			: [ ];
-		this.board = saveBoard;
-		return res;
-	}
+  getCheckSquares(color)
+  {
+    const pieces = Object.keys(V.ALICE_CODES);
+    const kp = this.kingPos[color];
+    const mirrorSide = (pieces.includes(this.getPiece(kp[0],kp[1])) ? 1 : 2);
+    let sideBoard = this.getSideBoard(mirrorSide);
+    let saveBoard = this.board;
+    this.board = sideBoard;
+    let res = this.isAttacked(this.kingPos[color], [V.GetOppCol(color)])
+      ? [ JSON.parse(JSON.stringify(this.kingPos[color])) ]
+      : [ ];
+    this.board = saveBoard;
+    return res;
+  }
 
-	updateVariables(move)
-	{
-		super.updateVariables(move); //standard king
-		const piece = move.vanish[0].p;
-		const c = move.vanish[0].c;
-		// "l" = Alice king
-		if (piece == "l")
-		{
-			this.kingPos[c][0] = move.appear[0].x;
-			this.kingPos[c][1] = move.appear[0].y;
-			this.castleFlags[c] = [false,false];
-		}
-	}
+  updateVariables(move)
+  {
+    super.updateVariables(move); //standard king
+    const piece = move.vanish[0].p;
+    const c = move.vanish[0].c;
+    // "l" = Alice king
+    if (piece == "l")
+    {
+      this.kingPos[c][0] = move.appear[0].x;
+      this.kingPos[c][1] = move.appear[0].y;
+      this.castleFlags[c] = [false,false];
+    }
+  }
 
-	unupdateVariables(move)
-	{
-		super.unupdateVariables(move);
-		const c = move.vanish[0].c;
-		if (move.vanish[0].p == "l")
-			this.kingPos[c] = [move.start.x, move.start.y];
-	}
+  unupdateVariables(move)
+  {
+    super.unupdateVariables(move);
+    const c = move.vanish[0].c;
+    if (move.vanish[0].p == "l")
+      this.kingPos[c] = [move.start.x, move.start.y];
+  }
 
-	getCurrentScore()
-	{
+  getCurrentScore()
+  {
     if (this.atLeastOneMove()) // game not over
       return "*";
 
     const pieces = Object.keys(V.ALICE_CODES);
-		const color = this.turn;
-		const kp = this.kingPos[color];
-		const mirrorSide = (pieces.includes(this.getPiece(kp[0],kp[1])) ? 1 : 2);
-		let sideBoard = this.getSideBoard(mirrorSide);
-		let saveBoard = this.board;
-		this.board = sideBoard;
-		let res = "*";
-		if (!this.isAttacked(this.kingPos[color], [V.GetOppCol(color)]))
-			res = "1/2";
-		else
-			res = (color == "w" ? "0-1" : "1-0");
-		this.board = saveBoard;
-		return res;
-	}
+    const color = this.turn;
+    const kp = this.kingPos[color];
+    const mirrorSide = (pieces.includes(this.getPiece(kp[0],kp[1])) ? 1 : 2);
+    let sideBoard = this.getSideBoard(mirrorSide);
+    let saveBoard = this.board;
+    this.board = sideBoard;
+    let res = "*";
+    if (!this.isAttacked(this.kingPos[color], [V.GetOppCol(color)]))
+      res = "1/2";
+    else
+      res = (color == "w" ? "0-1" : "1-0");
+    this.board = saveBoard;
+    return res;
+  }
 
-	static get VALUES()
-	{
-		return Object.assign(
-			ChessRules.VALUES,
-			{
-				's': 1,
-				'u': 5,
-				'o': 3,
-				'c': 3,
-				't': 9,
-				'l': 1000,
-			}
-		);
-	}
+  static get VALUES()
+  {
+    return Object.assign(
+      ChessRules.VALUES,
+      {
+        's': 1,
+        'u': 5,
+        'o': 3,
+        'c': 3,
+        't': 9,
+        'l': 1000,
+      }
+    );
+  }
 
-	getNotation(move)
-	{
-		if (move.appear.length == 2 && move.appear[0].p == V.KING)
-		{
-			if (move.end.y < move.start.y)
-				return "0-0-0";
-			else
-				return "0-0";
-		}
+  getNotation(move)
+  {
+    if (move.appear.length == 2 && move.appear[0].p == V.KING)
+    {
+      if (move.end.y < move.start.y)
+        return "0-0-0";
+      else
+        return "0-0";
+    }
 
-		const finalSquare = V.CoordsToSquare(move.end);
-		const piece = this.getPiece(move.start.x, move.start.y);
+    const finalSquare = V.CoordsToSquare(move.end);
+    const piece = this.getPiece(move.start.x, move.start.y);
 
-		const captureMark = (move.vanish.length > move.appear.length ? "x" : "");
-		let pawnMark = "";
-		if (["p","s"].includes(piece) && captureMark.length == 1)
-			pawnMark = V.CoordToColumn(move.start.y); //start column
+    const captureMark = (move.vanish.length > move.appear.length ? "x" : "");
+    let pawnMark = "";
+    if (["p","s"].includes(piece) && captureMark.length == 1)
+      pawnMark = V.CoordToColumn(move.start.y); //start column
 
-		// Piece or pawn movement
-		let notation = piece.toUpperCase() + pawnMark + captureMark + finalSquare;
-		if (['s','p'].includes(piece) && !['s','p'].includes(move.appear[0].p))
-		{
-			// Promotion
-			notation += "=" + move.appear[0].p.toUpperCase();
-		}
-		return notation;
-	}
+    // Piece or pawn movement
+    let notation = piece.toUpperCase() + pawnMark + captureMark + finalSquare;
+    if (['s','p'].includes(piece) && !['s','p'].includes(move.appear[0].p))
+    {
+      // Promotion
+      notation += "=" + move.appear[0].p.toUpperCase();
+    }
+    return notation;
+  }
 }
diff --git a/client/src/variants/Antiking.js b/client/src/variants/Antiking.js
index 7b57e74a..610dd257 100644
--- a/client/src/variants/Antiking.js
+++ b/client/src/variants/Antiking.js
@@ -4,204 +4,204 @@ import { randInt } from "@/utils/alea";
 
 export const VariantRules = class AntikingRules extends ChessRules
 {
-	static getPpath(b)
-	{
-		return b[1]=='a' ? "Antiking/"+b : b;
-	}
-
-	static get ANTIKING() { return 'a'; }
-
-	static get PIECES()
-	{
-		return ChessRules.PIECES.concat([V.ANTIKING]);
-	}
-
-	setOtherVariables(fen)
-	{
-		super.setOtherVariables(fen);
-		this.antikingPos = {'w':[-1,-1], 'b':[-1,-1]};
-		const rows = V.ParseFen(fen).position.split("/");
-		for (let i=0; i<rows.length; i++)
-		{
-			let k = 0;
-			for (let j=0; j<rows[i].length; j++)
-			{
-				switch (rows[i].charAt(j))
-				{
-					case 'a':
-						this.antikingPos['b'] = [i,k];
-						break;
-					case 'A':
-						this.antikingPos['w'] = [i,k];
-						break;
-					default:
-						const num = parseInt(rows[i].charAt(j));
-						if (!isNaN(num))
-							k += (num-1);
-				}
-				k++;
-			}
-		}
-	}
-
-	canTake([x1,y1], [x2,y2])
-	{
-		const piece1 = this.getPiece(x1,y1);
-		const piece2 = this.getPiece(x2,y2);
-		const color1 = this.getColor(x1,y1);
-		const color2 = this.getColor(x2,y2);
-		return piece2 != "a" &&
-			((piece1 != "a" && color1 != color2) || (piece1 == "a" && color1 == color2));
-	}
-
-	getPotentialMovesFrom([x,y])
-	{
-		switch (this.getPiece(x,y))
-		{
-			case V.ANTIKING:
-				return this.getPotentialAntikingMoves([x,y]);
-			default:
-				return super.getPotentialMovesFrom([x,y]);
-		}
-	}
-
-	getPotentialAntikingMoves(sq)
-	{
-		return this.getSlideNJumpMoves(sq,
-			V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep");
-	}
-
-	isAttacked(sq, colors)
-	{
-		return (super.isAttacked(sq, colors) || this.isAttackedByAntiking(sq, colors));
-	}
-
-	isAttackedByKing([x,y], colors)
-	{
-		if (this.getPiece(x,y) == V.ANTIKING)
-			return false; //antiking is not attacked by king
-		return this.isAttackedBySlideNJump([x,y], colors, V.KING,
-			V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep");
-	}
-
-	isAttackedByAntiking([x,y], colors)
-	{
-		if ([V.KING,V.ANTIKING].includes(this.getPiece(x,y)))
-			return false; //(anti)king is not attacked by antiking
-		return this.isAttackedBySlideNJump([x,y], colors, V.ANTIKING,
-			V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep");
-	}
-
-	underCheck(color)
-	{
-		const oppCol = V.GetOppCol(color);
-		let res = this.isAttacked(this.kingPos[color], [oppCol])
-			|| !this.isAttacked(this.antikingPos[color], [oppCol]);
-		return res;
-	}
-
-	getCheckSquares(color)
-	{
-		let res = super.getCheckSquares(color);
-		if (!this.isAttacked(this.antikingPos[color], [V.GetOppCol(color)]))
-			res.push(JSON.parse(JSON.stringify(this.antikingPos[color])));
-		return res;
-	}
-
-	updateVariables(move)
-	{
-		super.updateVariables(move);
-		const piece = move.vanish[0].p;
-		const c = move.vanish[0].c;
-		// Update antiking position
-		if (piece == V.ANTIKING)
-		{
-			this.antikingPos[c][0] = move.appear[0].x;
-			this.antikingPos[c][1] = move.appear[0].y;
-		}
-	}
-
-	unupdateVariables(move)
-	{
-		super.unupdateVariables(move);
-		const c = move.vanish[0].c;
-		if (move.vanish[0].p == V.ANTIKING)
-			this.antikingPos[c] = [move.start.x, move.start.y];
-	}
-
-	getCurrentScore()
-	{
+  static getPpath(b)
+  {
+    return b[1]=='a' ? "Antiking/"+b : b;
+  }
+
+  static get ANTIKING() { return 'a'; }
+
+  static get PIECES()
+  {
+    return ChessRules.PIECES.concat([V.ANTIKING]);
+  }
+
+  setOtherVariables(fen)
+  {
+    super.setOtherVariables(fen);
+    this.antikingPos = {'w':[-1,-1], 'b':[-1,-1]};
+    const rows = V.ParseFen(fen).position.split("/");
+    for (let i=0; i<rows.length; i++)
+    {
+      let k = 0;
+      for (let j=0; j<rows[i].length; j++)
+      {
+        switch (rows[i].charAt(j))
+        {
+          case 'a':
+            this.antikingPos['b'] = [i,k];
+            break;
+          case 'A':
+            this.antikingPos['w'] = [i,k];
+            break;
+          default:
+            const num = parseInt(rows[i].charAt(j));
+            if (!isNaN(num))
+              k += (num-1);
+        }
+        k++;
+      }
+    }
+  }
+
+  canTake([x1,y1], [x2,y2])
+  {
+    const piece1 = this.getPiece(x1,y1);
+    const piece2 = this.getPiece(x2,y2);
+    const color1 = this.getColor(x1,y1);
+    const color2 = this.getColor(x2,y2);
+    return piece2 != "a" &&
+      ((piece1 != "a" && color1 != color2) || (piece1 == "a" && color1 == color2));
+  }
+
+  getPotentialMovesFrom([x,y])
+  {
+    switch (this.getPiece(x,y))
+    {
+      case V.ANTIKING:
+        return this.getPotentialAntikingMoves([x,y]);
+      default:
+        return super.getPotentialMovesFrom([x,y]);
+    }
+  }
+
+  getPotentialAntikingMoves(sq)
+  {
+    return this.getSlideNJumpMoves(sq,
+      V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep");
+  }
+
+  isAttacked(sq, colors)
+  {
+    return (super.isAttacked(sq, colors) || this.isAttackedByAntiking(sq, colors));
+  }
+
+  isAttackedByKing([x,y], colors)
+  {
+    if (this.getPiece(x,y) == V.ANTIKING)
+      return false; //antiking is not attacked by king
+    return this.isAttackedBySlideNJump([x,y], colors, V.KING,
+      V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep");
+  }
+
+  isAttackedByAntiking([x,y], colors)
+  {
+    if ([V.KING,V.ANTIKING].includes(this.getPiece(x,y)))
+      return false; //(anti)king is not attacked by antiking
+    return this.isAttackedBySlideNJump([x,y], colors, V.ANTIKING,
+      V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep");
+  }
+
+  underCheck(color)
+  {
+    const oppCol = V.GetOppCol(color);
+    let res = this.isAttacked(this.kingPos[color], [oppCol])
+      || !this.isAttacked(this.antikingPos[color], [oppCol]);
+    return res;
+  }
+
+  getCheckSquares(color)
+  {
+    let res = super.getCheckSquares(color);
+    if (!this.isAttacked(this.antikingPos[color], [V.GetOppCol(color)]))
+      res.push(JSON.parse(JSON.stringify(this.antikingPos[color])));
+    return res;
+  }
+
+  updateVariables(move)
+  {
+    super.updateVariables(move);
+    const piece = move.vanish[0].p;
+    const c = move.vanish[0].c;
+    // Update antiking position
+    if (piece == V.ANTIKING)
+    {
+      this.antikingPos[c][0] = move.appear[0].x;
+      this.antikingPos[c][1] = move.appear[0].y;
+    }
+  }
+
+  unupdateVariables(move)
+  {
+    super.unupdateVariables(move);
+    const c = move.vanish[0].c;
+    if (move.vanish[0].p == V.ANTIKING)
+      this.antikingPos[c] = [move.start.x, move.start.y];
+  }
+
+  getCurrentScore()
+  {
     if (this.atLeastOneMove()) // game not over
       return "*";
 
     const color = this.turn;
-		const oppCol = V.GetOppCol(color);
-		if (!this.isAttacked(this.kingPos[color], [oppCol])
-			&& this.isAttacked(this.antikingPos[color], [oppCol]))
-		{
-			return "1/2";
-		}
-		return color == "w" ? "0-1" : "1-0";
-	}
-
-	static get VALUES() {
-		return Object.assign(
-			ChessRules.VALUES,
-			{ 'a': 1000 }
-		);
-	}
-
-	static GenRandInitFen()
-	{
-		let pieces = { "w": new Array(8), "b": new Array(8) };
-		let antikingPos = { "w": -1, "b": -1 };
-		for (let c of ["w","b"])
-		{
-			let positions = ArrayFun.range(8);
-
-			// Get random squares for bishops, but avoid corners; because,
-			// if an antiking blocks a cornered bishop, it can never be checkmated
-			let randIndex = 2 * randInt(1,4);
-			const bishop1Pos = positions[randIndex];
-			let randIndex_tmp = 2 * randInt(3) + 1;
-			const bishop2Pos = positions[randIndex_tmp];
-			positions.splice(Math.max(randIndex,randIndex_tmp), 1);
-			positions.splice(Math.min(randIndex,randIndex_tmp), 1);
-
-			randIndex = randInt(6);
-			const knight1Pos = positions[randIndex];
-			positions.splice(randIndex, 1);
-			randIndex = randInt(5);
-			const knight2Pos = positions[randIndex];
-			positions.splice(randIndex, 1);
-
-			randIndex = randInt(4);
-			const queenPos = positions[randIndex];
-			positions.splice(randIndex, 1);
-
-			const rook1Pos = positions[0];
-			const kingPos = positions[1];
-			const rook2Pos = positions[2];
-
-			// Random squares for antikings
-			antikingPos[c] = randInt(8);
-
-			pieces[c][rook1Pos] = 'r';
-			pieces[c][knight1Pos] = 'n';
-			pieces[c][bishop1Pos] = 'b';
-			pieces[c][queenPos] = 'q';
-			pieces[c][kingPos] = 'k';
-			pieces[c][bishop2Pos] = 'b';
-			pieces[c][knight2Pos] = 'n';
-			pieces[c][rook2Pos] = 'r';
-		}
-		const ranks23_black = "pppppppp/" + (antikingPos["w"]>0?antikingPos["w"]:"")
-			+ "A" + (antikingPos["w"]<7?7-antikingPos["w"]:"");
-		const ranks23_white = (antikingPos["b"]>0?antikingPos["b"]:"") + "a"
-			+ (antikingPos["b"]<7?7-antikingPos["b"]:"") + "/PPPPPPPP";
-		return pieces["b"].join("") + "/" + ranks23_black +
-			"/8/8/" +
-			ranks23_white + "/" + pieces["w"].join("").toUpperCase() +
-			" w 0 1111 -";
-	}
+    const oppCol = V.GetOppCol(color);
+    if (!this.isAttacked(this.kingPos[color], [oppCol])
+      && this.isAttacked(this.antikingPos[color], [oppCol]))
+    {
+      return "1/2";
+    }
+    return color == "w" ? "0-1" : "1-0";
+  }
+
+  static get VALUES() {
+    return Object.assign(
+      ChessRules.VALUES,
+      { 'a': 1000 }
+    );
+  }
+
+  static GenRandInitFen()
+  {
+    let pieces = { "w": new Array(8), "b": new Array(8) };
+    let antikingPos = { "w": -1, "b": -1 };
+    for (let c of ["w","b"])
+    {
+      let positions = ArrayFun.range(8);
+
+      // Get random squares for bishops, but avoid corners; because,
+      // if an antiking blocks a cornered bishop, it can never be checkmated
+      let randIndex = 2 * randInt(1,4);
+      const bishop1Pos = positions[randIndex];
+      let randIndex_tmp = 2 * randInt(3) + 1;
+      const bishop2Pos = positions[randIndex_tmp];
+      positions.splice(Math.max(randIndex,randIndex_tmp), 1);
+      positions.splice(Math.min(randIndex,randIndex_tmp), 1);
+
+      randIndex = randInt(6);
+      const knight1Pos = positions[randIndex];
+      positions.splice(randIndex, 1);
+      randIndex = randInt(5);
+      const knight2Pos = positions[randIndex];
+      positions.splice(randIndex, 1);
+
+      randIndex = randInt(4);
+      const queenPos = positions[randIndex];
+      positions.splice(randIndex, 1);
+
+      const rook1Pos = positions[0];
+      const kingPos = positions[1];
+      const rook2Pos = positions[2];
+
+      // Random squares for antikings
+      antikingPos[c] = randInt(8);
+
+      pieces[c][rook1Pos] = 'r';
+      pieces[c][knight1Pos] = 'n';
+      pieces[c][bishop1Pos] = 'b';
+      pieces[c][queenPos] = 'q';
+      pieces[c][kingPos] = 'k';
+      pieces[c][bishop2Pos] = 'b';
+      pieces[c][knight2Pos] = 'n';
+      pieces[c][rook2Pos] = 'r';
+    }
+    const ranks23_black = "pppppppp/" + (antikingPos["w"]>0?antikingPos["w"]:"")
+      + "A" + (antikingPos["w"]<7?7-antikingPos["w"]:"");
+    const ranks23_white = (antikingPos["b"]>0?antikingPos["b"]:"") + "a"
+      + (antikingPos["b"]<7?7-antikingPos["b"]:"") + "/PPPPPPPP";
+    return pieces["b"].join("") + "/" + ranks23_black +
+      "/8/8/" +
+      ranks23_white + "/" + pieces["w"].join("").toUpperCase() +
+      " w 0 1111 -";
+  }
 }
diff --git a/client/src/variants/Atomic.js b/client/src/variants/Atomic.js
index e1b1c161..f42d593f 100644
--- a/client/src/variants/Atomic.js
+++ b/client/src/variants/Atomic.js
@@ -2,145 +2,145 @@ import { ChessRules, PiPo } from "@/base_rules";
 
 export const VariantRules = class AtomicRules extends ChessRules
 {
-	getPotentialMovesFrom([x,y])
-	{
-		let moves = super.getPotentialMovesFrom([x,y]);
+  getPotentialMovesFrom([x,y])
+  {
+    let moves = super.getPotentialMovesFrom([x,y]);
 
-		// Handle explosions
-		moves.forEach(m => {
-			if (m.vanish.length > 1 && m.appear.length <= 1) //avoid castles
-			{
-				// Explosion! TODO(?): drop moves which explode our king here
-				let steps = [ [-1,-1],[-1,0],[-1,1],[0,-1],[0,1],[1,-1],[1,0],[1,1] ];
-				for (let step of steps)
-				{
-					let x = m.end.x + step[0];
-					let y = m.end.y + step[1];
-					if (V.OnBoard(x,y) && this.board[x][y] != V.EMPTY
-						&& this.getPiece(x,y) != V.PAWN)
-					{
-						m.vanish.push(
-							new PiPo({p:this.getPiece(x,y),c:this.getColor(x,y),x:x,y:y}));
-					}
-				}
-				m.end = {x:m.appear[0].x, y:m.appear[0].y};
-				m.appear.pop(); //Nothin appears in this case
-			}
-		});
+    // Handle explosions
+    moves.forEach(m => {
+      if (m.vanish.length > 1 && m.appear.length <= 1) //avoid castles
+      {
+        // Explosion! TODO(?): drop moves which explode our king here
+        let steps = [ [-1,-1],[-1,0],[-1,1],[0,-1],[0,1],[1,-1],[1,0],[1,1] ];
+        for (let step of steps)
+        {
+          let x = m.end.x + step[0];
+          let y = m.end.y + step[1];
+          if (V.OnBoard(x,y) && this.board[x][y] != V.EMPTY
+            && this.getPiece(x,y) != V.PAWN)
+          {
+            m.vanish.push(
+              new PiPo({p:this.getPiece(x,y),c:this.getColor(x,y),x:x,y:y}));
+          }
+        }
+        m.end = {x:m.appear[0].x, y:m.appear[0].y};
+        m.appear.pop(); //Nothin appears in this case
+      }
+    });
 
-		return moves;
-	}
+    return moves;
+  }
 
-	getPotentialKingMoves([x,y])
-	{
-		// King cannot capture:
-		let moves = [];
-		const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
-		for (let step of steps)
-		{
-			const i = x + step[0];
-			const j = y + step[1];
-			if (V.OnBoard(i,j) && this.board[i][j] == V.EMPTY)
-				moves.push(this.getBasicMove([x,y], [i,j]));
-		}
-		return moves.concat(this.getCastleMoves([x,y]));
-	}
+  getPotentialKingMoves([x,y])
+  {
+    // King cannot capture:
+    let moves = [];
+    const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
+    for (let step of steps)
+    {
+      const i = x + step[0];
+      const j = y + step[1];
+      if (V.OnBoard(i,j) && this.board[i][j] == V.EMPTY)
+        moves.push(this.getBasicMove([x,y], [i,j]));
+    }
+    return moves.concat(this.getCastleMoves([x,y]));
+  }
 
-	isAttacked(sq, colors)
-	{
-		if (this.getPiece(sq[0],sq[1]) == V.KING && this.isAttackedByKing(sq, colors))
-			return false; //king cannot take...
-		return (this.isAttackedByPawn(sq, colors)
-			|| this.isAttackedByRook(sq, colors)
-			|| this.isAttackedByKnight(sq, colors)
-			|| this.isAttackedByBishop(sq, colors)
-			|| this.isAttackedByQueen(sq, colors));
-	}
+  isAttacked(sq, colors)
+  {
+    if (this.getPiece(sq[0],sq[1]) == V.KING && this.isAttackedByKing(sq, colors))
+      return false; //king cannot take...
+    return (this.isAttackedByPawn(sq, colors)
+      || this.isAttackedByRook(sq, colors)
+      || this.isAttackedByKnight(sq, colors)
+      || this.isAttackedByBishop(sq, colors)
+      || this.isAttackedByQueen(sq, colors));
+  }
 
-	updateVariables(move)
-	{
-		super.updateVariables(move);
-		const color = move.vanish[0].c;
-		if (move.appear.length == 0) //capture
-		{
-			const firstRank = {"w": 7, "b": 0};
-			for (let c of ["w","b"])
-			{
-				// Did we explode king of color c ? (TODO: remove move earlier)
-				if (Math.abs(this.kingPos[c][0]-move.end.x) <= 1
-					&& Math.abs(this.kingPos[c][1]-move.end.y) <= 1)
-				{
-					this.kingPos[c] = [-1,-1];
-					this.castleFlags[c] = [false,false];
-				}
-				else
-				{
-					// Now check if init rook(s) exploded
-					if (Math.abs(move.end.x-firstRank[c]) <= 1)
-					{
-						if (Math.abs(move.end.y-this.INIT_COL_ROOK[c][0]) <= 1)
-							this.castleFlags[c][0] = false;
-						if (Math.abs(move.end.y-this.INIT_COL_ROOK[c][1]) <= 1)
-							this.castleFlags[c][1] = false;
-					}
-				}
-			}
-		}
-	}
+  updateVariables(move)
+  {
+    super.updateVariables(move);
+    const color = move.vanish[0].c;
+    if (move.appear.length == 0) //capture
+    {
+      const firstRank = {"w": 7, "b": 0};
+      for (let c of ["w","b"])
+      {
+        // Did we explode king of color c ? (TODO: remove move earlier)
+        if (Math.abs(this.kingPos[c][0]-move.end.x) <= 1
+          && Math.abs(this.kingPos[c][1]-move.end.y) <= 1)
+        {
+          this.kingPos[c] = [-1,-1];
+          this.castleFlags[c] = [false,false];
+        }
+        else
+        {
+          // Now check if init rook(s) exploded
+          if (Math.abs(move.end.x-firstRank[c]) <= 1)
+          {
+            if (Math.abs(move.end.y-this.INIT_COL_ROOK[c][0]) <= 1)
+              this.castleFlags[c][0] = false;
+            if (Math.abs(move.end.y-this.INIT_COL_ROOK[c][1]) <= 1)
+              this.castleFlags[c][1] = false;
+          }
+        }
+      }
+    }
+  }
 
-	unupdateVariables(move)
-	{
-		super.unupdateVariables(move);
-		const c = move.vanish[0].c;
-		const oppCol = V.GetOppCol(c);
-		if ([this.kingPos[c][0],this.kingPos[oppCol][0]].some(e => { return e < 0; }))
-		{
-			// There is a chance that last move blowed some king away..
-			for (let psq of move.vanish)
-			{
-				if (psq.p == 'k')
-					this.kingPos[psq.c==c ? c : oppCol] = [psq.x, psq.y];
-			}
-		}
-	}
+  unupdateVariables(move)
+  {
+    super.unupdateVariables(move);
+    const c = move.vanish[0].c;
+    const oppCol = V.GetOppCol(c);
+    if ([this.kingPos[c][0],this.kingPos[oppCol][0]].some(e => { return e < 0; }))
+    {
+      // There is a chance that last move blowed some king away..
+      for (let psq of move.vanish)
+      {
+        if (psq.p == 'k')
+          this.kingPos[psq.c==c ? c : oppCol] = [psq.x, psq.y];
+      }
+    }
+  }
 
-	underCheck(color)
-	{
-		const oppCol = V.GetOppCol(color);
-		let res = undefined;
-		// If our king disappeared, move is not valid
-		if (this.kingPos[color][0] < 0)
-			res = true;
-		// If opponent king disappeared, move is valid
-		else if (this.kingPos[oppCol][0] < 0)
-			res = false;
-		// Otherwise, if we remain under check, move is not valid
-		else
-			res = this.isAttacked(this.kingPos[color], [oppCol]);
-		return res;
-	}
+  underCheck(color)
+  {
+    const oppCol = V.GetOppCol(color);
+    let res = undefined;
+    // If our king disappeared, move is not valid
+    if (this.kingPos[color][0] < 0)
+      res = true;
+    // If opponent king disappeared, move is valid
+    else if (this.kingPos[oppCol][0] < 0)
+      res = false;
+    // Otherwise, if we remain under check, move is not valid
+    else
+      res = this.isAttacked(this.kingPos[color], [oppCol]);
+    return res;
+  }
 
-	getCheckSquares(color)
-	{
-		let res = [ ];
-		if (this.kingPos[color][0] >= 0 //king might have exploded
-			&& this.isAttacked(this.kingPos[color], [V.GetOppCol(color)]))
-		{
-			res = [ JSON.parse(JSON.stringify(this.kingPos[color])) ]
-		}
-		return res;
-	}
+  getCheckSquares(color)
+  {
+    let res = [ ];
+    if (this.kingPos[color][0] >= 0 //king might have exploded
+      && this.isAttacked(this.kingPos[color], [V.GetOppCol(color)]))
+    {
+      res = [ JSON.parse(JSON.stringify(this.kingPos[color])) ]
+    }
+    return res;
+  }
 
-	getCurrentScore()
-	{
-		const color = this.turn;
-		const kp = this.kingPos[color];
-		if (kp[0] < 0) //king disappeared
-			return color == "w" ? "0-1" : "1-0";
+  getCurrentScore()
+  {
+    const color = this.turn;
+    const kp = this.kingPos[color];
+    if (kp[0] < 0) //king disappeared
+      return color == "w" ? "0-1" : "1-0";
     if (this.atLeastOneMove()) // game not over
       return "*";
-		if (!this.isAttacked(kp, [V.GetOppCol(color)]))
-			return "1/2";
-		return color == "w" ? "0-1" : "1-0"; //checkmate
-	}
+    if (!this.isAttacked(kp, [V.GetOppCol(color)]))
+      return "1/2";
+    return color == "w" ? "0-1" : "1-0"; //checkmate
+  }
 }
diff --git a/client/src/variants/Baroque.js b/client/src/variants/Baroque.js
index 9b5a3cdb..88c993a9 100644
--- a/client/src/variants/Baroque.js
+++ b/client/src/variants/Baroque.js
@@ -4,617 +4,617 @@ import { randInt } from "@/utils/alea";
 
 export const VariantRules = class BaroqueRules extends ChessRules
 {
-	static get HasFlags() { return false; }
-
-	static get HasEnpassant() { return false; }
-
-	static getPpath(b)
-	{
-		if (b[1] == "m") //'m' for Immobilizer (I is too similar to 1)
-			return "Baroque/" + b;
-		return b; //usual piece
-	}
-
-	static get PIECES()
-	{
-		return ChessRules.PIECES.concat([V.IMMOBILIZER]);
-	}
-
-	// No castling, but checks, so keep track of kings
-	setOtherVariables(fen)
-	{
-		this.kingPos = {'w':[-1,-1], 'b':[-1,-1]};
-		const fenParts = fen.split(" ");
-		const position = fenParts[0].split("/");
-		for (let i=0; i<position.length; i++)
-		{
-			let k = 0;
-			for (let j=0; j<position[i].length; j++)
-			{
-				switch (position[i].charAt(j))
-				{
-					case 'k':
-						this.kingPos['b'] = [i,k];
-						break;
-					case 'K':
-						this.kingPos['w'] = [i,k];
-						break;
-					default:
-						let num = parseInt(position[i].charAt(j));
-						if (!isNaN(num))
-							k += (num-1);
-				}
-				k++;
-			}
-		}
-	}
-
-	static get IMMOBILIZER() { return 'm'; }
-	// Although other pieces keep their names here for coding simplicity,
-	// keep in mind that:
-	//  - a "rook" is a coordinator, capturing by coordinating with the king
-	//  - a "knight" is a long-leaper, capturing as in draughts
-	//  - a "bishop" is a chameleon, capturing as its prey
-	//  - a "queen" is a withdrawer, capturing by moving away from pieces
-
-	// Is piece on square (x,y) immobilized?
-	isImmobilized([x,y])
-	{
-		const piece = this.getPiece(x,y);
-		const color = this.getColor(x,y);
-		const oppCol = V.GetOppCol(color);
-		const adjacentSteps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
-		outerLoop:
-		for (let step of adjacentSteps)
-		{
-			const [i,j] = [x+step[0],y+step[1]];
-			if (V.OnBoard(i,j) && this.board[i][j] != V.EMPTY
-				&& this.getColor(i,j) == oppCol)
-			{
-				const oppPiece = this.getPiece(i,j);
-				if (oppPiece == V.IMMOBILIZER)
-				{
-					// Moving is impossible only if this immobilizer is not neutralized
-					for (let step2 of adjacentSteps)
-					{
-						const [i2,j2] = [i+step2[0],j+step2[1]];
-						if (i2 == x && j2 == y)
-							continue; //skip initial piece!
-						if (V.OnBoard(i2,j2) && this.board[i2][j2] != V.EMPTY
-							&& this.getColor(i2,j2) == color)
-						{
-							if ([V.BISHOP,V.IMMOBILIZER].includes(this.getPiece(i2,j2)))
-								return false;
-						}
-					}
-					return true; //immobilizer isn't neutralized
-				}
-				// Chameleons can't be immobilized twice, because there is only one immobilizer
-				if (oppPiece == V.BISHOP && piece == V.IMMOBILIZER)
-					return true;
-			}
-		}
-		return false;
-	}
-
-	getPotentialMovesFrom([x,y])
-	{
-		// Pre-check: is thing on this square immobilized?
-		if (this.isImmobilized([x,y]))
-			return [];
-		switch (this.getPiece(x,y))
-		{
-			case V.IMMOBILIZER:
-				return this.getPotentialImmobilizerMoves([x,y]);
-			default:
-				return super.getPotentialMovesFrom([x,y]);
-		}
-	}
-
-	getSlideNJumpMoves([x,y], steps, oneStep)
-	{
-		const color = this.getColor(x,y);
-		const piece = this.getPiece(x,y);
-		let moves = [];
-		outerLoop:
-		for (let step of steps)
-		{
-			let i = x + step[0];
-			let j = y + step[1];
-			while (V.OnBoard(i,j) && this.board[i][j] == V.EMPTY)
-			{
-				moves.push(this.getBasicMove([x,y], [i,j]));
-				if (oneStep !== undefined)
-					continue outerLoop;
-				i += step[0];
-				j += step[1];
-			}
-			// Only king can take on occupied square:
-			if (piece==V.KING && V.OnBoard(i,j) && this.canTake([x,y], [i,j]))
-				moves.push(this.getBasicMove([x,y], [i,j]));
-		}
-		return moves;
-	}
-
-	// Modify capturing moves among listed pawn moves
-	addPawnCaptures(moves, byChameleon)
-	{
-		const steps = V.steps[V.ROOK];
-		const color = this.turn;
-		const oppCol = V.GetOppCol(color);
-		moves.forEach(m => {
-			if (!!byChameleon && m.start.x!=m.end.x && m.start.y!=m.end.y)
-				return; //chameleon not moving as pawn
-			// Try capturing in every direction
-			for (let step of steps)
-			{
-				const sq2 = [m.end.x+2*step[0],m.end.y+2*step[1]];
-				if (V.OnBoard(sq2[0],sq2[1]) && this.board[sq2[0]][sq2[1]] != V.EMPTY
-					&& this.getColor(sq2[0],sq2[1]) == color)
-				{
-					// Potential capture
-					const sq1 = [m.end.x+step[0],m.end.y+step[1]];
-					if (this.board[sq1[0]][sq1[1]] != V.EMPTY
-						&& this.getColor(sq1[0],sq1[1]) == oppCol)
-					{
-						const piece1 = this.getPiece(sq1[0],sq1[1]);
-						if (!byChameleon || piece1 == V.PAWN)
-						{
-							m.vanish.push(new PiPo({
-								x:sq1[0],
-								y:sq1[1],
-								c:oppCol,
-								p:piece1
-							}));
-						}
-					}
-				}
-			}
-		});
-	}
-
-	// "Pincer"
-	getPotentialPawnMoves([x,y])
-	{
-		let moves = super.getPotentialRookMoves([x,y]);
-		this.addPawnCaptures(moves);
-		return moves;
-	}
-
-	addRookCaptures(moves, byChameleon)
-	{
-		const color = this.turn;
-		const oppCol = V.GetOppCol(color);
-		const kp = this.kingPos[color];
-		moves.forEach(m => {
-			// Check piece-king rectangle (if any) corners for enemy pieces
-			if (m.end.x == kp[0] || m.end.y == kp[1])
-				return; //"flat rectangle"
-			const corner1 = [m.end.x, kp[1]];
-			const corner2 = [kp[0], m.end.y];
-			for (let [i,j] of [corner1,corner2])
-			{
-				if (this.board[i][j] != V.EMPTY && this.getColor(i,j) == oppCol)
-				{
-					const piece = this.getPiece(i,j);
-					if (!byChameleon || piece == V.ROOK)
-					{
-						m.vanish.push( new PiPo({
-							x:i,
-							y:j,
-							p:piece,
-							c:oppCol
-						}) );
-					}
-				}
-			}
-		});
-	}
-
-	// Coordinator
-	getPotentialRookMoves(sq)
-	{
-		let moves = super.getPotentialQueenMoves(sq);
-		this.addRookCaptures(moves);
-		return moves;
-	}
-
-	// Long-leaper
-	getKnightCaptures(startSquare, byChameleon)
-	{
-		// Look in every direction for captures
-		const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
-		const color = this.turn;
-		const oppCol = V.GetOppCol(color);
-		let moves = [];
-		const [x,y] = [startSquare[0],startSquare[1]];
-		const piece = this.getPiece(x,y); //might be a chameleon!
-		outerLoop:
-		for (let step of steps)
-		{
-			let [i,j] = [x+step[0], y+step[1]];
-			while (V.OnBoard(i,j) && this.board[i][j]==V.EMPTY)
-			{
-				i += step[0];
-				j += step[1];
-			}
-			if (!V.OnBoard(i,j) || this.getColor(i,j)==color
-				|| (!!byChameleon && this.getPiece(i,j)!=V.KNIGHT))
-			{
-				continue;
-			}
-			// last(thing), cur(thing) : stop if "cur" is our color, or beyond board limits,
-			// or if "last" isn't empty and cur neither. Otherwise, if cur is empty then
-			// add move until cur square; if cur is occupied then stop if !!byChameleon and
-			// the square not occupied by a leaper.
-			let last = [i,j];
-			let cur = [i+step[0],j+step[1]];
-			let vanished = [ new PiPo({x:x,y:y,c:color,p:piece}) ];
-			while (V.OnBoard(cur[0],cur[1]))
-			{
-				if (this.board[last[0]][last[1]] != V.EMPTY)
-				{
-					const oppPiece = this.getPiece(last[0],last[1]);
-					if (!!byChameleon && oppPiece != V.KNIGHT)
-						continue outerLoop;
-					// Something to eat:
-					vanished.push( new PiPo({x:last[0],y:last[1],c:oppCol,p:oppPiece}) );
-				}
-				if (this.board[cur[0]][cur[1]] != V.EMPTY)
-				{
-					if (this.getColor(cur[0],cur[1]) == color
-						|| this.board[last[0]][last[1]] != V.EMPTY) //TODO: redundant test
-					{
-						continue outerLoop;
-					}
-				}
-				else
-				{
-					moves.push(new Move({
-						appear: [ new PiPo({x:cur[0],y:cur[1],c:color,p:piece}) ],
-						vanish: JSON.parse(JSON.stringify(vanished)), //TODO: required?
-						start: {x:x,y:y},
-						end: {x:cur[0],y:cur[1]}
-					}));
-				}
-				last = [last[0]+step[0],last[1]+step[1]];
-				cur = [cur[0]+step[0],cur[1]+step[1]];
-			}
-		}
-		return moves;
-	}
-
-	// Long-leaper
-	getPotentialKnightMoves(sq)
-	{
-		return super.getPotentialQueenMoves(sq).concat(this.getKnightCaptures(sq));
-	}
-
-	getPotentialBishopMoves([x,y])
-	{
-		let moves = super.getPotentialQueenMoves([x,y])
-			.concat(this.getKnightCaptures([x,y],"asChameleon"));
-		// No "king capture" because king cannot remain under check
-		this.addPawnCaptures(moves, "asChameleon");
-		this.addRookCaptures(moves, "asChameleon");
-		this.addQueenCaptures(moves, "asChameleon");
-		// Post-processing: merge similar moves, concatenating vanish arrays
-		let mergedMoves = {};
-		moves.forEach(m => {
-			const key = m.end.x + V.size.x * m.end.y;
-			if (!mergedMoves[key])
-				mergedMoves[key] = m;
-			else
-			{
-				for (let i=1; i<m.vanish.length; i++)
-					mergedMoves[key].vanish.push(m.vanish[i]);
-			}
-		});
-		// Finally return an array
-		moves = [];
-		Object.keys(mergedMoves).forEach(k => { moves.push(mergedMoves[k]); });
-		return moves;
-	}
-
-	// Withdrawer
-	addQueenCaptures(moves, byChameleon)
-	{
-		if (moves.length == 0)
-			return;
-		const [x,y] = [moves[0].start.x,moves[0].start.y];
-		const adjacentSteps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
-		let capturingDirections = [];
-		const color = this.turn;
-		const oppCol = V.GetOppCol(color);
-		adjacentSteps.forEach(step => {
-			const [i,j] = [x+step[0],y+step[1]];
-			if (V.OnBoard(i,j) && this.board[i][j] != V.EMPTY && this.getColor(i,j) == oppCol
-				&& (!byChameleon || this.getPiece(i,j) == V.QUEEN))
-			{
-				capturingDirections.push(step);
-			}
-		});
-		moves.forEach(m => {
-			const step = [
-				m.end.x!=x ? (m.end.x-x)/Math.abs(m.end.x-x) : 0,
-				m.end.y!=y ? (m.end.y-y)/Math.abs(m.end.y-y) : 0
-			];
-			// NOTE: includes() and even _.isEqual() functions fail...
-			// TODO: this test should be done only once per direction
-			if (capturingDirections.some(dir =>
-				{ return (dir[0]==-step[0] && dir[1]==-step[1]); }))
-			{
-				const [i,j] = [x-step[0],y-step[1]];
-				m.vanish.push(new PiPo({
-					x:i,
-					y:j,
-					p:this.getPiece(i,j),
-					c:oppCol
-				}));
-			}
-		});
-	}
-
-	getPotentialQueenMoves(sq)
-	{
-		let moves = super.getPotentialQueenMoves(sq);
-		this.addQueenCaptures(moves);
-		return moves;
-	}
-
-	getPotentialImmobilizerMoves(sq)
-	{
-		// Immobilizer doesn't capture
-		return super.getPotentialQueenMoves(sq);
-	}
-
-	getPotentialKingMoves(sq)
-	{
-		return this.getSlideNJumpMoves(sq,
-			V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep");
-	}
-
-	// isAttacked() is OK because the immobilizer doesn't take
-
-	isAttackedByPawn([x,y], colors)
-	{
-		// Square (x,y) must be surroundable by two enemy pieces,
-		// and one of them at least should be a pawn (moving).
-		const dirs = [ [1,0],[0,1] ];
-		const steps = V.steps[V.ROOK];
-		for (let dir of dirs)
-		{
-			const [i1,j1] = [x-dir[0],y-dir[1]]; //"before"
-			const [i2,j2] = [x+dir[0],y+dir[1]]; //"after"
-			if (V.OnBoard(i1,j1) && V.OnBoard(i2,j2))
-			{
-				if ((this.board[i1][j1]!=V.EMPTY && colors.includes(this.getColor(i1,j1))
-					&& this.board[i2][j2]==V.EMPTY)
-						||
-					(this.board[i2][j2]!=V.EMPTY && colors.includes(this.getColor(i2,j2))
-					&& this.board[i1][j1]==V.EMPTY))
-				{
-					// Search a movable enemy pawn landing on the empty square
-					for (let step of steps)
-					{
-						let [ii,jj] = (this.board[i1][j1]==V.EMPTY ? [i1,j1] : [i2,j2]);
-						let [i3,j3] = [ii+step[0],jj+step[1]];
-						while (V.OnBoard(i3,j3) && this.board[i3][j3]==V.EMPTY)
-						{
-							i3 += step[0];
-							j3 += step[1];
-						}
-						if (V.OnBoard(i3,j3) && colors.includes(this.getColor(i3,j3))
-							&& this.getPiece(i3,j3) == V.PAWN && !this.isImmobilized([i3,j3]))
-						{
-							return true;
-						}
-					}
-				}
-			}
-		}
-		return false;
-	}
-
-	isAttackedByRook([x,y], colors)
-	{
-		// King must be on same column or row,
-		// and a rook should be able to reach a capturing square
-		// colors contains only one element, giving the oppCol and thus king position
-		const sameRow = (x == this.kingPos[colors[0]][0]);
-		const sameColumn = (y == this.kingPos[colors[0]][1]);
-		if (sameRow || sameColumn)
-		{
-			// Look for the enemy rook (maximum 1)
-			for (let i=0; i<V.size.x; i++)
-			{
-				for (let j=0; j<V.size.y; j++)
-				{
-					if (this.board[i][j] != V.EMPTY && colors.includes(this.getColor(i,j))
-						&& this.getPiece(i,j) == V.ROOK)
-					{
-						if (this.isImmobilized([i,j]))
-							return false; //because only one rook
-						// Can it reach a capturing square?
-						// Easy but quite suboptimal way (TODO): generate all moves (turn is OK)
-						const moves = this.getPotentialMovesFrom([i,j]);
-						for (let move of moves)
-						{
-							if (sameRow && move.end.y == y || sameColumn && move.end.x == x)
-								return true;
-						}
-					}
-				}
-			}
-		}
-		return false;
-	}
-
-	isAttackedByKnight([x,y], colors)
-	{
-		// Square (x,y) must be on same line as a knight,
-		// and there must be empty square(s) behind.
-		const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
-		outerLoop:
-		for (let step of steps)
-		{
-			const [i0,j0] = [x+step[0],y+step[1]];
-			if (V.OnBoard(i0,j0) && this.board[i0][j0] == V.EMPTY)
-			{
-				// Try in opposite direction:
-				let [i,j] = [x-step[0],y-step[1]];
-				while (V.OnBoard(i,j))
-				{
-					while (V.OnBoard(i,j) && this.board[i][j] == V.EMPTY)
-					{
-						i -= step[0];
-						j -= step[1];
-					}
-					if (V.OnBoard(i,j))
-					{
-						if (colors.includes(this.getColor(i,j)))
-						{
-							if (this.getPiece(i,j) == V.KNIGHT && !this.isImmobilized([i,j]))
-								return true;
-							continue outerLoop;
-						}
-						// [else] Our color, could be captured *if there was an empty space*
-						if (this.board[i+step[0]][j+step[1]] != V.EMPTY)
-							continue outerLoop;
-						i -= step[0];
-						j -= step[1];
-					}
-				}
-			}
-		}
-		return false;
-	}
-
-	isAttackedByBishop([x,y], colors)
-	{
-		// We cheat a little here: since this function is used exclusively for king,
-		// it's enough to check the immediate surrounding of the square.
-		const adjacentSteps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
-		for (let step of adjacentSteps)
-		{
-			const [i,j] = [x+step[0],y+step[1]];
-			if (V.OnBoard(i,j) && this.board[i][j]!=V.EMPTY
-				&& colors.includes(this.getColor(i,j)) && this.getPiece(i,j) == V.BISHOP)
-			{
-				return true; //bishops are never immobilized
-			}
-		}
-		return false;
-	}
-
-	isAttackedByQueen([x,y], colors)
-	{
-		// Square (x,y) must be adjacent to a queen, and the queen must have
-		// some free space in the opposite direction from (x,y)
-		const adjacentSteps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
-		for (let step of adjacentSteps)
-		{
-			const sq2 = [x+2*step[0],y+2*step[1]];
-			if (V.OnBoard(sq2[0],sq2[1]) && this.board[sq2[0]][sq2[1]] == V.EMPTY)
-			{
-				const sq1 = [x+step[0],y+step[1]];
-				if (this.board[sq1[0]][sq1[1]] != V.EMPTY
-					&& colors.includes(this.getColor(sq1[0],sq1[1]))
-					&& this.getPiece(sq1[0],sq1[1]) == V.QUEEN
-					&& !this.isImmobilized(sq1))
-				{
-					return true;
-				}
-			}
-		}
-		return false;
-	}
-
-	static get VALUES()
-	{
-		// TODO: totally experimental!
-		return {
-			'p': 1,
-			'r': 2,
-			'n': 5,
-			'b': 3,
-			'q': 3,
-			'm': 5,
-			'k': 1000
-		};
-	}
-
-	static get SEARCH_DEPTH() { return 2; } //TODO?
-
-	static GenRandInitFen()
-	{
-		let pieces = { "w": new Array(8), "b": new Array(8) };
-		// Shuffle pieces on first and last rank
-		for (let c of ["w","b"])
-		{
-			let positions = ArrayFun.range(8);
-			// Get random squares for every piece, totally freely
-
-			let randIndex = randInt(8);
-			const bishop1Pos = positions[randIndex];
-			positions.splice(randIndex, 1);
-
-			randIndex = randInt(7);
-			const bishop2Pos = positions[randIndex];
-			positions.splice(randIndex, 1);
-
-			randIndex = randInt(6);
-			const knight1Pos = positions[randIndex];
-			positions.splice(randIndex, 1);
-
-			randIndex = randInt(5);
-			const knight2Pos = positions[randIndex];
-			positions.splice(randIndex, 1);
-
-			randIndex = randInt(4);
-			const queenPos = positions[randIndex];
-			positions.splice(randIndex, 1);
-
-			randIndex = randInt(3);
-			const kingPos = positions[randIndex];
-			positions.splice(randIndex, 1);
-
-			randIndex = randInt(2);
-			const rookPos = positions[randIndex];
-			positions.splice(randIndex, 1);
-			const immobilizerPos = positions[0];
-
-			pieces[c][bishop1Pos] = 'b';
-			pieces[c][bishop2Pos] = 'b';
-			pieces[c][knight1Pos] = 'n';
-			pieces[c][knight2Pos] = 'n';
-			pieces[c][queenPos] = 'q';
-			pieces[c][kingPos] = 'k';
-			pieces[c][rookPos] = 'r';
-			pieces[c][immobilizerPos] = 'm';
-		}
-		return pieces["b"].join("") +
-			"/pppppppp/8/8/8/8/PPPPPPPP/" +
-			pieces["w"].join("").toUpperCase() +
-			" w 0";
-	}
-
-	getNotation(move)
-	{
-		const initialSquare = V.CoordsToSquare(move.start);
-		const finalSquare = V.CoordsToSquare(move.end);
-		let notation = undefined;
-		if (move.appear[0].p == V.PAWN)
-		{
-			// Pawn: generally ambiguous short notation, so we use full description
-			notation = "P" + initialSquare + finalSquare;
-		}
-		else if (move.appear[0].p == V.KING)
-			notation = "K" + (move.vanish.length>1 ? "x" : "") + finalSquare;
-		else
-			notation = move.appear[0].p.toUpperCase() + finalSquare;
-		if (move.vanish.length > 1 && move.appear[0].p != V.KING)
-			notation += "X"; //capture mark (not describing what is captured...)
-		return notation;
-	}
+  static get HasFlags() { return false; }
+
+  static get HasEnpassant() { return false; }
+
+  static getPpath(b)
+  {
+    if (b[1] == "m") //'m' for Immobilizer (I is too similar to 1)
+      return "Baroque/" + b;
+    return b; //usual piece
+  }
+
+  static get PIECES()
+  {
+    return ChessRules.PIECES.concat([V.IMMOBILIZER]);
+  }
+
+  // No castling, but checks, so keep track of kings
+  setOtherVariables(fen)
+  {
+    this.kingPos = {'w':[-1,-1], 'b':[-1,-1]};
+    const fenParts = fen.split(" ");
+    const position = fenParts[0].split("/");
+    for (let i=0; i<position.length; i++)
+    {
+      let k = 0;
+      for (let j=0; j<position[i].length; j++)
+      {
+        switch (position[i].charAt(j))
+        {
+          case 'k':
+            this.kingPos['b'] = [i,k];
+            break;
+          case 'K':
+            this.kingPos['w'] = [i,k];
+            break;
+          default:
+            let num = parseInt(position[i].charAt(j));
+            if (!isNaN(num))
+              k += (num-1);
+        }
+        k++;
+      }
+    }
+  }
+
+  static get IMMOBILIZER() { return 'm'; }
+  // Although other pieces keep their names here for coding simplicity,
+  // keep in mind that:
+  //  - a "rook" is a coordinator, capturing by coordinating with the king
+  //  - a "knight" is a long-leaper, capturing as in draughts
+  //  - a "bishop" is a chameleon, capturing as its prey
+  //  - a "queen" is a withdrawer, capturing by moving away from pieces
+
+  // Is piece on square (x,y) immobilized?
+  isImmobilized([x,y])
+  {
+    const piece = this.getPiece(x,y);
+    const color = this.getColor(x,y);
+    const oppCol = V.GetOppCol(color);
+    const adjacentSteps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
+    outerLoop:
+    for (let step of adjacentSteps)
+    {
+      const [i,j] = [x+step[0],y+step[1]];
+      if (V.OnBoard(i,j) && this.board[i][j] != V.EMPTY
+        && this.getColor(i,j) == oppCol)
+      {
+        const oppPiece = this.getPiece(i,j);
+        if (oppPiece == V.IMMOBILIZER)
+        {
+          // Moving is impossible only if this immobilizer is not neutralized
+          for (let step2 of adjacentSteps)
+          {
+            const [i2,j2] = [i+step2[0],j+step2[1]];
+            if (i2 == x && j2 == y)
+              continue; //skip initial piece!
+            if (V.OnBoard(i2,j2) && this.board[i2][j2] != V.EMPTY
+              && this.getColor(i2,j2) == color)
+            {
+              if ([V.BISHOP,V.IMMOBILIZER].includes(this.getPiece(i2,j2)))
+                return false;
+            }
+          }
+          return true; //immobilizer isn't neutralized
+        }
+        // Chameleons can't be immobilized twice, because there is only one immobilizer
+        if (oppPiece == V.BISHOP && piece == V.IMMOBILIZER)
+          return true;
+      }
+    }
+    return false;
+  }
+
+  getPotentialMovesFrom([x,y])
+  {
+    // Pre-check: is thing on this square immobilized?
+    if (this.isImmobilized([x,y]))
+      return [];
+    switch (this.getPiece(x,y))
+    {
+      case V.IMMOBILIZER:
+        return this.getPotentialImmobilizerMoves([x,y]);
+      default:
+        return super.getPotentialMovesFrom([x,y]);
+    }
+  }
+
+  getSlideNJumpMoves([x,y], steps, oneStep)
+  {
+    const color = this.getColor(x,y);
+    const piece = this.getPiece(x,y);
+    let moves = [];
+    outerLoop:
+    for (let step of steps)
+    {
+      let i = x + step[0];
+      let j = y + step[1];
+      while (V.OnBoard(i,j) && this.board[i][j] == V.EMPTY)
+      {
+        moves.push(this.getBasicMove([x,y], [i,j]));
+        if (oneStep !== undefined)
+          continue outerLoop;
+        i += step[0];
+        j += step[1];
+      }
+      // Only king can take on occupied square:
+      if (piece==V.KING && V.OnBoard(i,j) && this.canTake([x,y], [i,j]))
+        moves.push(this.getBasicMove([x,y], [i,j]));
+    }
+    return moves;
+  }
+
+  // Modify capturing moves among listed pawn moves
+  addPawnCaptures(moves, byChameleon)
+  {
+    const steps = V.steps[V.ROOK];
+    const color = this.turn;
+    const oppCol = V.GetOppCol(color);
+    moves.forEach(m => {
+      if (!!byChameleon && m.start.x!=m.end.x && m.start.y!=m.end.y)
+        return; //chameleon not moving as pawn
+      // Try capturing in every direction
+      for (let step of steps)
+      {
+        const sq2 = [m.end.x+2*step[0],m.end.y+2*step[1]];
+        if (V.OnBoard(sq2[0],sq2[1]) && this.board[sq2[0]][sq2[1]] != V.EMPTY
+          && this.getColor(sq2[0],sq2[1]) == color)
+        {
+          // Potential capture
+          const sq1 = [m.end.x+step[0],m.end.y+step[1]];
+          if (this.board[sq1[0]][sq1[1]] != V.EMPTY
+            && this.getColor(sq1[0],sq1[1]) == oppCol)
+          {
+            const piece1 = this.getPiece(sq1[0],sq1[1]);
+            if (!byChameleon || piece1 == V.PAWN)
+            {
+              m.vanish.push(new PiPo({
+                x:sq1[0],
+                y:sq1[1],
+                c:oppCol,
+                p:piece1
+              }));
+            }
+          }
+        }
+      }
+    });
+  }
+
+  // "Pincer"
+  getPotentialPawnMoves([x,y])
+  {
+    let moves = super.getPotentialRookMoves([x,y]);
+    this.addPawnCaptures(moves);
+    return moves;
+  }
+
+  addRookCaptures(moves, byChameleon)
+  {
+    const color = this.turn;
+    const oppCol = V.GetOppCol(color);
+    const kp = this.kingPos[color];
+    moves.forEach(m => {
+      // Check piece-king rectangle (if any) corners for enemy pieces
+      if (m.end.x == kp[0] || m.end.y == kp[1])
+        return; //"flat rectangle"
+      const corner1 = [m.end.x, kp[1]];
+      const corner2 = [kp[0], m.end.y];
+      for (let [i,j] of [corner1,corner2])
+      {
+        if (this.board[i][j] != V.EMPTY && this.getColor(i,j) == oppCol)
+        {
+          const piece = this.getPiece(i,j);
+          if (!byChameleon || piece == V.ROOK)
+          {
+            m.vanish.push( new PiPo({
+              x:i,
+              y:j,
+              p:piece,
+              c:oppCol
+            }) );
+          }
+        }
+      }
+    });
+  }
+
+  // Coordinator
+  getPotentialRookMoves(sq)
+  {
+    let moves = super.getPotentialQueenMoves(sq);
+    this.addRookCaptures(moves);
+    return moves;
+  }
+
+  // Long-leaper
+  getKnightCaptures(startSquare, byChameleon)
+  {
+    // Look in every direction for captures
+    const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
+    const color = this.turn;
+    const oppCol = V.GetOppCol(color);
+    let moves = [];
+    const [x,y] = [startSquare[0],startSquare[1]];
+    const piece = this.getPiece(x,y); //might be a chameleon!
+    outerLoop:
+    for (let step of steps)
+    {
+      let [i,j] = [x+step[0], y+step[1]];
+      while (V.OnBoard(i,j) && this.board[i][j]==V.EMPTY)
+      {
+        i += step[0];
+        j += step[1];
+      }
+      if (!V.OnBoard(i,j) || this.getColor(i,j)==color
+        || (!!byChameleon && this.getPiece(i,j)!=V.KNIGHT))
+      {
+        continue;
+      }
+      // last(thing), cur(thing) : stop if "cur" is our color, or beyond board limits,
+      // or if "last" isn't empty and cur neither. Otherwise, if cur is empty then
+      // add move until cur square; if cur is occupied then stop if !!byChameleon and
+      // the square not occupied by a leaper.
+      let last = [i,j];
+      let cur = [i+step[0],j+step[1]];
+      let vanished = [ new PiPo({x:x,y:y,c:color,p:piece}) ];
+      while (V.OnBoard(cur[0],cur[1]))
+      {
+        if (this.board[last[0]][last[1]] != V.EMPTY)
+        {
+          const oppPiece = this.getPiece(last[0],last[1]);
+          if (!!byChameleon && oppPiece != V.KNIGHT)
+            continue outerLoop;
+          // Something to eat:
+          vanished.push( new PiPo({x:last[0],y:last[1],c:oppCol,p:oppPiece}) );
+        }
+        if (this.board[cur[0]][cur[1]] != V.EMPTY)
+        {
+          if (this.getColor(cur[0],cur[1]) == color
+            || this.board[last[0]][last[1]] != V.EMPTY) //TODO: redundant test
+          {
+            continue outerLoop;
+          }
+        }
+        else
+        {
+          moves.push(new Move({
+            appear: [ new PiPo({x:cur[0],y:cur[1],c:color,p:piece}) ],
+            vanish: JSON.parse(JSON.stringify(vanished)), //TODO: required?
+            start: {x:x,y:y},
+            end: {x:cur[0],y:cur[1]}
+          }));
+        }
+        last = [last[0]+step[0],last[1]+step[1]];
+        cur = [cur[0]+step[0],cur[1]+step[1]];
+      }
+    }
+    return moves;
+  }
+
+  // Long-leaper
+  getPotentialKnightMoves(sq)
+  {
+    return super.getPotentialQueenMoves(sq).concat(this.getKnightCaptures(sq));
+  }
+
+  getPotentialBishopMoves([x,y])
+  {
+    let moves = super.getPotentialQueenMoves([x,y])
+      .concat(this.getKnightCaptures([x,y],"asChameleon"));
+    // No "king capture" because king cannot remain under check
+    this.addPawnCaptures(moves, "asChameleon");
+    this.addRookCaptures(moves, "asChameleon");
+    this.addQueenCaptures(moves, "asChameleon");
+    // Post-processing: merge similar moves, concatenating vanish arrays
+    let mergedMoves = {};
+    moves.forEach(m => {
+      const key = m.end.x + V.size.x * m.end.y;
+      if (!mergedMoves[key])
+        mergedMoves[key] = m;
+      else
+      {
+        for (let i=1; i<m.vanish.length; i++)
+          mergedMoves[key].vanish.push(m.vanish[i]);
+      }
+    });
+    // Finally return an array
+    moves = [];
+    Object.keys(mergedMoves).forEach(k => { moves.push(mergedMoves[k]); });
+    return moves;
+  }
+
+  // Withdrawer
+  addQueenCaptures(moves, byChameleon)
+  {
+    if (moves.length == 0)
+      return;
+    const [x,y] = [moves[0].start.x,moves[0].start.y];
+    const adjacentSteps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
+    let capturingDirections = [];
+    const color = this.turn;
+    const oppCol = V.GetOppCol(color);
+    adjacentSteps.forEach(step => {
+      const [i,j] = [x+step[0],y+step[1]];
+      if (V.OnBoard(i,j) && this.board[i][j] != V.EMPTY && this.getColor(i,j) == oppCol
+        && (!byChameleon || this.getPiece(i,j) == V.QUEEN))
+      {
+        capturingDirections.push(step);
+      }
+    });
+    moves.forEach(m => {
+      const step = [
+        m.end.x!=x ? (m.end.x-x)/Math.abs(m.end.x-x) : 0,
+        m.end.y!=y ? (m.end.y-y)/Math.abs(m.end.y-y) : 0
+      ];
+      // NOTE: includes() and even _.isEqual() functions fail...
+      // TODO: this test should be done only once per direction
+      if (capturingDirections.some(dir =>
+        { return (dir[0]==-step[0] && dir[1]==-step[1]); }))
+      {
+        const [i,j] = [x-step[0],y-step[1]];
+        m.vanish.push(new PiPo({
+          x:i,
+          y:j,
+          p:this.getPiece(i,j),
+          c:oppCol
+        }));
+      }
+    });
+  }
+
+  getPotentialQueenMoves(sq)
+  {
+    let moves = super.getPotentialQueenMoves(sq);
+    this.addQueenCaptures(moves);
+    return moves;
+  }
+
+  getPotentialImmobilizerMoves(sq)
+  {
+    // Immobilizer doesn't capture
+    return super.getPotentialQueenMoves(sq);
+  }
+
+  getPotentialKingMoves(sq)
+  {
+    return this.getSlideNJumpMoves(sq,
+      V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep");
+  }
+
+  // isAttacked() is OK because the immobilizer doesn't take
+
+  isAttackedByPawn([x,y], colors)
+  {
+    // Square (x,y) must be surroundable by two enemy pieces,
+    // and one of them at least should be a pawn (moving).
+    const dirs = [ [1,0],[0,1] ];
+    const steps = V.steps[V.ROOK];
+    for (let dir of dirs)
+    {
+      const [i1,j1] = [x-dir[0],y-dir[1]]; //"before"
+      const [i2,j2] = [x+dir[0],y+dir[1]]; //"after"
+      if (V.OnBoard(i1,j1) && V.OnBoard(i2,j2))
+      {
+        if ((this.board[i1][j1]!=V.EMPTY && colors.includes(this.getColor(i1,j1))
+          && this.board[i2][j2]==V.EMPTY)
+            ||
+          (this.board[i2][j2]!=V.EMPTY && colors.includes(this.getColor(i2,j2))
+          && this.board[i1][j1]==V.EMPTY))
+        {
+          // Search a movable enemy pawn landing on the empty square
+          for (let step of steps)
+          {
+            let [ii,jj] = (this.board[i1][j1]==V.EMPTY ? [i1,j1] : [i2,j2]);
+            let [i3,j3] = [ii+step[0],jj+step[1]];
+            while (V.OnBoard(i3,j3) && this.board[i3][j3]==V.EMPTY)
+            {
+              i3 += step[0];
+              j3 += step[1];
+            }
+            if (V.OnBoard(i3,j3) && colors.includes(this.getColor(i3,j3))
+              && this.getPiece(i3,j3) == V.PAWN && !this.isImmobilized([i3,j3]))
+            {
+              return true;
+            }
+          }
+        }
+      }
+    }
+    return false;
+  }
+
+  isAttackedByRook([x,y], colors)
+  {
+    // King must be on same column or row,
+    // and a rook should be able to reach a capturing square
+    // colors contains only one element, giving the oppCol and thus king position
+    const sameRow = (x == this.kingPos[colors[0]][0]);
+    const sameColumn = (y == this.kingPos[colors[0]][1]);
+    if (sameRow || sameColumn)
+    {
+      // Look for the enemy rook (maximum 1)
+      for (let i=0; i<V.size.x; i++)
+      {
+        for (let j=0; j<V.size.y; j++)
+        {
+          if (this.board[i][j] != V.EMPTY && colors.includes(this.getColor(i,j))
+            && this.getPiece(i,j) == V.ROOK)
+          {
+            if (this.isImmobilized([i,j]))
+              return false; //because only one rook
+            // Can it reach a capturing square?
+            // Easy but quite suboptimal way (TODO): generate all moves (turn is OK)
+            const moves = this.getPotentialMovesFrom([i,j]);
+            for (let move of moves)
+            {
+              if (sameRow && move.end.y == y || sameColumn && move.end.x == x)
+                return true;
+            }
+          }
+        }
+      }
+    }
+    return false;
+  }
+
+  isAttackedByKnight([x,y], colors)
+  {
+    // Square (x,y) must be on same line as a knight,
+    // and there must be empty square(s) behind.
+    const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
+    outerLoop:
+    for (let step of steps)
+    {
+      const [i0,j0] = [x+step[0],y+step[1]];
+      if (V.OnBoard(i0,j0) && this.board[i0][j0] == V.EMPTY)
+      {
+        // Try in opposite direction:
+        let [i,j] = [x-step[0],y-step[1]];
+        while (V.OnBoard(i,j))
+        {
+          while (V.OnBoard(i,j) && this.board[i][j] == V.EMPTY)
+          {
+            i -= step[0];
+            j -= step[1];
+          }
+          if (V.OnBoard(i,j))
+          {
+            if (colors.includes(this.getColor(i,j)))
+            {
+              if (this.getPiece(i,j) == V.KNIGHT && !this.isImmobilized([i,j]))
+                return true;
+              continue outerLoop;
+            }
+            // [else] Our color, could be captured *if there was an empty space*
+            if (this.board[i+step[0]][j+step[1]] != V.EMPTY)
+              continue outerLoop;
+            i -= step[0];
+            j -= step[1];
+          }
+        }
+      }
+    }
+    return false;
+  }
+
+  isAttackedByBishop([x,y], colors)
+  {
+    // We cheat a little here: since this function is used exclusively for king,
+    // it's enough to check the immediate surrounding of the square.
+    const adjacentSteps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
+    for (let step of adjacentSteps)
+    {
+      const [i,j] = [x+step[0],y+step[1]];
+      if (V.OnBoard(i,j) && this.board[i][j]!=V.EMPTY
+        && colors.includes(this.getColor(i,j)) && this.getPiece(i,j) == V.BISHOP)
+      {
+        return true; //bishops are never immobilized
+      }
+    }
+    return false;
+  }
+
+  isAttackedByQueen([x,y], colors)
+  {
+    // Square (x,y) must be adjacent to a queen, and the queen must have
+    // some free space in the opposite direction from (x,y)
+    const adjacentSteps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
+    for (let step of adjacentSteps)
+    {
+      const sq2 = [x+2*step[0],y+2*step[1]];
+      if (V.OnBoard(sq2[0],sq2[1]) && this.board[sq2[0]][sq2[1]] == V.EMPTY)
+      {
+        const sq1 = [x+step[0],y+step[1]];
+        if (this.board[sq1[0]][sq1[1]] != V.EMPTY
+          && colors.includes(this.getColor(sq1[0],sq1[1]))
+          && this.getPiece(sq1[0],sq1[1]) == V.QUEEN
+          && !this.isImmobilized(sq1))
+        {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  static get VALUES()
+  {
+    // TODO: totally experimental!
+    return {
+      'p': 1,
+      'r': 2,
+      'n': 5,
+      'b': 3,
+      'q': 3,
+      'm': 5,
+      'k': 1000
+    };
+  }
+
+  static get SEARCH_DEPTH() { return 2; } //TODO?
+
+  static GenRandInitFen()
+  {
+    let pieces = { "w": new Array(8), "b": new Array(8) };
+    // Shuffle pieces on first and last rank
+    for (let c of ["w","b"])
+    {
+      let positions = ArrayFun.range(8);
+      // Get random squares for every piece, totally freely
+
+      let randIndex = randInt(8);
+      const bishop1Pos = positions[randIndex];
+      positions.splice(randIndex, 1);
+
+      randIndex = randInt(7);
+      const bishop2Pos = positions[randIndex];
+      positions.splice(randIndex, 1);
+
+      randIndex = randInt(6);
+      const knight1Pos = positions[randIndex];
+      positions.splice(randIndex, 1);
+
+      randIndex = randInt(5);
+      const knight2Pos = positions[randIndex];
+      positions.splice(randIndex, 1);
+
+      randIndex = randInt(4);
+      const queenPos = positions[randIndex];
+      positions.splice(randIndex, 1);
+
+      randIndex = randInt(3);
+      const kingPos = positions[randIndex];
+      positions.splice(randIndex, 1);
+
+      randIndex = randInt(2);
+      const rookPos = positions[randIndex];
+      positions.splice(randIndex, 1);
+      const immobilizerPos = positions[0];
+
+      pieces[c][bishop1Pos] = 'b';
+      pieces[c][bishop2Pos] = 'b';
+      pieces[c][knight1Pos] = 'n';
+      pieces[c][knight2Pos] = 'n';
+      pieces[c][queenPos] = 'q';
+      pieces[c][kingPos] = 'k';
+      pieces[c][rookPos] = 'r';
+      pieces[c][immobilizerPos] = 'm';
+    }
+    return pieces["b"].join("") +
+      "/pppppppp/8/8/8/8/PPPPPPPP/" +
+      pieces["w"].join("").toUpperCase() +
+      " w 0";
+  }
+
+  getNotation(move)
+  {
+    const initialSquare = V.CoordsToSquare(move.start);
+    const finalSquare = V.CoordsToSquare(move.end);
+    let notation = undefined;
+    if (move.appear[0].p == V.PAWN)
+    {
+      // Pawn: generally ambiguous short notation, so we use full description
+      notation = "P" + initialSquare + finalSquare;
+    }
+    else if (move.appear[0].p == V.KING)
+      notation = "K" + (move.vanish.length>1 ? "x" : "") + finalSquare;
+    else
+      notation = move.appear[0].p.toUpperCase() + finalSquare;
+    if (move.vanish.length > 1 && move.appear[0].p != V.KING)
+      notation += "X"; //capture mark (not describing what is captured...)
+    return notation;
+  }
 }
diff --git a/client/src/variants/Berolina.js b/client/src/variants/Berolina.js
index 05152b43..592b25a6 100644
--- a/client/src/variants/Berolina.js
+++ b/client/src/variants/Berolina.js
@@ -2,136 +2,136 @@ import { ChessRules } from "@/base_rules";
 
 export const VariantRules = class  BerolinaRules extends ChessRules
 {
-	// En-passant after 2-sq jump
-	getEpSquare(moveOrSquare)
-	{
-		if (!moveOrSquare)
-			return undefined;
-		if (typeof moveOrSquare === "string")
-		{
-			const square = moveOrSquare;
-			if (square == "-")
-				return undefined;
-			// Enemy pawn initial column must be given too:
-			let res = [];
-			const epParts = square.split(",");
-			res.push(V.SquareToCoords(epParts[0]));
-			res.push(V.ColumnToCoord(epParts[1]));
-			return res;
-		}
-		// Argument is a move:
-		const move = moveOrSquare;
-		const [sx,ex,sy] = [move.start.x,move.end.x,move.start.y];
-		if (this.getPiece(sx,sy) == V.PAWN && Math.abs(sx - ex) == 2)
-		{
-			return
-			[
-				{
-					x: (ex + sx)/2,
-					y: (move.end.y + sy)/2
-				},
-				move.end.y
-			];
-		}
-		return undefined; //default
-	}
+  // En-passant after 2-sq jump
+  getEpSquare(moveOrSquare)
+  {
+    if (!moveOrSquare)
+      return undefined;
+    if (typeof moveOrSquare === "string")
+    {
+      const square = moveOrSquare;
+      if (square == "-")
+        return undefined;
+      // Enemy pawn initial column must be given too:
+      let res = [];
+      const epParts = square.split(",");
+      res.push(V.SquareToCoords(epParts[0]));
+      res.push(V.ColumnToCoord(epParts[1]));
+      return res;
+    }
+    // Argument is a move:
+    const move = moveOrSquare;
+    const [sx,ex,sy] = [move.start.x,move.end.x,move.start.y];
+    if (this.getPiece(sx,sy) == V.PAWN && Math.abs(sx - ex) == 2)
+    {
+      return
+      [
+        {
+          x: (ex + sx)/2,
+          y: (move.end.y + sy)/2
+        },
+        move.end.y
+      ];
+    }
+    return undefined; //default
+  }
 
-	// Special pawns movements
-	getPotentialPawnMoves([x,y])
-	{
-		const color = this.turn;
-		let moves = [];
-		const [sizeX,sizeY] = [V.size.x,V.size.y];
-		const shiftX = (color == "w" ? -1 : 1);
-		const firstRank = (color == 'w' ? sizeX-1 : 0);
-		const startRank = (color == "w" ? sizeX-2 : 1);
-		const lastRank = (color == "w" ? 0 : sizeX-1);
-		const finalPieces = x + shiftX == lastRank
-			? [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN]
-			: [V.PAWN];
+  // Special pawns movements
+  getPotentialPawnMoves([x,y])
+  {
+    const color = this.turn;
+    let moves = [];
+    const [sizeX,sizeY] = [V.size.x,V.size.y];
+    const shiftX = (color == "w" ? -1 : 1);
+    const firstRank = (color == 'w' ? sizeX-1 : 0);
+    const startRank = (color == "w" ? sizeX-2 : 1);
+    const lastRank = (color == "w" ? 0 : sizeX-1);
+    const finalPieces = x + shiftX == lastRank
+      ? [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN]
+      : [V.PAWN];
 
-		// One square diagonally
-		for (let shiftY of [-1,1])
-		{
-			if (this.board[x+shiftX][y+shiftY] == V.EMPTY)
-			{
-				for (let piece of finalPieces)
-				{
-					moves.push(this.getBasicMove([x,y], [x+shiftX,y+shiftY],
-						{c:color,p:piece}));
-				}
-				if (x == startRank && y+2*shiftY>=0 && y+2*shiftY<sizeY
-					&& this.board[x+2*shiftX][y+2*shiftY] == V.EMPTY)
-				{
-					// Two squares jump
-					moves.push(this.getBasicMove([x,y], [x+2*shiftX,y+2*shiftY]));
-				}
-			}
-		}
-		// Capture
-		if (this.board[x+shiftX][y] != V.EMPTY
-			&& this.canTake([x,y], [x+shiftX,y]))
-		{
-			for (let piece of finalPieces)
-				moves.push(this.getBasicMove([x,y], [x+shiftX,y], {c:color,p:piece}));
-		}
+    // One square diagonally
+    for (let shiftY of [-1,1])
+    {
+      if (this.board[x+shiftX][y+shiftY] == V.EMPTY)
+      {
+        for (let piece of finalPieces)
+        {
+          moves.push(this.getBasicMove([x,y], [x+shiftX,y+shiftY],
+            {c:color,p:piece}));
+        }
+        if (x == startRank && y+2*shiftY>=0 && y+2*shiftY<sizeY
+          && this.board[x+2*shiftX][y+2*shiftY] == V.EMPTY)
+        {
+          // Two squares jump
+          moves.push(this.getBasicMove([x,y], [x+2*shiftX,y+2*shiftY]));
+        }
+      }
+    }
+    // Capture
+    if (this.board[x+shiftX][y] != V.EMPTY
+      && this.canTake([x,y], [x+shiftX,y]))
+    {
+      for (let piece of finalPieces)
+        moves.push(this.getBasicMove([x,y], [x+shiftX,y], {c:color,p:piece}));
+    }
 
-		// En passant
-		const Lep = this.epSquares.length;
-		const epSquare = this.epSquares[Lep-1]; //always at least one element
-		if (!!epSquare && epSquare[0].x == x+shiftX && epSquare[0].y == y
-			&& Math.abs(epSquare[1] - y) == 1)
-		{
-			let enpassantMove = this.getBasicMove([x,y], [x+shiftX,y]);
-			enpassantMove.vanish.push({
-				x: x,
-				y: epSquare[1],
-				p: 'p',
-				c: this.getColor(x,epSquare[1])
-			});
-			moves.push(enpassantMove);
-		}
+    // En passant
+    const Lep = this.epSquares.length;
+    const epSquare = this.epSquares[Lep-1]; //always at least one element
+    if (!!epSquare && epSquare[0].x == x+shiftX && epSquare[0].y == y
+      && Math.abs(epSquare[1] - y) == 1)
+    {
+      let enpassantMove = this.getBasicMove([x,y], [x+shiftX,y]);
+      enpassantMove.vanish.push({
+        x: x,
+        y: epSquare[1],
+        p: 'p',
+        c: this.getColor(x,epSquare[1])
+      });
+      moves.push(enpassantMove);
+    }
 
-		return moves;
-	}
+    return moves;
+  }
 
-	isAttackedByPawn([x,y], colors)
-	{
-		for (let c of colors)
-		{
-			let pawnShift = (c=="w" ? 1 : -1);
-			if (x+pawnShift>=0 && x+pawnShift<V.size.x)
-			{
-				if (this.getPiece(x+pawnShift,y)==V.PAWN
-					&& this.getColor(x+pawnShift,y)==c)
-				{
-					return true;
-				}
-			}
-		}
-		return false;
-	}
+  isAttackedByPawn([x,y], colors)
+  {
+    for (let c of colors)
+    {
+      let pawnShift = (c=="w" ? 1 : -1);
+      if (x+pawnShift>=0 && x+pawnShift<V.size.x)
+      {
+        if (this.getPiece(x+pawnShift,y)==V.PAWN
+          && this.getColor(x+pawnShift,y)==c)
+        {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
 
-	getNotation(move)
-	{
-		const piece = this.getPiece(move.start.x, move.start.y);
-		if (piece == V.PAWN)
-		{
-			// Pawn move
-			const finalSquare = V.CoordsToSquare(move.end);
-			let notation = "";
-			if (move.vanish.length == 2) //capture
-				notation = "Px" + finalSquare;
-			else
-			{
-				// No capture: indicate the initial square for potential ambiguity
-				const startSquare = V.CoordsToSquare(move.start);
-				notation = startSquare + finalSquare;
-			}
-			if (move.appear[0].p != V.PAWN) //promotion
-				notation += "=" + move.appear[0].p.toUpperCase();
-			return notation;
-		}
-		return super.getNotation(move); //all other pieces are orthodox
-	}
+  getNotation(move)
+  {
+    const piece = this.getPiece(move.start.x, move.start.y);
+    if (piece == V.PAWN)
+    {
+      // Pawn move
+      const finalSquare = V.CoordsToSquare(move.end);
+      let notation = "";
+      if (move.vanish.length == 2) //capture
+        notation = "Px" + finalSquare;
+      else
+      {
+        // No capture: indicate the initial square for potential ambiguity
+        const startSquare = V.CoordsToSquare(move.start);
+        notation = startSquare + finalSquare;
+      }
+      if (move.appear[0].p != V.PAWN) //promotion
+        notation += "=" + move.appear[0].p.toUpperCase();
+      return notation;
+    }
+    return super.getNotation(move); //all other pieces are orthodox
+  }
 }
diff --git a/client/src/variants/Checkered.js b/client/src/variants/Checkered.js
index 586c88ee..afbf21e2 100644
--- a/client/src/variants/Checkered.js
+++ b/client/src/variants/Checkered.js
@@ -2,49 +2,49 @@ import { ChessRules } from "@/base_rules";
 
 export const VariantRules = class CheckeredRules extends ChessRules
 {
-	static getPpath(b)
-	{
-		return b[0]=='c' ? "Checkered/"+b : b;
-	}
-
-	static board2fen(b)
-	{
-		const checkered_codes = {
-			'p': 's',
-			'q': 't',
-			'r': 'u',
-			'b': 'c',
-			'n': 'o',
-		};
-		if (b[0]=="c")
-			return checkered_codes[b[1]];
-		return ChessRules.board2fen(b);
-	}
-
-	static fen2board(f)
-	{
-		// Tolerate upper-case versions of checkered pieces (why not?)
-		const checkered_pieces = {
-			's': 'p',
-			'S': 'p',
-			't': 'q',
-			'T': 'q',
-			'u': 'r',
-			'U': 'r',
-			'c': 'b',
-			'C': 'b',
-			'o': 'n',
-			'O': 'n',
-		};
-		if (Object.keys(checkered_pieces).includes(f))
-			return 'c'+checkered_pieces[f];
-		return ChessRules.fen2board(f);
-	}
-
-	static get PIECES()
-	{
-		return ChessRules.PIECES.concat(['s','t','u','c','o']);
-	}
+  static getPpath(b)
+  {
+    return b[0]=='c' ? "Checkered/"+b : b;
+  }
+
+  static board2fen(b)
+  {
+    const checkered_codes = {
+      'p': 's',
+      'q': 't',
+      'r': 'u',
+      'b': 'c',
+      'n': 'o',
+    };
+    if (b[0]=="c")
+      return checkered_codes[b[1]];
+    return ChessRules.board2fen(b);
+  }
+
+  static fen2board(f)
+  {
+    // Tolerate upper-case versions of checkered pieces (why not?)
+    const checkered_pieces = {
+      's': 'p',
+      'S': 'p',
+      't': 'q',
+      'T': 'q',
+      'u': 'r',
+      'U': 'r',
+      'c': 'b',
+      'C': 'b',
+      'o': 'n',
+      'O': 'n',
+    };
+    if (Object.keys(checkered_pieces).includes(f))
+      return 'c'+checkered_pieces[f];
+    return ChessRules.fen2board(f);
+  }
+
+  static get PIECES()
+  {
+    return ChessRules.PIECES.concat(['s','t','u','c','o']);
+  }
 
   setOtherVariables(fen)
   {
@@ -65,7 +65,7 @@ export const VariantRules = class CheckeredRules extends ChessRules
 
   static IsGoodFen(fen)
   {
-	  if (!ChessRules.IsGoodFen(fen))
+    if (!ChessRules.IsGoodFen(fen))
       return false;
     const fenParts = fen.split(" ");
     if (fenParts.length != 6)
@@ -75,40 +75,40 @@ export const VariantRules = class CheckeredRules extends ChessRules
     return true;
   }
 
-	static IsGoodFlags(flags)
-	{
-		// 4 for castle + 16 for pawns
-		return !!flags.match(/^[01]{20,20}$/);
-	}
-
-	setFlags(fenflags)
-	{
-		super.setFlags(fenflags); //castleFlags
-		this.pawnFlags =
-		{
-			"w": [...Array(8).fill(true)], //pawns can move 2 squares?
-			"b": [...Array(8).fill(true)],
-		};
-		if (!fenflags)
-			return;
-		const flags = fenflags.substr(4); //skip first 4 digits, for castle
-		for (let c of ['w','b'])
-		{
-			for (let i=0; i<8; i++)
-				this.pawnFlags[c][i] = (flags.charAt((c=='w'?0:8)+i) == '1');
-		}
-	}
-
-	aggregateFlags()
-	{
-		return [this.castleFlags, this.pawnFlags];
-	}
-
-	disaggregateFlags(flags)
-	{
-		this.castleFlags = flags[0];
-		this.pawnFlags = flags[1];
-	}
+  static IsGoodFlags(flags)
+  {
+    // 4 for castle + 16 for pawns
+    return !!flags.match(/^[01]{20,20}$/);
+  }
+
+  setFlags(fenflags)
+  {
+    super.setFlags(fenflags); //castleFlags
+    this.pawnFlags =
+    {
+      "w": [...Array(8).fill(true)], //pawns can move 2 squares?
+      "b": [...Array(8).fill(true)],
+    };
+    if (!fenflags)
+      return;
+    const flags = fenflags.substr(4); //skip first 4 digits, for castle
+    for (let c of ['w','b'])
+    {
+      for (let i=0; i<8; i++)
+        this.pawnFlags[c][i] = (flags.charAt((c=='w'?0:8)+i) == '1');
+    }
+  }
+
+  aggregateFlags()
+  {
+    return [this.castleFlags, this.pawnFlags];
+  }
+
+  disaggregateFlags(flags)
+  {
+    this.castleFlags = flags[0];
+    this.pawnFlags = flags[1];
+  }
 
   getCmove(move)
   {
@@ -117,171 +117,171 @@ export const VariantRules = class CheckeredRules extends ChessRules
     return null;
   }
 
-	canTake([x1,y1], [x2,y2])
-	{
-		const color1 = this.getColor(x1,y1);
-		const color2 = this.getColor(x2,y2);
-		// Checkered aren't captured
-		return color1 != color2 && color2 != 'c' && (color1 != 'c' || color2 != this.turn);
-	}
-
-	// Post-processing: apply "checkerization" of standard moves
-	getPotentialMovesFrom([x,y])
-	{
-		let standardMoves = super.getPotentialMovesFrom([x,y]);
-		const lastRank = this.turn == "w" ? 0 : 7;
-		if (this.getPiece(x,y) == V.KING)
-			return standardMoves; //king has to be treated differently (for castles)
-		let moves = [];
-		standardMoves.forEach(m => {
-			if (m.vanish[0].p == V.PAWN)
-			{
-				if (Math.abs(m.end.x-m.start.x)==2 && !this.pawnFlags[this.turn][m.start.y])
-					return; //skip forbidden 2-squares jumps
-				if (this.board[m.end.x][m.end.y] == V.EMPTY && m.vanish.length==2
-					&& this.getColor(m.start.x,m.start.y) == 'c')
-				{
-					return; //checkered pawns cannot take en-passant
-				}
-			}
-			if (m.vanish.length == 1)
-				moves.push(m); //no capture
-			else
-			{
-				// A capture occured (m.vanish.length == 2)
-				m.appear[0].c = "c";
-				moves.push(m);
-				if (m.appear[0].p != m.vanish[1].p //avoid promotions (already treated):
-					&& (m.vanish[0].p != V.PAWN || m.end.x != lastRank))
-				{
-					// Add transformation into captured piece
-					let m2 = JSON.parse(JSON.stringify(m));
-					m2.appear[0].p = m.vanish[1].p;
-					moves.push(m2);
-				}
-			}
-		});
-		return moves;
-	}
-
-	canIplay(side, [x,y])
-	{
-		return (side == this.turn && [side,'c'].includes(this.getColor(x,y)));
-	}
-
-	// Does m2 un-do m1 ? (to disallow undoing checkered moves)
-	oppositeMoves(m1, m2)
-	{
-		return (!!m1 && m2.appear[0].c == 'c'
+  canTake([x1,y1], [x2,y2])
+  {
+    const color1 = this.getColor(x1,y1);
+    const color2 = this.getColor(x2,y2);
+    // Checkered aren't captured
+    return color1 != color2 && color2 != 'c' && (color1 != 'c' || color2 != this.turn);
+  }
+
+  // Post-processing: apply "checkerization" of standard moves
+  getPotentialMovesFrom([x,y])
+  {
+    let standardMoves = super.getPotentialMovesFrom([x,y]);
+    const lastRank = this.turn == "w" ? 0 : 7;
+    if (this.getPiece(x,y) == V.KING)
+      return standardMoves; //king has to be treated differently (for castles)
+    let moves = [];
+    standardMoves.forEach(m => {
+      if (m.vanish[0].p == V.PAWN)
+      {
+        if (Math.abs(m.end.x-m.start.x)==2 && !this.pawnFlags[this.turn][m.start.y])
+          return; //skip forbidden 2-squares jumps
+        if (this.board[m.end.x][m.end.y] == V.EMPTY && m.vanish.length==2
+          && this.getColor(m.start.x,m.start.y) == 'c')
+        {
+          return; //checkered pawns cannot take en-passant
+        }
+      }
+      if (m.vanish.length == 1)
+        moves.push(m); //no capture
+      else
+      {
+        // A capture occured (m.vanish.length == 2)
+        m.appear[0].c = "c";
+        moves.push(m);
+        if (m.appear[0].p != m.vanish[1].p //avoid promotions (already treated):
+          && (m.vanish[0].p != V.PAWN || m.end.x != lastRank))
+        {
+          // Add transformation into captured piece
+          let m2 = JSON.parse(JSON.stringify(m));
+          m2.appear[0].p = m.vanish[1].p;
+          moves.push(m2);
+        }
+      }
+    });
+    return moves;
+  }
+
+  canIplay(side, [x,y])
+  {
+    return (side == this.turn && [side,'c'].includes(this.getColor(x,y)));
+  }
+
+  // Does m2 un-do m1 ? (to disallow undoing checkered moves)
+  oppositeMoves(m1, m2)
+  {
+    return (!!m1 && m2.appear[0].c == 'c'
       && m2.appear.length == 1 && m2.vanish.length == 1
-			&& m1.start.x == m2.end.x && m1.end.x == m2.start.x
-			&& m1.start.y == m2.end.y && m1.end.y == m2.start.y);
-	}
-
-	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
-			if (this.oppositeMoves(this.cmoves[L-1], m))
-				return false;
-			this.play(m);
-			const res = !this.underCheck(color);
-			this.undo(m);
-			return res;
-		});
-	}
-
-	isAttackedByPawn([x,y], colors)
-	{
-		for (let c of colors)
-		{
-			const color = (c=="c" ? this.turn : c);
-			let pawnShift = (color=="w" ? 1 : -1);
-			if (x+pawnShift>=0 && x+pawnShift<8)
-			{
-				for (let i of [-1,1])
-				{
-					if (y+i>=0 && y+i<8 && this.getPiece(x+pawnShift,y+i)==V.PAWN
-						&& this.getColor(x+pawnShift,y+i)==c)
-					{
-						return true;
-					}
-				}
-			}
-		}
-		return false;
-	}
-
-	underCheck(color)
-	{
-		return this.isAttacked(this.kingPos[color], [V.GetOppCol(color),'c']);
-	}
-
-	getCheckSquares(color)
-	{
-		// Artifically change turn, for checkered pawns
-		this.turn = V.GetOppCol(color);
-		const kingAttacked = this.isAttacked(
-			this.kingPos[color], [V.GetOppCol(color),'c']);
-		let res = kingAttacked
-			? [JSON.parse(JSON.stringify(this.kingPos[color]))] //need to duplicate!
-			: [];
-		this.turn = color;
-		return res;
-	}
-
-	updateVariables(move)
-	{
-		super.updateVariables(move);
-		// Does this move turn off a 2-squares pawn flag?
-		const secondRank = [1,6];
-		if (secondRank.includes(move.start.x) && move.vanish[0].p == V.PAWN)
-			this.pawnFlags[move.start.x==6 ? "w" : "b"][move.start.y] = false;
-	}
-
-	getCurrentScore()
-	{
+      && m1.start.x == m2.end.x && m1.end.x == m2.start.x
+      && m1.start.y == m2.end.y && m1.end.y == m2.start.y);
+  }
+
+  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
+      if (this.oppositeMoves(this.cmoves[L-1], m))
+        return false;
+      this.play(m);
+      const res = !this.underCheck(color);
+      this.undo(m);
+      return res;
+    });
+  }
+
+  isAttackedByPawn([x,y], colors)
+  {
+    for (let c of colors)
+    {
+      const color = (c=="c" ? this.turn : c);
+      let pawnShift = (color=="w" ? 1 : -1);
+      if (x+pawnShift>=0 && x+pawnShift<8)
+      {
+        for (let i of [-1,1])
+        {
+          if (y+i>=0 && y+i<8 && this.getPiece(x+pawnShift,y+i)==V.PAWN
+            && this.getColor(x+pawnShift,y+i)==c)
+          {
+            return true;
+          }
+        }
+      }
+    }
+    return false;
+  }
+
+  underCheck(color)
+  {
+    return this.isAttacked(this.kingPos[color], [V.GetOppCol(color),'c']);
+  }
+
+  getCheckSquares(color)
+  {
+    // Artifically change turn, for checkered pawns
+    this.turn = V.GetOppCol(color);
+    const kingAttacked = this.isAttacked(
+      this.kingPos[color], [V.GetOppCol(color),'c']);
+    let res = kingAttacked
+      ? [JSON.parse(JSON.stringify(this.kingPos[color]))] //need to duplicate!
+      : [];
+    this.turn = color;
+    return res;
+  }
+
+  updateVariables(move)
+  {
+    super.updateVariables(move);
+    // Does this move turn off a 2-squares pawn flag?
+    const secondRank = [1,6];
+    if (secondRank.includes(move.start.x) && move.vanish[0].p == V.PAWN)
+      this.pawnFlags[move.start.x==6 ? "w" : "b"][move.start.y] = false;
+  }
+
+  getCurrentScore()
+  {
     if (this.atLeastOneMove()) // game not over
       return "*";
 
     const color = this.turn;
-		// Artifically change turn, for checkered pawns
-		this.turn = V.GetOppCol(this.turn);
-		const res = this.isAttacked(this.kingPos[color], [V.GetOppCol(color),'c'])
-			? (color == "w" ? "0-1" : "1-0")
-			: "1/2";
-		this.turn = V.GetOppCol(this.turn);
-		return res;
-	}
-
-	evalPosition()
-	{
-		let evaluation = 0;
-		//Just count material for now, considering checkered neutral (...)
-		for (let i=0; i<V.size.x; i++)
-		{
-			for (let j=0; j<V.size.y; j++)
-			{
-				if (this.board[i][j] != V.EMPTY)
-				{
-					const sqColor = this.getColor(i,j);
-					const sign = sqColor == "w" ? 1 : (sqColor=="b" ? -1 : 0);
-					evaluation += sign * V.VALUES[this.getPiece(i,j)];
-				}
-			}
-		}
-		return evaluation;
-	}
-
-	static GenRandInitFen()
-	{
-		const randFen = ChessRules.GenRandInitFen();
-		// Add 16 pawns flags + empty cmove:
-		return randFen.replace(" w 0 1111", " w 0 11111111111111111111 -");
-	}
+    // Artifically change turn, for checkered pawns
+    this.turn = V.GetOppCol(this.turn);
+    const res = this.isAttacked(this.kingPos[color], [V.GetOppCol(color),'c'])
+      ? (color == "w" ? "0-1" : "1-0")
+      : "1/2";
+    this.turn = V.GetOppCol(this.turn);
+    return res;
+  }
+
+  evalPosition()
+  {
+    let evaluation = 0;
+    //Just count material for now, considering checkered neutral (...)
+    for (let i=0; i<V.size.x; i++)
+    {
+      for (let j=0; j<V.size.y; j++)
+      {
+        if (this.board[i][j] != V.EMPTY)
+        {
+          const sqColor = this.getColor(i,j);
+          const sign = sqColor == "w" ? 1 : (sqColor=="b" ? -1 : 0);
+          evaluation += sign * V.VALUES[this.getPiece(i,j)];
+        }
+      }
+    }
+    return evaluation;
+  }
+
+  static GenRandInitFen()
+  {
+    const randFen = ChessRules.GenRandInitFen();
+    // Add 16 pawns flags + empty cmove:
+    return randFen.replace(" w 0 1111", " w 0 11111111111111111111 -");
+  }
 
   static ParseFen(fen)
   {
@@ -301,17 +301,17 @@ export const VariantRules = class CheckeredRules extends ChessRules
     return super.getFen() + " " + cmoveFen;
   }
 
-	getFlagsFen()
-	{
-		let fen = super.getFlagsFen();
-		// Add pawns flags
-		for (let c of ['w','b'])
-		{
-			for (let i=0; i<8; i++)
-				fen += this.pawnFlags[c][i] ? '1' : '0';
-		}
-		return fen;
-	}
+  getFlagsFen()
+  {
+    let fen = super.getFlagsFen();
+    // Add pawns flags
+    for (let c of ['w','b'])
+    {
+      for (let i=0; i<8; i++)
+        fen += this.pawnFlags[c][i] ? '1' : '0';
+    }
+    return fen;
+  }
 
   // TODO (design): this cmove update here or in (un)updateVariables ?
   play(move)
@@ -326,46 +326,46 @@ export const VariantRules = class CheckeredRules extends ChessRules
     super.undo(move);
   }
 
-	getNotation(move)
-	{
-		if (move.appear.length == 2)
-		{
-			// Castle
-			if (move.end.y < move.start.y)
-				return "0-0-0";
-			else
-				return "0-0";
-		}
-
-		// Translate final square
-		const finalSquare = V.CoordsToSquare(move.end);
-
-		const piece = this.getPiece(move.start.x, move.start.y);
-		if (piece == V.PAWN)
-		{
-			// Pawn move
-			let notation = "";
-			if (move.vanish.length > 1)
-			{
-				// Capture
-				const startColumn = V.CoordToColumn(move.start.y);
-				notation = startColumn + "x" + finalSquare +
-					"=" + move.appear[0].p.toUpperCase();
-			}
-			else //no capture
-			{
-				notation = finalSquare;
-				if (move.appear.length > 0 && piece != move.appear[0].p) //promotion
-					notation += "=" + move.appear[0].p.toUpperCase();
-			}
-			return notation;
-		}
-
-		else
-		{
-			// Piece movement
-			return piece.toUpperCase() + (move.vanish.length > 1 ? "x" : "") + finalSquare
-				+ (move.vanish.length > 1 ? "=" + move.appear[0].p.toUpperCase() : "");
-		}
-	}
+  getNotation(move)
+  {
+    if (move.appear.length == 2)
+    {
+      // Castle
+      if (move.end.y < move.start.y)
+        return "0-0-0";
+      else
+        return "0-0";
+    }
+
+    // Translate final square
+    const finalSquare = V.CoordsToSquare(move.end);
+
+    const piece = this.getPiece(move.start.x, move.start.y);
+    if (piece == V.PAWN)
+    {
+      // Pawn move
+      let notation = "";
+      if (move.vanish.length > 1)
+      {
+        // Capture
+        const startColumn = V.CoordToColumn(move.start.y);
+        notation = startColumn + "x" + finalSquare +
+          "=" + move.appear[0].p.toUpperCase();
+      }
+      else //no capture
+      {
+        notation = finalSquare;
+        if (move.appear.length > 0 && piece != move.appear[0].p) //promotion
+          notation += "=" + move.appear[0].p.toUpperCase();
+      }
+      return notation;
+    }
+
+    else
+    {
+      // Piece movement
+      return piece.toUpperCase() + (move.vanish.length > 1 ? "x" : "") + finalSquare
+        + (move.vanish.length > 1 ? "=" + move.appear[0].p.toUpperCase() : "");
+    }
+  }
 }
diff --git a/client/src/variants/Chess960.js b/client/src/variants/Chess960.js
index 36397f69..74f4d1d5 100644
--- a/client/src/variants/Chess960.js
+++ b/client/src/variants/Chess960.js
@@ -1,5 +1,5 @@
 import { ChessRules } from "@/base_rules";
 export const VariantRules = class Chess960Rules extends ChessRules
 {
-	// Standard rules
+  // Standard rules
 }
diff --git a/client/src/variants/Crazyhouse.js b/client/src/variants/Crazyhouse.js
index ec903c2f..82997274 100644
--- a/client/src/variants/Crazyhouse.js
+++ b/client/src/variants/Crazyhouse.js
@@ -3,284 +3,284 @@ import { ArrayFun} from "@/utils/array";
 
 export const VariantRules = class CrazyhouseRules extends ChessRules
 {
-	static IsGoodFen(fen)
-	{
-		if (!ChessRules.IsGoodFen(fen))
-			return false;
-		const fenParsed = V.ParseFen(fen);
-		// 5) Check reserves
-		if (!fenParsed.reserve || !fenParsed.reserve.match(/^[0-9]{10,10}$/))
-			return false;
-		// 6) Check promoted array
-		if (!fenParsed.promoted)
-			return false;
-		if (fenParsed.promoted == "-")
-			return true; //no promoted piece on board
-		const squares = fenParsed.promoted.split(",");
-		for (let square of squares)
-		{
-			const c = V.SquareToCoords(square);
-			if (c.y < 0 || c.y > V.size.y || isNaN(c.x) || c.x < 0 || c.x > V.size.x)
-				return false;
-		}
-		return true;
-	}
+  static IsGoodFen(fen)
+  {
+    if (!ChessRules.IsGoodFen(fen))
+      return false;
+    const fenParsed = V.ParseFen(fen);
+    // 5) Check reserves
+    if (!fenParsed.reserve || !fenParsed.reserve.match(/^[0-9]{10,10}$/))
+      return false;
+    // 6) Check promoted array
+    if (!fenParsed.promoted)
+      return false;
+    if (fenParsed.promoted == "-")
+      return true; //no promoted piece on board
+    const squares = fenParsed.promoted.split(",");
+    for (let square of squares)
+    {
+      const c = V.SquareToCoords(square);
+      if (c.y < 0 || c.y > V.size.y || isNaN(c.x) || c.x < 0 || c.x > V.size.x)
+        return false;
+    }
+    return true;
+  }
 
-	static ParseFen(fen)
-	{
-		const fenParts = fen.split(" ");
-		return Object.assign(
-			ChessRules.ParseFen(fen),
-			{
-				reserve: fenParts[5],
-				promoted: fenParts[6],
-			}
-		);
-	}
+  static ParseFen(fen)
+  {
+    const fenParts = fen.split(" ");
+    return Object.assign(
+      ChessRules.ParseFen(fen),
+      {
+        reserve: fenParts[5],
+        promoted: fenParts[6],
+      }
+    );
+  }
 
-	static GenRandInitFen()
-	{
-		return ChessRules.GenRandInitFen() + " 0000000000 -";
-	}
+  static GenRandInitFen()
+  {
+    return ChessRules.GenRandInitFen() + " 0000000000 -";
+  }
 
-	getFen()
-	{
-		return super.getFen() + " " + this.getReserveFen() + " " + this.getPromotedFen();
-	}
+  getFen()
+  {
+    return super.getFen() + " " + this.getReserveFen() + " " + this.getPromotedFen();
+  }
 
-	getReserveFen()
-	{
-		let counts = new Array(10);
-		for (let i=0; i<V.PIECES.length-1; i++) //-1: no king reserve
-		{
-			counts[i] = this.reserve["w"][V.PIECES[i]];
-			counts[5+i] = this.reserve["b"][V.PIECES[i]];
-		}
-		return counts.join("");
-	}
+  getReserveFen()
+  {
+    let counts = new Array(10);
+    for (let i=0; i<V.PIECES.length-1; i++) //-1: no king reserve
+    {
+      counts[i] = this.reserve["w"][V.PIECES[i]];
+      counts[5+i] = this.reserve["b"][V.PIECES[i]];
+    }
+    return counts.join("");
+  }
 
-	getPromotedFen()
-	{
-		let res = "";
-		for (let i=0; i<V.size.x; i++)
-		{
-			for (let j=0; j<V.size.y; j++)
-			{
-				if (this.promoted[i][j])
-					res += V.CoordsToSquare({x:i,y:j});
-			}
-		}
-		if (res.length > 0)
-			res = res.slice(0,-1); //remove last comma
-		else
-			res = "-";
-		return res;
-	}
+  getPromotedFen()
+  {
+    let res = "";
+    for (let i=0; i<V.size.x; i++)
+    {
+      for (let j=0; j<V.size.y; j++)
+      {
+        if (this.promoted[i][j])
+          res += V.CoordsToSquare({x:i,y:j});
+      }
+    }
+    if (res.length > 0)
+      res = res.slice(0,-1); //remove last comma
+    else
+      res = "-";
+    return res;
+  }
 
-	setOtherVariables(fen)
-	{
-		super.setOtherVariables(fen);
-		const fenParsed = V.ParseFen(fen);
-		// Also init reserves (used by the interface to show landable pieces)
-		this.reserve =
-		{
-			"w":
-			{
-				[V.PAWN]: parseInt(fenParsed.reserve[0]),
-				[V.ROOK]: parseInt(fenParsed.reserve[1]),
-				[V.KNIGHT]: parseInt(fenParsed.reserve[2]),
-				[V.BISHOP]: parseInt(fenParsed.reserve[3]),
-				[V.QUEEN]: parseInt(fenParsed.reserve[4]),
-			},
-			"b":
-			{
-				[V.PAWN]: parseInt(fenParsed.reserve[5]),
-				[V.ROOK]: parseInt(fenParsed.reserve[6]),
-				[V.KNIGHT]: parseInt(fenParsed.reserve[7]),
-				[V.BISHOP]: parseInt(fenParsed.reserve[8]),
-				[V.QUEEN]: parseInt(fenParsed.reserve[9]),
-			}
-		};
-		this.promoted = ArrayFun.init(V.size.x, V.size.y, false);
-		if (fenParsed.promoted != "-")
-		{
-			for (let square of fenParsed.promoted.split(","))
-			{
-				const [x,y] = V.SquareToCoords(square);
-				promoted[x][y] = true;
-			}
-		}
-	}
+  setOtherVariables(fen)
+  {
+    super.setOtherVariables(fen);
+    const fenParsed = V.ParseFen(fen);
+    // Also init reserves (used by the interface to show landable pieces)
+    this.reserve =
+    {
+      "w":
+      {
+        [V.PAWN]: parseInt(fenParsed.reserve[0]),
+        [V.ROOK]: parseInt(fenParsed.reserve[1]),
+        [V.KNIGHT]: parseInt(fenParsed.reserve[2]),
+        [V.BISHOP]: parseInt(fenParsed.reserve[3]),
+        [V.QUEEN]: parseInt(fenParsed.reserve[4]),
+      },
+      "b":
+      {
+        [V.PAWN]: parseInt(fenParsed.reserve[5]),
+        [V.ROOK]: parseInt(fenParsed.reserve[6]),
+        [V.KNIGHT]: parseInt(fenParsed.reserve[7]),
+        [V.BISHOP]: parseInt(fenParsed.reserve[8]),
+        [V.QUEEN]: parseInt(fenParsed.reserve[9]),
+      }
+    };
+    this.promoted = ArrayFun.init(V.size.x, V.size.y, false);
+    if (fenParsed.promoted != "-")
+    {
+      for (let square of fenParsed.promoted.split(","))
+      {
+        const [x,y] = V.SquareToCoords(square);
+        promoted[x][y] = true;
+      }
+    }
+  }
 
-	getColor(i,j)
-	{
-		if (i >= V.size.x)
-			return (i==V.size.x ? "w" : "b");
-		return this.board[i][j].charAt(0);
-	}
+  getColor(i,j)
+  {
+    if (i >= V.size.x)
+      return (i==V.size.x ? "w" : "b");
+    return this.board[i][j].charAt(0);
+  }
 
-	getPiece(i,j)
-	{
-		if (i >= V.size.x)
-			return V.RESERVE_PIECES[j];
-		return this.board[i][j].charAt(1);
-	}
+  getPiece(i,j)
+  {
+    if (i >= V.size.x)
+      return V.RESERVE_PIECES[j];
+    return this.board[i][j].charAt(1);
+  }
 
-	// Used by the interface:
-	getReservePpath(color, index)
-	{
-		return color + V.RESERVE_PIECES[index];
-	}
+  // Used by the interface:
+  getReservePpath(color, index)
+  {
+    return color + V.RESERVE_PIECES[index];
+  }
 
-	// Ordering on reserve pieces
-	static get RESERVE_PIECES()
-	{
-		return [V.PAWN,V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN];
-	}
+  // Ordering on reserve pieces
+  static get RESERVE_PIECES()
+  {
+    return [V.PAWN,V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN];
+  }
 
-	getReserveMoves([x,y])
-	{
-		const color = this.turn;
-		const p = V.RESERVE_PIECES[y];
-		if (this.reserve[color][p] == 0)
-			return [];
-		let moves = [];
-		const pawnShift = (p==V.PAWN ? 1 : 0);
-		for (let i=pawnShift; i<V.size.x-pawnShift; i++)
-		{
-			for (let j=0; j<V.size.y; j++)
-			{
-				if (this.board[i][j] == V.EMPTY)
-				{
-					let mv = new Move({
-						appear: [
-							new PiPo({
-								x: i,
-								y: j,
-								c: color,
-								p: p
-							})
-						],
-						vanish: [],
-						start: {x:x, y:y}, //a bit artificial...
-						end: {x:i, y:j}
-					});
-					moves.push(mv);
-				}
-			}
-		}
-		return moves;
-	}
+  getReserveMoves([x,y])
+  {
+    const color = this.turn;
+    const p = V.RESERVE_PIECES[y];
+    if (this.reserve[color][p] == 0)
+      return [];
+    let moves = [];
+    const pawnShift = (p==V.PAWN ? 1 : 0);
+    for (let i=pawnShift; i<V.size.x-pawnShift; i++)
+    {
+      for (let j=0; j<V.size.y; j++)
+      {
+        if (this.board[i][j] == V.EMPTY)
+        {
+          let mv = new Move({
+            appear: [
+              new PiPo({
+                x: i,
+                y: j,
+                c: color,
+                p: p
+              })
+            ],
+            vanish: [],
+            start: {x:x, y:y}, //a bit artificial...
+            end: {x:i, y:j}
+          });
+          moves.push(mv);
+        }
+      }
+    }
+    return moves;
+  }
 
-	getPotentialMovesFrom([x,y])
-	{
-		if (x >= V.size.x)
-		{
-			// Reserves, outside of board: x == sizeX(+1)
-			return this.getReserveMoves([x,y]);
-		}
-		// Standard moves
-		return super.getPotentialMovesFrom([x,y]);
-	}
+  getPotentialMovesFrom([x,y])
+  {
+    if (x >= V.size.x)
+    {
+      // Reserves, outside of board: x == sizeX(+1)
+      return this.getReserveMoves([x,y]);
+    }
+    // Standard moves
+    return super.getPotentialMovesFrom([x,y]);
+  }
 
-	getAllValidMoves()
-	{
-		let moves = super.getAllValidMoves();
-		const color = this.turn;
-		for (let i=0; i<V.RESERVE_PIECES.length; i++)
-			moves = moves.concat(this.getReserveMoves([V.size.x+(color=="w"?0:1),i]));
-		return this.filterValid(moves);
-	}
+  getAllValidMoves()
+  {
+    let moves = super.getAllValidMoves();
+    const color = this.turn;
+    for (let i=0; i<V.RESERVE_PIECES.length; i++)
+      moves = moves.concat(this.getReserveMoves([V.size.x+(color=="w"?0:1),i]));
+    return this.filterValid(moves);
+  }
 
-	atLeastOneMove()
-	{
-		if (!super.atLeastOneMove())
-		{
-			const color = this.turn;
-			// Search one reserve move
-			for (let i=0; i<V.RESERVE_PIECES.length; i++)
-			{
-				let moves = this.filterValid(
-					this.getReserveMoves([V.size.x+(this.turn=="w"?0:1), i]) );
-				if (moves.length > 0)
-					return true;
-			}
-			return false;
-		}
-		return true;
-	}
+  atLeastOneMove()
+  {
+    if (!super.atLeastOneMove())
+    {
+      const color = this.turn;
+      // Search one reserve move
+      for (let i=0; i<V.RESERVE_PIECES.length; i++)
+      {
+        let moves = this.filterValid(
+          this.getReserveMoves([V.size.x+(this.turn=="w"?0:1), i]) );
+        if (moves.length > 0)
+          return true;
+      }
+      return false;
+    }
+    return true;
+  }
 
-	updateVariables(move)
-	{
-		super.updateVariables(move);
-		if (move.vanish.length == 2 && move.appear.length == 2)
-			return; //skip castle
-		const color = move.appear[0].c;
-		if (move.vanish.length == 0)
-		{
-			this.reserve[color][move.appear[0].p]--;
-			return;
-		}
-		move.movePromoted = this.promoted[move.start.x][move.start.y];
-		move.capturePromoted = this.promoted[move.end.x][move.end.y]
-		this.promoted[move.start.x][move.start.y] = false;
-		this.promoted[move.end.x][move.end.y] = move.movePromoted
-			|| (move.vanish[0].p == V.PAWN && move.appear[0].p != V.PAWN);
-		if (move.capturePromoted)
-			this.reserve[color][V.PAWN]++;
-		else if (move.vanish.length == 2)
-			this.reserve[color][move.vanish[1].p]++;
-	}
+  updateVariables(move)
+  {
+    super.updateVariables(move);
+    if (move.vanish.length == 2 && move.appear.length == 2)
+      return; //skip castle
+    const color = move.appear[0].c;
+    if (move.vanish.length == 0)
+    {
+      this.reserve[color][move.appear[0].p]--;
+      return;
+    }
+    move.movePromoted = this.promoted[move.start.x][move.start.y];
+    move.capturePromoted = this.promoted[move.end.x][move.end.y]
+    this.promoted[move.start.x][move.start.y] = false;
+    this.promoted[move.end.x][move.end.y] = move.movePromoted
+      || (move.vanish[0].p == V.PAWN && move.appear[0].p != V.PAWN);
+    if (move.capturePromoted)
+      this.reserve[color][V.PAWN]++;
+    else if (move.vanish.length == 2)
+      this.reserve[color][move.vanish[1].p]++;
+  }
 
-	unupdateVariables(move)
-	{
-		super.unupdateVariables(move);
-		if (move.vanish.length == 2 && move.appear.length == 2)
-			return;
-		const color = this.turn;
-		if (move.vanish.length == 0)
-		{
-			this.reserve[color][move.appear[0].p]++;
-			return;
-		}
-		if (move.movePromoted)
-			this.promoted[move.start.x][move.start.y] = true;
-		this.promoted[move.end.x][move.end.y] = move.capturePromoted;
-		if (move.capturePromoted)
-			this.reserve[color][V.PAWN]--;
-		else if (move.vanish.length == 2)
-			this.reserve[color][move.vanish[1].p]--;
-	}
+  unupdateVariables(move)
+  {
+    super.unupdateVariables(move);
+    if (move.vanish.length == 2 && move.appear.length == 2)
+      return;
+    const color = this.turn;
+    if (move.vanish.length == 0)
+    {
+      this.reserve[color][move.appear[0].p]++;
+      return;
+    }
+    if (move.movePromoted)
+      this.promoted[move.start.x][move.start.y] = true;
+    this.promoted[move.end.x][move.end.y] = move.capturePromoted;
+    if (move.capturePromoted)
+      this.reserve[color][V.PAWN]--;
+    else if (move.vanish.length == 2)
+      this.reserve[color][move.vanish[1].p]--;
+  }
 
-	static get SEARCH_DEPTH() { return 2; } //high branching factor
+  static get SEARCH_DEPTH() { return 2; } //high branching factor
 
-	evalPosition()
-	{
-		let evaluation = super.evalPosition();
-		// Add reserves:
-		for (let i=0; i<V.RESERVE_PIECES.length; i++)
-		{
-			const p = V.RESERVE_PIECES[i];
-			evaluation += this.reserve["w"][p] * V.VALUES[p];
-			evaluation -= this.reserve["b"][p] * V.VALUES[p];
-		}
-		return evaluation;
-	}
+  evalPosition()
+  {
+    let evaluation = super.evalPosition();
+    // Add reserves:
+    for (let i=0; i<V.RESERVE_PIECES.length; i++)
+    {
+      const p = V.RESERVE_PIECES[i];
+      evaluation += this.reserve["w"][p] * V.VALUES[p];
+      evaluation -= this.reserve["b"][p] * V.VALUES[p];
+    }
+    return evaluation;
+  }
 
-	getNotation(move)
-	{
-		if (move.vanish.length > 0)
-			return super.getNotation(move);
-		// Rebirth:
-		const piece =
-			(move.appear[0].p != V.PAWN ? move.appear[0].p.toUpperCase() : "");
-		return piece + "@" + V.CoordsToSquare(move.end);
-	}
+  getNotation(move)
+  {
+    if (move.vanish.length > 0)
+      return super.getNotation(move);
+    // Rebirth:
+    const piece =
+      (move.appear[0].p != V.PAWN ? move.appear[0].p.toUpperCase() : "");
+    return piece + "@" + V.CoordsToSquare(move.end);
+  }
 
-	getLongNotation(move)
-	{
-		if (move.vanish.length > 0)
-			return super.getLongNotation(move);
-		return "@" + V.CoordsToSquare(move.end);
-	}
+  getLongNotation(move)
+  {
+    if (move.vanish.length > 0)
+      return super.getLongNotation(move);
+    return "@" + V.CoordsToSquare(move.end);
+  }
 }
diff --git a/client/src/variants/Dark.js b/client/src/variants/Dark.js
index bc14cfa3..c0bfb09c 100644
--- a/client/src/variants/Dark.js
+++ b/client/src/variants/Dark.js
@@ -4,295 +4,295 @@ import { randInt } from "@/utils/alea";
 
 export const VariantRules = class DarkRules extends ChessRules
 {
-	// Standard rules, in the shadow
-	setOtherVariables(fen)
-	{
-		super.setOtherVariables(fen);
-		const [sizeX,sizeY] = [V.size.x,V.size.y];
-		this.enlightened = {
-			"w": ArrayFun.init(sizeX,sizeY),
-			"b": ArrayFun.init(sizeX,sizeY)
-		};
-		// Setup enlightened: squares reachable by each side
-		// (TODO: one side would be enough ?)
-		this.updateEnlightened();
-	}
+  // Standard rules, in the shadow
+  setOtherVariables(fen)
+  {
+    super.setOtherVariables(fen);
+    const [sizeX,sizeY] = [V.size.x,V.size.y];
+    this.enlightened = {
+      "w": ArrayFun.init(sizeX,sizeY),
+      "b": ArrayFun.init(sizeX,sizeY)
+    };
+    // Setup enlightened: squares reachable by each side
+    // (TODO: one side would be enough ?)
+    this.updateEnlightened();
+  }
 
-	updateEnlightened()
-	{
-		for (let i=0; i<V.size.x; i++)
-		{
-			for (let j=0; j<V.size.y; j++)
-			{
-				this.enlightened["w"][i][j] = false;
-				this.enlightened["b"][i][j] = false;
-			}
-		}
-		const pawnShift = {"w":-1, "b":1};
-		// Initialize with pieces positions (which are seen)
-		for (let i=0; i<V.size.x; i++)
-		{
-			for (let j=0; j<V.size.y; j++)
-			{
-				if (this.board[i][j] != V.EMPTY)
-				{
-					const color = this.getColor(i,j);
-					this.enlightened[color][i][j] = true;
-					// Add potential squares visible by "impossible pawn capture"
-					if (this.getPiece(i,j) == V.PAWN)
-					{
-						for (let shiftY of [-1,1])
-						{
-							if (V.OnBoard(i+pawnShift[color],j+shiftY)
-								&& this.board[i+pawnShift[color]][j+shiftY] == V.EMPTY)
-							{
-								this.enlightened[color][i+pawnShift[color]][j+shiftY] = true;
-							}
-						}
-					}
-				}
-			}
-		}
-		const currentTurn = this.turn;
-		this.turn = "w";
-		const movesWhite = this.getAllValidMoves();
-		this.turn = "b";
-		const movesBlack = this.getAllValidMoves();
-		this.turn = currentTurn;
-		for (let move of movesWhite)
-			this.enlightened["w"][move.end.x][move.end.y] = true;
-		for (let move of movesBlack)
-			this.enlightened["b"][move.end.x][move.end.y] = true;
-	}
+  updateEnlightened()
+  {
+    for (let i=0; i<V.size.x; i++)
+    {
+      for (let j=0; j<V.size.y; j++)
+      {
+        this.enlightened["w"][i][j] = false;
+        this.enlightened["b"][i][j] = false;
+      }
+    }
+    const pawnShift = {"w":-1, "b":1};
+    // Initialize with pieces positions (which are seen)
+    for (let i=0; i<V.size.x; i++)
+    {
+      for (let j=0; j<V.size.y; j++)
+      {
+        if (this.board[i][j] != V.EMPTY)
+        {
+          const color = this.getColor(i,j);
+          this.enlightened[color][i][j] = true;
+          // Add potential squares visible by "impossible pawn capture"
+          if (this.getPiece(i,j) == V.PAWN)
+          {
+            for (let shiftY of [-1,1])
+            {
+              if (V.OnBoard(i+pawnShift[color],j+shiftY)
+                && this.board[i+pawnShift[color]][j+shiftY] == V.EMPTY)
+              {
+                this.enlightened[color][i+pawnShift[color]][j+shiftY] = true;
+              }
+            }
+          }
+        }
+      }
+    }
+    const currentTurn = this.turn;
+    this.turn = "w";
+    const movesWhite = this.getAllValidMoves();
+    this.turn = "b";
+    const movesBlack = this.getAllValidMoves();
+    this.turn = currentTurn;
+    for (let move of movesWhite)
+      this.enlightened["w"][move.end.x][move.end.y] = true;
+    for (let move of movesBlack)
+      this.enlightened["b"][move.end.x][move.end.y] = true;
+  }
 
-	// Has to be redefined to avoid an infinite loop
-	getAllValidMoves()
-	{
-		const color = this.turn;
-		const oppCol = V.GetOppCol(color);
-		let potentialMoves = [];
-		for (let i=0; i<V.size.x; i++)
-		{
-			for (let j=0; j<V.size.y; j++)
-			{
-				if (this.board[i][j] != V.EMPTY && this.getColor(i,j) == color)
-					Array.prototype.push.apply(potentialMoves, this.getPotentialMovesFrom([i,j]));
-			}
-		}
-		return potentialMoves; //because there are no checks
-	}
+  // Has to be redefined to avoid an infinite loop
+  getAllValidMoves()
+  {
+    const color = this.turn;
+    const oppCol = V.GetOppCol(color);
+    let potentialMoves = [];
+    for (let i=0; i<V.size.x; i++)
+    {
+      for (let j=0; j<V.size.y; j++)
+      {
+        if (this.board[i][j] != V.EMPTY && this.getColor(i,j) == color)
+          Array.prototype.push.apply(potentialMoves, this.getPotentialMovesFrom([i,j]));
+      }
+    }
+    return potentialMoves; //because there are no checks
+  }
 
-	atLeastOneMove()
-	{
-		if (this.kingPos[this.turn][0] < 0)
-			return false;
-		return true; //TODO: is it right?
-	}
+  atLeastOneMove()
+  {
+    if (this.kingPos[this.turn][0] < 0)
+      return false;
+    return true; //TODO: is it right?
+  }
 
-	underCheck(color)
-	{
-		return false; //there is no check
-	}
+  underCheck(color)
+  {
+    return false; //there is no check
+  }
 
-	getCheckSquares(color)
-	{
-		return [];
-	}
+  getCheckSquares(color)
+  {
+    return [];
+  }
 
-	updateVariables(move)
-	{
-		super.updateVariables(move);
-		if (move.vanish.length >= 2 && move.vanish[1].p == V.KING)
-		{
-			// We took opponent king ! (because if castle vanish[1] is a rook)
-			this.kingPos[this.turn] = [-1,-1];
-		}
+  updateVariables(move)
+  {
+    super.updateVariables(move);
+    if (move.vanish.length >= 2 && move.vanish[1].p == V.KING)
+    {
+      // We took opponent king ! (because if castle vanish[1] is a rook)
+      this.kingPos[this.turn] = [-1,-1];
+    }
 
-		// Update lights for both colors:
-		this.updateEnlightened();
-	}
+    // Update lights for both colors:
+    this.updateEnlightened();
+  }
 
-	unupdateVariables(move)
-	{
-		super.unupdateVariables(move);
-		const c = move.vanish[0].c;
-		const oppCol = V.GetOppCol(c);
-		if (this.kingPos[oppCol][0] < 0)
-		{
-			// Last move took opponent's king
-			for (let psq of move.vanish)
-			{
-				if (psq.p == 'k')
-				{
-					this.kingPos[oppCol] = [psq.x, psq.y];
-					break;
-				}
-			}
-		}
+  unupdateVariables(move)
+  {
+    super.unupdateVariables(move);
+    const c = move.vanish[0].c;
+    const oppCol = V.GetOppCol(c);
+    if (this.kingPos[oppCol][0] < 0)
+    {
+      // Last move took opponent's king
+      for (let psq of move.vanish)
+      {
+        if (psq.p == 'k')
+        {
+          this.kingPos[oppCol] = [psq.x, psq.y];
+          break;
+        }
+      }
+    }
 
-		// Update lights for both colors:
-		this.updateEnlightened();
-	}
+    // Update lights for both colors:
+    this.updateEnlightened();
+  }
 
   getCurrentScore()
   {
-		const color = this.turn;
-		const kp = this.kingPos[color];
-		if (kp[0] < 0) //king disappeared
-			return (color == "w" ? "0-1" : "1-0");
+    const color = this.turn;
+    const kp = this.kingPos[color];
+    if (kp[0] < 0) //king disappeared
+      return (color == "w" ? "0-1" : "1-0");
     if (this.atLeastOneMove()) // game not over
       return "*";
     return "1/2"; //no moves but kings still there (seems impossible)
-	}
+  }
 
-	static get THRESHOLD_MATE()
-	{
-		return 500; //checkmates evals may be slightly below 1000
-	}
+  static get THRESHOLD_MATE()
+  {
+    return 500; //checkmates evals may be slightly below 1000
+  }
 
-	// In this special situation, we just look 1 half move ahead
-	getComputerMove()
-	{
-		const maxeval = V.INFINITY;
-		const color = this.turn;
-		const oppCol = V.GetOppCol(color);
-		const pawnShift = (color == "w" ? -1 : 1);
+  // In this special situation, we just look 1 half move ahead
+  getComputerMove()
+  {
+    const maxeval = V.INFINITY;
+    const color = this.turn;
+    const oppCol = V.GetOppCol(color);
+    const pawnShift = (color == "w" ? -1 : 1);
 
-		// Do not cheat: the current enlightment is all we can see
-		const myLight = JSON.parse(JSON.stringify(this.enlightened[color]));
+    // Do not cheat: the current enlightment is all we can see
+    const myLight = JSON.parse(JSON.stringify(this.enlightened[color]));
 
-		// Can a slider on (i,j) apparently take my king?
-		// NOTE: inaccurate because assume yes if some squares are shadowed
-		const sliderTake = ([i,j], piece) => {
-		  const kp = this.kingPos[color];
-			let step = undefined;
-			if (piece == V.BISHOP)
-			{
-				if (Math.abs(kp[0] - i) == Math.abs(kp[1] - j))
-				{
-					step =
-					[
-						(i-kp[0]) / Math.abs(i-kp[0]),
-						(j-kp[1]) / Math.abs(j-kp[1])
-					];
-				}
-			}
-			else if (piece == V.ROOK)
-			{
-				if (kp[0] == i)
-					step = [0, (j-kp[1]) / Math.abs(j-kp[1])];
-				else if (kp[1] == j)
-					step = [(i-kp[0]) / Math.abs(i-kp[0]), 0];
-			}
-			if (!step)
-				return false;
-			// Check for obstacles
-			let obstacle = false;
-			for (
+    // Can a slider on (i,j) apparently take my king?
+    // NOTE: inaccurate because assume yes if some squares are shadowed
+    const sliderTake = ([i,j], piece) => {
+      const kp = this.kingPos[color];
+      let step = undefined;
+      if (piece == V.BISHOP)
+      {
+        if (Math.abs(kp[0] - i) == Math.abs(kp[1] - j))
+        {
+          step =
+          [
+            (i-kp[0]) / Math.abs(i-kp[0]),
+            (j-kp[1]) / Math.abs(j-kp[1])
+          ];
+        }
+      }
+      else if (piece == V.ROOK)
+      {
+        if (kp[0] == i)
+          step = [0, (j-kp[1]) / Math.abs(j-kp[1])];
+        else if (kp[1] == j)
+          step = [(i-kp[0]) / Math.abs(i-kp[0]), 0];
+      }
+      if (!step)
+        return false;
+      // Check for obstacles
+      let obstacle = false;
+      for (
         let x=kp[0]+step[0], y=kp[1]+step[1];
-			  x != i && y != j;
-				x += step[0], y += step[1])
+        x != i && y != j;
+        x += step[0], y += step[1])
       {
-				if (myLight[x][y] && this.board[x][y] != V.EMPTY)
-				{
-					obstacle = true;
-					break;
-				}
-			}
-			if (!obstacle)
-				return true;
-			return false;
-		};
+        if (myLight[x][y] && this.board[x][y] != V.EMPTY)
+        {
+          obstacle = true;
+          break;
+        }
+      }
+      if (!obstacle)
+        return true;
+      return false;
+    };
 
-		// Do I see something which can take my king ?
-		const kingThreats = () => {
-		  const kp = this.kingPos[color];
-			for (let i=0; i<V.size.x; i++)
-			{
-				for (let j=0; j<V.size.y; j++)
-				{
-					if (myLight[i][j] && this.board[i][j] != V.EMPTY
-						&& this.getColor(i,j) != color)
-					{
-						switch (this.getPiece(i,j))
-						{
-							case V.PAWN:
-								if (kp[0] + pawnShift == i && Math.abs(kp[1]-j) == 1)
-									return true;
-								break;
-							case V.KNIGHT:
-								if ((Math.abs(kp[0] - i) == 2 && Math.abs(kp[1] - j) == 1) ||
-									(Math.abs(kp[0] - i) == 1 && Math.abs(kp[1] - j) == 2))
-								{
-									return true;
-								}
-								break;
-							case V.KING:
-								if (Math.abs(kp[0] - i) == 1 && Math.abs(kp[1] - j) == 1)
-									return true;
-								break;
-							case V.BISHOP:
-								if (sliderTake([i,j], V.BISHOP))
-									return true;
-								break;
-							case V.ROOK:
-								if (sliderTake([i,j], V.ROOK))
-									return true;
-								break;
-							case V.QUEEN:
-								if (sliderTake([i,j], V.BISHOP) || sliderTake([i,j], V.ROOK))
-									return true;
-								break;
-						}
-					}
-				}
-			}
-			return false;
-		};
+    // Do I see something which can take my king ?
+    const kingThreats = () => {
+      const kp = this.kingPos[color];
+      for (let i=0; i<V.size.x; i++)
+      {
+        for (let j=0; j<V.size.y; j++)
+        {
+          if (myLight[i][j] && this.board[i][j] != V.EMPTY
+            && this.getColor(i,j) != color)
+          {
+            switch (this.getPiece(i,j))
+            {
+              case V.PAWN:
+                if (kp[0] + pawnShift == i && Math.abs(kp[1]-j) == 1)
+                  return true;
+                break;
+              case V.KNIGHT:
+                if ((Math.abs(kp[0] - i) == 2 && Math.abs(kp[1] - j) == 1) ||
+                  (Math.abs(kp[0] - i) == 1 && Math.abs(kp[1] - j) == 2))
+                {
+                  return true;
+                }
+                break;
+              case V.KING:
+                if (Math.abs(kp[0] - i) == 1 && Math.abs(kp[1] - j) == 1)
+                  return true;
+                break;
+              case V.BISHOP:
+                if (sliderTake([i,j], V.BISHOP))
+                  return true;
+                break;
+              case V.ROOK:
+                if (sliderTake([i,j], V.ROOK))
+                  return true;
+                break;
+              case V.QUEEN:
+                if (sliderTake([i,j], V.BISHOP) || sliderTake([i,j], V.ROOK))
+                  return true;
+                break;
+            }
+          }
+        }
+      }
+      return false;
+    };
 
-		let moves = this.getAllValidMoves();
-		for (let move of moves)
-		{
-			this.play(move);
-			if (this.kingPos[oppCol][0] >= 0 && kingThreats())
-			{
-				// We didn't take opponent king, and our king will be captured: bad
-				move.eval = -maxeval;
-			}
-			this.undo(move);
+    let moves = this.getAllValidMoves();
+    for (let move of moves)
+    {
+      this.play(move);
+      if (this.kingPos[oppCol][0] >= 0 && kingThreats())
+      {
+        // We didn't take opponent king, and our king will be captured: bad
+        move.eval = -maxeval;
+      }
+      this.undo(move);
 
       if (!!move.eval)
-				continue;
+        continue;
 
-			move.eval = 0; //a priori...
+      move.eval = 0; //a priori...
 
-			// Can I take something ? If yes, do it if it seems good...
-			if (move.vanish.length == 2 && move.vanish[1].c != color) //avoid castle
-			{
-				const myPieceVal = V.VALUES[move.appear[0].p];
-				const hisPieceVal = V.VALUES[move.vanish[1].p];
-				if (myPieceVal <= hisPieceVal)
-					move.eval = hisPieceVal - myPieceVal + 2; //favor captures
-				else
-				{
-					// Taking a pawn with minor piece,
-					// or minor piece or pawn with a rook,
-					// or anything but a queen with a queen,
-					// or anything with a king.
-					// ==> Do it at random, although
-					//     this is clearly inferior to what a human can deduce...
-					move.eval = (Math.random() < 0.5 ? 1 : -1);
-				}
-			}
-		}
+      // Can I take something ? If yes, do it if it seems good...
+      if (move.vanish.length == 2 && move.vanish[1].c != color) //avoid castle
+      {
+        const myPieceVal = V.VALUES[move.appear[0].p];
+        const hisPieceVal = V.VALUES[move.vanish[1].p];
+        if (myPieceVal <= hisPieceVal)
+          move.eval = hisPieceVal - myPieceVal + 2; //favor captures
+        else
+        {
+          // Taking a pawn with minor piece,
+          // or minor piece or pawn with a rook,
+          // or anything but a queen with a queen,
+          // or anything with a king.
+          // ==> Do it at random, although
+          //     this is clearly inferior to what a human can deduce...
+          move.eval = (Math.random() < 0.5 ? 1 : -1);
+        }
+      }
+    }
 
-		// TODO: also need to implement the case when an opponent piece (in light)
-		// is threatening something - maybe not the king, but e.g. pawn takes rook.
+    // TODO: also need to implement the case when an opponent piece (in light)
+    // is threatening something - maybe not the king, but e.g. pawn takes rook.
 
-		moves.sort((a,b) => b.eval - a.eval);
-		let candidates = [0];
-		for (let j=1; j<moves.length && moves[j].eval == moves[0].eval; j++)
-			candidates.push(j);
-		return moves[candidates[randInt(candidates.length)]];
-	}
+    moves.sort((a,b) => b.eval - a.eval);
+    let candidates = [0];
+    for (let j=1; j<moves.length && moves[j].eval == moves[0].eval; j++)
+      candidates.push(j);
+    return moves[candidates[randInt(candidates.length)]];
+  }
 }
diff --git a/client/src/variants/Extinction.js b/client/src/variants/Extinction.js
index 748f9e46..8d860aeb 100644
--- a/client/src/variants/Extinction.js
+++ b/client/src/variants/Extinction.js
@@ -2,130 +2,130 @@ import { ChessRules } from "@/base_rules";
 
 export const VariantRules = class  ExtinctionRules extends ChessRules
 {
-	setOtherVariables(fen)
-	{
-		super.setOtherVariables(fen);
-		const pos = V.ParseFen(fen).position;
-		// NOTE: no need for safety "|| []", because each piece type must be present
-		// (otherwise game is already over!)
-		this.material =
-		{
-			"w":
-			{
-				[V.KING]: pos.match(/K/g).length,
-				[V.QUEEN]: pos.match(/Q/g).length,
-				[V.ROOK]: pos.match(/R/g).length,
-				[V.KNIGHT]: pos.match(/N/g).length,
-				[V.BISHOP]: pos.match(/B/g).length,
-				[V.PAWN]: pos.match(/P/g).length
-			},
-			"b":
-			{
-				[V.KING]: pos.match(/k/g).length,
-				[V.QUEEN]: pos.match(/q/g).length,
-				[V.ROOK]: pos.match(/r/g).length,
-				[V.KNIGHT]: pos.match(/n/g).length,
-				[V.BISHOP]: pos.match(/b/g).length,
-				[V.PAWN]: pos.match(/p/g).length
-			}
-		};
-	}
+  setOtherVariables(fen)
+  {
+    super.setOtherVariables(fen);
+    const pos = V.ParseFen(fen).position;
+    // NOTE: no need for safety "|| []", because each piece type must be present
+    // (otherwise game is already over!)
+    this.material =
+    {
+      "w":
+      {
+        [V.KING]: pos.match(/K/g).length,
+        [V.QUEEN]: pos.match(/Q/g).length,
+        [V.ROOK]: pos.match(/R/g).length,
+        [V.KNIGHT]: pos.match(/N/g).length,
+        [V.BISHOP]: pos.match(/B/g).length,
+        [V.PAWN]: pos.match(/P/g).length
+      },
+      "b":
+      {
+        [V.KING]: pos.match(/k/g).length,
+        [V.QUEEN]: pos.match(/q/g).length,
+        [V.ROOK]: pos.match(/r/g).length,
+        [V.KNIGHT]: pos.match(/n/g).length,
+        [V.BISHOP]: pos.match(/b/g).length,
+        [V.PAWN]: pos.match(/p/g).length
+      }
+    };
+  }
 
-	getPotentialPawnMoves([x,y])
-	{
-		let moves = super.getPotentialPawnMoves([x,y]);
-		// Add potential promotions into king
-		const color = this.turn;
-		const shift = (color == "w" ? -1 : 1);
-		const lastRank = (color == "w" ? 0 : V.size.x-1);
+  getPotentialPawnMoves([x,y])
+  {
+    let moves = super.getPotentialPawnMoves([x,y]);
+    // Add potential promotions into king
+    const color = this.turn;
+    const shift = (color == "w" ? -1 : 1);
+    const lastRank = (color == "w" ? 0 : V.size.x-1);
 
-		if (x+shift == lastRank)
-		{
-			// Normal move
-			if (this.board[x+shift][y] == V.EMPTY)
-				moves.push(this.getBasicMove([x,y], [x+shift,y], {c:color,p:V.KING}));
-			// Captures
-			if (y>0 && this.board[x+shift][y-1] != V.EMPTY
-				&& this.canTake([x,y], [x+shift,y-1]))
-			{
-				moves.push(this.getBasicMove([x,y], [x+shift,y-1], {c:color,p:V.KING}));
-			}
-			if (y<V.size.y-1 && this.board[x+shift][y+1] != V.EMPTY
-				&& this.canTake([x,y], [x+shift,y+1]))
-			{
-				moves.push(this.getBasicMove([x,y], [x+shift,y+1], {c:color,p:V.KING}));
-			}
-		}
+    if (x+shift == lastRank)
+    {
+      // Normal move
+      if (this.board[x+shift][y] == V.EMPTY)
+        moves.push(this.getBasicMove([x,y], [x+shift,y], {c:color,p:V.KING}));
+      // Captures
+      if (y>0 && this.board[x+shift][y-1] != V.EMPTY
+        && this.canTake([x,y], [x+shift,y-1]))
+      {
+        moves.push(this.getBasicMove([x,y], [x+shift,y-1], {c:color,p:V.KING}));
+      }
+      if (y<V.size.y-1 && this.board[x+shift][y+1] != V.EMPTY
+        && this.canTake([x,y], [x+shift,y+1]))
+      {
+        moves.push(this.getBasicMove([x,y], [x+shift,y+1], {c:color,p:V.KING}));
+      }
+    }
 
-		return moves;
-	}
+    return moves;
+  }
 
-	// TODO: verify this assertion
-	atLeastOneMove()
-	{
-		return true; //always at least one possible move
-	}
+  // TODO: verify this assertion
+  atLeastOneMove()
+  {
+    return true; //always at least one possible move
+  }
 
-	underCheck(color)
-	{
-		return false; //there is no check
-	}
+  underCheck(color)
+  {
+    return false; //there is no check
+  }
 
-	getCheckSquares(color)
-	{
-		return [];
-	}
+  getCheckSquares(color)
+  {
+    return [];
+  }
 
-	updateVariables(move)
-	{
-		super.updateVariables(move);
-		// Treat the promotion case: (not the capture part)
-		if (move.appear[0].p != move.vanish[0].p)
-		{
-			this.material[move.appear[0].c][move.appear[0].p]++;
-			this.material[move.appear[0].c][V.PAWN]--;
-		}
-		if (move.vanish.length==2 && move.appear.length==1) //capture
-			this.material[move.vanish[1].c][move.vanish[1].p]--;
-	}
+  updateVariables(move)
+  {
+    super.updateVariables(move);
+    // Treat the promotion case: (not the capture part)
+    if (move.appear[0].p != move.vanish[0].p)
+    {
+      this.material[move.appear[0].c][move.appear[0].p]++;
+      this.material[move.appear[0].c][V.PAWN]--;
+    }
+    if (move.vanish.length==2 && move.appear.length==1) //capture
+      this.material[move.vanish[1].c][move.vanish[1].p]--;
+  }
 
-	unupdateVariables(move)
-	{
-		super.unupdateVariables(move);
-		if (move.appear[0].p != move.vanish[0].p)
-		{
-			this.material[move.appear[0].c][move.appear[0].p]--;
-			this.material[move.appear[0].c][V.PAWN]++;
-		}
-		if (move.vanish.length==2 && move.appear.length==1)
-			this.material[move.vanish[1].c][move.vanish[1].p]++;
-	}
+  unupdateVariables(move)
+  {
+    super.unupdateVariables(move);
+    if (move.appear[0].p != move.vanish[0].p)
+    {
+      this.material[move.appear[0].c][move.appear[0].p]--;
+      this.material[move.appear[0].c][V.PAWN]++;
+    }
+    if (move.vanish.length==2 && move.appear.length==1)
+      this.material[move.vanish[1].c][move.vanish[1].p]++;
+  }
 
-	getCurrentScore()
-	{
-		if (this.atLeastOneMove()) // game not over?
-		{
-			const color = this.turn;
-			if (Object.keys(this.material[color]).some(
-				p => { return this.material[color][p] == 0; }))
-			{
-				return (this.turn == "w" ? "0-1" : "1-0");
-			}
-			return "*";
-		}
+  getCurrentScore()
+  {
+    if (this.atLeastOneMove()) // game not over?
+    {
+      const color = this.turn;
+      if (Object.keys(this.material[color]).some(
+        p => { return this.material[color][p] == 0; }))
+      {
+        return (this.turn == "w" ? "0-1" : "1-0");
+      }
+      return "*";
+    }
 
-		return (this.turn == "w" ? "0-1" : "1-0"); //NOTE: currently unreachable...
-	}
+    return (this.turn == "w" ? "0-1" : "1-0"); //NOTE: currently unreachable...
+  }
 
-	evalPosition()
-	{
-		const color = this.turn;
-		if (Object.keys(this.material[color]).some(
-			p => { return this.material[color][p] == 0; }))
-		{
-			// Very negative (resp. positive) if white (reps. black) pieces set is incomplete
-			return (color=="w"?-1:1) * V.INFINITY;
-		}
-		return super.evalPosition();
-	}
+  evalPosition()
+  {
+    const color = this.turn;
+    if (Object.keys(this.material[color]).some(
+      p => { return this.material[color][p] == 0; }))
+    {
+      // Very negative (resp. positive) if white (reps. black) pieces set is incomplete
+      return (color=="w"?-1:1) * V.INFINITY;
+    }
+    return super.evalPosition();
+  }
 }
diff --git a/client/src/variants/Grand.js b/client/src/variants/Grand.js
index 6ddfbd7c..a53f59e9 100644
--- a/client/src/variants/Grand.js
+++ b/client/src/variants/Grand.js
@@ -6,388 +6,388 @@ import { randInt } from "@/utils/alea";
 // https://www.chessvariants.com/large.dir/freeling.html
 export const VariantRules = class GrandRules extends ChessRules
 {
-	static getPpath(b)
-	{
-		return ([V.MARSHALL,V.CARDINAL].includes(b[1]) ? "Grand/" : "") + b;
-	}
-
-	static IsGoodFen(fen)
-	{
-		if (!ChessRules.IsGoodFen(fen))
-			return false;
-		const fenParsed = V.ParseFen(fen);
-		// 5) Check captures
-		if (!fenParsed.captured || !fenParsed.captured.match(/^[0-9]{14,14}$/))
-			return false;
-		return true;
-	}
-
-	static IsGoodEnpassant(enpassant)
-	{
-		if (enpassant != "-")
-		{
-			const squares = enpassant.split(",");
-			if (squares.length > 2)
-				return false;
-			for (let sq of squares)
-			{
-				const ep = V.SquareToCoords(sq);
-				if (isNaN(ep.x) || !V.OnBoard(ep))
-					return false;
-			}
-		}
-		return true;
-	}
-
-	static ParseFen(fen)
-	{
-		const fenParts = fen.split(" ");
-		return Object.assign(
-			ChessRules.ParseFen(fen),
-			{ captured: fenParts[5] }
-		);
-	}
-
-	getFen()
-	{
-		return super.getFen() + " " + this.getCapturedFen();
-	}
-
-	getCapturedFen()
-	{
-		let counts = [...Array(14).fill(0)];
-		let i = 0;
-		for (let j=0; j<V.PIECES.length; j++)
-		{
-			if (V.PIECES[j] == V.KING) //no king captured
-				continue;
-			counts[i] = this.captured["w"][V.PIECES[i]];
-			counts[7+i] = this.captured["b"][V.PIECES[i]];
-			i++;
-		}
-		return counts.join("");
-	}
-
-	setOtherVariables(fen)
-	{
-		super.setOtherVariables(fen);
-		const fenParsed = V.ParseFen(fen);
-		// Initialize captured pieces' counts from FEN
-		this.captured =
-		{
-			"w":
-			{
-				[V.PAWN]: parseInt(fenParsed.captured[0]),
-				[V.ROOK]: parseInt(fenParsed.captured[1]),
-				[V.KNIGHT]: parseInt(fenParsed.captured[2]),
-				[V.BISHOP]: parseInt(fenParsed.captured[3]),
-				[V.QUEEN]: parseInt(fenParsed.captured[4]),
-				[V.MARSHALL]: parseInt(fenParsed.captured[5]),
-				[V.CARDINAL]: parseInt(fenParsed.captured[6]),
-			},
-			"b":
-			{
-				[V.PAWN]: parseInt(fenParsed.captured[7]),
-				[V.ROOK]: parseInt(fenParsed.captured[8]),
-				[V.KNIGHT]: parseInt(fenParsed.captured[9]),
-				[V.BISHOP]: parseInt(fenParsed.captured[10]),
-				[V.QUEEN]: parseInt(fenParsed.captured[11]),
-				[V.MARSHALL]: parseInt(fenParsed.captured[12]),
-				[V.CARDINAL]: parseInt(fenParsed.captured[13]),
-			}
-		};
-	}
-
-	static get size() { return {x:10,y:10}; }
-
-	static get MARSHALL() { return 'm'; } //rook+knight
-	static get CARDINAL() { return 'c'; } //bishop+knight
-
-	static get PIECES()
-	{
-		return ChessRules.PIECES.concat([V.MARSHALL,V.CARDINAL]);
-	}
-
-	// There may be 2 enPassant squares (if pawn jump 3 squares)
-	getEnpassantFen()
-	{
-		const L = this.epSquares.length;
-		if (!this.epSquares[L-1])
-			return "-"; //no en-passant
-		let res = "";
-		this.epSquares[L-1].forEach(sq => {
-			res += V.CoordsToSquare(sq) + ",";
-		});
-		return res.slice(0,-1); //remove last comma
-	}
-
-	// En-passant after 2-sq or 3-sq jumps
-	getEpSquare(moveOrSquare)
-	{
-		if (!moveOrSquare)
-			return undefined;
-		if (typeof moveOrSquare === "string")
-		{
-			const square = moveOrSquare;
-			if (square == "-")
-				return undefined;
-			let res = [];
-			square.split(",").forEach(sq => {
-				res.push(V.SquareToCoords(sq));
-			});
-			return res;
-		}
-		// Argument is a move:
-		const move = moveOrSquare;
-		const [sx,sy,ex] = [move.start.x,move.start.y,move.end.x];
-		if (this.getPiece(sx,sy) == V.PAWN && Math.abs(sx - ex) >= 2)
-		{
-			const step = (ex-sx) / Math.abs(ex-sx);
-			let res = [{
-				x: sx + step,
-				y: sy
-			}];
-			if (sx + 2*step != ex) //3-squares move
-			{
-				res.push({
-					x: sx + 2*step,
-					y: sy
-				});
-			}
-			return res;
-		}
-		return undefined; //default
-	}
-
-	getPotentialMovesFrom([x,y])
-	{
-		switch (this.getPiece(x,y))
-		{
-			case V.MARSHALL:
-				return this.getPotentialMarshallMoves([x,y]);
-			case V.CARDINAL:
-				return this.getPotentialCardinalMoves([x,y]);
-			default:
-				return super.getPotentialMovesFrom([x,y])
-		}
-	}
-
-	// Special pawn rules: promotions to captured friendly pieces,
-	// optional on ranks 8-9 and mandatory on rank 10.
-	getPotentialPawnMoves([x,y])
-	{
-		const color = this.turn;
-		let moves = [];
-		const [sizeX,sizeY] = [V.size.x,V.size.y];
-		const shiftX = (color == "w" ? -1 : 1);
-		const startRanks = (color == "w" ? [sizeX-2,sizeX-3] : [1,2]);
-		const lastRanks = (color == "w" ? [0,1,2] : [sizeX-1,sizeX-2,sizeX-3]);
-		const promotionPieces =
-			[V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN,V.MARSHALL,V.CARDINAL];
-
-		// Always x+shiftX >= 0 && x+shiftX < sizeX, because no pawns on last rank
-		let finalPieces = undefined;
-		if (lastRanks.includes(x + shiftX))
-		{
-			finalPieces = promotionPieces.filter(p => this.captured[color][p] > 0);
-			if (x + shiftX != lastRanks[0])
-				finalPieces.push(V.PAWN);
-		}
-		else
-			finalPieces = [V.PAWN];
-		if (this.board[x+shiftX][y] == V.EMPTY)
-		{
-			// One square forward
-			for (let piece of finalPieces)
-				moves.push(this.getBasicMove([x,y], [x+shiftX,y], {c:color,p:piece}));
-			if (startRanks.includes(x))
-			{
-				if (this.board[x+2*shiftX][y] == V.EMPTY)
-				{
-					// Two squares jump
-					moves.push(this.getBasicMove([x,y], [x+2*shiftX,y]));
-					if (x==startRanks[0] && this.board[x+3*shiftX][y] == V.EMPTY)
-					{
-						// Three squares jump
-						moves.push(this.getBasicMove([x,y], [x+3*shiftX,y]));
-					}
-				}
-			}
-		}
-		// Captures
-		for (let shiftY of [-1,1])
-		{
-			if (y + shiftY >= 0 && y + shiftY < sizeY
-				&& this.board[x+shiftX][y+shiftY] != V.EMPTY
-				&& this.canTake([x,y], [x+shiftX,y+shiftY]))
-			{
-				for (let piece of finalPieces)
-				{
-					moves.push(this.getBasicMove([x,y], [x+shiftX,y+shiftY],
-						{c:color,p:piece}));
-				}
-			}
-		}
-
-		// En passant
-		const Lep = this.epSquares.length;
-		const epSquare = this.epSquares[Lep-1];
-		if (!!epSquare)
-		{
-			for (let epsq of epSquare)
-			{
-				// TODO: some redundant checks
-				if (epsq.x == x+shiftX && Math.abs(epsq.y - y) == 1)
-				{
-					var enpassantMove = this.getBasicMove([x,y], [epsq.x,epsq.y]);
-					// WARNING: the captured pawn may be diagonally behind us,
-					// if it's a 3-squares jump and we take on 1st passing square
-					const px = (this.board[x][epsq.y] != V.EMPTY ? x : x - shiftX);
-					enpassantMove.vanish.push({
-						x: px,
-						y: epsq.y,
-						p: 'p',
-						c: this.getColor(px,epsq.y)
-					});
-					moves.push(enpassantMove);
-				}
-			}
-		}
-
-		return moves;
-	}
-
-	// TODO: different castle?
-
-	getPotentialMarshallMoves(sq)
-	{
-		return this.getSlideNJumpMoves(sq, V.steps[V.ROOK]).concat(
-			this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep"));
-	}
-
-	getPotentialCardinalMoves(sq)
-	{
-		return this.getSlideNJumpMoves(sq, V.steps[V.BISHOP]).concat(
-			this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep"));
-	}
-
-	isAttacked(sq, colors)
-	{
-		return super.isAttacked(sq, colors)
-			|| this.isAttackedByMarshall(sq, colors)
-			|| this.isAttackedByCardinal(sq, colors);
-	}
-
-	isAttackedByMarshall(sq, colors)
-	{
-		return this.isAttackedBySlideNJump(sq, colors, V.MARSHALL, V.steps[V.ROOK])
-			|| this.isAttackedBySlideNJump(
-				sq, colors, V.MARSHALL, V.steps[V.KNIGHT], "oneStep");
-	}
-
-	isAttackedByCardinal(sq, colors)
-	{
-		return this.isAttackedBySlideNJump(sq, colors, V.CARDINAL, V.steps[V.BISHOP])
-			|| this.isAttackedBySlideNJump(
-				sq, colors, V.CARDINAL, V.steps[V.KNIGHT], "oneStep");
-	}
-
-	updateVariables(move)
-	{
-		super.updateVariables(move);
-		if (move.vanish.length == 2 && move.appear.length == 1)
-		{
-			// Capture: update this.captured
-			this.captured[move.vanish[1].c][move.vanish[1].p]++;
-		}
-		if (move.vanish[0].p != move.appear[0].p)
-		{
-			// Promotion: update this.captured
-			this.captured[move.vanish[0].c][move.appear[0].p]--;
-		}
-	}
-
-	unupdateVariables(move)
-	{
-		super.unupdateVariables(move);
-		if (move.vanish.length == 2 && move.appear.length == 1)
-			this.captured[move.vanish[1].c][move.vanish[1].p]--;
-		if (move.vanish[0].p != move.appear[0].p)
-			this.captured[move.vanish[0].c][move.appear[0].p]++;
-	}
-
-	static get VALUES()
-	{
-		return Object.assign(
-			ChessRules.VALUES,
-			{'c': 5, 'm': 7} //experimental
-		);
-	}
-
-	static get SEARCH_DEPTH() { return 2; }
-
-	// TODO: this function could be generalized and shared better (how ?!...)
-	static GenRandInitFen()
-	{
-		let pieces = { "w": new Array(10), "b": new Array(10) };
-		// Shuffle pieces on first and last rank
-		for (let c of ["w","b"])
-		{
-			let positions = ArrayFun.range(10);
-
-			// Get random squares for bishops
-			let randIndex = 2 * randInt(5);
-			let bishop1Pos = positions[randIndex];
-			// The second bishop must be on a square of different color
-			let randIndex_tmp = 2 * randInt(5) + 1;
-			let bishop2Pos = positions[randIndex_tmp];
-			// Remove chosen squares
-			positions.splice(Math.max(randIndex,randIndex_tmp), 1);
-			positions.splice(Math.min(randIndex,randIndex_tmp), 1);
-
-			// Get random squares for knights
-			randIndex = randInt(8);
-			let knight1Pos = positions[randIndex];
-			positions.splice(randIndex, 1);
-			randIndex = randInt(7);
-			let knight2Pos = positions[randIndex];
-			positions.splice(randIndex, 1);
-
-			// Get random square for queen
-			randIndex = randInt(6);
-			let queenPos = positions[randIndex];
-			positions.splice(randIndex, 1);
-
-			// ...random square for marshall
-			randIndex = randInt(5);
-			let marshallPos = positions[randIndex];
-			positions.splice(randIndex, 1);
-
-			// ...random square for cardinal
-			randIndex = randInt(4);
-			let cardinalPos = positions[randIndex];
-			positions.splice(randIndex, 1);
-
-			// Rooks and king positions are now fixed, because of the ordering rook-king-rook
-			let rook1Pos = positions[0];
-			let kingPos = positions[1];
-			let rook2Pos = positions[2];
-
-			// Finally put the shuffled pieces in the board array
-			pieces[c][rook1Pos] = 'r';
-			pieces[c][knight1Pos] = 'n';
-			pieces[c][bishop1Pos] = 'b';
-			pieces[c][queenPos] = 'q';
-			pieces[c][marshallPos] = 'm';
-			pieces[c][cardinalPos] = 'c';
-			pieces[c][kingPos] = 'k';
-			pieces[c][bishop2Pos] = 'b';
-			pieces[c][knight2Pos] = 'n';
-			pieces[c][rook2Pos] = 'r';
-		}
-		return pieces["b"].join("") +
-			"/pppppppppp/10/10/10/10/10/10/PPPPPPPPPP/" +
-			pieces["w"].join("").toUpperCase() +
-			" w 0 1111 - 00000000000000";
-	}
+  static getPpath(b)
+  {
+    return ([V.MARSHALL,V.CARDINAL].includes(b[1]) ? "Grand/" : "") + b;
+  }
+
+  static IsGoodFen(fen)
+  {
+    if (!ChessRules.IsGoodFen(fen))
+      return false;
+    const fenParsed = V.ParseFen(fen);
+    // 5) Check captures
+    if (!fenParsed.captured || !fenParsed.captured.match(/^[0-9]{14,14}$/))
+      return false;
+    return true;
+  }
+
+  static IsGoodEnpassant(enpassant)
+  {
+    if (enpassant != "-")
+    {
+      const squares = enpassant.split(",");
+      if (squares.length > 2)
+        return false;
+      for (let sq of squares)
+      {
+        const ep = V.SquareToCoords(sq);
+        if (isNaN(ep.x) || !V.OnBoard(ep))
+          return false;
+      }
+    }
+    return true;
+  }
+
+  static ParseFen(fen)
+  {
+    const fenParts = fen.split(" ");
+    return Object.assign(
+      ChessRules.ParseFen(fen),
+      { captured: fenParts[5] }
+    );
+  }
+
+  getFen()
+  {
+    return super.getFen() + " " + this.getCapturedFen();
+  }
+
+  getCapturedFen()
+  {
+    let counts = [...Array(14).fill(0)];
+    let i = 0;
+    for (let j=0; j<V.PIECES.length; j++)
+    {
+      if (V.PIECES[j] == V.KING) //no king captured
+        continue;
+      counts[i] = this.captured["w"][V.PIECES[i]];
+      counts[7+i] = this.captured["b"][V.PIECES[i]];
+      i++;
+    }
+    return counts.join("");
+  }
+
+  setOtherVariables(fen)
+  {
+    super.setOtherVariables(fen);
+    const fenParsed = V.ParseFen(fen);
+    // Initialize captured pieces' counts from FEN
+    this.captured =
+    {
+      "w":
+      {
+        [V.PAWN]: parseInt(fenParsed.captured[0]),
+        [V.ROOK]: parseInt(fenParsed.captured[1]),
+        [V.KNIGHT]: parseInt(fenParsed.captured[2]),
+        [V.BISHOP]: parseInt(fenParsed.captured[3]),
+        [V.QUEEN]: parseInt(fenParsed.captured[4]),
+        [V.MARSHALL]: parseInt(fenParsed.captured[5]),
+        [V.CARDINAL]: parseInt(fenParsed.captured[6]),
+      },
+      "b":
+      {
+        [V.PAWN]: parseInt(fenParsed.captured[7]),
+        [V.ROOK]: parseInt(fenParsed.captured[8]),
+        [V.KNIGHT]: parseInt(fenParsed.captured[9]),
+        [V.BISHOP]: parseInt(fenParsed.captured[10]),
+        [V.QUEEN]: parseInt(fenParsed.captured[11]),
+        [V.MARSHALL]: parseInt(fenParsed.captured[12]),
+        [V.CARDINAL]: parseInt(fenParsed.captured[13]),
+      }
+    };
+  }
+
+  static get size() { return {x:10,y:10}; }
+
+  static get MARSHALL() { return 'm'; } //rook+knight
+  static get CARDINAL() { return 'c'; } //bishop+knight
+
+  static get PIECES()
+  {
+    return ChessRules.PIECES.concat([V.MARSHALL,V.CARDINAL]);
+  }
+
+  // There may be 2 enPassant squares (if pawn jump 3 squares)
+  getEnpassantFen()
+  {
+    const L = this.epSquares.length;
+    if (!this.epSquares[L-1])
+      return "-"; //no en-passant
+    let res = "";
+    this.epSquares[L-1].forEach(sq => {
+      res += V.CoordsToSquare(sq) + ",";
+    });
+    return res.slice(0,-1); //remove last comma
+  }
+
+  // En-passant after 2-sq or 3-sq jumps
+  getEpSquare(moveOrSquare)
+  {
+    if (!moveOrSquare)
+      return undefined;
+    if (typeof moveOrSquare === "string")
+    {
+      const square = moveOrSquare;
+      if (square == "-")
+        return undefined;
+      let res = [];
+      square.split(",").forEach(sq => {
+        res.push(V.SquareToCoords(sq));
+      });
+      return res;
+    }
+    // Argument is a move:
+    const move = moveOrSquare;
+    const [sx,sy,ex] = [move.start.x,move.start.y,move.end.x];
+    if (this.getPiece(sx,sy) == V.PAWN && Math.abs(sx - ex) >= 2)
+    {
+      const step = (ex-sx) / Math.abs(ex-sx);
+      let res = [{
+        x: sx + step,
+        y: sy
+      }];
+      if (sx + 2*step != ex) //3-squares move
+      {
+        res.push({
+          x: sx + 2*step,
+          y: sy
+        });
+      }
+      return res;
+    }
+    return undefined; //default
+  }
+
+  getPotentialMovesFrom([x,y])
+  {
+    switch (this.getPiece(x,y))
+    {
+      case V.MARSHALL:
+        return this.getPotentialMarshallMoves([x,y]);
+      case V.CARDINAL:
+        return this.getPotentialCardinalMoves([x,y]);
+      default:
+        return super.getPotentialMovesFrom([x,y])
+    }
+  }
+
+  // Special pawn rules: promotions to captured friendly pieces,
+  // optional on ranks 8-9 and mandatory on rank 10.
+  getPotentialPawnMoves([x,y])
+  {
+    const color = this.turn;
+    let moves = [];
+    const [sizeX,sizeY] = [V.size.x,V.size.y];
+    const shiftX = (color == "w" ? -1 : 1);
+    const startRanks = (color == "w" ? [sizeX-2,sizeX-3] : [1,2]);
+    const lastRanks = (color == "w" ? [0,1,2] : [sizeX-1,sizeX-2,sizeX-3]);
+    const promotionPieces =
+      [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN,V.MARSHALL,V.CARDINAL];
+
+    // Always x+shiftX >= 0 && x+shiftX < sizeX, because no pawns on last rank
+    let finalPieces = undefined;
+    if (lastRanks.includes(x + shiftX))
+    {
+      finalPieces = promotionPieces.filter(p => this.captured[color][p] > 0);
+      if (x + shiftX != lastRanks[0])
+        finalPieces.push(V.PAWN);
+    }
+    else
+      finalPieces = [V.PAWN];
+    if (this.board[x+shiftX][y] == V.EMPTY)
+    {
+      // One square forward
+      for (let piece of finalPieces)
+        moves.push(this.getBasicMove([x,y], [x+shiftX,y], {c:color,p:piece}));
+      if (startRanks.includes(x))
+      {
+        if (this.board[x+2*shiftX][y] == V.EMPTY)
+        {
+          // Two squares jump
+          moves.push(this.getBasicMove([x,y], [x+2*shiftX,y]));
+          if (x==startRanks[0] && this.board[x+3*shiftX][y] == V.EMPTY)
+          {
+            // Three squares jump
+            moves.push(this.getBasicMove([x,y], [x+3*shiftX,y]));
+          }
+        }
+      }
+    }
+    // Captures
+    for (let shiftY of [-1,1])
+    {
+      if (y + shiftY >= 0 && y + shiftY < sizeY
+        && this.board[x+shiftX][y+shiftY] != V.EMPTY
+        && this.canTake([x,y], [x+shiftX,y+shiftY]))
+      {
+        for (let piece of finalPieces)
+        {
+          moves.push(this.getBasicMove([x,y], [x+shiftX,y+shiftY],
+            {c:color,p:piece}));
+        }
+      }
+    }
+
+    // En passant
+    const Lep = this.epSquares.length;
+    const epSquare = this.epSquares[Lep-1];
+    if (!!epSquare)
+    {
+      for (let epsq of epSquare)
+      {
+        // TODO: some redundant checks
+        if (epsq.x == x+shiftX && Math.abs(epsq.y - y) == 1)
+        {
+          var enpassantMove = this.getBasicMove([x,y], [epsq.x,epsq.y]);
+          // WARNING: the captured pawn may be diagonally behind us,
+          // if it's a 3-squares jump and we take on 1st passing square
+          const px = (this.board[x][epsq.y] != V.EMPTY ? x : x - shiftX);
+          enpassantMove.vanish.push({
+            x: px,
+            y: epsq.y,
+            p: 'p',
+            c: this.getColor(px,epsq.y)
+          });
+          moves.push(enpassantMove);
+        }
+      }
+    }
+
+    return moves;
+  }
+
+  // TODO: different castle?
+
+  getPotentialMarshallMoves(sq)
+  {
+    return this.getSlideNJumpMoves(sq, V.steps[V.ROOK]).concat(
+      this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep"));
+  }
+
+  getPotentialCardinalMoves(sq)
+  {
+    return this.getSlideNJumpMoves(sq, V.steps[V.BISHOP]).concat(
+      this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep"));
+  }
+
+  isAttacked(sq, colors)
+  {
+    return super.isAttacked(sq, colors)
+      || this.isAttackedByMarshall(sq, colors)
+      || this.isAttackedByCardinal(sq, colors);
+  }
+
+  isAttackedByMarshall(sq, colors)
+  {
+    return this.isAttackedBySlideNJump(sq, colors, V.MARSHALL, V.steps[V.ROOK])
+      || this.isAttackedBySlideNJump(
+        sq, colors, V.MARSHALL, V.steps[V.KNIGHT], "oneStep");
+  }
+
+  isAttackedByCardinal(sq, colors)
+  {
+    return this.isAttackedBySlideNJump(sq, colors, V.CARDINAL, V.steps[V.BISHOP])
+      || this.isAttackedBySlideNJump(
+        sq, colors, V.CARDINAL, V.steps[V.KNIGHT], "oneStep");
+  }
+
+  updateVariables(move)
+  {
+    super.updateVariables(move);
+    if (move.vanish.length == 2 && move.appear.length == 1)
+    {
+      // Capture: update this.captured
+      this.captured[move.vanish[1].c][move.vanish[1].p]++;
+    }
+    if (move.vanish[0].p != move.appear[0].p)
+    {
+      // Promotion: update this.captured
+      this.captured[move.vanish[0].c][move.appear[0].p]--;
+    }
+  }
+
+  unupdateVariables(move)
+  {
+    super.unupdateVariables(move);
+    if (move.vanish.length == 2 && move.appear.length == 1)
+      this.captured[move.vanish[1].c][move.vanish[1].p]--;
+    if (move.vanish[0].p != move.appear[0].p)
+      this.captured[move.vanish[0].c][move.appear[0].p]++;
+  }
+
+  static get VALUES()
+  {
+    return Object.assign(
+      ChessRules.VALUES,
+      {'c': 5, 'm': 7} //experimental
+    );
+  }
+
+  static get SEARCH_DEPTH() { return 2; }
+
+  // TODO: this function could be generalized and shared better (how ?!...)
+  static GenRandInitFen()
+  {
+    let pieces = { "w": new Array(10), "b": new Array(10) };
+    // Shuffle pieces on first and last rank
+    for (let c of ["w","b"])
+    {
+      let positions = ArrayFun.range(10);
+
+      // Get random squares for bishops
+      let randIndex = 2 * randInt(5);
+      let bishop1Pos = positions[randIndex];
+      // The second bishop must be on a square of different color
+      let randIndex_tmp = 2 * randInt(5) + 1;
+      let bishop2Pos = positions[randIndex_tmp];
+      // Remove chosen squares
+      positions.splice(Math.max(randIndex,randIndex_tmp), 1);
+      positions.splice(Math.min(randIndex,randIndex_tmp), 1);
+
+      // Get random squares for knights
+      randIndex = randInt(8);
+      let knight1Pos = positions[randIndex];
+      positions.splice(randIndex, 1);
+      randIndex = randInt(7);
+      let knight2Pos = positions[randIndex];
+      positions.splice(randIndex, 1);
+
+      // Get random square for queen
+      randIndex = randInt(6);
+      let queenPos = positions[randIndex];
+      positions.splice(randIndex, 1);
+
+      // ...random square for marshall
+      randIndex = randInt(5);
+      let marshallPos = positions[randIndex];
+      positions.splice(randIndex, 1);
+
+      // ...random square for cardinal
+      randIndex = randInt(4);
+      let cardinalPos = positions[randIndex];
+      positions.splice(randIndex, 1);
+
+      // Rooks and king positions are now fixed, because of the ordering rook-king-rook
+      let rook1Pos = positions[0];
+      let kingPos = positions[1];
+      let rook2Pos = positions[2];
+
+      // Finally put the shuffled pieces in the board array
+      pieces[c][rook1Pos] = 'r';
+      pieces[c][knight1Pos] = 'n';
+      pieces[c][bishop1Pos] = 'b';
+      pieces[c][queenPos] = 'q';
+      pieces[c][marshallPos] = 'm';
+      pieces[c][cardinalPos] = 'c';
+      pieces[c][kingPos] = 'k';
+      pieces[c][bishop2Pos] = 'b';
+      pieces[c][knight2Pos] = 'n';
+      pieces[c][rook2Pos] = 'r';
+    }
+    return pieces["b"].join("") +
+      "/pppppppppp/10/10/10/10/10/10/PPPPPPPPPP/" +
+      pieces["w"].join("").toUpperCase() +
+      " w 0 1111 - 00000000000000";
+  }
 }
diff --git a/client/src/variants/Losers.js b/client/src/variants/Losers.js
index 86b00f90..d815ee7a 100644
--- a/client/src/variants/Losers.js
+++ b/client/src/variants/Losers.js
@@ -4,106 +4,106 @@ import { randInt } from "@/utils/alea";
 
 export const VariantRules = class LosersRules extends ChessRules
 {
-	static get HasFlags() { return false; }
-
-	getPotentialPawnMoves([x,y])
-	{
-		let moves = super.getPotentialPawnMoves([x,y]);
-
-		// Complete with promotion(s) into king, if possible
-		const color = this.turn;
-		const shift = (color == "w" ? -1 : 1);
-		const lastRank = (color == "w" ? 0 : V.size.x-1);
-		if (x+shift == lastRank)
-		{
-			// Normal move
-			if (this.board[x+shift][y] == V.EMPTY)
-				moves.push(this.getBasicMove([x,y], [x+shift,y], {c:color,p:V.KING}));
-			// Captures
-			if (y>0 && this.canTake([x,y], [x+shift,y-1])
-				&& this.board[x+shift][y-1] != V.EMPTY)
-			{
-				moves.push(this.getBasicMove([x,y], [x+shift,y-1], {c:color,p:V.KING}));
-			}
-			if (y<V.size.y-1 && this.canTake([x,y], [x+shift,y+1])
-				&& this.board[x+shift][y+1] != V.EMPTY)
-			{
-				moves.push(this.getBasicMove([x,y], [x+shift,y+1], {c:color,p:V.KING}));
-			}
-		}
-
-		return moves;
-	}
-
-	getPotentialKingMoves(sq)
-	{
-		// No castle:
-		return this.getSlideNJumpMoves(sq,
-			V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep");
-	}
-
-	// Stop at the first capture found (if any)
-	atLeastOneCapture()
-	{
-		const color = this.turn;
-		const oppCol = V.GetOppCol(color);
-		for (let i=0; i<V.size.x; i++)
-		{
-			for (let j=0; j<V.size.y; j++)
-			{
-				if (this.board[i][j] != V.EMPTY && this.getColor(i,j) != oppCol)
-				{
-					const moves = this.getPotentialMovesFrom([i,j]);
-					if (moves.length > 0)
-					{
-						for (let k=0; k<moves.length; k++)
-						{
-							if (moves[k].vanish.length==2 && this.filterValid([moves[k]]).length > 0)
-								return true;
-						}
-					}
-				}
-			}
-		}
-		return false;
-	}
-
-	// Trim all non-capturing moves
-	static KeepCaptures(moves)
-	{
-		return moves.filter(m => { return m.vanish.length == 2; });
-	}
-
-	getPossibleMovesFrom(sq)
-	{
-		let moves = this.filterValid( this.getPotentialMovesFrom(sq) );
-		// This is called from interface: we need to know if a capture is possible
-		if (this.atLeastOneCapture())
-			moves = V.KeepCaptures(moves);
-		return moves;
-	}
-
-	getAllValidMoves()
-	{
-		let moves = super.getAllValidMoves();
-		if (moves.some(m => { return m.vanish.length == 2; }))
-			moves = V.KeepCaptures(moves);
-		return moves;
-	}
-
-	underCheck(color)
-	{
-		return false; //No notion of check
-	}
-
-	getCheckSquares(move)
-	{
-		return [];
-	}
-
-	// No variables update because no royal king + no castling
-	updateVariables(move) { }
-	unupdateVariables(move) { }
+  static get HasFlags() { return false; }
+
+  getPotentialPawnMoves([x,y])
+  {
+    let moves = super.getPotentialPawnMoves([x,y]);
+
+    // Complete with promotion(s) into king, if possible
+    const color = this.turn;
+    const shift = (color == "w" ? -1 : 1);
+    const lastRank = (color == "w" ? 0 : V.size.x-1);
+    if (x+shift == lastRank)
+    {
+      // Normal move
+      if (this.board[x+shift][y] == V.EMPTY)
+        moves.push(this.getBasicMove([x,y], [x+shift,y], {c:color,p:V.KING}));
+      // Captures
+      if (y>0 && this.canTake([x,y], [x+shift,y-1])
+        && this.board[x+shift][y-1] != V.EMPTY)
+      {
+        moves.push(this.getBasicMove([x,y], [x+shift,y-1], {c:color,p:V.KING}));
+      }
+      if (y<V.size.y-1 && this.canTake([x,y], [x+shift,y+1])
+        && this.board[x+shift][y+1] != V.EMPTY)
+      {
+        moves.push(this.getBasicMove([x,y], [x+shift,y+1], {c:color,p:V.KING}));
+      }
+    }
+
+    return moves;
+  }
+
+  getPotentialKingMoves(sq)
+  {
+    // No castle:
+    return this.getSlideNJumpMoves(sq,
+      V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep");
+  }
+
+  // Stop at the first capture found (if any)
+  atLeastOneCapture()
+  {
+    const color = this.turn;
+    const oppCol = V.GetOppCol(color);
+    for (let i=0; i<V.size.x; i++)
+    {
+      for (let j=0; j<V.size.y; j++)
+      {
+        if (this.board[i][j] != V.EMPTY && this.getColor(i,j) != oppCol)
+        {
+          const moves = this.getPotentialMovesFrom([i,j]);
+          if (moves.length > 0)
+          {
+            for (let k=0; k<moves.length; k++)
+            {
+              if (moves[k].vanish.length==2 && this.filterValid([moves[k]]).length > 0)
+                return true;
+            }
+          }
+        }
+      }
+    }
+    return false;
+  }
+
+  // Trim all non-capturing moves
+  static KeepCaptures(moves)
+  {
+    return moves.filter(m => { return m.vanish.length == 2; });
+  }
+
+  getPossibleMovesFrom(sq)
+  {
+    let moves = this.filterValid( this.getPotentialMovesFrom(sq) );
+    // This is called from interface: we need to know if a capture is possible
+    if (this.atLeastOneCapture())
+      moves = V.KeepCaptures(moves);
+    return moves;
+  }
+
+  getAllValidMoves()
+  {
+    let moves = super.getAllValidMoves();
+    if (moves.some(m => { return m.vanish.length == 2; }))
+      moves = V.KeepCaptures(moves);
+    return moves;
+  }
+
+  underCheck(color)
+  {
+    return false; //No notion of check
+  }
+
+  getCheckSquares(move)
+  {
+    return [];
+  }
+
+  // No variables update because no royal king + no castling
+  updateVariables(move) { }
+  unupdateVariables(move) { }
 
   getCurrentScore()
   {
@@ -111,82 +111,82 @@ export const VariantRules = class LosersRules extends ChessRules
       return "*";
 
     // No valid move: the side who cannot move wins
-		return (this.turn == "w" ? "1-0" : "0-1");
-	}
-
-	static get VALUES()
-	{
-		// Experimental...
-		return {
-			'p': 1,
-			'r': 7,
-			'n': 3,
-			'b': 3,
-			'q': 5,
-			'k': 5
-		};
-	}
-
-	static get SEARCH_DEPTH() { return 4; }
-
-	evalPosition()
-	{
-		return - super.evalPosition(); //better with less material
-	}
-
-	static GenRandInitFen()
-	{
-		let pieces = { "w": new Array(8), "b": new Array(8) };
-		// Shuffle pieces on first and last rank
-		for (let c of ["w","b"])
-		{
-			let positions = ArrayFun.range(8);
-
-			// Get random squares for bishops
-			let randIndex = 2 * randInt(4);
-			let bishop1Pos = positions[randIndex];
-			// The second bishop must be on a square of different color
-			let randIndex_tmp = 2 * randInt(4) + 1;
-			let bishop2Pos = positions[randIndex_tmp];
-			// Remove chosen squares
-			positions.splice(Math.max(randIndex,randIndex_tmp), 1);
-			positions.splice(Math.min(randIndex,randIndex_tmp), 1);
-
-			// Get random squares for knights
-			randIndex = randInt(6);
-			let knight1Pos = positions[randIndex];
-			positions.splice(randIndex, 1);
-			randIndex = randInt(5);
-			let knight2Pos = positions[randIndex];
-			positions.splice(randIndex, 1);
-
-			// Get random square for queen
-			randIndex = randInt(4);
-			let queenPos = positions[randIndex];
-			positions.splice(randIndex, 1);
-
-			// Random square for king (no castle)
-			randIndex = randInt(3);
-			let kingPos = positions[randIndex];
-			positions.splice(randIndex, 1);
-
-			// Rooks positions are now fixed
-			let rook1Pos = positions[0];
-			let rook2Pos = positions[1];
-
-			// Finally put the shuffled pieces in the board array
-			pieces[c][rook1Pos] = 'r';
-			pieces[c][knight1Pos] = 'n';
-			pieces[c][bishop1Pos] = 'b';
-			pieces[c][queenPos] = 'q';
-			pieces[c][kingPos] = 'k';
-			pieces[c][bishop2Pos] = 'b';
-			pieces[c][knight2Pos] = 'n';
-			pieces[c][rook2Pos] = 'r';
-		}
-		return pieces["b"].join("") +
-			"/pppppppp/8/8/8/8/PPPPPPPP/" +
-			pieces["w"].join("").toUpperCase() +
-			" w 0 -"; //en-passant allowed, but no flags
-	}
+    return (this.turn == "w" ? "1-0" : "0-1");
+  }
+
+  static get VALUES()
+  {
+    // Experimental...
+    return {
+      'p': 1,
+      'r': 7,
+      'n': 3,
+      'b': 3,
+      'q': 5,
+      'k': 5
+    };
+  }
+
+  static get SEARCH_DEPTH() { return 4; }
+
+  evalPosition()
+  {
+    return - super.evalPosition(); //better with less material
+  }
+
+  static GenRandInitFen()
+  {
+    let pieces = { "w": new Array(8), "b": new Array(8) };
+    // Shuffle pieces on first and last rank
+    for (let c of ["w","b"])
+    {
+      let positions = ArrayFun.range(8);
+
+      // Get random squares for bishops
+      let randIndex = 2 * randInt(4);
+      let bishop1Pos = positions[randIndex];
+      // The second bishop must be on a square of different color
+      let randIndex_tmp = 2 * randInt(4) + 1;
+      let bishop2Pos = positions[randIndex_tmp];
+      // Remove chosen squares
+      positions.splice(Math.max(randIndex,randIndex_tmp), 1);
+      positions.splice(Math.min(randIndex,randIndex_tmp), 1);
+
+      // Get random squares for knights
+      randIndex = randInt(6);
+      let knight1Pos = positions[randIndex];
+      positions.splice(randIndex, 1);
+      randIndex = randInt(5);
+      let knight2Pos = positions[randIndex];
+      positions.splice(randIndex, 1);
+
+      // Get random square for queen
+      randIndex = randInt(4);
+      let queenPos = positions[randIndex];
+      positions.splice(randIndex, 1);
+
+      // Random square for king (no castle)
+      randIndex = randInt(3);
+      let kingPos = positions[randIndex];
+      positions.splice(randIndex, 1);
+
+      // Rooks positions are now fixed
+      let rook1Pos = positions[0];
+      let rook2Pos = positions[1];
+
+      // Finally put the shuffled pieces in the board array
+      pieces[c][rook1Pos] = 'r';
+      pieces[c][knight1Pos] = 'n';
+      pieces[c][bishop1Pos] = 'b';
+      pieces[c][queenPos] = 'q';
+      pieces[c][kingPos] = 'k';
+      pieces[c][bishop2Pos] = 'b';
+      pieces[c][knight2Pos] = 'n';
+      pieces[c][rook2Pos] = 'r';
+    }
+    return pieces["b"].join("") +
+      "/pppppppp/8/8/8/8/PPPPPPPP/" +
+      pieces["w"].join("").toUpperCase() +
+      " w 0 -"; //en-passant allowed, but no flags
+  }
 }
diff --git a/client/src/variants/Magnetic.js b/client/src/variants/Magnetic.js
index ea745dd6..3789de38 100644
--- a/client/src/variants/Magnetic.js
+++ b/client/src/variants/Magnetic.js
@@ -2,210 +2,210 @@ import { ChessRules, PiPo } from "@/base_rules";
 
 export const VariantRules = class  MagneticRules extends ChessRules
 {
-	static get HasEnpassant() { return false; }
+  static get HasEnpassant() { return false; }
 
-	getPotentialMovesFrom([x,y])
-	{
-		let standardMoves = super.getPotentialMovesFrom([x,y]);
-		let moves = [];
-		standardMoves.forEach(m => {
-			let newMove_s = this.applyMagneticLaws(m);
-			if (newMove_s.length == 1)
-				moves.push(newMove_s[0]);
-			else //promotion
-				moves = moves.concat(newMove_s);
-		});
-		return moves;
-	}
+  getPotentialMovesFrom([x,y])
+  {
+    let standardMoves = super.getPotentialMovesFrom([x,y]);
+    let moves = [];
+    standardMoves.forEach(m => {
+      let newMove_s = this.applyMagneticLaws(m);
+      if (newMove_s.length == 1)
+        moves.push(newMove_s[0]);
+      else //promotion
+        moves = moves.concat(newMove_s);
+    });
+    return moves;
+  }
 
-	// Complete a move with magnetic actions
-	// TODO: job is done multiple times for (normal) promotions.
-	applyMagneticLaws(move)
-	{
-		if (move.appear[0].p == V.KING && move.appear.length==1)
-			return [move]; //kings are not charged
-		const aIdx = (move.appear[0].p != V.KING ? 0 : 1); //if castling, rook is charged
-		const [x,y] = [move.appear[aIdx].x, move.appear[aIdx].y];
-		const color = this.turn;
-		const lastRank = (color=="w" ? 0 : 7);
-		const standardMove = JSON.parse(JSON.stringify(move));
-		this.play(standardMove);
-		for (let step of [[-1,0],[1,0],[0,-1],[0,1]])
-		{
-			let [i,j] = [x+step[0],y+step[1]];
-			while (V.OnBoard(i,j))
-			{
-				if (this.board[i][j] != V.EMPTY)
-				{
-					// Found something. Same color or not?
-					if (this.getColor(i,j) != color)
-					{
-						// Attraction
-						if ((Math.abs(i-x)>=2 || Math.abs(j-y)>=2) && this.getPiece(i,j) != V.KING)
-						{
-							move.vanish.push(
-								new PiPo({
-									p:this.getPiece(i,j),
-									c:this.getColor(i,j),
-									x:i,
-									y:j
-								})
-							);
-							move.appear.push(
-								new PiPo({
-									p:this.getPiece(i,j),
-									c:this.getColor(i,j),
-									x:x+step[0],
-									y:y+step[1]
-								})
-							);
-						}
-					}
-					else
-					{
-						// Repulsion
-						if (this.getPiece(i,j) != V.KING)
-						{
-							// Push it until we meet an obstacle or edge of the board
-							let [ii,jj] = [i+step[0],j+step[1]];
-							while (V.OnBoard(ii,jj))
-							{
-								if (this.board[ii][jj] != V.EMPTY)
-									break;
-								ii += step[0];
-								jj += step[1];
-							}
-							ii -= step[0];
-							jj -= step[1];
-							if (Math.abs(ii-i)>=1 || Math.abs(jj-j)>=1)
-							{
-								move.vanish.push(
-									new PiPo({
-										p:this.getPiece(i,j),
-										c:this.getColor(i,j),
-										x:i,
-										y:j
-									})
-								);
-								move.appear.push(
-									new PiPo({
-										p:this.getPiece(i,j),
-										c:this.getColor(i,j),
-										x:ii,
-										y:jj
-									})
-								);
-							}
-						}
-					}
-					break;
-				}
-				i += step[0];
-				j += step[1];
-			}
-		}
-		this.undo(standardMove);
-		let moves = [];
-		// Scan move for pawn (max 1) on 8th rank
-		for (let i=1; i<move.appear.length; i++)
-		{
-			if (move.appear[i].p==V.PAWN && move.appear[i].c==color
-				&& move.appear[i].x==lastRank)
-			{
-				move.appear[i].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[i].p = piece;
-					moves.push(cmove);
-				}
-				// Swap appear[i] and appear[0] for moves presentation (TODO: this is awkward)
-				moves.forEach(m => {
-					let tmp = m.appear[0];
-					m.appear[0] = m.appear[i];
-					m.appear[i] = tmp;
-				});
-				break;
-			}
-		}
-		if (moves.length == 0) //no pawn on 8th rank
-			moves.push(move);
-		return moves;
-	}
+  // Complete a move with magnetic actions
+  // TODO: job is done multiple times for (normal) promotions.
+  applyMagneticLaws(move)
+  {
+    if (move.appear[0].p == V.KING && move.appear.length==1)
+      return [move]; //kings are not charged
+    const aIdx = (move.appear[0].p != V.KING ? 0 : 1); //if castling, rook is charged
+    const [x,y] = [move.appear[aIdx].x, move.appear[aIdx].y];
+    const color = this.turn;
+    const lastRank = (color=="w" ? 0 : 7);
+    const standardMove = JSON.parse(JSON.stringify(move));
+    this.play(standardMove);
+    for (let step of [[-1,0],[1,0],[0,-1],[0,1]])
+    {
+      let [i,j] = [x+step[0],y+step[1]];
+      while (V.OnBoard(i,j))
+      {
+        if (this.board[i][j] != V.EMPTY)
+        {
+          // Found something. Same color or not?
+          if (this.getColor(i,j) != color)
+          {
+            // Attraction
+            if ((Math.abs(i-x)>=2 || Math.abs(j-y)>=2) && this.getPiece(i,j) != V.KING)
+            {
+              move.vanish.push(
+                new PiPo({
+                  p:this.getPiece(i,j),
+                  c:this.getColor(i,j),
+                  x:i,
+                  y:j
+                })
+              );
+              move.appear.push(
+                new PiPo({
+                  p:this.getPiece(i,j),
+                  c:this.getColor(i,j),
+                  x:x+step[0],
+                  y:y+step[1]
+                })
+              );
+            }
+          }
+          else
+          {
+            // Repulsion
+            if (this.getPiece(i,j) != V.KING)
+            {
+              // Push it until we meet an obstacle or edge of the board
+              let [ii,jj] = [i+step[0],j+step[1]];
+              while (V.OnBoard(ii,jj))
+              {
+                if (this.board[ii][jj] != V.EMPTY)
+                  break;
+                ii += step[0];
+                jj += step[1];
+              }
+              ii -= step[0];
+              jj -= step[1];
+              if (Math.abs(ii-i)>=1 || Math.abs(jj-j)>=1)
+              {
+                move.vanish.push(
+                  new PiPo({
+                    p:this.getPiece(i,j),
+                    c:this.getColor(i,j),
+                    x:i,
+                    y:j
+                  })
+                );
+                move.appear.push(
+                  new PiPo({
+                    p:this.getPiece(i,j),
+                    c:this.getColor(i,j),
+                    x:ii,
+                    y:jj
+                  })
+                );
+              }
+            }
+          }
+          break;
+        }
+        i += step[0];
+        j += step[1];
+      }
+    }
+    this.undo(standardMove);
+    let moves = [];
+    // Scan move for pawn (max 1) on 8th rank
+    for (let i=1; i<move.appear.length; i++)
+    {
+      if (move.appear[i].p==V.PAWN && move.appear[i].c==color
+        && move.appear[i].x==lastRank)
+      {
+        move.appear[i].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[i].p = piece;
+          moves.push(cmove);
+        }
+        // Swap appear[i] and appear[0] for moves presentation (TODO: this is awkward)
+        moves.forEach(m => {
+          let tmp = m.appear[0];
+          m.appear[0] = m.appear[i];
+          m.appear[i] = tmp;
+        });
+        break;
+      }
+    }
+    if (moves.length == 0) //no pawn on 8th rank
+      moves.push(move);
+    return moves;
+  }
 
-	atLeastOneMove()
-	{
-		if (this.kingPos[this.turn][0] < 0)
-			return false;
-		return true; //TODO: is it right?
-	}
+  atLeastOneMove()
+  {
+    if (this.kingPos[this.turn][0] < 0)
+      return false;
+    return true; //TODO: is it right?
+  }
 
-	underCheck(color)
-	{
-		return false; //there is no check
-	}
+  underCheck(color)
+  {
+    return false; //there is no check
+  }
 
-	getCheckSquares(move)
-	{
-		return [];
-	}
+  getCheckSquares(move)
+  {
+    return [];
+  }
 
-	updateVariables(move)
-	{
-		super.updateVariables(move);
-		const c = move.vanish[0].c;
-		if (move.vanish.length >= 2 && move.vanish[1].p == V.KING)
-		{
-			// We took opponent king !
-			const oppCol = V.GetOppCol(c);
-			this.kingPos[oppCol] = [-1,-1];
-			this.castleFlags[oppCol] = [false,false];
-		}
-		// Did we magnetically move our (init) rooks or opponents' ones ?
-		const firstRank = (c == "w" ? 7 : 0);
-		const oppFirstRank = 7 - firstRank;
-		const oppCol = V.GetOppCol(c);
-		move.vanish.forEach(psq => {
-			if (psq.x == firstRank && this.INIT_COL_ROOK[c].includes(psq.y))
-				this.castleFlags[c][psq.y==this.INIT_COL_ROOK[c][0] ? 0 : 1] = false;
-			else if (psq.x == oppFirstRank && this.INIT_COL_ROOK[oppCol].includes(psq.y))
-				this.castleFlags[oppCol][psq.y==this.INIT_COL_ROOK[oppCol][0] ? 0 : 1] = false;
-		});
-	}
+  updateVariables(move)
+  {
+    super.updateVariables(move);
+    const c = move.vanish[0].c;
+    if (move.vanish.length >= 2 && move.vanish[1].p == V.KING)
+    {
+      // We took opponent king !
+      const oppCol = V.GetOppCol(c);
+      this.kingPos[oppCol] = [-1,-1];
+      this.castleFlags[oppCol] = [false,false];
+    }
+    // Did we magnetically move our (init) rooks or opponents' ones ?
+    const firstRank = (c == "w" ? 7 : 0);
+    const oppFirstRank = 7 - firstRank;
+    const oppCol = V.GetOppCol(c);
+    move.vanish.forEach(psq => {
+      if (psq.x == firstRank && this.INIT_COL_ROOK[c].includes(psq.y))
+        this.castleFlags[c][psq.y==this.INIT_COL_ROOK[c][0] ? 0 : 1] = false;
+      else if (psq.x == oppFirstRank && this.INIT_COL_ROOK[oppCol].includes(psq.y))
+        this.castleFlags[oppCol][psq.y==this.INIT_COL_ROOK[oppCol][0] ? 0 : 1] = false;
+    });
+  }
 
-	unupdateVariables(move)
-	{
-		super.unupdateVariables(move);
-		const c = move.vanish[0].c;
-		const oppCol = V.GetOppCol(c);
-		if (this.kingPos[oppCol][0] < 0)
-		{
-			// Last move took opponent's king
-			for (let psq of move.vanish)
-			{
-				if (psq.p == 'k')
-				{
-					this.kingPos[oppCol] = [psq.x, psq.y];
-					break;
-				}
-			}
-		}
-	}
+  unupdateVariables(move)
+  {
+    super.unupdateVariables(move);
+    const c = move.vanish[0].c;
+    const oppCol = V.GetOppCol(c);
+    if (this.kingPos[oppCol][0] < 0)
+    {
+      // Last move took opponent's king
+      for (let psq of move.vanish)
+      {
+        if (psq.p == 'k')
+        {
+          this.kingPos[oppCol] = [psq.x, psq.y];
+          break;
+        }
+      }
+    }
+  }
 
-	getCurrentScore()
-	{
-		const color = this.turn;
-		const kp = this.kingPos[color];
-		if (kp[0] < 0) //king disappeared
-			return (color == "w" ? "0-1" : "1-0");
+  getCurrentScore()
+  {
+    const color = this.turn;
+    const kp = this.kingPos[color];
+    if (kp[0] < 0) //king disappeared
+      return (color == "w" ? "0-1" : "1-0");
     if (this.atLeastOneMove()) // game not over
       return "*";
     return "1/2"; //no moves but kings still there
-	}
+  }
 
-	static get THRESHOLD_MATE()
-	{
-		return 500; //checkmates evals may be slightly below 1000
-	}
+  static get THRESHOLD_MATE()
+  {
+    return 500; //checkmates evals may be slightly below 1000
+  }
 }
diff --git a/client/src/variants/Marseille.js b/client/src/variants/Marseille.js
index ac1782f1..31c363d2 100644
--- a/client/src/variants/Marseille.js
+++ b/client/src/variants/Marseille.js
@@ -3,297 +3,297 @@ import { randInt } from "@/utils/alea";
 
 export const VariantRules = class MarseilleRules extends ChessRules
 {
-	static IsGoodEnpassant(enpassant)
-	{
-		if (enpassant != "-")
-		{
-			const squares = enpassant.split(",");
-			if (squares.length > 2)
-				return false;
-			for (let sq of squares)
-			{
-				const ep = V.SquareToCoords(sq);
-				if (isNaN(ep.x) || !V.OnBoard(ep))
-					return false;
-			}
-		}
-		return true;
-	}
+  static IsGoodEnpassant(enpassant)
+  {
+    if (enpassant != "-")
+    {
+      const squares = enpassant.split(",");
+      if (squares.length > 2)
+        return false;
+      for (let sq of squares)
+      {
+        const ep = V.SquareToCoords(sq);
+        if (isNaN(ep.x) || !V.OnBoard(ep))
+          return false;
+      }
+    }
+    return true;
+  }
 
-	getTurnFen()
-	{
-		return this.turn + this.subTurn;
-	}
+  getTurnFen()
+  {
+    return this.turn + this.subTurn;
+  }
 
-	// There may be 2 enPassant squares (if 2 pawns jump 2 squares in same turn)
-	getEnpassantFen()
-	{
-		const L = this.epSquares.length;
-		if (this.epSquares[L-1].every(epsq => epsq === undefined))
-			return "-"; //no en-passant
-		let res = "";
-		this.epSquares[L-1].forEach(epsq => {
-			if (!!epsq)
-				res += V.CoordsToSquare(epsq) + ",";
-		});
-		return res.slice(0,-1); //remove last comma
-	}
+  // There may be 2 enPassant squares (if 2 pawns jump 2 squares in same turn)
+  getEnpassantFen()
+  {
+    const L = this.epSquares.length;
+    if (this.epSquares[L-1].every(epsq => epsq === undefined))
+      return "-"; //no en-passant
+    let res = "";
+    this.epSquares[L-1].forEach(epsq => {
+      if (!!epsq)
+        res += V.CoordsToSquare(epsq) + ",";
+    });
+    return res.slice(0,-1); //remove last comma
+  }
 
-	setOtherVariables(fen)
-	{
-		const parsedFen = V.ParseFen(fen);
-		this.setFlags(parsedFen.flags);
-		if (parsedFen.enpassant == "-")
-			this.epSquares = [ [undefined] ];
-		else
-		{
-			let res = [];
-			const squares = parsedFen.enpassant.split(",");
-			for (let sq of squares)
-				res.push(V.SquareToCoords(sq));
-			this.epSquares = [ res ];
-		}
-		this.scanKingsRooks(fen);
-		// Extract subTurn from turn indicator: "w" (first move), or
-		// "w1" or "w2" white subturn 1 or 2, and same for black
-		const fullTurn = V.ParseFen(fen).turn;
-		this.turn = fullTurn[0];
-		this.subTurn = (fullTurn[1] || 0); //"w0" = special code for first move in game
-	}
+  setOtherVariables(fen)
+  {
+    const parsedFen = V.ParseFen(fen);
+    this.setFlags(parsedFen.flags);
+    if (parsedFen.enpassant == "-")
+      this.epSquares = [ [undefined] ];
+    else
+    {
+      let res = [];
+      const squares = parsedFen.enpassant.split(",");
+      for (let sq of squares)
+        res.push(V.SquareToCoords(sq));
+      this.epSquares = [ res ];
+    }
+    this.scanKingsRooks(fen);
+    // Extract subTurn from turn indicator: "w" (first move), or
+    // "w1" or "w2" white subturn 1 or 2, and same for black
+    const fullTurn = V.ParseFen(fen).turn;
+    this.turn = fullTurn[0];
+    this.subTurn = (fullTurn[1] || 0); //"w0" = special code for first move in game
+  }
 
-	getPotentialPawnMoves([x,y])
-	{
-		const color = this.turn;
-		let moves = [];
-		const [sizeX,sizeY] = [V.size.x,V.size.y];
-		const shiftX = (color == "w" ? -1 : 1);
-		const firstRank = (color == 'w' ? sizeX-1 : 0);
-		const startRank = (color == "w" ? sizeX-2 : 1);
-		const lastRank = (color == "w" ? 0 : sizeX-1);
-		const finalPieces = x + shiftX == lastRank
-			? [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN]
-			: [V.PAWN];
+  getPotentialPawnMoves([x,y])
+  {
+    const color = this.turn;
+    let moves = [];
+    const [sizeX,sizeY] = [V.size.x,V.size.y];
+    const shiftX = (color == "w" ? -1 : 1);
+    const firstRank = (color == 'w' ? sizeX-1 : 0);
+    const startRank = (color == "w" ? sizeX-2 : 1);
+    const lastRank = (color == "w" ? 0 : sizeX-1);
+    const finalPieces = x + shiftX == lastRank
+      ? [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN]
+      : [V.PAWN];
 
-		// One square forward
-		if (this.board[x+shiftX][y] == V.EMPTY)
-		{
-			for (let piece of finalPieces)
-			{
-				moves.push(this.getBasicMove([x,y], [x+shiftX,y],
-					{c:color,p:piece}));
-			}
-			// Next condition because pawns on 1st rank can generally jump
-			if ([startRank,firstRank].includes(x)
-				&& this.board[x+2*shiftX][y] == V.EMPTY)
-			{
-				// Two squares jump
-				moves.push(this.getBasicMove([x,y], [x+2*shiftX,y]));
-			}
-		}
-		// Captures
-		for (let shiftY of [-1,1])
-		{
-			if (y + shiftY >= 0 && y + shiftY < sizeY
-				&& this.board[x+shiftX][y+shiftY] != V.EMPTY
-				&& this.canTake([x,y], [x+shiftX,y+shiftY]))
-			{
-				for (let piece of finalPieces)
-				{
-					moves.push(this.getBasicMove([x,y], [x+shiftX,y+shiftY],
-						{c:color,p:piece}));
-				}
-			}
-		}
+    // One square forward
+    if (this.board[x+shiftX][y] == V.EMPTY)
+    {
+      for (let piece of finalPieces)
+      {
+        moves.push(this.getBasicMove([x,y], [x+shiftX,y],
+          {c:color,p:piece}));
+      }
+      // Next condition because pawns on 1st rank can generally jump
+      if ([startRank,firstRank].includes(x)
+        && this.board[x+2*shiftX][y] == V.EMPTY)
+      {
+        // Two squares jump
+        moves.push(this.getBasicMove([x,y], [x+2*shiftX,y]));
+      }
+    }
+    // Captures
+    for (let shiftY of [-1,1])
+    {
+      if (y + shiftY >= 0 && y + shiftY < sizeY
+        && this.board[x+shiftX][y+shiftY] != V.EMPTY
+        && this.canTake([x,y], [x+shiftX,y+shiftY]))
+      {
+        for (let piece of finalPieces)
+        {
+          moves.push(this.getBasicMove([x,y], [x+shiftX,y+shiftY],
+            {c:color,p:piece}));
+        }
+      }
+    }
 
-		// En passant: always OK if subturn 1,
-		// OK on subturn 2 only if enPassant was played at subturn 1
-		// (and if there are two e.p. squares available).
-		const Lep = this.epSquares.length;
-		const epSquares = this.epSquares[Lep-1]; //always at least one element
-		let epSqs = [];
-		epSquares.forEach(sq => {
-			if (!!sq)
-				epSqs.push(sq);
-		});
-		if (epSqs.length == 0)
-			return moves;
-		const oppCol = V.GetOppCol(color);
-		for (let sq of epSqs)
-		{
-			if (this.subTurn == 1 || (epSqs.length == 2 &&
-				// Was this en-passant capture already played at subturn 1 ?
-				// (Or maybe the opponent filled the en-passant square with a piece)
-				this.board[epSqs[0].x][epSqs[0].y] != V.EMPTY))
-			{
-				if (sq.x == x+shiftX && Math.abs(sq.y - y) == 1
-					// Add condition "enemy pawn must be present"
-					&& this.getPiece(x,sq.y) == V.PAWN && this.getColor(x,sq.y) == oppCol)
-				{
-					let epMove = this.getBasicMove([x,y], [sq.x,sq.y]);
-					epMove.vanish.push({
-						x: x,
-						y: sq.y,
-						p: 'p',
-						c: oppCol
-					});
-					moves.push(epMove);
-				}
-			}
-		}
+    // En passant: always OK if subturn 1,
+    // OK on subturn 2 only if enPassant was played at subturn 1
+    // (and if there are two e.p. squares available).
+    const Lep = this.epSquares.length;
+    const epSquares = this.epSquares[Lep-1]; //always at least one element
+    let epSqs = [];
+    epSquares.forEach(sq => {
+      if (!!sq)
+        epSqs.push(sq);
+    });
+    if (epSqs.length == 0)
+      return moves;
+    const oppCol = V.GetOppCol(color);
+    for (let sq of epSqs)
+    {
+      if (this.subTurn == 1 || (epSqs.length == 2 &&
+        // Was this en-passant capture already played at subturn 1 ?
+        // (Or maybe the opponent filled the en-passant square with a piece)
+        this.board[epSqs[0].x][epSqs[0].y] != V.EMPTY))
+      {
+        if (sq.x == x+shiftX && Math.abs(sq.y - y) == 1
+          // Add condition "enemy pawn must be present"
+          && this.getPiece(x,sq.y) == V.PAWN && this.getColor(x,sq.y) == oppCol)
+        {
+          let epMove = this.getBasicMove([x,y], [sq.x,sq.y]);
+          epMove.vanish.push({
+            x: x,
+            y: sq.y,
+            p: 'p',
+            c: oppCol
+          });
+          moves.push(epMove);
+        }
+      }
+    }
 
-		return moves;
-	}
+    return moves;
+  }
 
-	play(move)
-	{
-		move.flags = JSON.stringify(this.aggregateFlags());
-		move.turn = this.turn + this.subTurn;
-		V.PlayOnBoard(this.board, move);
-		const epSq = this.getEpSquare(move);
-		if (this.subTurn == 0) //first move in game
-		{
-			this.turn = "b";
+  play(move)
+  {
+    move.flags = JSON.stringify(this.aggregateFlags());
+    move.turn = this.turn + this.subTurn;
+    V.PlayOnBoard(this.board, move);
+    const epSq = this.getEpSquare(move);
+    if (this.subTurn == 0) //first move in game
+    {
+      this.turn = "b";
       this.subTurn = 1;
-			this.epSquares.push([epSq]);
-		}
-		// Does this move give check on subturn 1? If yes, skip subturn 2
-		else if (this.subTurn==1 && this.underCheck(V.GetOppCol(this.turn)))
-		{
-			this.turn = V.GetOppCol(this.turn);
-			this.epSquares.push([epSq]);
-			move.checkOnSubturn1 = true;
-		}
-		else
-		{
-			if (this.subTurn == 2)
-			{
-				this.turn = V.GetOppCol(this.turn);
-				let lastEpsq = this.epSquares[this.epSquares.length-1];
-				lastEpsq.push(epSq);
-			}
-			else
-				this.epSquares.push([epSq]);
-			this.subTurn = 3 - this.subTurn;
-		}
-		this.updateVariables(move);
-	}
+      this.epSquares.push([epSq]);
+    }
+    // Does this move give check on subturn 1? If yes, skip subturn 2
+    else if (this.subTurn==1 && this.underCheck(V.GetOppCol(this.turn)))
+    {
+      this.turn = V.GetOppCol(this.turn);
+      this.epSquares.push([epSq]);
+      move.checkOnSubturn1 = true;
+    }
+    else
+    {
+      if (this.subTurn == 2)
+      {
+        this.turn = V.GetOppCol(this.turn);
+        let lastEpsq = this.epSquares[this.epSquares.length-1];
+        lastEpsq.push(epSq);
+      }
+      else
+        this.epSquares.push([epSq]);
+      this.subTurn = 3 - this.subTurn;
+    }
+    this.updateVariables(move);
+  }
 
-	undo(move)
-	{
-		this.disaggregateFlags(JSON.parse(move.flags));
-		V.UndoOnBoard(this.board, move);
-		if (move.turn[1] == '0' || move.checkOnSubturn1 || this.subTurn == 2)
-			this.epSquares.pop();
-		else //this.subTurn == 1
-		{
-			let lastEpsq = this.epSquares[this.epSquares.length-1];
-			lastEpsq.pop();
-		}
-		this.turn = move.turn[0];
-		this.subTurn = parseInt(move.turn[1]);
-		this.unupdateVariables(move);
-	}
+  undo(move)
+  {
+    this.disaggregateFlags(JSON.parse(move.flags));
+    V.UndoOnBoard(this.board, move);
+    if (move.turn[1] == '0' || move.checkOnSubturn1 || this.subTurn == 2)
+      this.epSquares.pop();
+    else //this.subTurn == 1
+    {
+      let lastEpsq = this.epSquares[this.epSquares.length-1];
+      lastEpsq.pop();
+    }
+    this.turn = move.turn[0];
+    this.subTurn = parseInt(move.turn[1]);
+    this.unupdateVariables(move);
+  }
 
-	// NOTE:  GenRandInitFen() is OK,
-	// since at first move turn indicator is just "w"
+  // NOTE:  GenRandInitFen() is OK,
+  // since at first move turn indicator is just "w"
 
-	static get VALUES()
-	{
-		return {
-			'p': 1,
-			'r': 5,
-			'n': 3,
-			'b': 3,
-			'q': 7, //slightly less than in orthodox game
-			'k': 1000
-		};
-	}
+  static get VALUES()
+  {
+    return {
+      'p': 1,
+      'r': 5,
+      'n': 3,
+      'b': 3,
+      'q': 7, //slightly less than in orthodox game
+      'k': 1000
+    };
+  }
 
-	// No alpha-beta here, just adapted min-max at depth 2(+1)
-	getComputerMove()
-	{
-		if (this.subTurn == 2)
-			return null; //TODO: imperfect interface setup
+  // No alpha-beta here, just adapted min-max at depth 2(+1)
+  getComputerMove()
+  {
+    if (this.subTurn == 2)
+      return null; //TODO: imperfect interface setup
 
-		const maxeval = V.INFINITY;
-		const color = this.turn;
-		const oppCol = V.GetOppCol(this.turn);
+    const maxeval = V.INFINITY;
+    const color = this.turn;
+    const oppCol = V.GetOppCol(this.turn);
 
-		// Search best (half) move for opponent turn
-		const getBestMoveEval = () => {
-			const turnBefore = this.turn + this.subTurn;
-			let score = this.getCurrentScore();
-			if (score != "*")
-			{
-				if (score == "1/2")
-					return 0;
-				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 doesn't give check
-				// Otherwise it's color,1. In both cases the next test makes sense
-				if (score != "*")
-				{
-					if (score == "1/2")
-						res = (oppCol == "w" ? Math.max(res, 0) : Math.min(res, 0));
-					else
-					{
-						// Found a mate
-						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;
-		};
+    // Search best (half) move for opponent turn
+    const getBestMoveEval = () => {
+      const turnBefore = this.turn + this.subTurn;
+      let score = this.getCurrentScore();
+      if (score != "*")
+      {
+        if (score == "1/2")
+          return 0;
+        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 doesn't give check
+        // Otherwise it's color,1. In both cases the next test makes sense
+        if (score != "*")
+        {
+          if (score == "1/2")
+            res = (oppCol == "w" ? Math.max(res, 0) : Math.min(res, 0));
+          else
+          {
+            // Found a mate
+            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;
+    };
 
-		let moves11 = this.getAllValidMoves();
-		let doubleMoves = [];
-		// Rank moves using a min-max at depth 2
-		for (let i=0; i<moves11.length; i++)
-		{
-			this.play(moves11[i]);
-			if (this.turn != color)
-			{
-				// We gave check with last move: search the best opponent move
-				doubleMoves.push({moves:[moves11[i]], eval:getBestMoveEval()});
-			}
-			else
-			{
-				let moves12 = this.getAllValidMoves();
-				for (let j=0; j<moves12.length; j++)
-				{
-					this.play(moves12[j]);
-					doubleMoves.push({
-						moves:[moves11[i],moves12[j]],
-						eval:getBestMoveEval()});
-					this.undo(moves12[j]);
-				}
-			}
-			this.undo(moves11[i]);
-		}
+    let moves11 = this.getAllValidMoves();
+    let doubleMoves = [];
+    // Rank moves using a min-max at depth 2
+    for (let i=0; i<moves11.length; i++)
+    {
+      this.play(moves11[i]);
+      if (this.turn != color)
+      {
+        // We gave check with last move: search the best opponent move
+        doubleMoves.push({moves:[moves11[i]], eval:getBestMoveEval()});
+      }
+      else
+      {
+        let moves12 = this.getAllValidMoves();
+        for (let j=0; j<moves12.length; j++)
+        {
+          this.play(moves12[j]);
+          doubleMoves.push({
+            moves:[moves11[i],moves12[j]],
+            eval:getBestMoveEval()});
+          this.undo(moves12[j]);
+        }
+      }
+      this.undo(moves11[i]);
+    }
 
-		doubleMoves.sort( (a,b) => {
-			return (color=="w" ? 1 : -1) * (b.eval - a.eval); });
-		let candidates = [0]; //indices of candidates moves
-		for (let i=1;
-			i<doubleMoves.length && doubleMoves[i].eval == doubleMoves[0].eval;
-			i++)
-		{
-			candidates.push(i);
-		}
+    doubleMoves.sort( (a,b) => {
+      return (color=="w" ? 1 : -1) * (b.eval - a.eval); });
+    let candidates = [0]; //indices of candidates moves
+    for (let i=1;
+      i<doubleMoves.length && doubleMoves[i].eval == doubleMoves[0].eval;
+      i++)
+    {
+      candidates.push(i);
+    }
 
-		const selected = doubleMoves[randInt(candidates.length)].moves;
-		if (selected.length == 1)
-			return selected[0];
-		return selected;
-	}
+    const selected = doubleMoves[randInt(candidates.length)].moves;
+    if (selected.length == 1)
+      return selected[0];
+    return selected;
+  }
 }
diff --git a/client/src/variants/Upsidedown.js b/client/src/variants/Upsidedown.js
index de722168..bf3e37c4 100644
--- a/client/src/variants/Upsidedown.js
+++ b/client/src/variants/Upsidedown.js
@@ -4,71 +4,71 @@ import { ArrayFun } from "@/utils/array";
 
 export const VariantRules = class UpsidedownRules extends ChessRules
 {
-	static get HasFlags() { return false; }
+  static get HasFlags() { return false; }
 
-	static get HasEnpassant() { return false; }
+  static get HasEnpassant() { return false; }
 
-	getPotentialKingMoves(sq)
-	{
-		// No castle
-		return this.getSlideNJumpMoves(sq,
-			V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep");
-	}
+  getPotentialKingMoves(sq)
+  {
+    // No castle
+    return this.getSlideNJumpMoves(sq,
+      V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep");
+  }
 
-	static GenRandInitFen()
-	{
-		let pieces = { "w": new Array(8), "b": new Array(8) };
-		for (let c of ["w","b"])
-		{
-			let positions = ArrayFun.range(8);
+  static GenRandInitFen()
+  {
+    let pieces = { "w": new Array(8), "b": new Array(8) };
+    for (let c of ["w","b"])
+    {
+      let positions = ArrayFun.range(8);
 
-			let randIndex = randInt(8);
-			const kingPos = positions[randIndex];
-			positions.splice(randIndex, 1);
+      let randIndex = randInt(8);
+      const kingPos = positions[randIndex];
+      positions.splice(randIndex, 1);
 
-			// At least a knight must be next to the king:
-			let knight1Pos = undefined;
-			if (kingPos == 0)
-				knight1Pos = 1;
-			else if (kingPos == V.size.y-1)
-				knight1Pos = V.size.y-2;
-			else
-				knight1Pos = kingPos + (Math.random() < 0.5 ? 1 : -1);
-			// Search for knight1Pos index in positions and remove it
-			const knight1Index = positions.indexOf(knight1Pos);
-			positions.splice(knight1Index, 1);
+      // At least a knight must be next to the king:
+      let knight1Pos = undefined;
+      if (kingPos == 0)
+        knight1Pos = 1;
+      else if (kingPos == V.size.y-1)
+        knight1Pos = V.size.y-2;
+      else
+        knight1Pos = kingPos + (Math.random() < 0.5 ? 1 : -1);
+      // Search for knight1Pos index in positions and remove it
+      const knight1Index = positions.indexOf(knight1Pos);
+      positions.splice(knight1Index, 1);
 
-			// King+knight1 are on two consecutive squares: one light, one dark
-			randIndex = 2 * randInt(3);
-			const bishop1Pos = positions[randIndex];
-			let randIndex_tmp = 2 * randInt(3) + 1;
-			const bishop2Pos = positions[randIndex_tmp];
-			positions.splice(Math.max(randIndex,randIndex_tmp), 1);
-			positions.splice(Math.min(randIndex,randIndex_tmp), 1);
+      // King+knight1 are on two consecutive squares: one light, one dark
+      randIndex = 2 * randInt(3);
+      const bishop1Pos = positions[randIndex];
+      let randIndex_tmp = 2 * randInt(3) + 1;
+      const bishop2Pos = positions[randIndex_tmp];
+      positions.splice(Math.max(randIndex,randIndex_tmp), 1);
+      positions.splice(Math.min(randIndex,randIndex_tmp), 1);
 
-			randIndex = randInt(4);
-			const knight2Pos = positions[randIndex];
-			positions.splice(randIndex, 1);
+      randIndex = randInt(4);
+      const knight2Pos = positions[randIndex];
+      positions.splice(randIndex, 1);
 
-			randIndex = randInt(3);
-			const queenPos = positions[randIndex];
-			positions.splice(randIndex, 1);
+      randIndex = randInt(3);
+      const queenPos = positions[randIndex];
+      positions.splice(randIndex, 1);
 
-			const rook1Pos = positions[0];
-			const rook2Pos = positions[1];
+      const rook1Pos = positions[0];
+      const rook2Pos = positions[1];
 
-			pieces[c][rook1Pos] = 'r';
-			pieces[c][knight1Pos] = 'n';
-			pieces[c][bishop1Pos] = 'b';
-			pieces[c][queenPos] = 'q';
-			pieces[c][kingPos] = 'k';
-			pieces[c][bishop2Pos] = 'b';
-			pieces[c][knight2Pos] = 'n';
-			pieces[c][rook2Pos] = 'r';
-		}
-		return pieces["w"].join("").toUpperCase() +
-			"/PPPPPPPP/8/8/8/8/pppppppp/" +
-			pieces["b"].join("") +
-			" w 0"; //no castle, no en-passant
-	}
+      pieces[c][rook1Pos] = 'r';
+      pieces[c][knight1Pos] = 'n';
+      pieces[c][bishop1Pos] = 'b';
+      pieces[c][queenPos] = 'q';
+      pieces[c][kingPos] = 'k';
+      pieces[c][bishop2Pos] = 'b';
+      pieces[c][knight2Pos] = 'n';
+      pieces[c][rook2Pos] = 'r';
+    }
+    return pieces["w"].join("").toUpperCase() +
+      "/PPPPPPPP/8/8/8/8/pppppppp/" +
+      pieces["b"].join("") +
+      " w 0"; //no castle, no en-passant
+  }
 }
diff --git a/client/src/variants/Wildebeest.js b/client/src/variants/Wildebeest.js
index 20a32ccf..a30e7f91 100644
--- a/client/src/variants/Wildebeest.js
+++ b/client/src/variants/Wildebeest.js
@@ -4,293 +4,293 @@ import { sample, randInt } from "@/utils/alea";
 
 export const VariantRules = class  WildebeestRules extends ChessRules
 {
-	static getPpath(b)
-	{
-		return ([V.CAMEL,V.WILDEBEEST].includes(b[1]) ? "Wildebeest/" : "") + b;
-	}
+  static getPpath(b)
+  {
+    return ([V.CAMEL,V.WILDEBEEST].includes(b[1]) ? "Wildebeest/" : "") + b;
+  }
 
-	static get size() { return {x:10,y:11}; }
+  static get size() { return {x:10,y:11}; }
 
-	static get CAMEL() { return 'c'; }
-	static get WILDEBEEST() { return 'w'; }
+  static get CAMEL() { return 'c'; }
+  static get WILDEBEEST() { return 'w'; }
 
-	static get PIECES()
-	{
-		return ChessRules.PIECES.concat([V.CAMEL,V.WILDEBEEST]);
-	}
+  static get PIECES()
+  {
+    return ChessRules.PIECES.concat([V.CAMEL,V.WILDEBEEST]);
+  }
 
-	static get steps()
-	{
-		return Object.assign(
-			ChessRules.steps, //add camel moves:
-			{'c': [ [-3,-1],[-3,1],[-1,-3],[-1,3],[1,-3],[1,3],[3,-1],[3,1] ]}
-		);
-	}
+  static get steps()
+  {
+    return Object.assign(
+      ChessRules.steps, //add camel moves:
+      {'c': [ [-3,-1],[-3,1],[-1,-3],[-1,3],[1,-3],[1,3],[3,-1],[3,1] ]}
+    );
+  }
 
-	static IsGoodEnpassant(enpassant)
-	{
-		if (enpassant != "-")
-		{
-			const squares = enpassant.split(",");
-			if (squares.length > 2)
-				return false;
-			for (let sq of squares)
-			{
-				const ep = V.SquareToCoords(sq);
-				if (isNaN(ep.x) || !V.OnBoard(ep))
-					return false;
-			}
-		}
-		return true;
-	}
+  static IsGoodEnpassant(enpassant)
+  {
+    if (enpassant != "-")
+    {
+      const squares = enpassant.split(",");
+      if (squares.length > 2)
+        return false;
+      for (let sq of squares)
+      {
+        const ep = V.SquareToCoords(sq);
+        if (isNaN(ep.x) || !V.OnBoard(ep))
+          return false;
+      }
+    }
+    return true;
+  }
 
-	// There may be 2 enPassant squares (if pawn jump 3 squares)
-	getEnpassantFen()
-	{
-		const L = this.epSquares.length;
-		if (!this.epSquares[L-1])
-			return "-"; //no en-passant
-		let res = "";
-		this.epSquares[L-1].forEach(sq => {
-			res += V.CoordsToSquare(sq) + ",";
-		});
-		return res.slice(0,-1); //remove last comma
-	}
+  // There may be 2 enPassant squares (if pawn jump 3 squares)
+  getEnpassantFen()
+  {
+    const L = this.epSquares.length;
+    if (!this.epSquares[L-1])
+      return "-"; //no en-passant
+    let res = "";
+    this.epSquares[L-1].forEach(sq => {
+      res += V.CoordsToSquare(sq) + ",";
+    });
+    return res.slice(0,-1); //remove last comma
+  }
 
-	// En-passant after 2-sq or 3-sq jumps
-	getEpSquare(moveOrSquare)
-	{
-		if (!moveOrSquare)
-			return undefined;
-		if (typeof moveOrSquare === "string")
-		{
-			const square = moveOrSquare;
-			if (square == "-")
-				return undefined;
-			let res = [];
-			square.split(",").forEach(sq => {
-				res.push(V.SquareToCoords(sq));
-			});
-			return res;
-		}
-		// Argument is a move:
-		const move = moveOrSquare;
-		const [sx,sy,ex] = [move.start.x,move.start.y,move.end.x];
-		if (this.getPiece(sx,sy) == V.PAWN && Math.abs(sx - ex) >= 2)
-		{
-			const step = (ex-sx) / Math.abs(ex-sx);
-			let res = [{
-				x: sx + step,
-				y: sy
-			}];
-			if (sx + 2*step != ex) //3-squares move
-			{
-				res.push({
-					x: sx + 2*step,
-					y: sy
-				});
-			}
-			return res;
-		}
-		return undefined; //default
-	}
+  // En-passant after 2-sq or 3-sq jumps
+  getEpSquare(moveOrSquare)
+  {
+    if (!moveOrSquare)
+      return undefined;
+    if (typeof moveOrSquare === "string")
+    {
+      const square = moveOrSquare;
+      if (square == "-")
+        return undefined;
+      let res = [];
+      square.split(",").forEach(sq => {
+        res.push(V.SquareToCoords(sq));
+      });
+      return res;
+    }
+    // Argument is a move:
+    const move = moveOrSquare;
+    const [sx,sy,ex] = [move.start.x,move.start.y,move.end.x];
+    if (this.getPiece(sx,sy) == V.PAWN && Math.abs(sx - ex) >= 2)
+    {
+      const step = (ex-sx) / Math.abs(ex-sx);
+      let res = [{
+        x: sx + step,
+        y: sy
+      }];
+      if (sx + 2*step != ex) //3-squares move
+      {
+        res.push({
+          x: sx + 2*step,
+          y: sy
+        });
+      }
+      return res;
+    }
+    return undefined; //default
+  }
 
-	getPotentialMovesFrom([x,y])
-	{
-		switch (this.getPiece(x,y))
-		{
-			case V.CAMEL:
-				return this.getPotentialCamelMoves([x,y]);
-			case V.WILDEBEEST:
-				return this.getPotentialWildebeestMoves([x,y]);
-			default:
-				return super.getPotentialMovesFrom([x,y])
-		}
-	}
+  getPotentialMovesFrom([x,y])
+  {
+    switch (this.getPiece(x,y))
+    {
+      case V.CAMEL:
+        return this.getPotentialCamelMoves([x,y]);
+      case V.WILDEBEEST:
+        return this.getPotentialWildebeestMoves([x,y]);
+      default:
+        return super.getPotentialMovesFrom([x,y])
+    }
+  }
 
-	// Pawns jump 2 or 3 squares, and promote to queen or wildebeest
-	getPotentialPawnMoves([x,y])
-	{
-		const color = this.turn;
-		let moves = [];
-		const [sizeX,sizeY] = [V.size.x,V.size.y];
-		const shiftX = (color == "w" ? -1 : 1);
-		const startRanks = (color == "w" ? [sizeX-2,sizeX-3] : [1,2]);
-		const lastRank = (color == "w" ? 0 : sizeX-1);
-		const finalPieces = x + shiftX == lastRank
-			? [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN]
-			: [V.PAWN];
+  // Pawns jump 2 or 3 squares, and promote to queen or wildebeest
+  getPotentialPawnMoves([x,y])
+  {
+    const color = this.turn;
+    let moves = [];
+    const [sizeX,sizeY] = [V.size.x,V.size.y];
+    const shiftX = (color == "w" ? -1 : 1);
+    const startRanks = (color == "w" ? [sizeX-2,sizeX-3] : [1,2]);
+    const lastRank = (color == "w" ? 0 : sizeX-1);
+    const finalPieces = x + shiftX == lastRank
+      ? [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN]
+      : [V.PAWN];
 
-		if (this.board[x+shiftX][y] == V.EMPTY)
-		{
-			// One square forward
-			for (let piece of finalPieces)
-				moves.push(this.getBasicMove([x,y], [x+shiftX,y], {c:color,p:piece}));
-			if (startRanks.includes(x))
-			{
-				if (this.board[x+2*shiftX][y] == V.EMPTY)
-				{
-					// Two squares jump
-					moves.push(this.getBasicMove([x,y], [x+2*shiftX,y]));
-					if (x==startRanks[0] && this.board[x+3*shiftX][y] == V.EMPTY)
-					{
-						// Three squares jump
-						moves.push(this.getBasicMove([x,y], [x+3*shiftX,y]));
-					}
-				}
-			}
-		}
-		// Captures
-		for (let shiftY of [-1,1])
-		{
-			if (y + shiftY >= 0 && y + shiftY < sizeY
-				&& this.board[x+shiftX][y+shiftY] != V.EMPTY
-				&& this.canTake([x,y], [x+shiftX,y+shiftY]))
-			{
-				for (let piece of finalPieces)
-				{
-					moves.push(this.getBasicMove([x,y], [x+shiftX,y+shiftY],
-						{c:color,p:piece}));
-				}
-			}
-		}
+    if (this.board[x+shiftX][y] == V.EMPTY)
+    {
+      // One square forward
+      for (let piece of finalPieces)
+        moves.push(this.getBasicMove([x,y], [x+shiftX,y], {c:color,p:piece}));
+      if (startRanks.includes(x))
+      {
+        if (this.board[x+2*shiftX][y] == V.EMPTY)
+        {
+          // Two squares jump
+          moves.push(this.getBasicMove([x,y], [x+2*shiftX,y]));
+          if (x==startRanks[0] && this.board[x+3*shiftX][y] == V.EMPTY)
+          {
+            // Three squares jump
+            moves.push(this.getBasicMove([x,y], [x+3*shiftX,y]));
+          }
+        }
+      }
+    }
+    // Captures
+    for (let shiftY of [-1,1])
+    {
+      if (y + shiftY >= 0 && y + shiftY < sizeY
+        && this.board[x+shiftX][y+shiftY] != V.EMPTY
+        && this.canTake([x,y], [x+shiftX,y+shiftY]))
+      {
+        for (let piece of finalPieces)
+        {
+          moves.push(this.getBasicMove([x,y], [x+shiftX,y+shiftY],
+            {c:color,p:piece}));
+        }
+      }
+    }
 
-		// En passant
-		const Lep = this.epSquares.length;
-		const epSquare = this.epSquares[Lep-1];
-		if (!!epSquare)
-		{
-			for (let epsq of epSquare)
-			{
-				// TODO: some redundant checks
-				if (epsq.x == x+shiftX && Math.abs(epsq.y - y) == 1)
-				{
-					var enpassantMove = this.getBasicMove([x,y], [epsq.x,epsq.y]);
-					// WARNING: the captured pawn may be diagonally behind us,
-					// if it's a 3-squares jump and we take on 1st passing square
-					const px = (this.board[x][epsq.y] != V.EMPTY ? x : x - shiftX);
-					enpassantMove.vanish.push({
-						x: px,
-						y: epsq.y,
-						p: 'p',
-						c: this.getColor(px,epsq.y)
-					});
-					moves.push(enpassantMove);
-				}
-			}
-		}
+    // En passant
+    const Lep = this.epSquares.length;
+    const epSquare = this.epSquares[Lep-1];
+    if (!!epSquare)
+    {
+      for (let epsq of epSquare)
+      {
+        // TODO: some redundant checks
+        if (epsq.x == x+shiftX && Math.abs(epsq.y - y) == 1)
+        {
+          var enpassantMove = this.getBasicMove([x,y], [epsq.x,epsq.y]);
+          // WARNING: the captured pawn may be diagonally behind us,
+          // if it's a 3-squares jump and we take on 1st passing square
+          const px = (this.board[x][epsq.y] != V.EMPTY ? x : x - shiftX);
+          enpassantMove.vanish.push({
+            x: px,
+            y: epsq.y,
+            p: 'p',
+            c: this.getColor(px,epsq.y)
+          });
+          moves.push(enpassantMove);
+        }
+      }
+    }
 
-		return moves;
-	}
+    return moves;
+  }
 
-	// TODO: wildebeest castle
+  // TODO: wildebeest castle
 
-	getPotentialCamelMoves(sq)
-	{
-		return this.getSlideNJumpMoves(sq, V.steps[V.CAMEL], "oneStep");
-	}
+  getPotentialCamelMoves(sq)
+  {
+    return this.getSlideNJumpMoves(sq, V.steps[V.CAMEL], "oneStep");
+  }
 
-	getPotentialWildebeestMoves(sq)
-	{
-		return this.getSlideNJumpMoves(
-			sq, V.steps[V.KNIGHT].concat(V.steps[V.CAMEL]), "oneStep");
-	}
+  getPotentialWildebeestMoves(sq)
+  {
+    return this.getSlideNJumpMoves(
+      sq, V.steps[V.KNIGHT].concat(V.steps[V.CAMEL]), "oneStep");
+  }
 
-	isAttacked(sq, colors)
-	{
-		return super.isAttacked(sq, colors)
-			|| this.isAttackedByCamel(sq, colors)
-			|| this.isAttackedByWildebeest(sq, colors);
-	}
+  isAttacked(sq, colors)
+  {
+    return super.isAttacked(sq, colors)
+      || this.isAttackedByCamel(sq, colors)
+      || this.isAttackedByWildebeest(sq, colors);
+  }
 
-	isAttackedByCamel(sq, colors)
-	{
-		return this.isAttackedBySlideNJump(sq, colors,
-			V.CAMEL, V.steps[V.CAMEL], "oneStep");
-	}
+  isAttackedByCamel(sq, colors)
+  {
+    return this.isAttackedBySlideNJump(sq, colors,
+      V.CAMEL, V.steps[V.CAMEL], "oneStep");
+  }
 
-	isAttackedByWildebeest(sq, colors)
-	{
-		return this.isAttackedBySlideNJump(sq, colors, V.WILDEBEEST,
-			V.steps[V.KNIGHT].concat(V.steps[V.CAMEL]), "oneStep");
-	}
+  isAttackedByWildebeest(sq, colors)
+  {
+    return this.isAttackedBySlideNJump(sq, colors, V.WILDEBEEST,
+      V.steps[V.KNIGHT].concat(V.steps[V.CAMEL]), "oneStep");
+  }
 
   getCurrentScore()
   {
     if (this.atLeastOneMove()) // game not over
       return "*";
 
-		// No valid move: game is lost (stalemate is a win)
-		return (this.turn == "w" ? "0-1" : "1-0");
-	}
+    // No valid move: game is lost (stalemate is a win)
+    return (this.turn == "w" ? "0-1" : "1-0");
+  }
 
-	static get VALUES() {
-		return Object.assign(
-			ChessRules.VALUES,
-			{'c': 3, 'w': 7} //experimental
-		);
-	}
+  static get VALUES() {
+    return Object.assign(
+      ChessRules.VALUES,
+      {'c': 3, 'w': 7} //experimental
+    );
+  }
 
-	static get SEARCH_DEPTH() { return 2; }
+  static get SEARCH_DEPTH() { return 2; }
 
-	static GenRandInitFen()
-	{
-		let pieces = { "w": new Array(10), "b": new Array(10) };
-		for (let c of ["w","b"])
-		{
-			let positions = ArrayFun.range(11);
+  static GenRandInitFen()
+  {
+    let pieces = { "w": new Array(10), "b": new Array(10) };
+    for (let c of ["w","b"])
+    {
+      let positions = ArrayFun.range(11);
 
-			// Get random squares for bishops + camels (different colors)
-			let randIndexes = sample(ArrayFun.range(6), 2)
+      // Get random squares for bishops + camels (different colors)
+      let randIndexes = sample(ArrayFun.range(6), 2)
         .map(i => { return 2*i; });
-			let bishop1Pos = positions[randIndexes[0]];
-			let camel1Pos = positions[randIndexes[1]];
-			// The second bishop (camel) must be on a square of different color
-			let randIndexes_tmp = sample(ArrayFun.range(5), 2)
+      let bishop1Pos = positions[randIndexes[0]];
+      let camel1Pos = positions[randIndexes[1]];
+      // The second bishop (camel) must be on a square of different color
+      let randIndexes_tmp = sample(ArrayFun.range(5), 2)
         .map(i => { return 2*i+1; });
-			let bishop2Pos = positions[randIndexes_tmp[0]];
-			let camel2Pos = positions[randIndexes_tmp[1]];
-			for (let idx of randIndexes.concat(randIndexes_tmp)
-				.sort((a,b) => { return b-a; })) //largest indices first
-			{
-				positions.splice(idx, 1);
-			}
+      let bishop2Pos = positions[randIndexes_tmp[0]];
+      let camel2Pos = positions[randIndexes_tmp[1]];
+      for (let idx of randIndexes.concat(randIndexes_tmp)
+        .sort((a,b) => { return b-a; })) //largest indices first
+      {
+        positions.splice(idx, 1);
+      }
 
-			let randIndex = randInt(7);
-			let knight1Pos = positions[randIndex];
-			positions.splice(randIndex, 1);
-			randIndex = randInt(6);
-			let knight2Pos = positions[randIndex];
-			positions.splice(randIndex, 1);
+      let randIndex = randInt(7);
+      let knight1Pos = positions[randIndex];
+      positions.splice(randIndex, 1);
+      randIndex = randInt(6);
+      let knight2Pos = positions[randIndex];
+      positions.splice(randIndex, 1);
 
-			randIndex = randInt(5);
-			let queenPos = positions[randIndex];
-			positions.splice(randIndex, 1);
+      randIndex = randInt(5);
+      let queenPos = positions[randIndex];
+      positions.splice(randIndex, 1);
 
-			// Random square for wildebeest
-			randIndex = randInt(4);
-			let wildebeestPos = positions[randIndex];
-			positions.splice(randIndex, 1);
+      // Random square for wildebeest
+      randIndex = randInt(4);
+      let wildebeestPos = positions[randIndex];
+      positions.splice(randIndex, 1);
 
-			let rook1Pos = positions[0];
-			let kingPos = positions[1];
-			let rook2Pos = positions[2];
+      let rook1Pos = positions[0];
+      let kingPos = positions[1];
+      let rook2Pos = positions[2];
 
-			pieces[c][rook1Pos] = 'r';
-			pieces[c][knight1Pos] = 'n';
-			pieces[c][bishop1Pos] = 'b';
-			pieces[c][queenPos] = 'q';
-			pieces[c][camel1Pos] = 'c';
-			pieces[c][camel2Pos] = 'c';
-			pieces[c][wildebeestPos] = 'w';
-			pieces[c][kingPos] = 'k';
-			pieces[c][bishop2Pos] = 'b';
-			pieces[c][knight2Pos] = 'n';
-			pieces[c][rook2Pos] = 'r';
-		}
-		return pieces["b"].join("") +
-			"/ppppppppppp/11/11/11/11/11/11/PPPPPPPPPPP/" +
-			pieces["w"].join("").toUpperCase() +
-			" w 0 1111 -";
-	}
+      pieces[c][rook1Pos] = 'r';
+      pieces[c][knight1Pos] = 'n';
+      pieces[c][bishop1Pos] = 'b';
+      pieces[c][queenPos] = 'q';
+      pieces[c][camel1Pos] = 'c';
+      pieces[c][camel2Pos] = 'c';
+      pieces[c][wildebeestPos] = 'w';
+      pieces[c][kingPos] = 'k';
+      pieces[c][bishop2Pos] = 'b';
+      pieces[c][knight2Pos] = 'n';
+      pieces[c][rook2Pos] = 'r';
+    }
+    return pieces["b"].join("") +
+      "/ppppppppppp/11/11/11/11/11/11/PPPPPPPPPPP/" +
+      pieces["w"].join("").toUpperCase() +
+      " w 0 1111 -";
+  }
 }
diff --git a/client/src/variants/Zen.js b/client/src/variants/Zen.js
index db2146a7..b8ce0713 100644
--- a/client/src/variants/Zen.js
+++ b/client/src/variants/Zen.js
@@ -2,228 +2,228 @@ import { ChessRules } from "@/base_rules";
 
 export const VariantRules = class ZenRules extends ChessRules
 {
-	// NOTE: enPassant, if enabled, would need to redefine carefully getEpSquare
-	static get HasEnpassant() { return false; }
-
-	// TODO(?): some duplicated code in 2 next functions
-	getSlideNJumpMoves([x,y], steps, oneStep)
-	{
-		const color = this.getColor(x,y);
-		let moves = [];
-		outerLoop:
-		for (let loop=0; loop<steps.length; loop++)
-		{
-			const step = steps[loop];
-			let i = x + step[0];
-			let j = y + step[1];
-			while (V.OnBoard(i,j) && this.board[i][j] == V.EMPTY)
-			{
-				moves.push(this.getBasicMove([x,y], [i,j]));
-				if (!!oneStep)
-					continue outerLoop;
-				i += step[0];
-				j += step[1];
-			}
-			// No capture check: handled elsewhere (next method)
-		}
-		return moves;
-	}
-
-	// follow steps from x,y until something is met.
-	// if met piece is opponent and same movement (asA): eat it!
-	findCaptures_aux([x,y], asA)
-	{
-		const color = this.getColor(x,y);
-		var moves = [];
-		const steps = asA != V.PAWN
-			? (asA==V.QUEEN ? V.steps[V.ROOK].concat(V.steps[V.BISHOP]) : V.steps[asA])
-			: color=='w' ? [[-1,-1],[-1,1]] : [[1,-1],[1,1]];
-		const oneStep = (asA==V.KNIGHT || asA==V.PAWN); //we don't capture king
-		const lastRank = (color == 'w' ? 0 : V.size.x-1);
-		const promotionPieces = [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN];
-		outerLoop:
-		for (let loop=0; loop<steps.length; loop++)
-		{
-			const step = steps[loop];
-			let i = x + step[0];
-			let j = y + step[1];
-			while (V.OnBoard(i,j) && this.board[i][j] == V.EMPTY)
-			{
-				if (oneStep)
-					continue outerLoop;
-				i += step[0];
-				j += step[1];
-			}
-			if (V.OnBoard(i,j) && this.getColor(i,j) == V.GetOppCol(color)
-				&& this.getPiece(i,j) == asA)
-			{
-				// eat!
-				if (this.getPiece(x,y) == V.PAWN && i == lastRank)
-				{
-					// Special case of promotion:
-					promotionPieces.forEach(p => {
-						moves.push(this.getBasicMove([x,y], [i,j], {c:color,p:p}));
-					});
-				}
-				else
-				{
-					// All other cases
-					moves.push(this.getBasicMove([x,y], [i,j]));
-				}
-			}
-		}
-		return moves;
-	}
-
-	// Find possible captures from a square: look in every direction!
-	findCaptures(sq)
-	{
-		let moves = [];
-
-		Array.prototype.push.apply(moves, this.findCaptures_aux(sq, V.PAWN));
-		Array.prototype.push.apply(moves, this.findCaptures_aux(sq, V.ROOK));
-		Array.prototype.push.apply(moves, this.findCaptures_aux(sq, V.KNIGHT));
-		Array.prototype.push.apply(moves, this.findCaptures_aux(sq, V.BISHOP));
-		Array.prototype.push.apply(moves, this.findCaptures_aux(sq, V.QUEEN));
-
-		return moves;
-	}
-
-	getPotentialPawnMoves([x,y])
-	{
-		const color = this.getColor(x,y);
-		let moves = [];
-		const [sizeX,sizeY] = [V.size.x,V.size.y];
-		const shift = (color == 'w' ? -1 : 1);
-		const startRank = (color == 'w' ? sizeY-2 : 1);
-		const firstRank = (color == 'w' ? sizeY-1 : 0);
-		const lastRank = (color == "w" ? 0 : sizeY-1);
-
-		if (x+shift != lastRank)
-		{
-			// Normal moves
-			if (this.board[x+shift][y] == V.EMPTY)
-			{
-				moves.push(this.getBasicMove([x,y], [x+shift,y]));
-				if ([startRank,firstRank].includes(x) && this.board[x+2*shift][y] == V.EMPTY)
-				{
-					//two squares jump
-					moves.push(this.getBasicMove([x,y], [x+2*shift,y]));
-				}
-			}
-		}
-
-		else //promotion
-		{
-			let promotionPieces = [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN];
-			promotionPieces.forEach(p => {
-				// Normal move
-				if (this.board[x+shift][y] == V.EMPTY)
-					moves.push(this.getBasicMove([x,y], [x+shift,y], {c:color,p:p}));
-			});
-		}
-
-		// No en passant here
-
-		// Add "zen" captures
-		Array.prototype.push.apply(moves, this.findCaptures([x,y]));
-
-		return moves;
-	}
-
-	getPotentialRookMoves(sq)
-	{
-		let noCaptures = this.getSlideNJumpMoves(sq, V.steps[V.ROOK]);
-		let captures = this.findCaptures(sq);
-		return noCaptures.concat(captures);
-	}
-
-	getPotentialKnightMoves(sq)
-	{
-		let noCaptures = this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep");
-		let captures = this.findCaptures(sq);
-		return noCaptures.concat(captures);
-	}
-
-	getPotentialBishopMoves(sq)
-	{
-		let noCaptures = this.getSlideNJumpMoves(sq, V.steps[V.BISHOP]);
-		let captures = this.findCaptures(sq);
-		return noCaptures.concat(captures);
-	}
-
-	getPotentialQueenMoves(sq)
-	{
-		let noCaptures = this.getSlideNJumpMoves(
-			sq, V.steps[V.ROOK].concat(V.steps[V.BISHOP]));
-		let captures = this.findCaptures(sq);
-		return noCaptures.concat(captures);
-	}
-
-	getPotentialKingMoves(sq)
-	{
-		// Initialize with normal moves
-		let noCaptures = this.getSlideNJumpMoves(sq,
-			V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep");
-		let captures = this.findCaptures(sq);
-		return noCaptures.concat(captures).concat(this.getCastleMoves(sq));
-	}
-
-	getNotation(move)
-	{
-		// Recognize special moves first
-		if (move.appear.length == 2)
-		{
-			// castle
-			if (move.end.y < move.start.y)
-				return "0-0-0";
-			else
-				return "0-0";
-		}
-
-		// Translate initial square (because pieces may fly unusually in this variant!)
-		const initialSquare = V.CoordsToSquare(move.start);
-
-		// Translate final square
-		const finalSquare = V.CoordsToSquare(move.end);
-
-		let notation = "";
-		const piece = this.getPiece(move.start.x, move.start.y);
-		if (piece == V.PAWN)
-		{
-			// pawn move (TODO: enPassant indication)
-			if (move.vanish.length > 1)
-			{
-				// capture
-				notation = initialSquare + "x" + finalSquare;
-			}
-			else //no capture
-				notation = finalSquare;
-			if (piece != move.appear[0].p) //promotion
-				notation += "=" + move.appear[0].p.toUpperCase();
-		}
-
-		else
-		{
-			// Piece movement
-			notation = piece.toUpperCase();
-			if (move.vanish.length > 1)
-				notation += initialSquare + "x";
-			notation += finalSquare;
-		}
-		return notation;
-	}
-
-	static get VALUES()
-	{
-		// TODO: experimental
-		return {
-			'p': 1,
-			'r': 3,
-			'n': 2,
-			'b': 2,
-			'q': 5,
-			'k': 1000
-		}
-	}
+  // NOTE: enPassant, if enabled, would need to redefine carefully getEpSquare
+  static get HasEnpassant() { return false; }
+
+  // TODO(?): some duplicated code in 2 next functions
+  getSlideNJumpMoves([x,y], steps, oneStep)
+  {
+    const color = this.getColor(x,y);
+    let moves = [];
+    outerLoop:
+    for (let loop=0; loop<steps.length; loop++)
+    {
+      const step = steps[loop];
+      let i = x + step[0];
+      let j = y + step[1];
+      while (V.OnBoard(i,j) && this.board[i][j] == V.EMPTY)
+      {
+        moves.push(this.getBasicMove([x,y], [i,j]));
+        if (!!oneStep)
+          continue outerLoop;
+        i += step[0];
+        j += step[1];
+      }
+      // No capture check: handled elsewhere (next method)
+    }
+    return moves;
+  }
+
+  // follow steps from x,y until something is met.
+  // if met piece is opponent and same movement (asA): eat it!
+  findCaptures_aux([x,y], asA)
+  {
+    const color = this.getColor(x,y);
+    var moves = [];
+    const steps = asA != V.PAWN
+      ? (asA==V.QUEEN ? V.steps[V.ROOK].concat(V.steps[V.BISHOP]) : V.steps[asA])
+      : color=='w' ? [[-1,-1],[-1,1]] : [[1,-1],[1,1]];
+    const oneStep = (asA==V.KNIGHT || asA==V.PAWN); //we don't capture king
+    const lastRank = (color == 'w' ? 0 : V.size.x-1);
+    const promotionPieces = [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN];
+    outerLoop:
+    for (let loop=0; loop<steps.length; loop++)
+    {
+      const step = steps[loop];
+      let i = x + step[0];
+      let j = y + step[1];
+      while (V.OnBoard(i,j) && this.board[i][j] == V.EMPTY)
+      {
+        if (oneStep)
+          continue outerLoop;
+        i += step[0];
+        j += step[1];
+      }
+      if (V.OnBoard(i,j) && this.getColor(i,j) == V.GetOppCol(color)
+        && this.getPiece(i,j) == asA)
+      {
+        // eat!
+        if (this.getPiece(x,y) == V.PAWN && i == lastRank)
+        {
+          // Special case of promotion:
+          promotionPieces.forEach(p => {
+            moves.push(this.getBasicMove([x,y], [i,j], {c:color,p:p}));
+          });
+        }
+        else
+        {
+          // All other cases
+          moves.push(this.getBasicMove([x,y], [i,j]));
+        }
+      }
+    }
+    return moves;
+  }
+
+  // Find possible captures from a square: look in every direction!
+  findCaptures(sq)
+  {
+    let moves = [];
+
+    Array.prototype.push.apply(moves, this.findCaptures_aux(sq, V.PAWN));
+    Array.prototype.push.apply(moves, this.findCaptures_aux(sq, V.ROOK));
+    Array.prototype.push.apply(moves, this.findCaptures_aux(sq, V.KNIGHT));
+    Array.prototype.push.apply(moves, this.findCaptures_aux(sq, V.BISHOP));
+    Array.prototype.push.apply(moves, this.findCaptures_aux(sq, V.QUEEN));
+
+    return moves;
+  }
+
+  getPotentialPawnMoves([x,y])
+  {
+    const color = this.getColor(x,y);
+    let moves = [];
+    const [sizeX,sizeY] = [V.size.x,V.size.y];
+    const shift = (color == 'w' ? -1 : 1);
+    const startRank = (color == 'w' ? sizeY-2 : 1);
+    const firstRank = (color == 'w' ? sizeY-1 : 0);
+    const lastRank = (color == "w" ? 0 : sizeY-1);
+
+    if (x+shift != lastRank)
+    {
+      // Normal moves
+      if (this.board[x+shift][y] == V.EMPTY)
+      {
+        moves.push(this.getBasicMove([x,y], [x+shift,y]));
+        if ([startRank,firstRank].includes(x) && this.board[x+2*shift][y] == V.EMPTY)
+        {
+          //two squares jump
+          moves.push(this.getBasicMove([x,y], [x+2*shift,y]));
+        }
+      }
+    }
+
+    else //promotion
+    {
+      let promotionPieces = [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN];
+      promotionPieces.forEach(p => {
+        // Normal move
+        if (this.board[x+shift][y] == V.EMPTY)
+          moves.push(this.getBasicMove([x,y], [x+shift,y], {c:color,p:p}));
+      });
+    }
+
+    // No en passant here
+
+    // Add "zen" captures
+    Array.prototype.push.apply(moves, this.findCaptures([x,y]));
+
+    return moves;
+  }
+
+  getPotentialRookMoves(sq)
+  {
+    let noCaptures = this.getSlideNJumpMoves(sq, V.steps[V.ROOK]);
+    let captures = this.findCaptures(sq);
+    return noCaptures.concat(captures);
+  }
+
+  getPotentialKnightMoves(sq)
+  {
+    let noCaptures = this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep");
+    let captures = this.findCaptures(sq);
+    return noCaptures.concat(captures);
+  }
+
+  getPotentialBishopMoves(sq)
+  {
+    let noCaptures = this.getSlideNJumpMoves(sq, V.steps[V.BISHOP]);
+    let captures = this.findCaptures(sq);
+    return noCaptures.concat(captures);
+  }
+
+  getPotentialQueenMoves(sq)
+  {
+    let noCaptures = this.getSlideNJumpMoves(
+      sq, V.steps[V.ROOK].concat(V.steps[V.BISHOP]));
+    let captures = this.findCaptures(sq);
+    return noCaptures.concat(captures);
+  }
+
+  getPotentialKingMoves(sq)
+  {
+    // Initialize with normal moves
+    let noCaptures = this.getSlideNJumpMoves(sq,
+      V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep");
+    let captures = this.findCaptures(sq);
+    return noCaptures.concat(captures).concat(this.getCastleMoves(sq));
+  }
+
+  getNotation(move)
+  {
+    // Recognize special moves first
+    if (move.appear.length == 2)
+    {
+      // castle
+      if (move.end.y < move.start.y)
+        return "0-0-0";
+      else
+        return "0-0";
+    }
+
+    // Translate initial square (because pieces may fly unusually in this variant!)
+    const initialSquare = V.CoordsToSquare(move.start);
+
+    // Translate final square
+    const finalSquare = V.CoordsToSquare(move.end);
+
+    let notation = "";
+    const piece = this.getPiece(move.start.x, move.start.y);
+    if (piece == V.PAWN)
+    {
+      // pawn move (TODO: enPassant indication)
+      if (move.vanish.length > 1)
+      {
+        // capture
+        notation = initialSquare + "x" + finalSquare;
+      }
+      else //no capture
+        notation = finalSquare;
+      if (piece != move.appear[0].p) //promotion
+        notation += "=" + move.appear[0].p.toUpperCase();
+    }
+
+    else
+    {
+      // Piece movement
+      notation = piece.toUpperCase();
+      if (move.vanish.length > 1)
+        notation += initialSquare + "x";
+      notation += finalSquare;
+    }
+    return notation;
+  }
+
+  static get VALUES()
+  {
+    // TODO: experimental
+    return {
+      'p': 1,
+      'r': 3,
+      'n': 2,
+      'b': 2,
+      'q': 5,
+      'k': 1000
+    }
+  }
 }
diff --git a/client/src/views/Hall.vue b/client/src/views/Hall.vue
index a2ce34d9..2180509c 100644
--- a/client/src/views/Hall.vue
+++ b/client/src/views/Hall.vue
@@ -466,7 +466,7 @@ export default {
     },
     newChallenge: async function() {
       if (this.newchallenge.vid == "")
-		    return alert("Please select a variant");
+        return alert("Please select a variant");
       const vname = this.getVname(this.newchallenge.vid);
       const vModule = await import("@/variants/" + vname + ".js");
       window.V = vModule.VariantRules;
diff --git a/client/src/views/MyGames.vue b/client/src/views/MyGames.vue
index c24e7203..0973d32c 100644
--- a/client/src/views/MyGames.vue
+++ b/client/src/views/MyGames.vue
@@ -25,7 +25,7 @@ export default {
   data: function() {
     return {
       st: store.state,
-			display: "live",
+      display: "live",
       games: [],
     };
   },
diff --git a/server/app.js b/server/app.js
index f97d925b..1fc03c59 100644
--- a/server/app.js
+++ b/server/app.js
@@ -12,17 +12,17 @@ app.use(favicon(path.join(__dirname, "static", "favicon.ico")));
 
 if (app.get('env') === 'development')
 {
-	// Full logging in development mode
-	app.use(logger('dev'));
+  // Full logging in development mode
+  app.use(logger('dev'));
 }
 else
 {
-	// http://dev.rdybarra.com/2016/06/23/Production-Logging-With-Morgan-In-Express/
-	app.set('trust proxy', true);
-	// In prod, only log error responses (https://github.com/expressjs/morgan)
-	app.use(logger('combined', {
-		skip: function (req, res) { return res.statusCode < 400 }
-	}));
+  // http://dev.rdybarra.com/2016/06/23/Production-Logging-With-Morgan-In-Express/
+  app.set('trust proxy', true);
+  // In prod, only log error responses (https://github.com/expressjs/morgan)
+  app.use(logger('combined', {
+    skip: function (req, res) { return res.statusCode < 400 }
+  }));
 }
 
 app.use(express.json());
@@ -33,14 +33,14 @@ app.use(express.static(path.join(__dirname, 'static'))); //client "prod" files
 // In development stage the client side has its own server
 if (params.cors.enable)
 {
-	app.use(function(req, res, next) {
-		res.header("Access-Control-Allow-Origin", params.cors.allowedOrigin);
-		res.header("Access-Control-Allow-Credentials", true); //for cookies
+  app.use(function(req, res, next) {
+    res.header("Access-Control-Allow-Origin", params.cors.allowedOrigin);
+    res.header("Access-Control-Allow-Credentials", true); //for cookies
     res.header("Access-Control-Allow-Headers",
       "Origin, X-Requested-With, Content-Type, Accept");
-	  res.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT, DELETE");
+    res.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT, DELETE");
     next();
-	});
+  });
 }
 
 // Routing (AJAX-only)
@@ -60,11 +60,11 @@ app.use(function(err, req, res, next) {
   // render the error page
   res.status(err.status || 500);
   res.send(`
-		<!doctype html>
-		<h1>= message</h1>
-		<h2>= error.status</h2>
-		<pre>#{error.stack}</pre>
-	`);
+    <!doctype html>
+    <h1>= message</h1>
+    <h2>= error.status</h2>
+    <pre>#{error.stack}</pre>
+  `);
 });
 
 module.exports = app;
diff --git a/server/bin/www b/server/bin/www
index 44751991..b3a2c732 100755
--- a/server/bin/www
+++ b/server/bin/www
@@ -30,10 +30,10 @@ var UserModel = require("../models/User");
 var ChallengeModel = require("../models/Challenge");
 var GameModel = require("../models/Game");
 cron.schedule('0 0 0 * * *', function() {
-	// Remove some old users, challenges and games every 24h
-	UserModel.cleanUsersDb();
-	ChallengeModel.removeOld();
-	GameModel.cleanGamesDb();
+  // Remove some old users, challenges and games every 24h
+  UserModel.cleanUsersDb();
+  ChallengeModel.removeOld();
+  GameModel.cleanGamesDb();
 });
 
 /**
diff --git a/server/config/parameters.js.dist b/server/config/parameters.js.dist
index 5b4301a6..264fac50 100644
--- a/server/config/parameters.js.dist
+++ b/server/config/parameters.js.dist
@@ -1,34 +1,34 @@
 module.exports =
 {
-	// For mail sending. NOTE: *no trailing slash*
-	siteURL: "http://localhost:8080",
+  // For mail sending. NOTE: *no trailing slash*
+  siteURL: "http://localhost:8080",
 
-	// To know in which environment the code run
-	env: process.env.NODE_ENV || 'development',
-	
-	// CORS: cross-origin resource sharing options
-	cors: {
-		enable: true,
-		allowedOrigin: "http://localhost:8080",
-	},
+  // To know in which environment the code run
+  env: process.env.NODE_ENV || 'development',
+  
+  // CORS: cross-origin resource sharing options
+  cors: {
+    enable: true,
+    allowedOrigin: "http://localhost:8080",
+  },
 
-	// Lifespan of a (login) cookie
-	cookieExpire: 183*24*60*60*1000, //6 months in milliseconds
+  // Lifespan of a (login) cookie
+  cookieExpire: 183*24*60*60*1000, //6 months in milliseconds
 
-	// Characters in a login token, and period of validity (in milliseconds)
-	token: {
-		length: 16,
-		expire: 30*60*1000, //30 minutes in milliseconds
-	},
+  // Characters in a login token, and period of validity (in milliseconds)
+  token: {
+    length: 16,
+    expire: 30*60*1000, //30 minutes in milliseconds
+  },
 
-	// Email settings
-	mail: {
-		host: "mail_host_address",
-		port: 465, //if secure; otherwise use 587
-		secure: true, //...or false
-		user: "mail_user_name",
-		pass: "mail_password",
-		noreply: "some_noreply_email",
-		contact: "some_contact_email",
-	},
+  // Email settings
+  mail: {
+    host: "mail_host_address",
+    port: 465, //if secure; otherwise use 587
+    secure: true, //...or false
+    user: "mail_user_name",
+    pass: "mail_password",
+    noreply: "some_noreply_email",
+    contact: "some_contact_email",
+  },
 };
diff --git a/server/db/populate.sql b/server/db/populate.sql
index 4659704f..b770c575 100644
--- a/server/db/populate.sql
+++ b/server/db/populate.sql
@@ -1,21 +1,21 @@
 -- Re-run this script after variants are added
 
 insert or ignore into Variants (name,description) values
-	('Alice', 'Both sides of the mirror'),
-	('Antiking', 'Keep antiking in check'),
-	('Atomic', 'Explosive captures'),
-	('Baroque', 'Exotic captures'),
-	('Berolina', 'Pawns move diagonally'),
-	('Checkered', 'Shared pieces'),
-	('Chess960', 'Standard rules'),
-	('Crazyhouse', 'Captures reborn'),
-	('Dark', 'In the shadow'),
-	('Extinction', 'Capture all of a kind'),
-	('Grand', 'Big board'),
-	('Losers', 'Lose all pieces'),
-	('Magnetic', 'Laws of attraction'),
-	('Marseille', 'Move twice'),
-	('Switching', 'Exchange pieces positions'),
-	('Upsidedown', 'Head upside down'),
-	('Wildebeest', 'Balanced sliders & leapers'),
-	('Zen', 'Reverse captures');
+  ('Alice', 'Both sides of the mirror'),
+  ('Antiking', 'Keep antiking in check'),
+  ('Atomic', 'Explosive captures'),
+  ('Baroque', 'Exotic captures'),
+  ('Berolina', 'Pawns move diagonally'),
+  ('Checkered', 'Shared pieces'),
+  ('Chess960', 'Standard rules'),
+  ('Crazyhouse', 'Captures reborn'),
+  ('Dark', 'In the shadow'),
+  ('Extinction', 'Capture all of a kind'),
+  ('Grand', 'Big board'),
+  ('Losers', 'Lose all pieces'),
+  ('Magnetic', 'Laws of attraction'),
+  ('Marseille', 'Move twice'),
+  ('Switching', 'Exchange pieces positions'),
+  ('Upsidedown', 'Head upside down'),
+  ('Wildebeest', 'Balanced sliders & leapers'),
+  ('Zen', 'Reverse captures');
diff --git a/server/gulpfile.js b/server/gulpfile.js
index ae739d96..99c58ab3 100644
--- a/server/gulpfile.js
+++ b/server/gulpfile.js
@@ -2,16 +2,16 @@ var gulp = require('gulp');
 var nodemon = require('gulp-nodemon'); //reload server on changes
 
 var nodemonOptions = {
-	script: 'bin/www',
-	ext: 'js',
-	env: { 'NODE_ENV': 'development' },
-	verbose: true,
-	watch: ['./','routes','bin']
+  script: 'bin/www',
+  ext: 'js',
+  env: { 'NODE_ENV': 'development' },
+  verbose: true,
+  watch: ['./','routes','bin']
 };
 
 gulp.task('start', function () {
-	nodemon(nodemonOptions)
-	.on('restart', function () {
-		console.log('restarted!')
-	});
+  nodemon(nodemonOptions)
+  .on('restart', function () {
+    console.log('restarted!')
+  });
 });
diff --git a/server/models/Game.js b/server/models/Game.js
index 5e1c7990..da59b4a6 100644
--- a/server/models/Game.js
+++ b/server/models/Game.js
@@ -49,11 +49,11 @@ const GameModel =
     return "";
   },
 
-	create: function(vid, fen, timeControl, players, cb)
-	{
-		db.serialize(function() {
-			let query =
-				"INSERT INTO Games"
+  create: function(vid, fen, timeControl, players, cb)
+  {
+    db.serialize(function() {
+      let query =
+        "INSERT INTO Games"
         + " (vid, fenStart, fen, score, timeControl, created, drawOffer)"
         + " VALUES (" + vid + ",'" + fen + "','" + fen + "','*','"
         + timeControl + "'," + Date.now() + "," + false + ")";
@@ -68,51 +68,51 @@ const GameModel =
           db.run(query);
         });
         cb(null, {gid: this.lastID});
-			});
-		});
-	},
+      });
+    });
+  },
 
-	// TODO: queries here could be async, and wait for all to complete
-	getOne: function(id, cb)
-	{
-		db.serialize(function() {
+  // TODO: queries here could be async, and wait for all to complete
+  getOne: function(id, cb)
+  {
+    db.serialize(function() {
       // TODO: optimize queries?
-			let query =
+      let query =
         // NOTE: g.scoreMsg can be NULL
         // (in this case score = "*" and no reason to look at it)
-				"SELECT g.id, g.vid, g.fen, g.fenStart, g.timeControl, g.score, " +
+        "SELECT g.id, g.vid, g.fen, g.fenStart, g.timeControl, g.score, " +
           "g.scoreMsg, v.name AS vname " +
-				"FROM Games g " +
+        "FROM Games g " +
         "JOIN Variants v " +
         "  ON g.vid = v.id " +
-				"WHERE g.id = " + id;
-			db.get(query, (err,gameInfo) => {
-				if (!!err)
-					return cb(err);
-				query =
-					"SELECT p.uid, p.color, u.name " +
-					"FROM Players p " +
+        "WHERE g.id = " + id;
+      db.get(query, (err,gameInfo) => {
+        if (!!err)
+          return cb(err);
+        query =
+          "SELECT p.uid, p.color, u.name " +
+          "FROM Players p " +
           "JOIN Users u " +
           "  ON p.uid = u.id " +
-					"WHERE p.gid = " + id;
-				db.all(query, (err2,players) => {
-					if (!!err2)
-						return cb(err2);
-					query =
-						"SELECT squares, played, idx " +
-						"FROM Moves " +
-						"WHERE gid = " + id;
-					db.all(query, (err3,moves) => {
-						if (!!err3)
-							return cb(err3);
-			      query =
+          "WHERE p.gid = " + id;
+        db.all(query, (err2,players) => {
+          if (!!err2)
+            return cb(err2);
+          query =
+            "SELECT squares, played, idx " +
+            "FROM Moves " +
+            "WHERE gid = " + id;
+          db.all(query, (err3,moves) => {
+            if (!!err3)
+              return cb(err3);
+            query =
               "SELECT msg, name, added " +
               "FROM Chats " +
               "WHERE gid = " + id;
-			      db.all(query, (err4,chats) => {
-						  if (!!err4)
-							  return cb(err4);
-						  const game = Object.assign({},
+            db.all(query, (err4,chats) => {
+              if (!!err4)
+                return cb(err4);
+              const game = Object.assign({},
                 gameInfo,
                 {
                   players: players,
@@ -120,41 +120,41 @@ const GameModel =
                   chats: chats,
                 }
               );
-						  return cb(null, game);
+              return cb(null, game);
             });
-					});
-				});
-			});
-		});
-	},
+          });
+        });
+      });
+    });
+  },
 
-	getByUser: function(uid, excluded, cb)
-	{
-		db.serialize(function() {
-			// Next query is fine because a player appear at most once in a game
-			const query =
-				"SELECT gid " +
-				"FROM Players " +
-				"WHERE uid " + (excluded ? "<>" : "=") + " " + uid;
-			db.all(query, (err,gameIds) => {
-				if (!!err)
-					return cb(err);
+  getByUser: function(uid, excluded, cb)
+  {
+    db.serialize(function() {
+      // Next query is fine because a player appear at most once in a game
+      const query =
+        "SELECT gid " +
+        "FROM Players " +
+        "WHERE uid " + (excluded ? "<>" : "=") + " " + uid;
+      db.all(query, (err,gameIds) => {
+        if (!!err)
+          return cb(err);
         gameIds = gameIds || []; //might be empty
-				let gameArray = [];
-				for (let i=0; i<gameIds.length; i++)
-				{
-					GameModel.getOne(gameIds[i]["gid"], (err2,game) => {
-						if (!!err2)
-							return cb(err2);
-						gameArray.push(game);
-						// Call callback function only when gameArray is complete:
-						if (i == gameIds.length - 1)
-							return cb(null, gameArray);
-					});
-				}
-			});
-		});
-	},
+        let gameArray = [];
+        for (let i=0; i<gameIds.length; i++)
+        {
+          GameModel.getOne(gameIds[i]["gid"], (err2,game) => {
+            if (!!err2)
+              return cb(err2);
+            gameArray.push(game);
+            // Call callback function only when gameArray is complete:
+            if (i == gameIds.length - 1)
+              return cb(null, gameArray);
+          });
+        }
+      });
+    });
+  },
 
   getPlayers: function(id, cb)
   {
@@ -193,7 +193,7 @@ const GameModel =
   // obj can have fields move, chat, fen, drawOffer and/or score
   update: function(id, obj)
   {
-		db.parallelize(function() {
+    db.parallelize(function() {
       let query =
         "UPDATE Games " +
         "SET ";
@@ -224,35 +224,35 @@ const GameModel =
       }
       if (!!obj.chat)
       {
-			  query =
-	        "INSERT INTO Chats (gid, msg, name, added) VALUES ("
+        query =
+          "INSERT INTO Chats (gid, msg, name, added) VALUES ("
             + id + ",?,'" + obj.chat.name + "'," + Date.now() + ")";
         db.run(query, obj.chat.msg);
       }
     });
   },
 
-	remove: function(id)
-	{
-		db.parallelize(function() {
-			let query =
-				"DELETE FROM Games " +
-				"WHERE id = " + id;
-			db.run(query);
-			query =
-				"DELETE FROM Players " +
-				"WHERE gid = " + id;
-			db.run(query);
-			query =
-				"DELETE FROM Moves " +
-				"WHERE gid = " + id;
-			db.run(query);
-			query =
-				"DELETE FROM Chats " +
-				"WHERE gid = " + id;
-			db.run(query);
-		});
-	},
+  remove: function(id)
+  {
+    db.parallelize(function() {
+      let query =
+        "DELETE FROM Games " +
+        "WHERE id = " + id;
+      db.run(query);
+      query =
+        "DELETE FROM Players " +
+        "WHERE gid = " + id;
+      db.run(query);
+      query =
+        "DELETE FROM Moves " +
+        "WHERE gid = " + id;
+      db.run(query);
+      query =
+        "DELETE FROM Chats " +
+        "WHERE gid = " + id;
+      db.run(query);
+    });
+  },
 
   cleanGamesDb: function()
   {
diff --git a/server/models/User.js b/server/models/User.js
index c0516156..1a5397db 100644
--- a/server/models/User.js
+++ b/server/models/User.js
@@ -17,53 +17,53 @@ var sendEmail = require('../utils/mailer');
 
 const UserModel =
 {
-	checkNameEmail: function(o)
-	{
-		if (typeof o.name === "string")
-		{
-			if (o.name.length == 0)
-				return "Empty name";
-			if (!o.name.match(/^[\w]+$/))
-				return "Bad characters in name";
-		}
-		if (typeof o.email === "string")
-		{
-			if (o.email.length == 0)
-				return "Empty email";
-			if (!o.email.match(/^[\w.+-]+@[\w.+-]+$/))
-				return "Bad characters in email";
-		}
+  checkNameEmail: function(o)
+  {
+    if (typeof o.name === "string")
+    {
+      if (o.name.length == 0)
+        return "Empty name";
+      if (!o.name.match(/^[\w]+$/))
+        return "Bad characters in name";
+    }
+    if (typeof o.email === "string")
+    {
+      if (o.email.length == 0)
+        return "Empty email";
+      if (!o.email.match(/^[\w.+-]+@[\w.+-]+$/))
+        return "Bad characters in email";
+    }
     return ""; //NOTE: not required, but more consistent... (?!)
-	},
+  },
 
-	// NOTE: parameters are already cleaned (in controller), thus no sanitization here
-	create: function(name, email, notify, callback)
-	{
-		db.serialize(function() {
-			const insertQuery =
-				"INSERT INTO Users " +
-				"(name, email, notify, created) VALUES " +
-				"('" + name + "', '" + email + "', " + notify + "," + Date.now() + ")";
-			db.run(insertQuery, err => {
-				if (!!err)
-					return callback(err);
-				db.get("SELECT last_insert_rowid() AS rowid", callback);
-			});
-		});
-	},
+  // NOTE: parameters are already cleaned (in controller), thus no sanitization here
+  create: function(name, email, notify, callback)
+  {
+    db.serialize(function() {
+      const insertQuery =
+        "INSERT INTO Users " +
+        "(name, email, notify, created) VALUES " +
+        "('" + name + "', '" + email + "', " + notify + "," + Date.now() + ")";
+      db.run(insertQuery, err => {
+        if (!!err)
+          return callback(err);
+        db.get("SELECT last_insert_rowid() AS rowid", callback);
+      });
+    });
+  },
 
-	// Find one user (by id, name, email, or token)
-	getOne: function(by, value, cb)
-	{
-		const delimiter = (typeof value === "string" ? "'" : "");
-		db.serialize(function() {
-			const query =
-				"SELECT * " +
-				"FROM Users " +
-				"WHERE " + by + " = " + delimiter + value + delimiter;
-			db.get(query, cb);
-		});
-	},
+  // Find one user (by id, name, email, or token)
+  getOne: function(by, value, cb)
+  {
+    const delimiter = (typeof value === "string" ? "'" : "");
+    db.serialize(function() {
+      const query =
+        "SELECT * " +
+        "FROM Users " +
+        "WHERE " + by + " = " + delimiter + value + delimiter;
+      db.get(query, cb);
+    });
+  },
 
   getByIds: function(ids, cb) {
     db.serialize(function() {
@@ -75,65 +75,65 @@ const UserModel =
     });
   },
 
-	/////////
-	// MODIFY
+  /////////
+  // MODIFY
 
-	setLoginToken: function(token, uid, cb)
-	{
-		db.serialize(function() {
-			const query =
-				"UPDATE Users " +
-				"SET loginToken = '" + token + "', loginTime = " + Date.now() + " " +
-				"WHERE id = " + uid;
-			db.run(query, cb);
-		});
-	},
+  setLoginToken: function(token, uid, cb)
+  {
+    db.serialize(function() {
+      const query =
+        "UPDATE Users " +
+        "SET loginToken = '" + token + "', loginTime = " + Date.now() + " " +
+        "WHERE id = " + uid;
+      db.run(query, cb);
+    });
+  },
 
-	// Set session token only if empty (first login)
-	// NOTE: weaker security (but avoid to re-login everywhere after each logout)
-	// TODO: option would be to reset all tokens periodically, e.g. every 3 months
+  // Set session token only if empty (first login)
+  // NOTE: weaker security (but avoid to re-login everywhere after each logout)
+  // TODO: option would be to reset all tokens periodically, e.g. every 3 months
   trySetSessionToken: function(uid, cb)
-	{
-		// Also empty the login token to invalidate future attempts
-		db.serialize(function() {
-			const querySessionToken =
-				"SELECT sessionToken " +
-				"FROM Users " +
-				"WHERE id = " + uid;
-			db.get(querySessionToken, (err,ret) => {
-				if (!!err)
-					return cb(err);
-				const token = ret.sessionToken || genToken(params.token.length);
-				const queryUpdate =
-					"UPDATE Users " +
-					"SET loginToken = NULL" +
-					(!ret.sessionToken ? (", sessionToken = '" + token + "'") : "") + " " +
-					"WHERE id = " + uid;
-				db.run(queryUpdate);
-				cb(null, token);
-			});
-		});
-	},
+  {
+    // Also empty the login token to invalidate future attempts
+    db.serialize(function() {
+      const querySessionToken =
+        "SELECT sessionToken " +
+        "FROM Users " +
+        "WHERE id = " + uid;
+      db.get(querySessionToken, (err,ret) => {
+        if (!!err)
+          return cb(err);
+        const token = ret.sessionToken || genToken(params.token.length);
+        const queryUpdate =
+          "UPDATE Users " +
+          "SET loginToken = NULL" +
+          (!ret.sessionToken ? (", sessionToken = '" + token + "'") : "") + " " +
+          "WHERE id = " + uid;
+        db.run(queryUpdate);
+        cb(null, token);
+      });
+    });
+  },
 
-	updateSettings: function(user, cb)
-	{
-		db.serialize(function() {
-			const query =
-				"UPDATE Users " +
-				"SET name = '" + user.name + "'" +
-				", email = '" + user.email + "'" +
-				", notify = " + user.notify + " " +
-				"WHERE id = " + user.id;
-			db.run(query, cb);
-		});
-	},
+  updateSettings: function(user, cb)
+  {
+    db.serialize(function() {
+      const query =
+        "UPDATE Users " +
+        "SET name = '" + user.name + "'" +
+        ", email = '" + user.email + "'" +
+        ", notify = " + user.notify + " " +
+        "WHERE id = " + user.id;
+      db.run(query, cb);
+    });
+  },
 
   /////////////////
   // NOTIFICATIONS
 
   tryNotify: function(oppId, message)
   {
-		UserModel.getOne("id", oppId, (err,opp) => {
+    UserModel.getOne("id", oppId, (err,opp) => {
       if (!err || !opp.notify)
         return; //error is ignored here (TODO: should be logged)
       const subject = "vchess.club - notification";
diff --git a/server/models/Variant.js b/server/models/Variant.js
index 20bce4a9..dccc89b5 100644
--- a/server/models/Variant.js
+++ b/server/models/Variant.js
@@ -9,17 +9,17 @@ var db = require("../utils/database");
 
 const VariantModel =
 {
-	getAll: function(callback)
-	{
-		db.serialize(function() {
-			const query =
-				"SELECT * " +
-				"FROM Variants";
-			db.all(query, callback);
-		});
-	},
+  getAll: function(callback)
+  {
+    db.serialize(function() {
+      const query =
+        "SELECT * " +
+        "FROM Variants";
+      db.all(query, callback);
+    });
+  },
 
-	//create, update, delete: directly in DB
+  //create, update, delete: directly in DB
 }
 
 module.exports = VariantModel;
diff --git a/server/routes/games.js b/server/routes/games.js
index e9d9ab46..24bfc82c 100644
--- a/server/routes/games.js
+++ b/server/routes/games.js
@@ -9,10 +9,10 @@ var params = require("../config/parameters");
 // From main hall, start game between players 0 and 1
 router.post("/games", access.logged, access.ajax, (req,res) => {
   const gameInfo = req.body.gameInfo;
-	if (!Array.isArray(gameInfo.players) ||
+  if (!Array.isArray(gameInfo.players) ||
     !gameInfo.players.some(p => p.id == req.userId))
   {
-		return res.json({errmsg: "Cannot start someone else's game"});
+    return res.json({errmsg: "Cannot start someone else's game"});
   }
   const cid = req.body.cid;
   // Check all entries of gameInfo + cid:
@@ -25,29 +25,29 @@ router.post("/games", access.logged, access.ajax, (req,res) => {
   if (!!error)
     return res.json({errmsg:error});
   ChallengeModel.remove(cid);
-	GameModel.create(
+  GameModel.create(
     gameInfo.vid, gameInfo.fen, gameInfo.timeControl, gameInfo.players,
-		(err,ret) => {
-			access.checkRequest(res, err, ret, "Cannot create game", () => {
+    (err,ret) => {
+      access.checkRequest(res, err, ret, "Cannot create game", () => {
         const oppIdx = (gameInfo.players[0].id == req.userId ? 1 : 0);
         const oppId = gameInfo.players[oppIdx].id;
         UserModel.tryNotify(oppId,
           "New game: " + params.siteURL + "/game/" + ret.gid);
-				res.json({gameId: ret.gid});
-			});
-		}
-	);
+        res.json({gameId: ret.gid});
+      });
+    }
+  );
 });
 
 router.get("/games", access.ajax, (req,res) => {
-	const gameId = req.query["gid"];
-	if (!!gameId)
+  const gameId = req.query["gid"];
+  if (!!gameId)
   {
     GameModel.getOne(gameId, (err,game) => {
-		  access.checkRequest(res, err, game, "Game not found", () => {
+      access.checkRequest(res, err, game, "Game not found", () => {
         res.json({game: game});
-		  });
-	  });
+      });
+    });
   }
   else
   {
@@ -55,10 +55,10 @@ router.get("/games", access.ajax, (req,res) => {
     const userId = req.query["uid"];
     const excluded = !!req.query["excluded"];
     GameModel.getByUser(userId, excluded, (err,games) => {
-			if (!!err)
+      if (!!err)
         return res.json({errmsg: err.errmsg || err.toString()});
-			res.json({games: games});
-	  });
+      res.json({games: games});
+    });
   }
 });
 
@@ -70,11 +70,11 @@ router.put("/games", access.logged, access.ajax, (req,res) => {
   if (!gid.toString().match(/^[0-9]+$/))
     error = "Wrong game ID";
   const obj = req.body.newObj;
-	error = GameModel.checkGameUpdate(obj);
+  error = GameModel.checkGameUpdate(obj);
   if (!!error)
     return res.json({errmsg: error});
-	GameModel.update(gid, obj, (err) => {
-		if (!!err)
+  GameModel.update(gid, obj, (err) => {
+    if (!!err)
       return res.json(err);
     // Notify opponent if he enabled notifications:
     GameModel.getPlayers(gid, (err2,players) => {
@@ -85,7 +85,7 @@ router.put("/games", access.logged, access.ajax, (req,res) => {
         "New move in game: " + params.siteURL + "/game/" + gid);
     });
     res.json({});
-	});
+  });
 });
 
 module.exports = router;
diff --git a/server/routes/messages.js b/server/routes/messages.js
index b3c158eb..cd93b9fe 100644
--- a/server/routes/messages.js
+++ b/server/routes/messages.js
@@ -6,19 +6,19 @@ const params = require(__dirname.replace("/routes", "/config/parameters"));
 
 // Send a message through contact form
 router.post("/messages", (req,res,next) => {
-	if (!req.xhr)
-		return res.json({errmsg: "Unauthorized access"});
+  if (!req.xhr)
+    return res.json({errmsg: "Unauthorized access"});
   const from = req.body["email"];
-	const subject = req.body["subject"];
-	const body = req.body["content"];
+  const subject = req.body["subject"];
+  const body = req.body["content"];
 
-	// TODO: sanitize ?
-	mailer(from, params.mail.contact, subject, body, err => {
-		if (!!err)
-			return res.json({errmsg:err});
-		// OK, everything fine
-		res.json({}); //ignored
-	});
+  // TODO: sanitize ?
+  mailer(from, params.mail.contact, subject, body, err => {
+    if (!!err)
+      return res.json({errmsg:err});
+    // OK, everything fine
+    res.json({}); //ignored
+  });
 });
 
 module.exports = router;
diff --git a/server/routes/users.js b/server/routes/users.js
index 1d553dba..129cda23 100644
--- a/server/routes/users.js
+++ b/server/routes/users.js
@@ -18,7 +18,7 @@ router.get("/whoami", access.ajax, (req,res) => {
     });
   };
   const anonymous = {name:"", email:"", id:0, notify:false};
-	if (!req.cookies.token)
+  if (!req.cookies.token)
     return callback(anonymous);
   UserModel.getOne("sessionToken", req.cookies.token, function(err, user) {
     if (!!err || !user)
@@ -41,101 +41,101 @@ router.get("/users", access.ajax, (req,res) => {
 // to: object user (to who we send an email)
 function setAndSendLoginToken(subject, to, res)
 {
-	// Set login token and send welcome(back) email with auth link
-	const token = genToken(params.token.length);
-	UserModel.setLoginToken(token, to.id, err => {
-		if (!!err)
-			return res.json({errmsg: err.toString()});
-		const body =
-			"Hello " + to.name + "!\\n" +
-			"Access your account here: " +
-			params.siteURL + "/#/authenticate/" + token + "\\n" +
-			"Token will expire in " + params.token.expire/(1000*60) + " minutes."
-		sendEmail(params.mail.noreply, to.email, subject, body, err => {
-			res.json(err || {});
-		});
-	});
+  // Set login token and send welcome(back) email with auth link
+  const token = genToken(params.token.length);
+  UserModel.setLoginToken(token, to.id, err => {
+    if (!!err)
+      return res.json({errmsg: err.toString()});
+    const body =
+      "Hello " + to.name + "!\\n" +
+      "Access your account here: " +
+      params.siteURL + "/#/authenticate/" + token + "\\n" +
+      "Token will expire in " + params.token.expire/(1000*60) + " minutes."
+    sendEmail(params.mail.noreply, to.email, subject, body, err => {
+      res.json(err || {});
+    });
+  });
 }
 
 router.post('/register', access.unlogged, access.ajax, (req,res) => {
-	const name = req.body.name;
-	const email = req.body.email;
-	const notify = !!req.body.notify;
-	const error = UserModel.checkNameEmail({name: name, email: email});
-	if (!!error)
-		return res.json({errmsg: error});
-	UserModel.create(name, email, notify, (err,uid) => {
-		if (!!err)
-			return res.json({errmsg: err.toString()});
-		const user = {
-			id: uid["rowid"],
-			name: name,
-			email: email,
-		};
-		setAndSendLoginToken("Welcome to " + params.siteURL, user, res);
-	});
+  const name = req.body.name;
+  const email = req.body.email;
+  const notify = !!req.body.notify;
+  const error = UserModel.checkNameEmail({name: name, email: email});
+  if (!!error)
+    return res.json({errmsg: error});
+  UserModel.create(name, email, notify, (err,uid) => {
+    if (!!err)
+      return res.json({errmsg: err.toString()});
+    const user = {
+      id: uid["rowid"],
+      name: name,
+      email: email,
+    };
+    setAndSendLoginToken("Welcome to " + params.siteURL, user, res);
+  });
 });
 
 router.get('/sendtoken', access.unlogged, access.ajax, (req,res) => {
-	const nameOrEmail = decodeURIComponent(req.query.nameOrEmail);
-	const type = (nameOrEmail.indexOf('@') >= 0 ? "email" : "name");
-	const error = UserModel.checkNameEmail({[type]: nameOrEmail});
-	if (!!error)
-		return res.json({errmsg: error});
-	UserModel.getOne(type, nameOrEmail, (err,user) => {
-		access.checkRequest(res, err, user, "Unknown user", () => {
-			setAndSendLoginToken("Token for " + params.siteURL, user, res);
-		});
-	});
+  const nameOrEmail = decodeURIComponent(req.query.nameOrEmail);
+  const type = (nameOrEmail.indexOf('@') >= 0 ? "email" : "name");
+  const error = UserModel.checkNameEmail({[type]: nameOrEmail});
+  if (!!error)
+    return res.json({errmsg: error});
+  UserModel.getOne(type, nameOrEmail, (err,user) => {
+    access.checkRequest(res, err, user, "Unknown user", () => {
+      setAndSendLoginToken("Token for " + params.siteURL, user, res);
+    });
+  });
 });
 
 router.get('/authenticate', access.unlogged, access.ajax, (req,res) => {
   UserModel.getOne("loginToken", req.query.token, (err,user) => {
-		access.checkRequest(res, err, user, "Invalid token", () => {
+    access.checkRequest(res, err, user, "Invalid token", () => {
       // If token older than params.tokenExpire, do nothing
-			if (Date.now() > user.loginTime + params.token.expire)
-				return res.json({errmsg: "Token expired"});
-			// Generate session token (if not exists) + destroy login token
-			UserModel.trySetSessionToken(user.id, (err,token) => {
-				if (!!err)
-					return res.json({errmsg: err.toString()});
-				// Set cookie
+      if (Date.now() > user.loginTime + params.token.expire)
+        return res.json({errmsg: "Token expired"});
+      // Generate session token (if not exists) + destroy login token
+      UserModel.trySetSessionToken(user.id, (err,token) => {
+        if (!!err)
+          return res.json({errmsg: err.toString()});
+        // Set cookie
         res.cookie("token", token, {
-					httpOnly: true,
-					secure: !!params.siteURL.match(/^https/),
-					maxAge: params.cookieExpire,
-				});
-				res.json({
+          httpOnly: true,
+          secure: !!params.siteURL.match(/^https/),
+          maxAge: params.cookieExpire,
+        });
+        res.json({
           id: user.id,
           name: user.name,
           email: user.email,
           notify: user.notify,
         });
-			});
-		});
-	});
+      });
+    });
+  });
 });
 
 router.put('/update', access.logged, access.ajax, (req,res) => {
-	const name = req.body.name;
-	const email = req.body.email;
-	const error = UserModel.checkNameEmail({name: name, email: email});
-	if (!!error)
-		return res.json({errmsg: error});
-	const user = {
-		id: req.userId,
-		name: name,
-		email: email,
-		notify: !!req.body.notify,
-	};
-	UserModel.updateSettings(user, err => {
-		res.json(err ? {errmsg: err.toString()} : {});
-	});
+  const name = req.body.name;
+  const email = req.body.email;
+  const error = UserModel.checkNameEmail({name: name, email: email});
+  if (!!error)
+    return res.json({errmsg: error});
+  const user = {
+    id: req.userId,
+    name: name,
+    email: email,
+    notify: !!req.body.notify,
+  };
+  UserModel.updateSettings(user, err => {
+    res.json(err ? {errmsg: err.toString()} : {});
+  });
 });
 
 router.get('/logout', access.logged, access.ajax, (req,res) => {
-	res.clearCookie("token");
-	res.json({});
+  res.clearCookie("token");
+  res.json({});
 });
 
 module.exports = router;
diff --git a/server/routes/variants.js b/server/routes/variants.js
index f52c6a49..8153c7a7 100644
--- a/server/routes/variants.js
+++ b/server/routes/variants.js
@@ -6,11 +6,11 @@ const VariantModel = require("../models/Variant");
 const access = require("../utils/access");
 
 router.get('/variants', access.ajax, function(req, res, next) {
-	VariantModel.getAll((err,variants) => {
-		if (!!err)
-			return next(err);
-		res.json({variantArray:variants});
-	});
+  VariantModel.getAll((err,variants) => {
+    if (!!err)
+      return next(err);
+    res.json({variantArray:variants});
+  });
 });
 
 module.exports = router;
diff --git a/server/utils/access.js b/server/utils/access.js
index 2e2fa92d..d51c4b77 100644
--- a/server/utils/access.js
+++ b/server/utils/access.js
@@ -2,65 +2,65 @@ var UserModel = require("../models/User");
 
 module.exports =
 {
-	// Prevent access to "users pages"
-	logged: function(req, res, next) {
-		const callback = () => {
-			if (!loggedIn)
-				return res.json({errmsg: "Not logged in"});
-			next();
-		};
-		let loggedIn = undefined;
-		if (!req.cookies.token)
-		{
-			loggedIn = false;
-			callback();
-		}
-		else
-		{
-			UserModel.getOne("sessionToken", req.cookies.token, function(err, user) {
-				if (!!user)
-				{
-					req.userId = user.id;
-					req.userName = user.name;
-					loggedIn = true;
-				}
-				else
-				{
-					// Token in cookies presumably wrong: erase it
-					res.clearCookie("token");
-					loggedIn = false;
-				}
-				callback();
-			});
-		}
-	},
+  // Prevent access to "users pages"
+  logged: function(req, res, next) {
+    const callback = () => {
+      if (!loggedIn)
+        return res.json({errmsg: "Not logged in"});
+      next();
+    };
+    let loggedIn = undefined;
+    if (!req.cookies.token)
+    {
+      loggedIn = false;
+      callback();
+    }
+    else
+    {
+      UserModel.getOne("sessionToken", req.cookies.token, function(err, user) {
+        if (!!user)
+        {
+          req.userId = user.id;
+          req.userName = user.name;
+          loggedIn = true;
+        }
+        else
+        {
+          // Token in cookies presumably wrong: erase it
+          res.clearCookie("token");
+          loggedIn = false;
+        }
+        callback();
+      });
+    }
+  },
 
-	// Prevent access to "anonymous pages"
-	unlogged: function(req, res, next) {
-		// Just a quick heuristic, which should be enough
-		const loggedIn = !!req.cookies.token;
-		if (loggedIn)
-			return res.json({errmsg: "Already logged in"});
-		next();
-	},
+  // Prevent access to "anonymous pages"
+  unlogged: function(req, res, next) {
+    // Just a quick heuristic, which should be enough
+    const loggedIn = !!req.cookies.token;
+    if (loggedIn)
+      return res.json({errmsg: "Already logged in"});
+    next();
+  },
 
-	// Prevent direct access to AJAX results
-	ajax: function(req, res, next) {
+  // Prevent direct access to AJAX results
+  ajax: function(req, res, next) {
     if (!req.xhr)
-			return res.json({errmsg: "Unauthorized access"});
-		next();
-	},
+      return res.json({errmsg: "Unauthorized access"});
+    next();
+  },
 
-	// Check for errors before callback (continue page loading). TODO: better name.
-	checkRequest: function(res, err, out, msg, cb) {
-		if (!!err)
-			return res.json({errmsg: err.errmsg || err.toString()});
-		if (!out
-			|| (Array.isArray(out) && out.length == 0)
-			|| (typeof out === "object" && Object.keys(out).length == 0))
-		{
-			return res.json({errmsg: msg});
-		}
-		cb();
-	},
+  // Check for errors before callback (continue page loading). TODO: better name.
+  checkRequest: function(res, err, out, msg, cb) {
+    if (!!err)
+      return res.json({errmsg: err.errmsg || err.toString()});
+    if (!out
+      || (Array.isArray(out) && out.length == 0)
+      || (typeof out === "object" && Object.keys(out).length == 0))
+    {
+      return res.json({errmsg: msg});
+    }
+    cb();
+  },
 }
diff --git a/server/utils/database.js b/server/utils/database.js
index ae7c7a66..2904f2ae 100644
--- a/server/utils/database.js
+++ b/server/utils/database.js
@@ -2,7 +2,7 @@ const sqlite3 = require('sqlite3');
 const params = require("../config/parameters")
 
 if (params.env == "development")
-	sqlite3.verbose();
+  sqlite3.verbose();
 
 const DbPath = __dirname.replace("/utils", "/db/vchess.sqlite");
 const db = new sqlite3.Database(DbPath);
diff --git a/server/utils/mailer.js b/server/utils/mailer.js
index 86e647c2..9a42a174 100644
--- a/server/utils/mailer.js
+++ b/server/utils/mailer.js
@@ -3,42 +3,42 @@ const params = require("../config/parameters");
 
 module.exports = function(from, to, subject, body, cb)
 {
-	// Avoid the actual sending in development mode
-	if (params.env === 'development')
-	{
-		console.log("New mail: from " + from + " / to " + to);
-		console.log("Subject: " + subject);
-		let msgText = body.split('\\n');
-		msgText.forEach(msg => { console.log(msg); });
-		return cb();
-	}
+  // Avoid the actual sending in development mode
+  if (params.env === 'development')
+  {
+    console.log("New mail: from " + from + " / to " + to);
+    console.log("Subject: " + subject);
+    let msgText = body.split('\\n');
+    msgText.forEach(msg => { console.log(msg); });
+    return cb();
+  }
 
   // Create reusable transporter object using the default SMTP transport
-	const transporter = nodemailer.createTransport({
-		host: params.mail.host,
-		port: params.mail.port,
-		secure: params.mail.secure,
-		auth: {
-			user: params.mail.user,
-			pass: params.mail.pass
-		}
-	});
+  const transporter = nodemailer.createTransport({
+    host: params.mail.host,
+    port: params.mail.port,
+    secure: params.mail.secure,
+    auth: {
+      user: params.mail.user,
+      pass: params.mail.pass
+    }
+  });
 
-	// Setup email data with unicode symbols
-	const mailOptions = {
-		from: params.mail.noreply,
-		to: to,
-		subject: subject,
-		text: body,
+  // Setup email data with unicode symbols
+  const mailOptions = {
+    from: params.mail.noreply,
+    to: to,
+    subject: subject,
+    text: body,
     replyTo: from,
   };
 
-	// Send mail with the defined transport object
-	transporter.sendMail(mailOptions, (error, info) => {
-		if (!!error)
-			return cb(error);
+  // Send mail with the defined transport object
+  transporter.sendMail(mailOptions, (error, info) => {
+    if (!!error)
+      return cb(error);
     // Ignore info. Option:
-		//console.log('Message sent: %s', info.messageId);
-		return cb();
+    //console.log('Message sent: %s', info.messageId);
+    return cb();
   });
 }
diff --git a/server/utils/tokenGenerator.js b/server/utils/tokenGenerator.js
index b549198a..858bc3bf 100644
--- a/server/utils/tokenGenerator.js
+++ b/server/utils/tokenGenerator.js
@@ -1,13 +1,13 @@
 function randString()
 {
-	return Math.random().toString(36).substr(2); // remove `0.`
+  return Math.random().toString(36).substr(2); // remove `0.`
 }
 
 module.exports = function(tlen)
 {
-	let res = "";
-	let nbRands = Math.ceil(tlen/10); //10 = min length of a rand() string
-	for (let i = 0; i < nbRands; i++)
-		res += randString();
-	return res.substr(0, tlen);
+  let res = "";
+  let nbRands = Math.ceil(tlen/10); //10 = min length of a rand() string
+  for (let i = 0; i < nbRands; i++)
+    res += randString();
+  return res.substr(0, tlen);
 }
-- 
2.44.0