From 33b427488bb6ee5c505c3a024bccedbef763f80e Mon Sep 17 00:00:00 2001
From: Benjamin Auder <benjamin.auder@somewhere>
Date: Mon, 25 Jul 2022 19:02:57 +0200
Subject: [PATCH] Untested draft refactor both/moves/attack for pieces specs

---
 base_rules.js                   |  47 ++++++++------
 utils/alea.js                   |   2 +-
 variants.js                     |   6 +-
 variants/Absorption/class.js    |   6 +-
 variants/Alapo/class.js         |   6 +-
 variants/Ambiguous/class.js     |   2 +-
 variants/Antiking1/class.js     |  15 +----
 variants/Arena/class.js         |   4 +-
 variants/Atomic/class.js        |   4 +-
 variants/Avalam/class.js        |   2 +-
 variants/Balaklava/class.js     |  59 +++++++++++++++++
 variants/Balaklava/rules.html   |   1 +
 variants/Balaklava/style.css    |   1 +
 variants/Bario/class.js         |  11 ++--
 variants/Benedict/class.js      |  46 +------------
 variants/Berolina/class.js      |  13 +++-
 variants/Bicolour/class.js      | 111 ++++++++++++++++++++++++++++++++
 variants/Bicolour/rules.html    |   1 +
 variants/Bicolour/style.css     |   1 +
 variants/Brotherhood/class.js   |  20 ++++++
 variants/Brotherhood/rules.html |   6 ++
 variants/Brotherhood/style.css  |   1 +
 variants/Chakart/class.js       |   1 -
 variants/_Berolina/class.js     |  23 -------
 variants/_Berolina/pawnSpec.js  |  19 ++++++
 variants/_Flip/class.js         |  43 +++++++++++++
 26 files changed, 324 insertions(+), 127 deletions(-)
 create mode 100644 variants/Balaklava/class.js
 create mode 100644 variants/Balaklava/rules.html
 create mode 100644 variants/Balaklava/style.css
 create mode 100644 variants/Bicolour/class.js
 create mode 100644 variants/Bicolour/rules.html
 create mode 100644 variants/Bicolour/style.css
 create mode 100644 variants/Brotherhood/class.js
 create mode 100644 variants/Brotherhood/rules.html
 create mode 100644 variants/Brotherhood/style.css
 delete mode 100644 variants/_Berolina/class.js
 create mode 100644 variants/_Berolina/pawnSpec.js
 create mode 100644 variants/_Flip/class.js

diff --git a/base_rules.js b/base_rules.js
index 4ee389f..5b1a013 100644
--- a/base_rules.js
+++ b/base_rules.js
@@ -1241,13 +1241,13 @@ export default class ChessRules {
       },
       'r': {
         "class": "rook",
-        moves: [
+        both: [
           {steps: [[0, 1], [0, -1], [1, 0], [-1, 0]]}
         ]
       },
       'n': {
         "class": "knight",
-        moves: [
+        both: [
           {
             steps: [
               [1, 2], [1, -2], [-1, 2], [-1, -2],
@@ -1259,13 +1259,13 @@ export default class ChessRules {
       },
       'b': {
         "class": "bishop",
-        moves: [
+        both: [
           {steps: [[1, 1], [1, -1], [-1, 1], [-1, -1]]}
         ]
       },
       'q': {
         "class": "queen",
-        moves: [
+        both: [
           {
             steps: [
               [0, 1], [0, -1], [1, 0], [-1, 0],
@@ -1276,7 +1276,7 @@ export default class ChessRules {
       },
       'k': {
         "class": "king",
-        moves: [
+        both: [
           {
             steps: [
               [0, 1], [0, -1], [1, 0], [-1, 0],
@@ -1341,12 +1341,19 @@ export default class ChessRules {
 
   getStepSpec(color, x, y, piece) {
     let pieceType = piece;
-    const allSpecs = this.pieces(color, x, y);
+    let allSpecs = this.pieces(color, x, y);
     if (!piece)
       pieceType = this.getPieceType(x, y);
     else if (allSpecs[piece].moveas)
       pieceType = allSpecs[piece].moveas;
-    return allSpecs[pieceType];
+    let res = allSpecs[pieceType];
+    if (!res["both"])
+      res.both = [];
+    if (!res["moves"])
+      res.moves = [];
+    if (!res["attack"])
+      res.attack = [];
+    return res;
   }
 
   // Can thing on square1 capture thing on square2?
@@ -1380,7 +1387,7 @@ export default class ChessRules {
     const oppCol = C.GetOppCol(color);
     const piece = this.getPieceType(x, y);
     const stepSpec = this.getStepSpec(color, x, y, piece);
-    const attacks = stepSpec.attack || stepSpec.moves;
+    const attacks = stepSpec.both.concat(stepSpec.attack);
     for (let a of attacks) {
       outerLoop: for (let step of a.steps) {
         let [i, j] = [x + step[0], y + step[1]];
@@ -1779,7 +1786,7 @@ export default class ChessRules {
         elt.segments = this.getSegments(segments, segStart, end);
       res.push(elt);
     };
-    const exploreSteps = (stepArray) => {
+    const exploreSteps = (stepArray, mode) => {
       for (let s of stepArray) {
         outerLoop: for (let step of s.steps) {
           if (o.segments) {
@@ -1798,9 +1805,9 @@ export default class ChessRules {
                 !o.captureTarget ||
                 (o.captureTarget[0] == i && o.captureTarget[1] == j)
               ) {
-                if (o.one && !o.attackOnly)
+                if (o.one && mode != "attack")
                   return true;
-                if (!o.attackOnly)
+                if (mode != "attack")
                   addSquare(!o.captureTarget ? [i, j] : [x, y]);
                 if (o.captureTarget)
                   return res[0];
@@ -1823,9 +1830,9 @@ export default class ChessRules {
           if (!explored[i + "." + j]) {
             explored[i + "." + j] = true;
             if (allowed([x, y], [i, j])) {
-              if (o.one && !o.moveOnly)
+              if (o.one && mode != "moves")
                 return true;
-              if (!o.moveOnly)
+              if (mode != "moves")
                 addSquare(!o.captureTarget ? [i, j] : [x, y]);
               if (
                 o.captureTarget &&
@@ -1840,17 +1847,15 @@ export default class ChessRules {
       return undefined; //default, but let's explicit it
     };
     if (o.captureTarget)
-      return exploreSteps(o.captureSteps)
+      return exploreSteps(o.captureSteps, "attack");
     else {
       const stepSpec =
         o.stepSpec || this.getStepSpec(this.getColor(x, y), x, y);
       let outOne = false;
-      if (!o.attackOnly || !stepSpec.attack)
-        outOne = exploreSteps(stepSpec.moves);
-      if (!outOne && !o.moveOnly && !!stepSpec.attack) {
-        o.attackOnly = true; //ok because o is always a temporary object
-        outOne = exploreSteps(stepSpec.attack);
-      }
+      if (!o.attackOnly)
+        outOne = exploreSteps(stepSpec.both.concat(stepSpec.moves), "moves");
+      if (!outOne && !o.moveOnly)
+        outOne = exploreSteps(stepSpec.both.concat(stepSpec.attack), "attack");
       return (o.one ? outOne : res);
     }
   }
@@ -1873,7 +1878,7 @@ export default class ChessRules {
           if (this.canStepOver(x, y, apparentPiece))
             continue;
           const stepSpec = this.getStepSpec(colIJ, i, j);
-          const attacks = stepSpec.attack || stepSpec.moves;
+          const attacks = stepSpec.attack.concat(stepSpec.both);
           for (let a of attacks) {
             for (let s of a.steps) {
               // Quick check: if step isn't compatible, don't even try
diff --git a/utils/alea.js b/utils/alea.js
index 751d347..077970d 100644
--- a/utils/alea.js
+++ b/utils/alea.js
@@ -22,7 +22,7 @@ export const Random = {
       min = 0;
     }
     if (!Random.rand)
-      Random.setSeed(Math.floor(Math.random() * 1984));
+      Random.setSeed(Math.floor(Math.random() * 19840));
     return Math.floor(Random.rand() * (max - min)) + min;
   },
 
diff --git a/variants.js b/variants.js
index 7c51fcd..ffe77ac 100644
--- a/variants.js
+++ b/variants.js
@@ -14,14 +14,14 @@ const variants = [
   {name: 'Atomic', desc: 'Explosive captures'},
   {name: 'Avalam', desc: 'Build towers'},
   {name: 'Avalanche', desc: 'Pawnfalls'},
-//  {name: 'Balaklava', desc: 'Meet the Mammoth'},
+  {name: 'Balaklava', desc: 'Meet the Mammoth'},
   {name: 'Bario', desc: 'A quantum story'},
   {name: "Balanced", desc: "balanced chess"},
   {name: 'Baroque', desc: 'Exotic captures'},
   {name: "Benedict", desc: "Change colors"},
   {name: 'Berolina', desc: 'Pawns move diagonally'},
-//  {name: 'Bicolour', desc: 'Harassed kings'},
-//  {name: 'Brotherhood', desc: 'Friendly pieces'},
+  {name: 'Bicolour', desc: 'Harassed kings'},
+  {name: 'Brotherhood', desc: 'Friendly pieces'},
   {name: 'Cannibal', desc: 'Capture powers'},
 //  {name: 'Capablanca', desc: 'Capablanca Chess', disp: 'Capablanca Chess'},
   {name: 'Capture', desc: 'Mandatory captures'},
diff --git a/variants/Absorption/class.js b/variants/Absorption/class.js
index 66278db..5f58c32 100644
--- a/variants/Absorption/class.js
+++ b/variants/Absorption/class.js
@@ -26,7 +26,7 @@ export default class AbsorptionRules extends ChessRules {
       // amazon
       'a': {
         "class": "amazon",
-        moves: [
+        both: [
           {
             steps: [
               [0, 1], [0, -1], [1, 0], [-1, 0],
@@ -45,7 +45,7 @@ export default class AbsorptionRules extends ChessRules {
       // empress
       'e': {
         "class": "empress",
-        moves: [
+        both: [
           {
             steps: [
               [1, 0], [-1, 0], [0, 1], [0, -1]
@@ -63,7 +63,7 @@ export default class AbsorptionRules extends ChessRules {
       // princess
       's': {
         "class": "princess",
-        moves: [
+        both: [
           {
             steps: [
               [1, 1], [1, -1], [-1, 1], [-1, -1]
diff --git a/variants/Alapo/class.js b/variants/Alapo/class.js
index 199a169..e613140 100644
--- a/variants/Alapo/class.js
+++ b/variants/Alapo/class.js
@@ -91,7 +91,7 @@ export default class AlapoRules extends ChessRules {
         {"class": "bishop" + (this.playerColor != color ? "_inv" : "")}),
       's': { //"square"
         "class": "babyrook",
-        moves: [
+        both: [
           {
             steps: [[0, 1], [0, -1], [1, 0], [-1, 0]],
             range: 1
@@ -100,7 +100,7 @@ export default class AlapoRules extends ChessRules {
       },
       'c': { //"circle"
         "class": "babyqueen",
-        moves: [
+        both: [
           {
             steps: [
               [0, 1], [0, -1], [1, 0], [-1, 0],
@@ -112,7 +112,7 @@ export default class AlapoRules extends ChessRules {
       },
       't': { //"triangle"
         "class": "babybishop" + (this.playerColor != color ? "_inv" : ""),
-        moves: [
+        both: [
           {
             steps: [[1, 1], [1, -1], [-1, 1], [-1, -1]],
             range: 1
diff --git a/variants/Ambiguous/class.js b/variants/Ambiguous/class.js
index 4311c4d..6d9ac7e 100644
--- a/variants/Ambiguous/class.js
+++ b/variants/Ambiguous/class.js
@@ -131,7 +131,7 @@ export default class AmbiguousRules extends ChessRules {
       't': {"class": "target-queen", moves: []},
       'l': {"class": "target-king", moves: []}
     };
-    return Object.assign({ 'g': {"class": "target", moves: []} },
+    return Object.assign({ 'g': {"class": "target"} },
       targets, super.pieces(color, x, y));
   }
 
diff --git a/variants/Antiking1/class.js b/variants/Antiking1/class.js
index 4ef66f2..d536680 100644
--- a/variants/Antiking1/class.js
+++ b/variants/Antiking1/class.js
@@ -1,5 +1,6 @@
 import ChessRules from "/base_rules.js";
 import AbstractAntikingRules from "/variants/_Antiking/class.js";
+import BerolinaPawnSpec from "/variants/_Berolina/pawnSpec.js";
 
 export default class Antiking1Rules extends AbstractAntikingRules {
 
@@ -11,20 +12,8 @@ export default class Antiking1Rules extends AbstractAntikingRules {
   }
 
   pieces(color, x, y) {
-    const pawnShift = (color == "w" ? -1 : 1);
     let res = super.pieces(color, x, y);
-    res['p'].moves = [
-      {
-        steps: [[pawnShift, 1], [pawnShift, -1]],
-        range: 1
-      }
-    ];
-    res['p'].attack = [
-      {
-        steps: [[pawnShift, 0]],
-        range: 1
-      }
-    ];
+    res['p'] = BerolinaPawnSpec(color);
     return res;
   }
 
diff --git a/variants/Arena/class.js b/variants/Arena/class.js
index 801469a..5a53086 100644
--- a/variants/Arena/class.js
+++ b/variants/Arena/class.js
@@ -28,8 +28,8 @@ export default class ArenaRules extends ChessRules {
     const pawnShift = (color == "w" ? -1 : 1);
     Array.prototype.push.apply(pawnSpec.attack[0].steps,
                                [[-pawnShift, 1], [-pawnShift, -1]]);
-    queenSpec.moves[0].range = 3;
-    kingSpec.moves[0].range = 3;
+    queenSpec.both[0].range = 3;
+    kingSpec.both[0].range = 3;
     return Object.assign({},
       allSpecs,
       {
diff --git a/variants/Atomic/class.js b/variants/Atomic/class.js
index 5c73de2..4bd2712 100644
--- a/variants/Atomic/class.js
+++ b/variants/Atomic/class.js
@@ -45,8 +45,8 @@ export default class AtomicRules extends ChessRules {
                 c: c
               })
             ],
-            start: { x: x, y: y },
-            end: { x: x, y: y }
+            start: {x: x, y: y},
+            end: {x: x, y: y}
           })
         ];
       }
diff --git a/variants/Avalam/class.js b/variants/Avalam/class.js
index 95993e0..aaa2cf3 100644
--- a/variants/Avalam/class.js
+++ b/variants/Avalam/class.js
@@ -42,7 +42,7 @@ export default class AvalamRules extends ChessRules {
     return {
       'b': {
         "class": "stack",
-        moves: [{steps: steps, range: 1}]
+        both: [{steps: steps, range: 1}]
       },
       'c': {
         "class": "stack2",
diff --git a/variants/Balaklava/class.js b/variants/Balaklava/class.js
new file mode 100644
index 0000000..dd3cf62
--- /dev/null
+++ b/variants/Balaklava/class.js
@@ -0,0 +1,59 @@
+import ChessRules from "/base_rules.js";
+
+export default class BalaklavaRules extends ChessRules {
+
+  get pawnPromotions() {
+    return ['r', 'm', 'b', 'q'];
+  }
+
+  get hasEnpassant() {
+    return false;
+  }
+
+  pieces(color, x, y) {
+    let res = super.pieces(color, x, y);
+    const knightSpec = res['n'];
+    delete res['n'];
+    res['m'] = {
+      "class": "mammoth",
+      both: [
+        {
+          steps: [
+            [-2, -2], [-2, 0], [-2, 2],
+            [0, -2], [0, 2], [2, -2],
+            [2, 0], [2, 2]
+          ],
+          range: 1
+        }
+      ]
+    };
+    ['r', 'b', 'm', 'q'].forEach(p => res[p].moves = knightSpec.moves);
+    return res;
+  }
+
+  genRandInitBaseFen() {
+    const baseFen = super.genRandInitBaseFen();
+    return {
+      fen: baseFen.replace(/n/g, 'm').replace(/N/g, 'M'),
+      o: baseFen.o
+    };
+  }
+
+  pawnPostProcess(moves) {
+    if (moves.length == 0)
+      return [];
+    const color = moves[0].vanish[0].c;
+    const lastRank = (color == 'w' ? 0 : this.size.x - 1);
+    const noKnightPromotions = moves.filter(m => {
+      return (
+        m.end.x != lastRank ||
+        (
+          Math.abs(m.start.x - m.end.x) <= 1 &&
+          Math.abs(m.start.y - m.end.y) <= 1
+        )
+      );
+    });
+    return super.pawnPostProcess(noKnightPromotions);
+  }
+
+};
diff --git a/variants/Balaklava/rules.html b/variants/Balaklava/rules.html
new file mode 100644
index 0000000..c65158e
--- /dev/null
+++ b/variants/Balaklava/rules.html
@@ -0,0 +1 @@
+<p>TODO</p>
diff --git a/variants/Balaklava/style.css b/variants/Balaklava/style.css
new file mode 100644
index 0000000..a3550bc
--- /dev/null
+++ b/variants/Balaklava/style.css
@@ -0,0 +1 @@
+@import url("/base_pieces.css");
diff --git a/variants/Bario/class.js b/variants/Bario/class.js
index 01cb3d7..1874383 100644
--- a/variants/Bario/class.js
+++ b/variants/Bario/class.js
@@ -26,12 +26,7 @@ export default class BarioRules extends ChessRules {
 
   pieces(color, x, y) {
     return Object.assign(
-      {
-        'u': {
-          "class": "undefined",
-          moves: []
-        }
-      },
+      { 'u': {"class": "undefined"} },
       super.pieces(color, x, y)
     );
   }
@@ -165,8 +160,10 @@ export default class BarioRules extends ChessRules {
     if (super.underCheck(square_s, oppCol))
       return true;
     // Check potential specializations of undefined using reserve:
+    const inReserve = Object.keys(this.reserve[oppCol])
+                        .filter(k => this.reserve[oppCol][k] >= 1);
     const allAttacks = Array.prototype.concat.apply(
-      ['r', 'n', 'b', 'q'].map(p => this.pieces()[p].moves[0]));
+      inReserve.map(p => this.pieces()[p].both[0]));
     const [x, y] = square_s[0];
     for (let i=0; i<this.size.x; i++) {
       for (let j=0; j<this.size.y; j++) {
diff --git a/variants/Benedict/class.js b/variants/Benedict/class.js
index dfabddb..e6542c4 100644
--- a/variants/Benedict/class.js
+++ b/variants/Benedict/class.js
@@ -1,7 +1,7 @@
-import ChessRules from "/base_rules.js";
+import AbstractFlipRules from "/variants/_Flip/class.js";
 import PiPo from "/utils/PiPo.js";
 
-export default class BenedictRules extends ChessRules {
+export default class BenedictRules extends AbstractFlipRules {
 
   static get Options() {
     return {
@@ -25,14 +25,6 @@ export default class BenedictRules extends ChessRules {
     };
   }
 
-  get hasEnpassant() {
-    return false;
-  }
-
-  canTake() {
-    return false;
-  }
-
   pieces(color, x, y) {
     if (!this.options["cleopatra"])
       return super.pieces(color, x, y);
@@ -82,38 +74,4 @@ export default class BenedictRules extends ChessRules {
     return bMoves;
   }
 
-  playOnBoard(move) {
-    super.playOnBoard(move);
-    this.flipColorOf(move.flips);
-  }
-  undoOnBoard(move) {
-    super.undoOnBoard(move);
-    this.flipColorOf(move.flips);
-  }
-
-  flipColorOf(flips) {
-    for (let xy of flips) {
-      const newColor = C.GetOppCol(this.getColor(xy.x, xy.y));
-      this.board[xy.x][xy.y] = newColor + this.board[xy.x][xy.y][1];
-    }
-  }
-
-  // Moves cannot flip our king's color, so all are valid
-  filterValid(moves) {
-    return moves;
-  }
-
-  // A king under (regular) check flips color, and the game is over.
-  underCheck() {
-    return false;
-  }
-
-  playVisual(move, r) {
-    super.playVisual(move, r);
-    move.flips.forEach(f => {
-      this.g_pieces[f.x][f.y].classList.toggle("white");
-      this.g_pieces[f.x][f.y].classList.toggle("black");
-    });
-  }
-
 };
diff --git a/variants/Berolina/class.js b/variants/Berolina/class.js
index beabdc6..ef0ba89 100644
--- a/variants/Berolina/class.js
+++ b/variants/Berolina/class.js
@@ -1,3 +1,12 @@
-import AbstractBerolinaRules from "/variants/_Berolina/class.js";
+import ChessRules from "/base_rules.js";
+import BerolinaPawnSpec from "/variants/_Berolina/pawnSpec.js";
 
-export default class BerolinaRules extends AbstractBerolinaRules {};
+export default class BerolinaRules extends ChessRules {
+
+  pieces(color, x, y) {
+    let res = super.pieces(color, x, y);
+    res['p'] = BerolinaPawnSpec(color);
+    return res;
+  }
+
+};
diff --git a/variants/Bicolour/class.js b/variants/Bicolour/class.js
new file mode 100644
index 0000000..fd2cdb0
--- /dev/null
+++ b/variants/Bicolour/class.js
@@ -0,0 +1,111 @@
+import ChessRules from "/base_rules.js";
+import {Random} from "/utils/alea.js";
+import {ArrayFun} from "/utils/array.js";
+
+export class BicolourRules extends ChessRules {
+
+  // TODO: Options
+
+  get hasFlags() {
+    return false;
+  }
+
+  canTake([x1, y1], [x2, y2]) {
+    return (this.getPiece(x2, y2) == 'k' || super.canTake([x1, y1], [x2, y2]));
+  }
+
+  genRandInitBaseFen() {
+    if (this.options["randomness"] == 0)
+      return { fen: "rqbnkbnr/pppppppp/8/8/8/8/PPPPPPPP/RQBNKBNR", o: {} };
+
+    // Place pieces at random but the king cannot be next to a rook or queen.
+    // => One bishop and one knight should surround the king.
+    let pieces = {w: new Array(8), b: new Array(8)};
+    let flags = "";
+    for (let c of ["w", "b"]) {
+      if (c == 'b' && this.options["randomness"] == 1) {
+        pieces['b'] = pieces['w'];
+        break;
+      }
+      let positions = ArrayFun.range(8);
+      const kingPos = randInt(8);
+      let toRemove = [kingPos];
+      let knight1Pos = undefined;
+      let bishop1Pos = undefined;
+      if (kingPos == 0) {
+        if (Random.randBool())
+          knight1Pos = 1;
+        else
+          bishop1Pos = 1;
+        toRemove.push(1);
+      }
+      else if (kingPos == V.size.y - 1) {
+        if (Random.randBool())
+          knight1Pos = V.size.y - 2;
+        else
+          bishop1Pos = V.size.y - 2;
+        toRemove.push(V.size.y - 2);
+      }
+      else {
+        knight1Pos = kingPos + (Random.randBool() ? 1 : -1);
+        bishop1Pos = kingPos + (knight1Pos < kingPos ? 1 : -1);
+        toRemove.push(knight1Pos, bishop1Pos);
+      }
+      const firstPieces = [kingPos, knight1Pos, bishop1Pos]
+        .filter(elt => elt !== undefined);
+      firstPieces
+        .sort((a, b) => b - a)
+        .forEach(elt => positions.splice(elt, 1));
+      let randIndex = undefined;
+      if (bishop1Pos === undefined) {
+        const posWithIdx = positions.map((e,i) => { return {e: e, i: i}; });
+        let availableSquares = posWithIdx.filter(p => p.e % 2 == 0);
+        randIndex = randInt(availableSquares.length);
+        bishop1Pos = availableSquares[randIndex].e;
+        positions.splice(availableSquares[randIndex].i, 1);
+      }
+      const posWithIdx = positions.map((e,i) => { return {e: e, i: i}; });
+      const rem1B = bishop1Pos % 2;
+      let availableSquares = posWithIdx.filter(p => p.e % 2 == 1 - rem1B);
+      randIndex = randInt(availableSquares.length);
+      const bishop2Pos = availableSquares[randIndex].e;
+      positions.splice(availableSquares[randIndex].i, 1);
+      if (knight1Pos === undefined) {
+        randIndex = randInt(5);
+        knight1Pos = positions[randIndex];
+        positions.splice(randIndex, 1);
+      }
+      randIndex = randInt(4);
+      const knight2Pos = positions[randIndex];
+      positions.splice(randIndex, 1);
+      randIndex = randInt(3);
+      const queenPos = positions[randIndex];
+      positions.splice(randIndex, 1);
+      const rook1Pos = positions[0];
+      const rook2Pos = positions[1];
+      pieces[c][rook1Pos] = "r";
+      pieces[c][knight1Pos] = "n";
+      pieces[c][bishop1Pos] = "b";
+      pieces[c][queenPos] = "q";
+      pieces[c][kingPos] = "k";
+      pieces[c][bishop2Pos] = "b";
+      pieces[c][knight2Pos] = "n";
+      pieces[c][rook2Pos] = "r";
+    }
+
+    return {
+      fen: (
+        pieces["b"].join("") +
+        "/pppppppp/8/8/8/8/PPPPPPPP/" +
+        pieces["w"].join("").toUpperCase()
+      ),
+      o: {}
+    };
+  }
+
+  underCheck(color) {
+    const kingPos = this.searchKingPos(color)[0],
+    return (this.underAttack(kingPos, 'w') || this.underAttack(kingPos, 'b'));
+  }
+
+};
diff --git a/variants/Bicolour/rules.html b/variants/Bicolour/rules.html
new file mode 100644
index 0000000..c65158e
--- /dev/null
+++ b/variants/Bicolour/rules.html
@@ -0,0 +1 @@
+<p>TODO</p>
diff --git a/variants/Bicolour/style.css b/variants/Bicolour/style.css
new file mode 100644
index 0000000..a3550bc
--- /dev/null
+++ b/variants/Bicolour/style.css
@@ -0,0 +1 @@
+@import url("/base_pieces.css");
diff --git a/variants/Brotherhood/class.js b/variants/Brotherhood/class.js
new file mode 100644
index 0000000..94150c2
--- /dev/null
+++ b/variants/Brotherhood/class.js
@@ -0,0 +1,20 @@
+import ChessRules from "/base_rules.js";
+
+export default class BrotherhoodRules extends ChessRules {
+
+  canTake([x1, y1], [x2, y2]) {
+    if (!super.canTake([x1, y1], [x2, y2]))
+      return false;
+    const p1 = this.getPiece(x1, y1),
+          p2 = this.getPiece(x2, y2);
+    return (p1 != p2 || ['p', 'k'].some(symb => [p1, p2].includes(symb)));
+  }
+
+  getCurrentScore() {
+    if (this.atLeastOneMove(this.turn))
+      return "*";
+    // Stalemate = loss
+    return (this.turn == 'w' ? "0-1" : "1-0");
+  }
+
+};
diff --git a/variants/Brotherhood/rules.html b/variants/Brotherhood/rules.html
new file mode 100644
index 0000000..b65fb61
--- /dev/null
+++ b/variants/Brotherhood/rules.html
@@ -0,0 +1,6 @@
+<p>
+  Rooks, knights, bishops and queens cannot
+  capture enemy pieces of the same nature.
+</p>
+
+<p class="author">Gianluca Vecchi (1993).</p>
diff --git a/variants/Brotherhood/style.css b/variants/Brotherhood/style.css
new file mode 100644
index 0000000..a3550bc
--- /dev/null
+++ b/variants/Brotherhood/style.css
@@ -0,0 +1 @@
+@import url("/base_pieces.css");
diff --git a/variants/Chakart/class.js b/variants/Chakart/class.js
index 5202b48..96a53f3 100644
--- a/variants/Chakart/class.js
+++ b/variants/Chakart/class.js
@@ -116,7 +116,6 @@ export default class ChakartRules extends ChessRules {
       {
         'y': {
           // Virtual piece for "king remote shell captures"
-          moves: [],
           attack: [
             {
               steps: [
diff --git a/variants/_Berolina/class.js b/variants/_Berolina/class.js
deleted file mode 100644
index 6066e51..0000000
--- a/variants/_Berolina/class.js
+++ /dev/null
@@ -1,23 +0,0 @@
-import ChessRules from "/base_rules.js";
-
-export default class BerolinaRules extends ChessRules {
-
-  pieces(color, x, y) {
-    const pawnShift = (color == "w" ? -1 : 1);
-    let res = super.pieces(color, x, y);
-    res['p'].moves = [
-      {
-        steps: [[pawnShift, 1], [pawnShift, -1]],
-        range: 1
-      }
-    ];
-    res['p'].attack = [
-      {
-        steps: [[pawnShift, 0]],
-        range: 1
-      }
-    ];
-    return res;
-  }
-
-};
diff --git a/variants/_Berolina/pawnSpec.js b/variants/_Berolina/pawnSpec.js
new file mode 100644
index 0000000..088e7df
--- /dev/null
+++ b/variants/_Berolina/pawnSpec.js
@@ -0,0 +1,19 @@
+export default BerolinaPawnSpec = (color) => {
+
+  const pawnShift = (color == "w" ? -1 : 1);
+  return {
+    moves: [
+      {
+        steps: [[pawnShift, 1], [pawnShift, -1]],
+        range: 1
+      }
+    ],
+    attack: [
+      {
+        steps: [[pawnShift, 0]],
+        range: 1
+      }
+    ]
+  };
+
+};
diff --git a/variants/_Flip/class.js b/variants/_Flip/class.js
new file mode 100644
index 0000000..e2e2bf5
--- /dev/null
+++ b/variants/_Flip/class.js
@@ -0,0 +1,43 @@
+import ChessRules from "/base_rules.js";
+
+export default class AbstractFlipRules extends ChessRules {
+
+  // For games without captures (replaced by flips)
+  get hasEnpassant() {
+    return false;
+  }
+  canTake() {
+    return false;
+  }
+  filterValid(moves) {
+    return moves;
+  }
+  underCheck() {
+    return false;
+  }
+
+  playOnBoard(move) {
+    super.playOnBoard(move);
+    this.flipColorOf(move.flips);
+  }
+  undoOnBoard(move) {
+    super.undoOnBoard(move);
+    this.flipColorOf(move.flips);
+  }
+
+  flipColorOf(flips) {
+    for (let xy of flips) {
+      const newColor = C.GetOppCol(this.getColor(xy.x, xy.y));
+      this.board[xy.x][xy.y] = newColor + this.board[xy.x][xy.y][1];
+    }
+  }
+
+  playVisual(move, r) {
+    super.playVisual(move, r);
+    move.flips.forEach(f => {
+      this.g_pieces[f.x][f.y].classList.toggle("white");
+      this.g_pieces[f.x][f.y].classList.toggle("black");
+    });
+  }
+
+};
-- 
2.44.0