Add Rollerball variant
authorBenjamin Auder <benjamin.auder@somewhere>
Thu, 14 Jan 2021 00:50:33 +0000 (01:50 +0100)
committerBenjamin Auder <benjamin.auder@somewhere>
Thu, 14 Jan 2021 00:50:33 +0000 (01:50 +0100)
15 files changed:
.gitattributes
client/public/variants/Rollerball/rollerball_directions.gif [new file with mode: 0644]
client/public/variants/Rollerball/rook_example.gif [new file with mode: 0644]
client/src/translations/en.js
client/src/translations/es.js
client/src/translations/fr.js
client/src/translations/rules/Rollerball/en.pug
client/src/translations/rules/Rollerball/es.pug
client/src/translations/rules/Rollerball/fr.pug
client/src/translations/variants/en.pug
client/src/translations/variants/es.pug
client/src/translations/variants/fr.pug
client/src/variants/Omega.js
client/src/variants/Rollerball.js [new file with mode: 0644]
server/db/populate.sql

index ed55307..ac66ad0 100644 (file)
@@ -1,4 +1,5 @@
 *.ico filter=fat
 *.pdf filter=fat
 *.png filter=fat
 *.ico filter=fat
 *.pdf filter=fat
 *.png filter=fat
+*.gif filter=fat
 *.flac filter=fat
 *.flac filter=fat
diff --git a/client/public/variants/Rollerball/rollerball_directions.gif b/client/public/variants/Rollerball/rollerball_directions.gif
new file mode 100644 (file)
index 0000000..dff78d6
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat 0086b7f63ad2afec7e27c717ac39ae2c14b707bd                 6736
diff --git a/client/public/variants/Rollerball/rook_example.gif b/client/public/variants/Rollerball/rook_example.gif
new file mode 100644 (file)
index 0000000..4a219aa
--- /dev/null
@@ -0,0 +1 @@
+#$# git-fat 0971d21d6aa9f36da7f3442ef28b0b9596939588                 5546
index ef2ba14..6d79e55 100644 (file)
@@ -171,6 +171,7 @@ export const translations = {
   "Absorb powers": "Absorb powers",
   "All of the same color": "All of the same color",
   "Ancient rules": "Ancient rules",
   "Absorb powers": "Absorb powers",
   "All of the same color": "All of the same color",
   "Ancient rules": "Ancient rules",
+  "As in the movie": "As in the movie",
   "Attract opposite king": "Attract opposite king",
   "Augmented Queens": "Augmented Queens",
   "Balanced sliders & leapers": "Balanced sliders & leapers",
   "Attract opposite king": "Attract opposite king",
   "Augmented Queens": "Augmented Queens",
   "Balanced sliders & leapers": "Balanced sliders & leapers",
index 6aa5c10..a50a979 100644 (file)
@@ -171,6 +171,7 @@ export const translations = {
   "Absorb powers": "Absorber poderes",
   "All of the same color": "Todo el mismo color",
   "Ancient rules": "Viejas reglas",
   "Absorb powers": "Absorber poderes",
   "All of the same color": "Todo el mismo color",
   "Ancient rules": "Viejas reglas",
+  "As in the movie": "Como en la pelicula",
   "Attract opposite king": "Atraer al rey contrario",
   "Augmented Queens": "Damas aumentadas",
   "Balanced sliders & leapers": "Modos de desplazamiento equilibrados",
   "Attract opposite king": "Atraer al rey contrario",
   "Augmented Queens": "Damas aumentadas",
   "Balanced sliders & leapers": "Modos de desplazamiento equilibrados",
index a79d18c..925f141 100644 (file)
@@ -171,6 +171,7 @@ export const translations = {
   "Absorb powers": "Absorber les pouvoirs",
   "All of the same color": "Tout de la même couleur",
   "Ancient rules": "Règles anciennes",
   "Absorb powers": "Absorber les pouvoirs",
   "All of the same color": "Tout de la même couleur",
   "Ancient rules": "Règles anciennes",
+  "As in the movie": "Comme dans le film",
   "Attract opposite king": "Attirer le roi adverse",
   "Augmented Queens": "Dames augmentées",
   "Balanced sliders & leapers": "Modes de déplacement équilibrés",
   "Attract opposite king": "Attirer le roi adverse",
   "Augmented Queens": "Dames augmentées",
   "Balanced sliders & leapers": "Modes de déplacement équilibrés",
index 21203ba..686cc7a 100644 (file)
@@ -1 +1,47 @@
-p.boxed TODO
+p.boxed
+  | Pieces turn around the board, they cannot go in the middle.
+
+p.
+  Each side has only a king, two rooks, one bishop and two pawns.
+  Pieces can never enter the central area.
+  Pawns can only move "clockwise", acording to the image below: they move
+  and capture forward straight and diagonally in the arrow direction.
+
+figure
+  img.img-center(src="/variants/Rollerball/rollerball_directions.gif")
+  figcaption.text-center.
+    Main directions on the board.
+    The squares where direction changes are enlighted with white arrows.
+
+p.
+  When a pawn reaches the starting square of an enemy pawn, it promotes
+  into a bishop or rook.
+
+p.
+  Rooks and bishop move as usual, but are restricted to one square only when
+  moving counter-clockwise.
+  Moreover, they are allowed one rebound in certain circumstances:
+ul
+  li a bishop rebounds on the first wall met,
+  li a rook rebounds (at 90 degrees) if it reaches a corner.
+
+figure
+  img.img-center(src="/variants/Rollerball/rook_example.gif")
+  figcaption.text-center Some rook movements.
+
+p.
+  The king moves as in orthodox chess.
+  The goal is either to bring your king on the initial square of the
+  opponent's king, or to checkmate him.
+
+h3 More information
+
+p
+  | See the 
+  a(href="http://history.chess.free.fr/rollerball.htm") author's presentation
+  | , and the 
+  a(href="https://www.chessvariants.com/40.dir/rollerball/index.html")
+    | chessvariants page
+  | .
+
+p Inventor: Jean-Louis Cazaux (1998)
index 21203ba..9a8230a 100644 (file)
@@ -1 +1,49 @@
-p.boxed TODO
+p.boxed
+  | Las piezas giran alrededor del tablero, no pueden ir al centro.
+
+p.
+  Cada lado tiene un rey, dos torres, un alfil y dos peones.
+  Las piezas nunca pueden ir al área central.
+  Los peones solo se mueven siguiendo las "agujas del reloj", según la imagen
+  abajo: se mueven y capturan en línea recta o en diagonal
+  en la dirección de la flecha.
+
+figure
+  img.img-center(src="/variants/Rollerball/rollerball_directions.gif")
+  figcaption.text-center.
+    Direcciones principales en el tablero. Las casillas donde ocurre
+    los cambios de dirección están marcados con flechas blancas.
+
+p.
+  Cuando un peón llega a la casilla inicial de un peón enemigo, es promovido
+  en un alfil o en una torre.
+
+p.
+  Las torres y los alfiles se mueven como de costumbre, pero están
+  restringidos por una casilla solo cuando se mueven en sentido antihorario.
+  Además, pueden rebotar una vez en determinadas circunstancias:
+ul
+  li un loco rebota en la primera pared que encuentra,
+  li una torre rebota (90 grados) si golpea una esquina.
+
+figure
+  img.img-center(src="/variants/Rollerball/rook_example.gif")
+  figcaption.text-center Algunos movimientos de la torre.
+
+p.
+  El rey se mueve como en el ajedrez ortodoxo.
+  El objetivo es llevar a tu rey a la casilla de inicio del rey contrario,
+  o matarlo.
+
+h3 Más información
+
+p
+  | Ver la 
+  a(href="http://history.chess.free.fr/rollerball.htm")
+    | presentación del autor
+  | , y la 
+  a(href="https://www.chessvariants.com/40.dir/rollerball/index.html")
+    | página chessvariants
+  | .
+
+p Inventor: Jean-Louis Cazaux (1998)
index 21203ba..d3080c3 100644 (file)
@@ -1 +1,50 @@
-p.boxed TODO
+p.boxed
+  | Les pièces tournent autour de l'échiquier,
+  | elles ne peuvent pas aller au centre.
+
+p.
+  Chaque camp dispose d'un roi, deux tours, un fou et deux pions.
+  Les pièces ne peuvent jamais aller dans la zone centrale.
+  Les pions ne se déplacent que dans le "sens horaire", selon l'image
+  ci-dessous : ils se déplacent et capturent tout droit ou en diagonale
+  dans la direction de la flèche.
+
+figure
+  img.img-center(src="/variants/Rollerball/rollerball_directions.gif")
+  figcaption.text-center.
+    Principales directions sur l'échiquier. Les cases où survient
+    un changement de direction sont marquées par des flèches blanches.
+
+p.
+  Quand un pion atteint la case de départ d'un pion ennemi, il est promu
+  en un fou ou une tour.
+
+p.
+  Les tours et fous se déplacent comme d'habitude, mais sont restreints d'une
+  case seulement quand ils se déplacent dans le sens anti-horaire.
+  De plus, ils peuvent rebondir une fois dans certaines circonstances :
+ul
+  li un fou rebondit sur le premier mur rencontré,
+  li une tour rebondit (à 90 degrés) si elle atteint un coin.
+
+figure
+  img.img-center(src="/variants/Rollerball/rook_example.gif")
+  figcaption.text-center Quelques déplacements de la tour.
+
+p.
+  Le roi se déplace comme aux échecs orthodoxes.
+  L'objectif est soit d'amener votre roi sur la case de départ du roi adverse,
+  ou bien de le mater.
+
+h3 Plus d'information
+
+p
+  | Voir la 
+  a(href="http://history.chess.free.fr/rollerball.htm")
+    | présentation de l'auteur
+  | , et la 
+  a(href="https://www.chessvariants.com/40.dir/rollerball/index.html")
+    | page chessvariants
+  | .
+
+p Inventeur : Jean-Louis Cazaux (1998)
index ee21f97..66c1493 100644 (file)
@@ -448,6 +448,7 @@ p.
     "Kingsmaker",
     "Magnetic",
     "Relayup",
     "Kingsmaker",
     "Magnetic",
     "Relayup",
+    "Rollerball",
     "Takenmake",
     "Wormhole"
   ]
     "Takenmake",
     "Wormhole"
   ]
index 3874360..39eda80 100644 (file)
@@ -458,6 +458,7 @@ p.
     "Kingsmaker",
     "Magnetic",
     "Relayup",
     "Kingsmaker",
     "Magnetic",
     "Relayup",
+    "Rollerball",
     "Takenmake",
     "Wormhole"
   ]
     "Takenmake",
     "Wormhole"
   ]
index 07756b8..9cf10ed 100644 (file)
@@ -456,6 +456,7 @@ p.
     "Kingsmaker",
     "Magnetic",
     "Relayup",
     "Kingsmaker",
     "Magnetic",
     "Relayup",
+    "Rollerball",
     "Takenmake",
     "Wormhole"
   ]
     "Takenmake",
     "Wormhole"
   ]
index bac8d50..a4276ed 100644 (file)
@@ -278,12 +278,9 @@ export class OmegaRules extends ChessRules {
 
   getPotentialMovesFrom([x, y]) {
     switch (this.getPiece(x, y)) {
 
   getPotentialMovesFrom([x, y]) {
     switch (this.getPiece(x, y)) {
-      case V.CHAMPION:
-        return this.getPotentialChampionMoves([x, y]);
-      case V.WIZARD:
-        return this.getPotentialWizardMoves([x, y]);
-      default:
-        return super.getPotentialMovesFrom([x, y]);
+      case V.CHAMPION: return this.getPotentialChampionMoves([x, y]);
+      case V.WIZARD: return this.getPotentialWizardMoves([x, y]);
+      default: return super.getPotentialMovesFrom([x, y]);
     }
   }
 
     }
   }
 
diff --git a/client/src/variants/Rollerball.js b/client/src/variants/Rollerball.js
new file mode 100644 (file)
index 0000000..36373b2
--- /dev/null
@@ -0,0 +1,570 @@
+import { ChessRules } from "@/base_rules";
+
+export class RollerballRules extends ChessRules {
+
+  static get HasEnpassant() {
+    return false;
+  }
+
+  static get HasCastle() {
+    return false;
+  }
+
+  static get DarkBottomRight() {
+    return true;
+  }
+
+  static get PIECES() {
+    return [V.PAWN, V.KING, V.ROOK, V.BISHOP];
+  }
+
+  static get size() {
+    return { x: 7, y: 7 };
+  }
+
+  // TODO: the wall position should be checked too
+  static IsGoodPosition(position) {
+    if (position.length == 0) return false;
+    const rows = position.split("/");
+    if (rows.length != V.size.x) return false;
+    let kings = { "k": 0, "K": 0 };
+    for (let row of rows) {
+      let sumElts = 0;
+      for (let i = 0; i < row.length; i++) {
+        if (['K','k'].includes(row[i])) kings[row[i]]++;
+        if (['x'].concat(V.PIECES).includes(row[i].toLowerCase())) sumElts++;
+        else {
+          const num = parseInt(row[i], 10);
+          if (isNaN(num)) return false;
+          sumElts += num;
+        }
+      }
+      if (sumElts != V.size.y) return false;
+    }
+    if (Object.values(kings).some(v => v != 1)) return false;
+    return true;
+  }
+
+  // NOTE: canTake() is wrong, but next method is enough
+  static OnBoard(x, y) {
+    return (
+      (x >= 0 && x <= 6 && y >= 0 && y <= 6) &&
+      (![2, 3, 4].includes(x) || ![2, 3, 4].includes(y))
+    );
+  }
+
+  static IsGoodFlags(flags) {
+    // 2 for kings: last zone reached
+    return !!flags.match(/^[0-7]{2,2}$/);
+  }
+
+  setFlags(fenflags) {
+    this.kingFlags = {
+      w: parseInt(fenflags.charAt(0), 10),
+      b: parseInt(fenflags.charAt(1), 10)
+    };
+  }
+
+  aggregateFlags() {
+    return this.kingFlags;
+  }
+
+  disaggregateFlags(flags) {
+    this.kingFlags = flags;
+  }
+
+  getFlagsFen() {
+    return this.kingFlags['w'].toString() + this.kingFlags['b'].toString();
+  }
+
+  // For space in the middle:
+  static get NOTHING() {
+    return "xx";
+  }
+
+  static board2fen(b) {
+    if (b[0] == 'x') return 'x';
+    return ChessRules.board2fen(b);
+  }
+
+  static fen2board(f) {
+    if (f == 'x') return V.NOTHING;
+    return ChessRules.fen2board(f);
+  }
+
+  getPpath(b) {
+    if (b[0] == 'x') return "Omega/nothing";
+    return b;
+  }
+
+  static GenRandInitFen() {
+    return "2rbp2/2rkp2/2xxx2/2xxx2/2xxx2/2PKR2/2PBR2 w 0 00";
+  }
+
+  getPotentialMovesFrom(sq) {
+    switch (this.getPiece(sq[0], sq[1])) {
+      case V.PAWN: return this.getPotentialPawnMoves(sq);
+      case V.ROOK: return this.getPotentialRookMoves(sq);
+      case V.BISHOP: return this.getPotentialBishopMoves(sq);
+      case V.KING: return super.getPotentialKingMoves(sq);
+    }
+    return [];
+  }
+
+  getPotentialPawnMoves([x, y]) {
+    const c = this.turn;
+    // Need to know pawn area to deduce move options
+    const inMiddleX = [2, 3, 4].includes(x);
+    const inMiddleY = [2, 3, 4].includes(y);
+    // In rectangular areas on the sides?
+    if (inMiddleX) {
+      const forward = (y <= 1 ? -1 : 1);
+      return (
+        super.getSlideNJumpMoves(
+          [x, y], [[forward, -1], [forward, 0], [forward, 1]], "oneStep")
+      );
+    }
+    if (inMiddleY) {
+      const forward = (x <= 1 ? 1 : -1);
+      let moves =
+        super.getSlideNJumpMoves(
+          [x, y], [[-1, forward], [0, forward], [1, forward]], "oneStep");
+      // Promotions may happen:
+      let extraMoves = [];
+      moves.forEach(m => {
+        if (
+          (c == 'w' && x <= 1 && m.end.y == 4) ||
+          (c == 'b' && x >= 5 && m.end.y == 2)
+        ) {
+          m.appear[0].p = V.ROOK;
+          let m2 = JSON.parse(JSON.stringify(m));
+          m2.appear[0].p = V.BISHOP;
+          extraMoves.push(m2);
+        }
+      });
+      Array.prototype.push.apply(moves, extraMoves);
+      return moves;
+    }
+    // In a corner:
+    const toRight = (x == 0 && [0, 1, 5].includes(y)) || (x == 1 && y == 1);
+    const toLeft = (x == 6 && [1, 5, 6].includes(y)) || (x == 5 && y == 5);
+    const toUp = (y == 0 && [1, 5, 6].includes(x)) || (x == 5 && y == 1);
+    const toBottom = (y == 6 && [0, 1, 5].includes(x)) || (x == 1 && y == 5);
+    if (toRight || toLeft) {
+      const forward = (toRight ? 1 : -1);
+      return (
+        super.getSlideNJumpMoves(
+          [x, y], [[-1, forward], [0, forward], [1, forward]], "oneStep")
+      );
+    }
+    const forward = (toUp ? -1 : 1);
+    return (
+      super.getSlideNJumpMoves(
+        [x, y], [[forward, -1], [forward, 0], [forward, 1]], "oneStep")
+    );
+  }
+
+  getPotentialRookMoves([x, y]) {
+    let multiStep = [],
+        oneStep = [];
+    if (x <= 1) multiStep.push([0, 1]);
+    else oneStep.push([0, 1]);
+    if (y <= 1) multiStep.push([-1, 0]);
+    else oneStep.push([-1, 0]);
+    if (x >= 5) multiStep.push([0, -1]);
+    else oneStep.push([0, -1]);
+    if (y >= 5) multiStep.push([1, 0]);
+    else oneStep.push([1, 0]);
+    const c = this.turn;
+    let moves = super.getSlideNJumpMoves([x, y], oneStep, "oneStep");
+    for (let step of multiStep) {
+      let [i, j] = [x + step[0], y + step[1]];
+      while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
+        moves.push(this.getBasicMove([x, y], [i, j]));
+        i += step[0];
+        j += step[1];
+      }
+      if (V.OnBoard(i, j)) {
+        if (this.getColor(i, j) != c)
+          moves.push(this.getBasicMove([x, y], [i, j]));
+      }
+      else {
+        i -= step[0];
+        j -= step[1];
+        // Potential rebound if away from initial square
+        if (i != x || j != y) {
+          // Corners check
+          let nextStep = null;
+          if (i == 0 && j == 0) nextStep = [0, 1];
+          else if (i == 0 && j == 6) nextStep = [1, 0];
+          else if (i == 6 && j == 6) nextStep = [0, -1];
+          else if (i == 6 && j == 0) nextStep = [-1, 0];
+          if (!!nextStep) {
+            i += nextStep[0];
+            j += nextStep[1];
+            while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
+              moves.push(this.getBasicMove([x, y], [i, j]));
+              i += nextStep[0];
+              j += nextStep[1];
+            }
+            if (V.OnBoard(i, j) && this.getColor(i, j) != c)
+              moves.push(this.getBasicMove([x, y], [i, j]));
+          }
+        }
+      }
+    }
+    return moves;
+  }
+
+  static get DictBishopSteps() {
+    return {
+      "-1_-1": [-1, -1],
+      "-1_1": [-1, 1],
+      "1_-1": [1, -1],
+      "1_1": [1, 1]
+    };
+  }
+
+  getPotentialBishopMoves([x, y]) {
+    let multiStep = {};
+    if (x <= 1) {
+      multiStep["-1_1"] = [-1, 1];
+      multiStep["1_1"] = [1, 1];
+    }
+    if (y <= 1) {
+      multiStep["-1_-1"] = [-1, -1];
+      if (!multiStep["-1_1"]) multiStep["-1_1"] = [-1, 1];
+    }
+    if (x >= 5) {
+      multiStep["1_-1"] = [1, -1];
+      if (!multiStep["-1_-1"]) multiStep["-1_-1"] = [-1, -1];
+    }
+    if (y >= 5) {
+      if (!multiStep["1_-1"]) multiStep["1_-1"] = [1, -1];
+      if (!multiStep["1_1"]) multiStep["1_1"] = [1, 1];
+    }
+    let oneStep = [];
+    Object.keys(V.DictBishopSteps).forEach(str => {
+      if (!multiStep[str]) oneStep.push(V.DictBishopSteps[str]);
+    });
+    const c = this.turn;
+    let moves = super.getSlideNJumpMoves([x, y], oneStep, "oneStep");
+    for (let step of Object.values(multiStep)) {
+      let [i, j] = [x + step[0], y + step[1]];
+      while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
+        moves.push(this.getBasicMove([x, y], [i, j]));
+        i += step[0];
+        j += step[1];
+      }
+      if (V.OnBoard(i, j)) {
+        if (this.getColor(i, j) != c)
+          moves.push(this.getBasicMove([x, y], [i, j]));
+      }
+      else {
+        i -= step[0];
+        j -= step[1];
+        // Rebound, if we moved away from initial square
+        if (i != x || j != y) {
+          let nextStep = null;
+          if (step[0] == -1 && step[1] == -1) {
+            if (j == 0) nextStep = [-1, 1];
+            else nextStep = [1, -1];
+          }
+          else if (step[0] == -1 && step[1] == 1) {
+            if (i == 0) nextStep = [1, 1];
+            else nextStep = [-1, -1];
+          }
+          else if (step[0] == 1 && step[1] == -1) {
+            if (i == 6) nextStep = [-1, -1];
+            else nextStep = [1, 1];
+          }
+          else {
+            // step == [1, 1]
+            if (j == 6) nextStep = [1, -1];
+            else nextStep = [-1, 1];
+          }
+          i += nextStep[0];
+          j += nextStep[1];
+          while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
+            moves.push(this.getBasicMove([x, y], [i, j]));
+            i += nextStep[0];
+            j += nextStep[1];
+          }
+          if (V.OnBoard(i, j) && this.getColor(i, j) != c)
+            moves.push(this.getBasicMove([x, y], [i, j]));
+        }
+      }
+    }
+    return moves;
+  }
+
+  isAttacked(sq, color) {
+    return (
+      super.isAttackedByKing(sq, color) ||
+      this.isAttackedByRook(sq, color) ||
+      this.isAttackedByBishop(sq, color) ||
+      this.isAttackedByPawn(sq, color)
+    );
+  }
+
+  isAttackedByPawn([x, y], color) {
+    // Determine zone, shifted according to pawn movement
+    let attackDir = "";
+    let forward = 0;
+    if (
+      ([1, 2, 3, 4].includes(x) && y <= 1) ||
+      (x == 5 && y == 0)
+    ) {
+      attackDir = "vertical";
+      forward = 1;
+    }
+    else if (
+      ([2, 3, 4, 5].includes(x) && [5, 6].includes(y)) ||
+      (x == 1 && y == 6)
+    ) {
+      attackDir = "vertical";
+      forward = -1;
+    }
+    else if (
+      (x <= 1 && [2, 3, 4, 5].includes(y)) ||
+      (x == 0 && y == 1)
+    ) {
+      attackDir = "horizontal";
+      forward = -1;
+    }
+    else if (
+      (x >= 5 && [1, 2, 3, 4].includes(y)) ||
+      (x == 6 && y == 5)
+    ) {
+      attackDir = "horizontal";
+      forward = 1;
+    }
+    if (forward != 0) {
+      const steps =
+        attackDir == "vertical"
+          ? [ [forward, -1], [forward, 0], [forward, 1] ]
+          : [ [-1, forward], [0, forward], [1, forward] ];
+      return (
+        super.isAttackedBySlideNJump([x, y], color, V.PAWN, steps, "oneStep")
+      );
+    }
+    // In a corner: can be attacked by one square only
+    let step = null;
+    if (x == 0) {
+      if (y == 0) step = [1, 0];
+      else step = [0, -1];
+    }
+    else {
+      if (y == 0) step = [0, 1];
+      else step = [-1, 0];
+    }
+    return (
+      super.isAttackedBySlideNJump([x, y], color, V.PAWN, [step], "oneStep")
+    );
+  }
+
+  isAttackedByRook([x, y], color) {
+    // "Reversing" the code of getPotentialRookMoves()
+    let multiStep = [],
+        oneStep = [];
+    if (x <= 1) multiStep.push([0, -1]);
+    else oneStep.push([0, -1]);
+    if (y <= 1) multiStep.push([1, 0]);
+    else oneStep.push([1, 0]);
+    if (x >= 5) multiStep.push([0, 1]);
+    else oneStep.push([0, 1]);
+    if (y >= 5) multiStep.push([-1, 0]);
+    else oneStep.push([-1, 0]);
+    if (
+      super.isAttackedBySlideNJump([x, y], color, V.ROOK, oneStep, "oneStep")
+    ) {
+      return true;
+    }
+    for (let step of multiStep) {
+      let [i, j] = [x + step[0], y + step[1]];
+      while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
+        i += step[0];
+        j += step[1];
+      }
+      if (V.OnBoard(i, j)) {
+        if (this.getColor(i, j) == color && this.getPiece(i, j) == V.ROOK)
+          return true;
+      }
+      else {
+        i -= step[0];
+        j -= step[1];
+        if (i != x || j != y) {
+          let nextStep = null;
+          if (i == 0 && j == 0) nextStep = [1, 0];
+          else if (i == 0 && j == 6) nextStep = [0, -1];
+          else if (i == 6 && j == 6) nextStep = [-1, 0];
+          else if (i == 6 && j == 0) nextStep = [0, 1];
+          if (!!nextStep) {
+            i += nextStep[0];
+            j += nextStep[1];
+            while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
+              i += nextStep[0];
+              j += nextStep[1];
+            }
+            if (
+              V.OnBoard(i, j) &&
+              this.getColor(i, j) == color &&
+              this.getPiece(i, j) == V.ROOK
+            ) {
+              return true;
+            }
+          }
+        }
+      }
+    }
+    return false;
+  }
+
+  isAttackedByBishop([x, y], color) {
+    // "Reversing" the code of getPotentiaBishopMoves()
+    let multiStep = {};
+    if (x <= 1) {
+      multiStep["1_-1"] = [1, -1];
+      multiStep["-1_-1"] = [-1, -1];
+    }
+    if (y <= 1) {
+      multiStep["1_1"] = [1, 1];
+      if (!multiStep["1_-1"]) multiStep["1_-1"] = [1, -1];
+    }
+    if (x >= 5) {
+      multiStep["-1_1"] = [-1, 1];
+      if (!multiStep["1_1"]) multiStep["1_1"] = [1, 1];
+    }
+    if (y >= 5) {
+      if (!multiStep["-1_-1"]) multiStep["-1_-1"] = [-1, -1];
+      if (!multiStep["-1_1"]) multiStep["-1_1"] = [-1, 1];
+    }
+    let oneStep = [];
+    Object.keys(V.DictBishopSteps).forEach(str => {
+      if (!multiStep[str]) oneStep.push(V.DictBishopSteps[str]);
+    });
+    if (
+      super.isAttackedBySlideNJump([x, y], color, V.BISHOP, oneStep, "oneStep")
+    ) {
+      return true;
+    }
+    for (let step of Object.values(multiStep)) {
+      let [i, j] = [x + step[0], y + step[1]];
+      while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
+        i += step[0];
+        j += step[1];
+      }
+      if (V.OnBoard(i, j)) {
+        if (this.getColor(i, j) == color && this.getPiece(i, j) == V.BISHOP)
+          return true;
+      }
+      else {
+        i -= step[0];
+        j -= step[1];
+        if (i != x || j != y) {
+          let nextStep = null;
+          if (step[0] == -1 && step[1] == -1) {
+            if (j == 0) nextStep = [-1, 1];
+            else nextStep = [1, -1];
+          }
+          else if (step[0] == -1 && step[1] == 1) {
+            if (i == 0) nextStep = [1, 1];
+            else nextStep = [-1, -1];
+          }
+          else if (step[0] == 1 && step[1] == -1) {
+            if (i == 6) nextStep = [-1, -1];
+            else nextStep = [1, 1];
+          }
+          else {
+            // step == [1, 1]
+            if (j == 6) nextStep = [1, -1];
+            else nextStep = [-1, 1];
+          }
+          i += nextStep[0];
+          j += nextStep[1];
+          while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
+            i += nextStep[0];
+            j += nextStep[1];
+          }
+          if (
+            V.OnBoard(i, j) &&
+            this.getColor(i, j) == color &&
+            this.getPiece(i, j) == V.BISHOP
+          ) {
+            return true;
+          }
+        }
+      }
+    }
+    return false;
+  }
+
+  // The board is divided in areas determined by "distance to target"
+  // A zone n+1 must be reached from a zone n.
+  getKingZone([x, y], color) {
+    if (color == 'w') {
+      if (y >= 4) return -1; //"out of zone"
+      if (y == 3 && [5, 6].includes(x)) return 0;
+      if (x == 6) return 1;
+      if (x == 5) return 2;
+      if (x == 4) return 3;
+      if (x == 3 || y == 0) return 4;
+      if (y == 1) return 5;
+      if (x == 0 || y == 2) return 6;
+      return 7; //x == 1 && y == 3
+    }
+    // color == 'b':
+    if (y <= 2) return -1; //"out of zone"
+    if (y == 3 && [0, 1].includes(x)) return 0;
+    if (x == 0) return 1;
+    if (x == 1) return 2;
+    if (x == 2) return 3;
+    if (x == 3 || y == 6) return 4;
+    if (y == 5) return 5;
+    if (x == 6 || y == 4) return 6;
+    return 7; //x == 5 && y == 3
+  }
+
+  postPlay(move) {
+    super.postPlay(move);
+    if (move.vanish[0].p == V.KING) {
+      const c = move.vanish[0].c;
+      const z1 = this.getKingZone([move.vanish[0].x, move.vanish[0].y], c),
+            z2 = this.getKingZone([move.appear[0].x, move.appear[0].y], c);
+      if (
+        z1 >= 0 && z2 >= 0 && z1 < z2 &&
+        // There exist "zone jumps" (0 to 2 for example),
+        // so the following test "flag >= z1" is required.
+        this.kingFlags[c] >= z1 && this.kingFlags[c] < z2
+      ) {
+        this.kingFlags[c] = z2;
+      }
+    }
+  }
+
+  getCurrentScore() {
+    const oppCol = V.GetOppCol(this.turn);
+    if (this.kingFlags[oppCol] == 7) return (oppCol == 'w' ? "1-0" : "0-1");
+    return super.getCurrentScore();
+  }
+
+  static get SEARCH_DEPTH() {
+    return 4;
+  }
+
+  evalPosition() {
+    let evaluation = 0;
+    for (let i = 0; i < V.size.x; i++) {
+      for (let j = 0; j < V.size.y; j++) {
+        if (this.board[i][j] != V.EMPTY) {
+          const sign = this.getColor(i, j) == "w" ? 1 : -1;
+          const piece = this.getPiece(i, j);
+          if (piece != 'x') evaluation += sign * V.VALUES[piece];
+        }
+      }
+    }
+    // Taking flags into account in a rather naive way
+    return evaluation + this.kingFlags['w'] - this.kingFlags['b'];
+  }
+
+};
index dd36fc9..13f2e8a 100644 (file)
@@ -119,6 +119,7 @@ insert or ignore into Variants (name, description) values
   ('Relayup', 'Upgrade pieces'),
   ('Rifle', 'Shoot pieces'),
   ('Recycle', 'Reuse pieces'),
   ('Relayup', 'Upgrade pieces'),
   ('Rifle', 'Shoot pieces'),
   ('Recycle', 'Reuse pieces'),
+  ('Rollerball', 'As in the movie'),
   ('Rococo', 'Capture on the edge'),
   ('Rookpawns', 'Rook versus pawns'),
   ('Royalrace', 'Kings cross the 11x11 board'),
   ('Rococo', 'Capture on the edge'),
   ('Rookpawns', 'Rook versus pawns'),
   ('Royalrace', 'Kings cross the 11x11 board'),