Bugs fixing, finalization of rules in french+english
authorBenjamin Auder <benjamin.auder@somewhere>
Wed, 26 Dec 2018 21:45:23 +0000 (22:45 +0100)
committerBenjamin Auder <benjamin.auder@somewhere>
Wed, 26 Dec 2018 21:45:23 +0000 (22:45 +0100)
21 files changed:
TODO [new file with mode: 0644]
public/javascripts/base_rules.js
public/javascripts/components/game.js
public/javascripts/utils/printDiagram.js
public/javascripts/variants/Baroque.js
public/javascripts/variants/Berolina.js
public/javascripts/variants/Dark.js
public/javascripts/variants/Grand.js
public/javascripts/variants/Marseille.js
public/javascripts/variants/Upsidedown.js
public/javascripts/variants/Wildebeest.js
public/javascripts/variants/Zen.js
public/stylesheets/variant.sass
views/rules/Berolina/en.pug
views/rules/Berolina/fr.pug [new file with mode: 0644]
views/rules/Dark/en.pug
views/rules/Dark/fr.pug [new file with mode: 0644]
views/rules/Marseille/en.pug
views/rules/Marseille/fr.pug [new file with mode: 0644]
views/rules/Upsidedown/en.pug
views/rules/Upsidedown/fr.pug [new file with mode: 0644]

diff --git a/TODO b/TODO
new file mode 100644 (file)
index 0000000..3f79e69
--- /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
index 891948b..9599fdb 100644 (file)
@@ -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++)
index 6627252..ac03c8f 100644 (file)
@@ -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
index b7282fe..4727430 100644 (file)
@@ -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;
index 0b00c26..fe0e846 100644 (file)
@@ -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;
index 8ea3c9c..31630ab 100644 (file)
@@ -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
index 96f50de..f1bd6c0 100644 (file)
@@ -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:
index 7f5fe42..16963f8 100644 (file)
@@ -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";
        }
 }
 
index 6e72a23..618e833 100644 (file)
@@ -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;
index 1a81288..3e389d0 100644 (file)
@@ -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
        }
 }
 
index 357a5eb..293b3b1 100644 (file)
@@ -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);
                                }
index 66cd61f..0675fbc 100644 (file)
@@ -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
index c2e28a1..c5e38a8 100644 (file)
@@ -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
index 5cdfff9..d723bc4 100644 (file)
@@ -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 (file)
index 0000000..4ee1c25
--- /dev/null
@@ -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.
index 7dc9aa9..f4585e3 100644 (file)
@@ -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 (file)
index 0000000..b64224e
--- /dev/null
@@ -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.
index 5c66e78..a390e9d 100644 (file)
@@ -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 (file)
index 0000000..51c0fcd
--- /dev/null
@@ -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.
index ff70cd3..8d4fa73 100644 (file)
@@ -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 (file)
index 0000000..bc0a708
--- /dev/null
@@ -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.