From: Benjamin Auder <benjamin.auder@somewhere>
Date: Wed, 26 Dec 2018 21:45:23 +0000 (+0100)
Subject: Bugs fixing, finalization of rules in french+english
X-Git-Url: https://git.auder.net/doc/html/css/pieces/vendor/common.css?a=commitdiff_plain;h=69f3d8014e594ef949792d04d97b8286e9c2c268;p=vchess.git

Bugs fixing, finalization of rules in french+english
---

diff --git a/TODO b/TODO
new file mode 100644
index 00000000..3f79e69f
--- /dev/null
+++ b/TODO
@@ -0,0 +1,19 @@
+Finish rules translation in Spanish
+
+Design: final touch (gain extra space on top, using space on the right)
+Crazyhouse: my reserve vertically on the right, opponent just below board
+
+Bug MarseilleRules turn issue in computer game (last move is wrong)
+
+[Site "vchess.club"]
+[Variant "Marseille"]
+[Date "2018-12-26"]
+[White "Myself"]
+[Black "Computer"]
+[FenStart "nrqkbrnb/pppppppp/8/8/8/8/PPPPPPPP/RKQBNRBN"]
+[Fen "1rq3nb/1pk1p1p1/1Np1p2p/p7/8/2P5/PP2P1PP/RK1B2B1 w1 1000 -"]
+[Result "0-1"]
+
+1.f4 a5,h6 2.d3,c3 c6,Kc7 3.Qe3,Qe5 d6,dxe5 4.fxe5,e6 fxe6,Rxf1 5.Ng3,Nxf1 Nb6,Bg6 6.Nd2,Ne4 Bxe4,Bxd3 7.Nxd3,Nc5 Na4,Nxb6
+
+1.f2f4 a7a5,h7h6 2.d2d3,c2c3 c7c6,d8c7 3.c1e3,e3e5 d7d6,d6e5 4.f4e5,e5e6 f7e6,f8f1 5.h1g3,g3f1 a8b6,e8g6 6.f1d2,d2e4 g6e4,e4d3 7.e1d3,d3c5 c5a4,a4b6
diff --git a/public/javascripts/base_rules.js b/public/javascripts/base_rules.js
index 891948b9..9599fdbf 100644
--- a/public/javascripts/base_rules.js
+++ b/public/javascripts/base_rules.js
@@ -628,7 +628,8 @@ class ChessRules
 		const lastRank = (color == "w" ? 0 : sizeX-1);
 		const pawnColor = this.getColor(x,y); //can be different for checkered
 
-		if (x+shiftX >= 0 && x+shiftX < sizeX) //TODO: always true
+		// NOTE: next condition is generally true (no pawn on last rank)
+		if (x+shiftX >= 0 && x+shiftX < sizeX)
 		{
 			const finalPieces = x + shiftX == lastRank
 				? [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN]
@@ -1001,22 +1002,27 @@ class ChessRules
 		{
 			this.kingPos[c][0] = move.appear[0].x;
 			this.kingPos[c][1] = move.appear[0].y;
-			this.castleFlags[c] = [false,false];
+			if (V.HasFlags)
+				this.castleFlags[c] = [false,false];
 			return;
 		}
-		const oppCol = this.getOppCol(c);
-		const oppFirstRank = (V.size.x-1) - firstRank;
-		if (move.start.x == firstRank //our rook moves?
-			&& this.INIT_COL_ROOK[c].includes(move.start.y))
-		{
-			const flagIdx = (move.start.y == this.INIT_COL_ROOK[c][0] ? 0 : 1);
-			this.castleFlags[c][flagIdx] = false;
-		}
-		else if (move.end.x == oppFirstRank //we took opponent rook?
-			&& this.INIT_COL_ROOK[oppCol].includes(move.end.y))
+		if (V.HasFlags)
 		{
-			const flagIdx = (move.end.y == this.INIT_COL_ROOK[oppCol][0] ? 0 : 1);
-			this.castleFlags[oppCol][flagIdx] = false;
+			// Update castling flags if rooks are moved
+			const oppCol = this.getOppCol(c);
+			const oppFirstRank = (V.size.x-1) - firstRank;
+			if (move.start.x == firstRank //our rook moves?
+				&& this.INIT_COL_ROOK[c].includes(move.start.y))
+			{
+				const flagIdx = (move.start.y == this.INIT_COL_ROOK[c][0] ? 0 : 1);
+				this.castleFlags[c][flagIdx] = false;
+			}
+			else if (move.end.x == oppFirstRank //we took opponent rook?
+				&& this.INIT_COL_ROOK[oppCol].includes(move.end.y))
+			{
+				const flagIdx = (move.end.y == this.INIT_COL_ROOK[oppCol][0] ? 0 : 1);
+				this.castleFlags[oppCol][flagIdx] = false;
+			}
 		}
 	}
 
@@ -1244,7 +1250,7 @@ class ChessRules
 		}
 		else
 			return currentBest;
-		//console.log(moves1.map(m => { return [this.getNotation(m), m.eval]; }));
+//		console.log(moves1.map(m => { return [this.getNotation(m), m.eval]; }));
 
 		candidates = [0];
 		for (let j=1; j<moves1.length && moves1[j].eval == moves1[0].eval; j++)
diff --git a/public/javascripts/components/game.js b/public/javascripts/components/game.js
index 6627252f..ac03c8fc 100644
--- a/public/javascripts/components/game.js
+++ b/public/javascripts/components/game.js
@@ -1287,6 +1287,12 @@ Vue.component('my-game', {
 		},
 		clickComputerGame: function(e) {
 			this.getRidOfTooltip(e.currentTarget);
+			if (this.mode == "computer" && this.score == "*"
+				&& this.vr.turn != this.mycolor)
+			{
+				// Wait for computer reply first (avoid potential "ghost move" bug)
+				return;
+			}
 			this.newGame("computer");
 		},
 		clickFriendGame: function(e) {
@@ -1344,8 +1350,6 @@ Vue.component('my-game', {
 							return;
 						}
 					}
-					else if (score == "*")
-						return this.continueGame("computer");
 				}
 			}
 			else if (mode == "friend")
@@ -1416,7 +1420,7 @@ Vue.component('my-game', {
 			else if (mode == "computer")
 			{
 				this.compWorker.postMessage(["init",fen]);
-				if (this.mycolor != this.vr.turn)
+				if (score == "*" && this.mycolor != this.vr.turn)
 					this.playComputerMove();
 			}
 			//else: nothing special to do in friend mode
diff --git a/public/javascripts/utils/printDiagram.js b/public/javascripts/utils/printDiagram.js
index b7282fee..47274303 100644
--- a/public/javascripts/utils/printDiagram.js
+++ b/public/javascripts/utils/printDiagram.js
@@ -42,6 +42,32 @@ function getDiagram(args)
 					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;
diff --git a/public/javascripts/variants/Baroque.js b/public/javascripts/variants/Baroque.js
index 0b00c262..fe0e8466 100644
--- a/public/javascripts/variants/Baroque.js
+++ b/public/javascripts/variants/Baroque.js
@@ -1,4 +1,4 @@
-class UltimaRules extends ChessRules
+class BaroqueRules extends ChessRules
 {
 	static get HasFlags() { return false; }
 
@@ -7,7 +7,7 @@ class UltimaRules extends ChessRules
 	static getPpath(b)
 	{
 		if (b[1] == "m") //'m' for Immobilizer (I is too similar to 1)
-			return "Ultima/" + b;
+			return "Baroque/" + b;
 		return b; //usual piece
 	}
 
@@ -169,7 +169,7 @@ class UltimaRules extends ChessRules
 		});
 	}
 
-	// "Pincher"
+	// "Pincer"
 	getPotentialPawnMoves([x,y])
 	{
 		let moves = super.getPotentialRookMoves([x,y]);
@@ -526,18 +526,6 @@ class UltimaRules extends ChessRules
 		return false;
 	}
 
-	updateVariables(move)
-	{
-		// Just update king(s) position(s)
-		const piece = move.vanish[0].p;
-		const c = move.vanish[0].c;
-		if (piece == V.KING && move.appear.length > 0)
-		{
-			this.kingPos[c][0] = move.appear[0].x;
-			this.kingPos[c][1] = move.appear[0].y;
-		}
-	}
-
 	static get VALUES()
 	{
 		// TODO: totally experimental!
@@ -627,4 +615,4 @@ class UltimaRules extends ChessRules
 	}
 }
 
-const VariantRules = UltimaRules;
+const VariantRules = BaroqueRules;
diff --git a/public/javascripts/variants/Berolina.js b/public/javascripts/variants/Berolina.js
index 8ea3c9cc..31630ab5 100644
--- a/public/javascripts/variants/Berolina.js
+++ b/public/javascripts/variants/Berolina.js
@@ -44,37 +44,34 @@ class BerolinaRules extends ChessRules
 		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];
 
-		if (x+shiftX >= 0 && x+shiftX < sizeX) //TODO: always true
+		// One square diagonally
+		for (let shiftY of [-1,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)
 			{
-				if (this.board[x+shiftX][y+shiftY] == V.EMPTY)
+				for (let piece of finalPieces)
 				{
-					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]));
-					}
+					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}));
-			}
+		}
+		// 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
diff --git a/public/javascripts/variants/Dark.js b/public/javascripts/variants/Dark.js
index 96f50de7..f1bd6c0d 100644
--- a/public/javascripts/variants/Dark.js
+++ b/public/javascripts/variants/Dark.js
@@ -16,6 +16,7 @@ class DarkRules extends ChessRules
 
 	updateEnlightened()
 	{
+		const pawnShift = {"w":-1, "b":1};
 		// Initialize with pieces positions (which are seen)
 		for (let i=0; i<V.size.x; i++)
 		{
@@ -24,7 +25,22 @@ class DarkRules extends ChessRules
 				this.enlightened["w"][i][j] = false;
 				this.enlightened["b"][i][j] = false;
 				if (this.board[i][j] != V.EMPTY)
-					this.enlightened[this.getColor(i,j)][i][j] = true;
+				{
+					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;
@@ -75,19 +91,11 @@ class DarkRules extends ChessRules
 
 	updateVariables(move)
 	{
-		// Update kings positions
-		const piece = move.vanish[0].p;
-		const c = move.vanish[0].c;
-		if (piece == V.KING && move.appear.length > 0)
-		{
-			this.kingPos[c][0] = move.appear[0].x;
-			this.kingPos[c][1] = move.appear[0].y;
-		}
+		super.updateVariables(move);
 		if (move.vanish.length >= 2 && move.vanish[1].p == V.KING)
 		{
 			// We took opponent king !
-			const oppCol = this.getOppCol(c);
-			this.kingPos[oppCol] = [-1,-1];
+			this.kingPos[this.turn] = [-1,-1];
 		}
 
 		// Update moves for both colors:
diff --git a/public/javascripts/variants/Grand.js b/public/javascripts/variants/Grand.js
index 7f5fe422..16963f82 100644
--- a/public/javascripts/variants/Grand.js
+++ b/public/javascripts/variants/Grand.js
@@ -13,7 +13,7 @@ class GrandRules extends ChessRules
 			return false;
 		const fenParsed = V.ParseFen(fen);
 		// 5) Check captures
-		if (!fenParsed.captured || !fenParsed.captured.match(/^[0-9]{10,10}$/))
+		if (!fenParsed.captured || !fenParsed.captured.match(/^[0-9]{14,14}$/))
 			return false;
 		return true;
 	}
@@ -51,11 +51,15 @@ class GrandRules extends ChessRules
 
 	getCapturedFen()
 	{
-		let counts = _.map(_.range(10), 0);
-		for (let i=0; i<V.PIECES.length-1; i++) //-1: no king captured
+		let counts = _.map(_.range(14), 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[5+i] = this.captured["b"][V.PIECES[i]];
+			counts[7+i] = this.captured["b"][V.PIECES[i]];
+			i++;
 		}
 		return counts.join("");
 	}
@@ -74,14 +78,18 @@ class GrandRules extends ChessRules
 				[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[5]),
-				[V.ROOK]: parseInt(fenParsed.captured[6]),
-				[V.KNIGHT]: parseInt(fenParsed.captured[7]),
-				[V.BISHOP]: parseInt(fenParsed.captured[8]),
-				[V.QUEEN]: parseInt(fenParsed.captured[9]),
+				[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]),
 			}
 		};
 	}
@@ -167,80 +175,75 @@ class GrandRules extends ChessRules
 		const color = this.turn;
 		let moves = [];
 		const [sizeX,sizeY] = [V.size.x,V.size.y];
-		const shift = (color == "w" ? -1 : 1);
+		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];
 
-		if (x+shift >= 0 && x+shift < sizeX && x+shift != lastRanks[0])
+		// Always x+shiftX >= 0 && x+shiftX < sizeX, because no pawns on last rank
+		let finalPieces = undefined;
+		if (lastRanks.includes(x + shiftX))
 		{
-			// Normal moves
-			if (this.board[x+shift][y] == V.EMPTY)
+			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))
 			{
-				moves.push(this.getBasicMove([x,y], [x+shift,y]));
-				if (startRanks.includes(x) && this.board[x+2*shift][y] == V.EMPTY)
+				if (this.board[x+2*shiftX][y] == V.EMPTY)
 				{
 					// Two squares jump
-					moves.push(this.getBasicMove([x,y], [x+2*shift,y]));
-					if (x == startRanks[0] && this.board[x+3*shift][y] == V.EMPTY)
+					moves.push(this.getBasicMove([x,y], [x+2*shiftX,y]));
+					if (x==startRanks[0] && this.board[x+3*shiftX][y] == V.EMPTY)
 					{
-						// 3-squares jump
-						moves.push(this.getBasicMove([x,y], [x+3*shift,y]));
+						// Three squares jump
+						moves.push(this.getBasicMove([x,y], [x+3*shiftX,y]));
 					}
 				}
 			}
-			// 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]));
-			}
-			if (y<sizeY-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]));
-			}
 		}
-
-		if (lastRanks.includes(x+shift))
+		// Captures
+		for (let shiftY of [-1,1])
 		{
-			// Promotion
-			let promotionPieces = [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN,V.MARSHALL,V.CARDINAL];
-			promotionPieces.forEach(p => {
-				if (this.captured[color][p]==0)
-					return;
-				// Normal move
-				if (this.board[x+shift][y] == V.EMPTY)
-					moves.push(this.getBasicMove([x,y], [x+shift,y], {c:color,p:p}));
-				// 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:p}));
-				}
-				if (y<sizeY-1 && this.canTake([x,y], [x+shift,y+1])
-					&& this.board[x+shift][y+1] != V.EMPTY)
+			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+shift,y+1], {c:color,p:p}));
+					moves.push(this.getBasicMove([x,y], [x+shiftX,y+shiftY],
+						{c:color,p:piece}));
 				}
-			});
+			}
 		}
 
 		// En passant
 		const Lep = this.epSquares.length;
-		const epSquare = Lep>0 ? this.epSquares[Lep-1] : undefined;
+		const epSquare = this.epSquares[Lep-1];
 		if (!!epSquare)
 		{
 			for (let epsq of epSquare)
 			{
 				// TODO: some redundant checks
-				if (epsq.x == x+shift && Math.abs(epsq.y - y) == 1)
+				if (epsq.x == x+shiftX && Math.abs(epsq.y - y) == 1)
 				{
-					var enpassantMove = this.getBasicMove([x,y], [x+shift,epsq.y]);
+					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: x,
+						x: px,
 						y: epsq.y,
 						p: 'p',
-						c: this.getColor(x,epsq.y)
+						c: this.getColor(px,epsq.y)
 					});
 					moves.push(enpassantMove);
 				}
@@ -288,18 +291,25 @@ class GrandRules extends ChessRules
 	updateVariables(move)
 	{
 		super.updateVariables(move);
-		if (move.vanish.length==2 && move.appear.length==1 && move.vanish[1].p != V.PAWN)
+		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 && move.vanish[1].p != V.PAWN)
+		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()
@@ -374,7 +384,7 @@ class GrandRules extends ChessRules
 		return pieces["b"].join("") +
 			"/pppppppppp/10/10/10/10/10/10/PPPPPPPPPP/" +
 			pieces["w"].join("").toUpperCase() +
-			" w 1111 - 0000000000";
+			" w 1111 - 00000000000000";
 	}
 }
 
diff --git a/public/javascripts/variants/Marseille.js b/public/javascripts/variants/Marseille.js
index 6e72a236..618e8330 100644
--- a/public/javascripts/variants/Marseille.js
+++ b/public/javascripts/variants/Marseille.js
@@ -73,40 +73,37 @@ class MarseilleRules extends ChessRules
 		const startRank = (color == "w" ? sizeX-2 : 1);
 		const lastRank = (color == "w" ? 0 : sizeX-1);
 		const pawnColor = this.getColor(x,y); //can be different for checkered
+		const finalPieces = x + shiftX == lastRank
+			? [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN]
+			: [V.PAWN];
 
-		if (x+shiftX >= 0 && x+shiftX < sizeX) //TODO: always true
+		// One square forward
+		if (this.board[x+shiftX][y] == V.EMPTY)
 		{
-			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)
 			{
-				for (let piece of finalPieces)
-				{
-					moves.push(this.getBasicMove([x,y], [x+shiftX,y],
-						{c:pawnColor,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]));
-				}
+				moves.push(this.getBasicMove([x,y], [x+shiftX,y],
+					{c:pawnColor,p:piece}));
 			}
-			// Captures
-			for (let shiftY of [-1,1])
+			// Next condition because pawns on 1st rank can generally jump
+			if ([startRank,firstRank].includes(x)
+				&& this.board[x+2*shiftX][y] == V.EMPTY)
 			{
-				if (y + shiftY >= 0 && y + shiftY < sizeY
-					&& this.board[x+shiftX][y+shiftY] != V.EMPTY
-					&& this.canTake([x,y], [x+shiftX,y+shiftY]))
+				// 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)
 				{
-					for (let piece of finalPieces)
-					{
-						moves.push(this.getBasicMove([x,y], [x+shiftX,y+shiftY],
-							{c:pawnColor,p:piece}));
-					}
+					moves.push(this.getBasicMove([x,y], [x+shiftX,y+shiftY],
+						{c:pawnColor,p:piece}));
 				}
 			}
 		}
@@ -127,6 +124,7 @@ class MarseilleRules extends ChessRules
 		{
 			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)
@@ -362,7 +360,6 @@ class MarseilleRules extends ChessRules
 
 		return pgn;
 	}
-
 }
 
 const VariantRules = MarseilleRules;
diff --git a/public/javascripts/variants/Upsidedown.js b/public/javascripts/variants/Upsidedown.js
index 1a812887..3e389d0d 100644
--- a/public/javascripts/variants/Upsidedown.js
+++ b/public/javascripts/variants/Upsidedown.js
@@ -1,8 +1,8 @@
 class UpsidedownRules extends ChessRules
 {
-	static HasFlags() { return false; }
+	static get HasFlags() { return false; }
 
-	static HasEnpassant() { return false; }
+	static get HasEnpassant() { return false; }
 
 	getPotentialKingMoves(sq)
 	{
@@ -65,7 +65,7 @@ class UpsidedownRules extends ChessRules
 		return pieces["w"].join("").toUpperCase() +
 			"/PPPPPPPP/8/8/8/8/pppppppp/" +
 			pieces["b"].join("") +
-			" w 1111 -"; //add turn + flags + enpassant
+			" w"; //no castle, no en-passant
 	}
 }
 
diff --git a/public/javascripts/variants/Wildebeest.js b/public/javascripts/variants/Wildebeest.js
index 357a5eb8..293b3b15 100644
--- a/public/javascripts/variants/Wildebeest.js
+++ b/public/javascripts/variants/Wildebeest.js
@@ -110,78 +110,66 @@ class WildebeestRules extends ChessRules
 		const color = this.turn;
 		let moves = [];
 		const [sizeX,sizeY] = [V.size.x,V.size.y];
-		const shift = (color == "w" ? -1 : 1);
+		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 (x+shift >= 0 && x+shift < sizeX && x+shift != lastRank)
+		if (this.board[x+shiftX][y] == V.EMPTY)
 		{
-			// Normal moves
-			if (this.board[x+shift][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))
 			{
-				moves.push(this.getBasicMove([x,y], [x+shift,y]));
-				if (startRanks.includes(x) && this.board[x+2*shift][y] == V.EMPTY)
+				if (this.board[x+2*shiftX][y] == V.EMPTY)
 				{
 					// Two squares jump
-					moves.push(this.getBasicMove([x,y], [x+2*shift,y]));
-					if (x == startRanks[0] && this.board[x+3*shift][y] == V.EMPTY)
+					moves.push(this.getBasicMove([x,y], [x+2*shiftX,y]));
+					if (x==startRanks[0] && this.board[x+3*shiftX][y] == V.EMPTY)
 					{
-						// 3-squares jump
-						moves.push(this.getBasicMove([x,y], [x+3*shift,y]));
+						// Three squares jump
+						moves.push(this.getBasicMove([x,y], [x+3*shiftX,y]));
 					}
 				}
 			}
-			// 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]));
-			}
-			if (y<sizeY-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]));
-			}
 		}
-
-		if (x+shift == lastRank)
+		// Captures
+		for (let shiftY of [-1,1])
 		{
-			// Promotion
-			let promotionPieces = [V.QUEEN,V.WILDEBEEST];
-			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}));
-				// 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:p}));
-				}
-				if (y<sizeY-1 && this.canTake([x,y], [x+shift,y+1])
-					&& this.board[x+shift][y+1] != V.EMPTY)
+			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+shift,y+1], {c:color,p:p}));
+					moves.push(this.getBasicMove([x,y], [x+shiftX,y+shiftY],
+						{c:color,p:piece}));
 				}
-			});
+			}
 		}
 
 		// En passant
 		const Lep = this.epSquares.length;
-		const epSquare = Lep>0 ? this.epSquares[Lep-1] : undefined;
+		const epSquare = this.epSquares[Lep-1];
 		if (!!epSquare)
 		{
 			for (let epsq of epSquare)
 			{
 				// TODO: some redundant checks
-				if (epsq.x == x+shift && Math.abs(epsq.y - y) == 1)
+				if (epsq.x == x+shiftX && Math.abs(epsq.y - y) == 1)
 				{
-					var enpassantMove = this.getBasicMove([x,y], [x+shift,epsq.y]);
+					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: x,
+						x: px,
 						y: epsq.y,
 						p: 'p',
-						c: this.getColor(x,epsq.y)
+						c: this.getColor(px,epsq.y)
 					});
 					moves.push(enpassantMove);
 				}
diff --git a/public/javascripts/variants/Zen.js b/public/javascripts/variants/Zen.js
index 66cd61f4..0675fbc9 100644
--- a/public/javascripts/variants/Zen.js
+++ b/public/javascripts/variants/Zen.js
@@ -97,7 +97,7 @@ class ZenRules extends ChessRules
 		const firstRank = (color == 'w' ? sizeY-1 : 0);
 		const lastRank = (color == "w" ? 0 : sizeY-1);
 
-		if (x+shift >= 0 && x+shift < sizeX && x+shift != lastRank)
+		if (x+shift != lastRank)
 		{
 			// Normal moves
 			if (this.board[x+shift][y] == V.EMPTY)
@@ -111,9 +111,8 @@ class ZenRules extends ChessRules
 			}
 		}
 
-		if (x+shift == lastRank)
+		else //promotion
 		{
-			// Promotion
 			let promotionPieces = [V.ROOK,V.KNIGHT,V.BISHOP,V.QUEEN];
 			promotionPieces.forEach(p => {
 				// Normal move
diff --git a/public/stylesheets/variant.sass b/public/stylesheets/variant.sass
index c2e28a18..c5e38a89 100644
--- a/public/stylesheets/variant.sass
+++ b/public/stylesheets/variant.sass
@@ -195,9 +195,8 @@ div.board11
     background-color: #e6ee9c
     &:hover
       background-color: skyblue
-    .choice-piece
-      width: 90%
-      max-width: 100%
+    &.choice-piece
+      width: 100%
       height: auto
       display: block
 
@@ -313,6 +312,7 @@ figure.diagram-container
     display: block
     clear: both
     padding-top: 5px
+    font-size: 0.8em
 
 p.boxed
   background-color: #FFCC66
diff --git a/views/rules/Berolina/en.pug b/views/rules/Berolina/en.pug
index 5cdfff95..d723bc4e 100644
--- a/views/rules/Berolina/en.pug
+++ b/views/rules/Berolina/en.pug
@@ -15,21 +15,35 @@ h3 Basics
 
 p.
 	Only the pawn movements change, but since there are many on the board it's a
-	consequent change. They move diagonally instead of moving forward, and capture by
-	advancing to the next square vertically. The initial 2-squares jump is allowed,
-	as well as en-passant captures: after 1.d2b4 on the diagram,
-	1...Pxc3 e.p. is possible.
+	consequent change. They move forward diagonally instead of moving straight,
+	and capture by advancing to the next square vertically.
+
+figure.diagram-container
+	.diagram
+		| fen:8/8/5p2/5P2/P7/8/5P2/8 b5,e3,d4,g3,h4,e6,f6,g6:
+	figcaption Possible pawn moves
 
 p.
-	Note about notation: pawns
+	The initial 2-squares jump is allowed, as well as en-passant captures:
+	after 1.d2b4 on the diagram, 1...Pxc3 e.p. is possible.
+
+p.
+	About notation: since pawn captures are non-ambigous they writes e.g.
+	"Pxe6" ('P' is redundant but looks nicer); simple pawn moves are often
+	ambiguous, so they write for example "f2g3" (again, the starting row is
+	redundant but this also looks better).
 
 figure.diagram-container
 	.diagram
-		| fen:r3kbnr/pp3ppp/3p4/4p3/8/8/PPPPPPPP/R1BQKBNR:
-	figcaption After the moves 1.Nc3 d6?? 2.Nd5 e5 3.Nxc7
+		| fen:rnbqkbnr/p1pppppp/8/8/1Pp2P2/5N2/PPP1PPP1/R1BQKBNR c3:
+	figcaption.
+		After 1.Nf3 b7d5 2.h2f4 d5c4 3.d2b4, en-passant capture on marked square
+		is possible
 
-h3 More information
+h3 Other source
 
-p.
-	Possible starting point Wikipedia page ?
+p
+	| See for example the 
+	a(href="https://brainking.com/en/GameRules?tp=59") Berolina chess
+	| &nbsp;page on brainking.com.
 
diff --git a/views/rules/Berolina/fr.pug b/views/rules/Berolina/fr.pug
new file mode 100644
index 00000000..4ee1c25a
--- /dev/null
+++ b/views/rules/Berolina/fr.pug
@@ -0,0 +1,51 @@
+p.boxed
+	| Les pions avancent en diagonale, et capturent en montant
+	| d'une case verticalement.
+
+h3 Caractéristiques
+
+ul
+	li Échiquier: standard.
+	li Matériel: standard.
+	li Coups non capturants: mouvements de pions différents.
+	li Coups spéciaux: standards (prise en passant adaptée.
+	li Captures: standards (excepté les pions).
+	li Fin de partie: standard.
+
+h3 Bases
+
+p.
+	Seuls les déplacements de pions changent, mais puisqu'il y en a beaucoup sur
+	l'échiquier c'est un changement important. Ils se déplacent en diagonale
+	(toujours vers l'avant) au lieu d'avancer tout droit, et capturent en
+	montant d'une case verticalement.
+
+figure.diagram-container
+	.diagram
+		| fen:8/8/5p2/5P2/P7/8/5P2/8 b5,e3,d4,g3,h4,e6,f6,g6:
+	figcaption Possibles coups de pion
+
+p.
+	Le saut initial de deux cases est permis, ainsi que la prise en passant :
+	après 3.d2b4 sur le diagrame, 3...Pxc3 e.p. est possible.
+
+p.
+	Au sujet de la notation : puisque les captures effectuées par les pions ne
+	sont pas ambigues on les note par exemple "Pxe6" ('P' est redondant mais
+	l'écriture est plus jolie ainsi) ; en revanche les simples déplacements sont
+	en général ambigus, et donc notés par exemple "f2g3" (ici encore,
+	la rangée de départ est inutile mais l'écriture paraît mieux ainsi).
+
+figure.diagram-container
+	.diagram
+		| fen:rnbqkbnr/p1pppppp/8/8/1Pp2P2/5N2/PPP1PPP1/R1BQKBNR c3:
+	figcaption.
+		Après 1.Nf3 b7d5 2.h2f4 d5c4 3.d2b4, il est possible de capturer en passant
+		sur la case marquée
+
+h3 Autre source
+
+p
+	| Visitez par exemple la page 
+	a(href="https://brainking.com/fr/GameRules?tp=59") Échecs Berolina
+	| &nbsp;page sur brainking.com.
diff --git a/views/rules/Dark/en.pug b/views/rules/Dark/en.pug
index 7dc9aa9e..f4585e35 100644
--- a/views/rules/Dark/en.pug
+++ b/views/rules/Dark/en.pug
@@ -1,17 +1,14 @@
 p.boxed
 	| You only see what your pieces can reach. Incomplete information game.
 
-h3 Specifications
+h3 Divergences
 
 ul
-	li Chessboard: standard.
-	li Material: standard.
-	li Non-capturing moves: standard.
-	li Special moves: standard.
-	li Captures: standard.
 	li End of game: capture the king.
 
-p Incomplete information version of the orthodox game (also shuffled).
+p.
+	Incomplete information version of the orthodox game
+	(with randomized initial position).
 
 h3 Basics
 
@@ -20,11 +17,13 @@ p.
 	That is to say: empty squares where your pieces can go,
 	but also occupied squares where captures can be made.
 	For example on the illustration next, after 1.e4 d5 white sees a black
-	pawn on d5, the squares e5, h5, b5, a6 and all four first ranks.
+	pawn on d5, the squares e5, f5, h5, b5, a6 and all four first ranks.
+	The f5 square is visible (and empty) because of the
+	special case of pawn capture.
 
 figure.diagram-container
 	.diagram
-		| fen:8/8/8/3p4/4P3/8/PPPP1PPP/RNBQKBNR - w 8,7,b6,c6,d6,e6,f6,g6,h6,a5,c5,f5,g5:
+		| fen:8/8/8/3p4/4P3/8/PPPP1PPP/RNBQKBNR - w 8,7,b6-h6,a5,c5,g5:
 	figcaption Standard initial position after 1.e4 d5
 
 p.
@@ -33,13 +32,13 @@ p.
 	Good luck!
 
 p.
-	Note: the bot is not cheating - it really uses only the information described earlier.
-	Moreover, it is very basic and clearly less challenging that a human.
-	But it may be a fun start :)
+	Note: the bot is not cheating - it really uses only the information
+	described earlier. Moreover, it is very basic and clearly less
+	challenging that a human. But it may be a fun start :)
 
 h3 End of the game
 
-p Win by capturing the king (no checks, no stalemate).
+p Win by capturing the enemy king (no checks, no stalemate).
 
 h3 More information
 
diff --git a/views/rules/Dark/fr.pug b/views/rules/Dark/fr.pug
new file mode 100644
index 00000000..b64224e3
--- /dev/null
+++ b/views/rules/Dark/fr.pug
@@ -0,0 +1,53 @@
+p.boxed
+	| Vous ne voyez que ce que vos pièces peuvent atteindre.
+	| Jeu à information incomplète.
+
+h3 Divergences
+
+ul
+	li Fin de partie: capturer le roi.
+
+p.
+	Version à information incomplète du jeu d'échecs orthodoxe
+	(avec position initiale aléatoire).
+
+p Incomplete information version of the orthodox game (also shuffled).
+
+h3 Bases
+
+p.
+	Avant chaque coup, les joueurs ne voient que ce que leurs pièces peuvent
+	atteindre. C'est-à-dire : les cases vides où peuvent se déplacer les pièces,
+	mais aussi les cases occupées où des captures sont possibles.
+	Par exemple sur l'illustration suivante, après 1.e4 d5 les blancs voient
+	un pion noir en d5, les cases e5, f5, h5, b5, a6 et les quatre premières
+	rangées. La case f5 est visible (et vide) grâce au mode de capture
+	particulier des pions.
+
+figure.diagram-container
+	.diagram
+		| fen:8/8/8/3p4/4P3/8/PPPP1PPP/RNBQKBNR - w 8,7,b6-h6,a5,c5,g5:
+	figcaption Position de départ habituelle après 1.e4 d5
+
+p.
+	Choisissez votre coup prudemment, en vous basant sur ce qui est devinable des
+	derniers coups adverses. En particulier, compter le matériel est
+	indispensable. Bonne chance !
+
+p.
+	Note : le robot joueur ne triche pas - il n'utilise que l'information décrite
+	plus haut. De plus il est très basique et clairement moins performant qu'un
+	humain. Ceci dit il peut être amusant de commencer contre lui :)
+
+h3 Fin de partie
+
+p Gagnez en capturant le roi adverse (pas d'échecs, pas de pat).
+
+h3 Plus d'information
+
+p
+	| J'ai découvert cette variante sur 
+	a(href="https://www.buho21.com/") Buho21
+	| , qui dispose d'une belle interface mais dont l'esprit est très différent
+	| d'ici (système de classement, abonnement VIP ou publicités, etc.).
+	| Il semblait être le seul endroit où jouer en direct à cette variante.
diff --git a/views/rules/Marseille/en.pug b/views/rules/Marseille/en.pug
index 5c66e781..a390e9d9 100644
--- a/views/rules/Marseille/en.pug
+++ b/views/rules/Marseille/en.pug
@@ -18,24 +18,43 @@ p.
 h3 Basics
 
 p.
-	TODO: explain, every turn twice except if check on 1st turn, or
-	very first move in game.
-	En-passant: possible in any order if 2 ep squares,
-	otherwise has to be the first move.
-	OK even if opponent moved his pawn at subturn 1.
+	At the very first move of the game, white make only one move - as usual.
+	However, after that and for all the game each side must play twice at
+	every turn. There are two exceptions:
+
+ul
+	li.
+		If the first move gives check (maybe checkmate),
+		then a second move isn't played.
+	li.
+		If no move is available after the first move, then it's stalemate
+		and again, there is no second move.
 
 p.
-	PGN game notation: since there are two moves at each turn except on move 1,
-	a double move in the game is indicated as two comma-separated (ordered) moves,
-	as in 3.Na5,Bd3 e6,f4 (the two first are white moves,
+	About the PGN game notation: when a side plays two moves in a row,
+	they are separated (in order) by a comma in the PGN.
+	Example: 3.Na5,Bd3 e6,f4 (the two first are white moves,
 	the two others are black moves).
-	Sometimes a move gives check, or stalemate occurs after subTurn 1 :: just one move
 
 figure.diagram-container
 	.diagram
 		| fen:r1bqkbnr/pppp1p1p/2n5/4p2p/4P3/5N2/PPPP1PPP/RNB1KB1R:
 	figcaption After the moves 1.e4 e5,Nc6 2.Qh5,Nf3 g6,gxh5
 
+h3 En-passant capture
+
+p.
+	Capturing en-passant is allowed under certain conditions.
+	If the opponent moved a pawn allowing such a capture (once or twice),
+	then (to take it) you must capture en-passant at the first move of your turn.
+	After that, if (and only if) there is another en-passant capture available
+	you can play it on the second move.
+
+p.
+	Note: if a pawn 2-squares jump was made and then a piece landed at the
+	en-passant square at the second move, a pawn capture on this square
+	takes only the piece.
+
 h3 More information
 
 p
diff --git a/views/rules/Marseille/fr.pug b/views/rules/Marseille/fr.pug
new file mode 100644
index 00000000..51c0fcd0
--- /dev/null
+++ b/views/rules/Marseille/fr.pug
@@ -0,0 +1,56 @@
+p.boxed
+	| Jouez deux coups à chaque tour.
+
+h3 Divergences
+
+p.
+	La seule différence avec le jeu orthodoxe est la règle du double-coup, mais cela
+	affecte beaucoup le jeu.
+
+h3 Bases
+
+p.
+	Au tout début de la partie les blancs ne jouent qu'un seul coup, comme
+	d'habitude. Cependant, après cela et ce pour tout le reste de la partie
+	chaque camp doit jouer deux coups à chaque tour. Avec deux exceptions :
+
+ul
+	li.
+		Si le premier coup donne échec (peut-être mat),
+		alors un second coup n'est pas joué.
+	li.
+		Si aucun coup n'est autorisé après le premier, alors c'est pat et
+		une fois encore il n'y a pas de deuxième coup.
+
+p.
+	Au sujet du format PGN de la partie : quand un camp joue deux coups d'affilée,
+	ils sont séparés (dans l'ordre) par une virgule. Exemple : 3.Na5,Bd3 e6,f4
+	(les deux premiers sont des coups blancs, les deux suivants
+	sont des coups noirs).
+
+figure.diagram-container
+	.diagram
+		| fen:r1bqkbnr/pppp1p1p/2n5/4p2p/4P3/5N2/PPPP1PPP/RNB1KB1R:
+	figcaption Après les coups 1.e4 e5,Nc6 2.Qh5,Nf3 g6,gxh5
+
+h3 Prise en passant
+
+p.
+	Capturer en passant est autorisé sous certaines conditions.
+	Si l'adversaire a déplacé un pion permettant une telle capture
+	(une fois ou deux fois), alors pour en profiter il faut prendre en passant
+	dès le premier coup de votre tour. Ensuite, si (et seulement si) il y a
+	une autre prise en passant disponible, vous pouvez l'exécuter au second coup.
+
+p.
+	Note : si un pion se déplace de deux cases puis qu'une pièce occupe la case de
+	prise en passant au second coup d'un tour, une capture sur cette case ne
+	prendra que la pièce.
+
+h3 Plus d'information
+
+p
+	| Voir par exemple la page 
+	a(href="https://www.chessvariants.com/multimove.dir/marseill.html")
+		|	Échecs Marseillais
+	| &nbsp;sur chessvariants.com.
diff --git a/views/rules/Upsidedown/en.pug b/views/rules/Upsidedown/en.pug
index ff70cd30..8d4fa73b 100644
--- a/views/rules/Upsidedown/en.pug
+++ b/views/rules/Upsidedown/en.pug
@@ -1,37 +1,32 @@
 p.boxed
 	| Pawns start on the 7th rank. Move a knight to promote them.
 
-h3 Specifications
+h3 Divergences
 
-ul
-	li Chessboard: standard.
-	li Material: standard.
-	li Non-capturing moves: standard.
-	li Special moves: no castling (and no en-passant).
-	li Captures: standard.
-	li End of game: standard.
+p No castle, no en-passant capture.
 
 p.
-	...(Almost) Only the initial position changes, but this is a very big change.
+	...Only the initial position changes, but this makes a huge difference.
 	In particular, castling would be rather pointless so it's disabled here.
 	En-passant captures are impossible because all pawns already reached 7th rank.
 
-h3 Note on initial position
+h3 About the initial position
 
 p.
-	Since truly random start can allow un-defendable mate in 3 with a knight,
-	the kings touch at least one knight in the initial position.
+	Since truly random start can allow a mate in 3 with a knight,
+	the kings have at least one knight neighbor in the initial position.
 	This allows to move free out of potential check from the very beginning.
 
 p.
-	To illustrate this phenomenon, although it's defendable the standard initial
-	position allows the attack as 1.Na6 (threatening Nc5-Nd3#),
-	forcing the defense Nf3-Ne5. With a knight next to the king you have more options.
+	A less constraining condition would be to require the two knights to stand on
+	two squares of different colors, but it's not enough as proved by the
+	following diagram.
+	White can mate in 3: 1.Nc6 followed by Nb4 threatening both a2 and d3.
 
 figure.diagram-container
 	.diagram
-		| fen:R1BQKBNR/PPPPPPP/N7/8/8/8/pppppppp/rnbqkbnr:
-	figcaption Standard initial position after 1.Na6
+		| fen:RBN1BRRQ/PPPPPPP/8/4n3/8/8/Nppppppp/brkbqr1n:
+	figcaption After 1.Nc6 Nf3 2.Nb4 Ne5 (covers d3 but not a2) 3.Nxa2#
 
 h3 Source
 
diff --git a/views/rules/Upsidedown/fr.pug b/views/rules/Upsidedown/fr.pug
new file mode 100644
index 00000000..bc0a7087
--- /dev/null
+++ b/views/rules/Upsidedown/fr.pug
@@ -0,0 +1,38 @@
+p.boxed
+	| Pawns start on the 7th rank. Move a knight to promote them.
+
+h3 Specifications
+
+p Pas de roque ni prise en passant.
+
+p.
+	...Seule la position de départ change, mais c'est une énorme différence.
+	En particulier, le roque serait sans intérêt et est donc désactivé ici.
+	Les captures en passant sont également impossible car tous les pions
+	sont déjà sur la 7eme rangée.
+
+h3 Au sujet de la position initiale
+
+p.
+	Un placement complètement aléatoire des pièces peut permettre un mat en 3
+	à l'aide d'un cavalier : c'est pourquoi le roi est toujours à côté d'au moins
+	un cavalier en début de partie. Cela permet de se dégager des échecs dès le
+	premier coup.
+
+p.
+	Pour illustrer ce phénomène, les blancs peuvent mater en 3 dans la position
+	du diagramme suivant : 1.Na6 suivi de Nc5 puis Nd3#.
+	Si le cavalier noir était en g1 une défense serait possible par Nf3-Ne5 ;
+	mais avec un cavalier voisin du roi plus d'options sont offertes.
+
+figure.diagram-container
+	.diagram
+		| fen:R1BQKBNR/PPPPPPP/N7/8/8/8/pppppppp/rnbqkbrn c5,d3:
+	figcaption Standard initial position after 1.Na6
+
+h3 Source
+
+p
+	| Voir par exemple la page 
+	a(href="https://www.chessvariants.com/diffsetup.dir/upside.html") Échecs Upside down
+	| &nbsp;sur chessvariants.com.