Add Stealthbomb
authorBenjamin Auder <benjamin.auder@somewhere>
Wed, 24 Mar 2021 18:04:16 +0000 (19:04 +0100)
committerBenjamin Auder <benjamin.auder@somewhere>
Wed, 24 Mar 2021 18:04:16 +0000 (19:04 +0100)
27 files changed:
client/public/images/pieces/Stealthbomb/bc.svg [new symlink]
client/public/images/pieces/Stealthbomb/bl.svg [new symlink]
client/public/images/pieces/Stealthbomb/bo.svg [new symlink]
client/public/images/pieces/Stealthbomb/bs.svg [new symlink]
client/public/images/pieces/Stealthbomb/bt.svg [new symlink]
client/public/images/pieces/Stealthbomb/bu.svg [new symlink]
client/public/images/pieces/Stealthbomb/wc.svg [new symlink]
client/public/images/pieces/Stealthbomb/wl.svg [new symlink]
client/public/images/pieces/Stealthbomb/wo.svg [new symlink]
client/public/images/pieces/Stealthbomb/ws.svg [new symlink]
client/public/images/pieces/Stealthbomb/wt.svg [new symlink]
client/public/images/pieces/Stealthbomb/wu.svg [new symlink]
client/src/translations/about/en.pug
client/src/translations/about/es.pug
client/src/translations/about/fr.pug
client/src/translations/en.js
client/src/translations/es.js
client/src/translations/fr.js
client/src/translations/rules/Stealthbomb/en.pug [new file with mode: 0644]
client/src/translations/rules/Stealthbomb/es.pug [new file with mode: 0644]
client/src/translations/rules/Stealthbomb/fr.pug [new file with mode: 0644]
client/src/translations/variants/en.pug
client/src/translations/variants/es.pug
client/src/translations/variants/fr.pug
client/src/variants/Bario.js
client/src/variants/Stealthbomb.js [new file with mode: 0644]
server/db/populate.sql

diff --git a/client/public/images/pieces/Stealthbomb/bc.svg b/client/public/images/pieces/Stealthbomb/bc.svg
new file mode 120000 (symlink)
index 0000000..b30a26a
--- /dev/null
@@ -0,0 +1 @@
+../Alice/bc.svg
\ No newline at end of file
diff --git a/client/public/images/pieces/Stealthbomb/bl.svg b/client/public/images/pieces/Stealthbomb/bl.svg
new file mode 120000 (symlink)
index 0000000..a10d9e0
--- /dev/null
@@ -0,0 +1 @@
+../Alice/bl.svg
\ No newline at end of file
diff --git a/client/public/images/pieces/Stealthbomb/bo.svg b/client/public/images/pieces/Stealthbomb/bo.svg
new file mode 120000 (symlink)
index 0000000..1200186
--- /dev/null
@@ -0,0 +1 @@
+../Alice/bo.svg
\ No newline at end of file
diff --git a/client/public/images/pieces/Stealthbomb/bs.svg b/client/public/images/pieces/Stealthbomb/bs.svg
new file mode 120000 (symlink)
index 0000000..e8cf23a
--- /dev/null
@@ -0,0 +1 @@
+../Alice/bs.svg
\ No newline at end of file
diff --git a/client/public/images/pieces/Stealthbomb/bt.svg b/client/public/images/pieces/Stealthbomb/bt.svg
new file mode 120000 (symlink)
index 0000000..c517549
--- /dev/null
@@ -0,0 +1 @@
+../Alice/bt.svg
\ No newline at end of file
diff --git a/client/public/images/pieces/Stealthbomb/bu.svg b/client/public/images/pieces/Stealthbomb/bu.svg
new file mode 120000 (symlink)
index 0000000..09e6ea3
--- /dev/null
@@ -0,0 +1 @@
+../Alice/bu.svg
\ No newline at end of file
diff --git a/client/public/images/pieces/Stealthbomb/wc.svg b/client/public/images/pieces/Stealthbomb/wc.svg
new file mode 120000 (symlink)
index 0000000..d23af91
--- /dev/null
@@ -0,0 +1 @@
+../Alice/wc.svg
\ No newline at end of file
diff --git a/client/public/images/pieces/Stealthbomb/wl.svg b/client/public/images/pieces/Stealthbomb/wl.svg
new file mode 120000 (symlink)
index 0000000..51c1893
--- /dev/null
@@ -0,0 +1 @@
+../Alice/wl.svg
\ No newline at end of file
diff --git a/client/public/images/pieces/Stealthbomb/wo.svg b/client/public/images/pieces/Stealthbomb/wo.svg
new file mode 120000 (symlink)
index 0000000..4a85712
--- /dev/null
@@ -0,0 +1 @@
+../Alice/wo.svg
\ No newline at end of file
diff --git a/client/public/images/pieces/Stealthbomb/ws.svg b/client/public/images/pieces/Stealthbomb/ws.svg
new file mode 120000 (symlink)
index 0000000..659b2de
--- /dev/null
@@ -0,0 +1 @@
+../Alice/ws.svg
\ No newline at end of file
diff --git a/client/public/images/pieces/Stealthbomb/wt.svg b/client/public/images/pieces/Stealthbomb/wt.svg
new file mode 120000 (symlink)
index 0000000..447fc4f
--- /dev/null
@@ -0,0 +1 @@
+../Alice/wt.svg
\ No newline at end of file
diff --git a/client/public/images/pieces/Stealthbomb/wu.svg b/client/public/images/pieces/Stealthbomb/wu.svg
new file mode 120000 (symlink)
index 0000000..c1403b3
--- /dev/null
@@ -0,0 +1 @@
+../Alice/wu.svg
\ No newline at end of file
index f1e0c93..4979575 100644 (file)
@@ -44,7 +44,8 @@ h3 Related links
 #links
   a(href="https://www.chessvariants.com/") chessvariants.com
   a(href="https://greenchess.net/") greenchess.net
 #links
   a(href="https://www.chessvariants.com/") chessvariants.com
   a(href="https://greenchess.net/") greenchess.net
-  a(href="http://pychess-variants.herokuapp.com/") pychess-variants.com
+  a(href="https://www.pychess.org/") pychess.org
+  a(href="https://fishrandom.io/") fishrandom.io
   a(href="https://glukkazan.github.io/") Dagaz demo + server
   a(href="https://mindsports.nl/index.php") mindsports.nl
   a(href="https://www.jocly.com/#/games") jocly.com
   a(href="https://glukkazan.github.io/") Dagaz demo + server
   a(href="https://mindsports.nl/index.php") mindsports.nl
   a(href="https://www.jocly.com/#/games") jocly.com
index bcc6ff0..4b08b48 100644 (file)
@@ -43,7 +43,8 @@ h3 Enlaces relacionados
 #links
   a(href="https://www.chessvariants.com/") chessvariants.com
   a(href="https://greenchess.net/") greenchess.net
 #links
   a(href="https://www.chessvariants.com/") chessvariants.com
   a(href="https://greenchess.net/") greenchess.net
-  a(href="http://pychess-variants.herokuapp.com/") pychess-variants.com
+  a(href="https://www.pychess.org/") pychess.org
+  a(href="https://fishrandom.io/") fishrandom.io
   a(href="https://glukkazan.github.io/") Dagaz demo + servidor
   a(href="https://mindsports.nl/index.php") mindsports.nl
   a(href="https://www.jocly.com/#/games") jocly.com
   a(href="https://glukkazan.github.io/") Dagaz demo + servidor
   a(href="https://mindsports.nl/index.php") mindsports.nl
   a(href="https://www.jocly.com/#/games") jocly.com
index 537a79b..cf58ca7 100644 (file)
@@ -44,7 +44,8 @@ h3 Liens connexes
 #links
   a(href="https://www.chessvariants.com/") chessvariants.com
   a(href="https://greenchess.net/") greenchess.net
 #links
   a(href="https://www.chessvariants.com/") chessvariants.com
   a(href="https://greenchess.net/") greenchess.net
-  a(href="http://pychess-variants.herokuapp.com/") pychess-variants.com
+  a(href="https://www.pychess.org/") pychess.org
+  a(href="https://fishrandom.io/") fishrandom.io
   a(href="https://glukkazan.github.io/") Dagaz demo + serveur
   a(href="https://mindsports.nl/index.php") mindsports.nl
   a(href="https://www.jocly.com/#/games") jocly.com
   a(href="https://glukkazan.github.io/") Dagaz demo + serveur
   a(href="https://mindsports.nl/index.php") mindsports.nl
   a(href="https://www.jocly.com/#/games") jocly.com
index 604fbf8..a86b6e1 100644 (file)
@@ -181,6 +181,7 @@ export const translations = {
   "Augmented Queens": "Augmented Queens",
   "Balanced sliders & leapers": "Balanced sliders & leapers",
   "Baroque Music": "Baroque Music",
   "Augmented Queens": "Augmented Queens",
   "Balanced sliders & leapers": "Balanced sliders & leapers",
   "Baroque Music": "Baroque Music",
+  "Beware the bomb": "Beware the bomb",
   "Big board": "Big board",
   "Bishop versus pawns": "Bishop versus pawns",
   "Board upside down": "Board upside down",
   "Big board": "Big board",
   "Bishop versus pawns": "Bishop versus pawns",
   "Board upside down": "Board upside down",
index 9a099a2..a5cd67b 100644 (file)
@@ -181,6 +181,7 @@ export const translations = {
   "Augmented Queens": "Damas aumentadas",
   "Balanced sliders & leapers": "Modos de desplazamiento equilibrados",
   "Baroque Music": "Música Barroca",
   "Augmented Queens": "Damas aumentadas",
   "Balanced sliders & leapers": "Modos de desplazamiento equilibrados",
   "Baroque Music": "Música Barroca",
+  "Beware the bomb": "Cuidado con la bomba",
   "Big board": "Gran tablero",
   "Bishop versus pawns": "Alfil contra peones",
   "Board upside down": "Tablero al revés",
   "Big board": "Gran tablero",
   "Bishop versus pawns": "Alfil contra peones",
   "Board upside down": "Tablero al revés",
index 5bd4aea..9ca4717 100644 (file)
@@ -181,6 +181,7 @@ export const translations = {
   "Augmented Queens": "Dames augmentées",
   "Balanced sliders & leapers": "Modes de déplacement équilibrés",
   "Baroque Music": "Musique Baroque",
   "Augmented Queens": "Dames augmentées",
   "Balanced sliders & leapers": "Modes de déplacement équilibrés",
   "Baroque Music": "Musique Baroque",
+  "Beware the bomb": "Attention à la bombe",
   "Big board": "Grand échiquier",
   "Bishop versus pawns": "Fou contre pions",
   "Board upside down": "Échiquier à l'envers",
   "Big board": "Grand échiquier",
   "Bishop versus pawns": "Fou contre pions",
   "Board upside down": "Échiquier à l'envers",
diff --git a/client/src/translations/rules/Stealthbomb/en.pug b/client/src/translations/rules/Stealthbomb/en.pug
new file mode 100644 (file)
index 0000000..09ba3bd
--- /dev/null
@@ -0,0 +1,31 @@
+p.boxed.
+  One piece of each side hides a bomb,
+  which can be triggered instead of playing a move.
+
+p.
+  Each player secretly decides of a piece which will carry a bomb.
+  (Click on any piece at move 1).
+
+p.
+  At any time, instead of playing a move you may detonate the bomb:
+  bring the loaded piece onto your king.
+  On the diagrams below, bombs are placed at c2 and e4.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:r1bqk1nr/pppp1Qpp/8/2b1p3/2B1S3/2N5/PPoP1PPP/R1BK2NR:
+  .diagram.diag22
+    | fen:r1bqk1nr/pppp1Qpp/8/2b1p3/2B1S3/8/P4PPP/R5NR:
+  figcaption Not checkmate: explosion on c2 follows.
+
+p.
+  An explosion destroys everything a king step away from the bomb,
+  including the loaded piece. Explosion has priority over checkmate.
+
+h3 Source
+
+p
+  a(href="https://en.wikipedia.org/wiki/Beirut_chess") Beirut Chess
+  | . This variant is Stealthbomber on 
+  a(href="fishrandom.io") fishrandom.io
+  | , and "Stealthbomb" here... why not :-)
diff --git a/client/src/translations/rules/Stealthbomb/es.pug b/client/src/translations/rules/Stealthbomb/es.pug
new file mode 100644 (file)
index 0000000..758e7e9
--- /dev/null
@@ -0,0 +1,31 @@
+p.boxed.
+  Una pieza de cada campamento esconde una bomba,
+  que se puede activar en lugar de ejecutar un movimiento.
+
+p.
+  Cada jugador decide en secreto qué pieza llevará una bomba.
+  (Haga clic en una pieza en el primer movimiento).
+
+p.
+  En cualquier momento, puede detonar la bomba en lugar de hacer una jugada:
+  lleva la pieza cargada a tu rey.
+  En los siguientes diagramas, las bombas se colocan en c2 y e4.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:r1bqk1nr/pppp1Qpp/8/2b1p3/2B1S3/2N5/PPoP1PPP/R1BK2NR:
+  .diagram.diag22
+    | fen:r1bqk1nr/pppp1Qpp/8/2b1p3/2B1S3/8/P4PPP/R5NR:
+  figcaption No jaque mate: sigue la explosión en c2.
+
+p.
+  Una explosión destruye todo a un paso de la bomba, incluyendo
+  la pieza cargada. La explosión tiene prioridad sobre el jaque mate.
+
+h3 Fuente
+
+p
+  a(href="https://en.wikipedia.org/wiki/Beirut_chess") Beirut Chess
+  | . Esta variante es Stealthbomber en 
+  a(href="fishrandom.io") fishrandom.io
+  | , y "Stealthbomb" aquí... ¿Por qué no? :-)
diff --git a/client/src/translations/rules/Stealthbomb/fr.pug b/client/src/translations/rules/Stealthbomb/fr.pug
new file mode 100644 (file)
index 0000000..ecf61cc
--- /dev/null
@@ -0,0 +1,31 @@
+p.boxed.
+  Une pièce de chaque camp cache une bombe,
+  qui peut être déclenchée au lieu de jouer un coup.
+
+p.
+  Chaque joueur décide secrètement d'une pièce qui portera une bombe.
+  (Cliquez sur une pièce au premier coup).
+
+p.
+  À tout moment, vous pouvez faire exploser la bombe au lieu de jouer un coup :
+  amenez la pièce chargée sur votre roi.
+  Dans les diagrammes ci-dessous, les bombes sont placées en c2 et e4.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:r1bqk1nr/pppp1Qpp/8/2b1p3/2B1S3/2N5/PPoP1PPP/R1BK2NR:
+  .diagram.diag22
+    | fen:r1bqk1nr/pppp1Qpp/8/2b1p3/2B1S3/8/P4PPP/R5NR:
+  figcaption Pas mat : l'explosion en c2 suit.
+
+p.
+  Une explosion détruit tout à un pas de roi depuis la bombe, incluant
+  la pièce chargée. L'explosion a priorité sur le mat.
+
+h3 Source
+
+p
+  a(href="https://en.wikipedia.org/wiki/Beirut_chess") Beirut Chess
+  | . Cette variante est Stealthbomber sur 
+  a(href="fishrandom.io") fishrandom.io
+  | , et "Stealthbomb" ici... pourquoi pas :-)
index 5c13108..c933d9c 100644 (file)
@@ -222,6 +222,7 @@ p.
     "Dark",
     "Hidden",
     "Hiddenqueen",
     "Dark",
     "Hidden",
     "Hiddenqueen",
+    "Stealthbomb",
     "Synchrone1",
     "Synchrone2"
   ]
     "Synchrone1",
     "Synchrone2"
   ]
index fa4bab9..81fad93 100644 (file)
@@ -229,6 +229,7 @@ p.
     "Dark",
     "Hidden",
     "Hiddenqueen",
     "Dark",
     "Hidden",
     "Hiddenqueen",
+    "Stealthbomb",
     "Synchrone1",
     "Synchrone2"
   ]
     "Synchrone1",
     "Synchrone2"
   ]
index a2dccae..6743ce2 100644 (file)
@@ -228,6 +228,7 @@ p.
     "Dark",
     "Hidden",
     "Hiddenqueen",
     "Dark",
     "Hidden",
     "Hiddenqueen",
+    "Stealthbomb",
     "Synchrone1",
     "Synchrone2"
   ]
     "Synchrone1",
     "Synchrone2"
   ]
index e10bb3d..3564e74 100644 (file)
@@ -73,7 +73,7 @@ export class BarioRules extends ChessRules {
       vanish: [
         new PiPo({ x: square[0], y: square[1], c: c, p: V.UNDEFINED })
       ],
       vanish: [
         new PiPo({ x: square[0], y: square[1], c: c, p: V.UNDEFINED })
       ],
-      start: { x: -1, y: -1 },
+      start: { x: -1, y: -1 }
     });
   }
 
     });
   }
 
diff --git a/client/src/variants/Stealthbomb.js b/client/src/variants/Stealthbomb.js
new file mode 100644 (file)
index 0000000..ee73ef5
--- /dev/null
@@ -0,0 +1,228 @@
+import { ChessRules, Move, PiPo } from "@/base_rules";
+
+export class StealthbombRules extends ChessRules {
+
+  static get CanAnalyze() {
+    return false;
+  }
+
+  static get SomeHiddenMoves() {
+    return true;
+  }
+
+  static get BOMB_DECODE() {
+    return {
+      s: "p",
+      t: "q",
+      u: "r",
+      c: "b",
+      o: "n",
+      l: "k"
+    };
+  }
+  static get BOMB_CODE() {
+    return {
+      p: "s",
+      q: "t",
+      r: "u",
+      b: "c",
+      n: "o",
+      k: "l"
+    };
+  }
+
+  static get PIECES() {
+    return ChessRules.PIECES.concat(Object.keys(V.BOMB_DECODE));
+  }
+
+  getPiece(i, j) {
+    const piece = this.board[i][j].charAt(1);
+    if (
+      ChessRules.PIECES.includes(piece) ||
+      // 'side' is used to determine what I see: normal or "loaded" piece?
+      this.getColor(i, j) == this.side
+    ) {
+      return piece;
+    }
+    // Loaded piece, but no right to view it
+    return V.BOMB_DECODE[piece];
+  }
+
+  getPpath(b, color, score) {
+    if (Object.keys(V.BOMB_DECODE).includes(b[1])) {
+      // Supposed to be hidden.
+      if (score == "*" && (!color || color != b[0]))
+        return b[0] + V.BOMB_DECODE[b[1]];
+      return "Stealthbomb/" + b;
+    }
+    return b;
+  }
+
+  hoverHighlight([x, y]) {
+    const c = this.turn;
+    return (
+      this.movesCount <= 1 &&
+      (
+        (c == 'w' && x >= 6) ||
+        (c == 'b' && x <= 1)
+      )
+    );
+  }
+
+  onlyClick([x, y]) {
+    return (
+      this.movesCount <= 1 ||
+      // TODO: next line theoretically shouldn't be required...
+      (this.movesCount == 2 && this.getColor(x, y) != this.turn)
+    );
+  }
+
+  // Initiate the game by choosing a square for the bomb:
+  doClick(square) {
+    const c = this.turn;
+    if (
+      this.movesCount >= 2 ||
+      (
+        (c == 'w' && square[0] < 6) ||
+        (c == 'b' && square[0] > 2)
+      )
+    ) {
+      return null;
+    }
+    const [x, y] = square;
+    const piece = super.getPiece(x, y);
+    return new Move({
+      appear: [ new PiPo({ x: x, y: y, c: c, p: V.BOMB_CODE[piece] }) ],
+      vanish: [ new PiPo({ x: x, y: y, c: c, p: piece }) ],
+      start: { x: -1, y: -1 }
+    });
+  }
+
+  getPotentialMovesFrom([x, y]) {
+    if (this.movesCount <= 1) {
+      const setup = this.doClick([x, y]);
+      return (!setup ? [] : [setup]);
+    }
+    let moves = super.getPotentialMovesFrom([x, y]);
+    const c = this.turn;
+    // Add bomb explosion
+    if (Object.keys(V.BOMB_DECODE).includes(this.board[x][y][1])) {
+      let mv = new Move({
+        appear: [ ],
+        vanish: [ new PiPo({ x: x, y: y, c: c, p: this.board[x][y][1] }) ],
+        end: { x: this.kingPos[c][0], y: this.kingPos[c][1] }
+      });
+      for (let s of V.steps[V.ROOK].concat(V.steps[V.BISHOP])) {
+        let [i, j] = [x + s[0], y + s[1]];
+        if (V.OnBoard(i, j) && this.board[i][j] != V.EMPTY) {
+          mv.vanish.push(
+            new PiPo({
+              x: i,
+              y: j,
+              c: this.getColor(i, j),
+              p: this.board[i][j][1]
+            })
+          );
+        }
+      }
+      moves.push(mv);
+    }
+    return moves;
+  }
+
+  // NOTE: a lot of copy-paste from Atomic from here.
+  postPlay(move) {
+    if (this.movesCount >= 3) {
+      super.postPlay(move);
+      if (move.appear.length == 0) {
+        // Explosion
+        const firstRank = { w: 7, b: 0 };
+        for (let c of ["w", "b"]) {
+          // Did we explode king of color c ?
+          if (
+            Math.abs(this.kingPos[c][0] - move.start.x) <= 1 &&
+            Math.abs(this.kingPos[c][1] - move.start.y) <= 1
+          ) {
+            this.kingPos[c] = [-1, -1];
+            this.castleFlags[c] = [8, 8];
+          }
+          else {
+            // Now check if init rook(s) exploded
+            if (Math.abs(move.start.x - firstRank[c]) <= 1) {
+              if (Math.abs(move.start.y - this.castleFlags[c][0]) <= 1)
+                this.castleFlags[c][0] = 8;
+              if (Math.abs(move.start.y - this.castleFlags[c][1]) <= 1)
+                this.castleFlags[c][1] = 8;
+            }
+          }
+        }
+      }
+    }
+  }
+
+  postUndo(move) {
+    if (this.movesCount >= 2) {
+      super.postUndo(move);
+      const c = this.turn;
+      const oppCol = V.GetOppCol(c);
+      if ([this.kingPos[c][0], this.kingPos[oppCol][0]].some(e => e < 0)) {
+        // Last move exploded some king..
+        for (let psq of move.vanish) {
+          if (psq.p == "k")
+            this.kingPos[psq.c == c ? c : oppCol] = [psq.x, psq.y];
+        }
+      }
+    }
+  }
+
+  underCheck(color) {
+    const oppCol = V.GetOppCol(color);
+    let res = undefined;
+    // If our king disappeared, move is not valid
+    if (this.kingPos[color][0] < 0) res = true;
+    // If opponent king disappeared, move is valid
+    else if (this.kingPos[oppCol][0] < 0) res = false;
+    // Otherwise, if we remain under check, move is not valid
+    else res = this.isAttacked(this.kingPos[color], oppCol);
+    return res;
+  }
+
+  getCheckSquares() {
+    const color = this.turn;
+    let res = [];
+    if (
+      this.kingPos[color][0] >= 0 && //king might have exploded
+      this.isAttacked(this.kingPos[color], V.GetOppCol(color))
+    ) {
+      res = [JSON.parse(JSON.stringify(this.kingPos[color]))];
+    }
+    return res;
+  }
+
+  getCurrentScore() {
+    const color = this.turn;
+    const kp = this.kingPos[color];
+    if (kp[0] < 0)
+      // King disappeared
+      return color == "w" ? "0-1" : "1-0";
+    if (this.atLeastOneMove()) return "*";
+    if (!this.isAttacked(kp, V.GetOppCol(color))) return "1/2";
+    return color == "w" ? "0-1" : "1-0"; //checkmate
+  }
+
+  getNotation(move) {
+    if (this.movesCount <= 1) return "Bomb?";
+    const c = this.turn;
+    if (move.end.x == this.kingPos[c][0] && move.end.y == this.kingPos[c][1])
+      return V.CoordsToSquare(move.start) + "~X";
+    if (Object.keys(V.BOMB_DECODE).includes(move.vanish[0].p)) {
+      let cpMove = JSON.parse(JSON.stringify(move));
+      cpMove.vanish[0].p = V.BOMB_DECODE[move.vanish[0].p];
+      if (Object.keys(V.BOMB_DECODE).includes(move.appear[0].p))
+        cpMove.appear[0].p = V.BOMB_DECODE[move.appear[0].p];
+      return super.getNotation(cpMove);
+    }
+    return super.getNotation(move);
+  }
+
+};
index 1a13014..13a4fb8 100644 (file)
@@ -156,6 +156,7 @@ insert or ignore into Variants (name, description) values
   ('Spartan', 'Spartan versus Persians'),
   ('Squatter1', 'Squat last rank (v1)'),
   ('Squatter2', 'Squat last rank (v2)'),
   ('Spartan', 'Spartan versus Persians'),
   ('Squatter1', 'Squat last rank (v1)'),
   ('Squatter2', 'Squat last rank (v2)'),
+  ('Stealthbomb', 'Beware the bomb'),
   ('Suicide', 'Lose all pieces'),
   ('Suction', 'Attract opposite king'),
   ('Swap', 'Dangerous captures'),
   ('Suicide', 'Lose all pieces'),
   ('Suction', 'Attract opposite king'),
   ('Swap', 'Dangerous captures'),