From d5af4af2ac7d86bed9166916eb1610736647df0a Mon Sep 17 00:00:00 2001
From: Benjamin Auder <benjamin.auder@somewhere>
Date: Thu, 18 Mar 2021 23:08:11 +0100
Subject: [PATCH] Finalize Shinobi Chess

---
 client/src/components/Board.vue              |   3 +-
 client/src/translations/en.js                |   1 +
 client/src/translations/es.js                |   1 +
 client/src/translations/fr.js                |   1 +
 client/src/translations/rules/Shinobi/en.pug |  68 +++++++
 client/src/translations/rules/Shinobi/es.pug |  71 ++++++++
 client/src/translations/rules/Shinobi/fr.pug |  75 ++++++++
 client/src/translations/variants/en.pug      |   1 +
 client/src/translations/variants/es.pug      |   1 +
 client/src/translations/variants/fr.pug      |   1 +
 client/src/variants/Shinobi.js               | 181 +++++++++++--------
 server/db/populate.sql                       |   1 +
 12 files changed, 333 insertions(+), 72 deletions(-)
 create mode 100644 client/src/translations/rules/Shinobi/en.pug
 create mode 100644 client/src/translations/rules/Shinobi/es.pug
 create mode 100644 client/src/translations/rules/Shinobi/fr.pug

diff --git a/client/src/components/Board.vue b/client/src/components/Board.vue
index f20e7da6..799185e4 100644
--- a/client/src/components/Board.vue
+++ b/client/src/components/Board.vue
@@ -299,7 +299,8 @@ export default {
         (!myReserveTop && !!this.vr.reserve[playingColor])
       );
       // Center reserves, assuming same number of pieces for each side:
-      const nbReservePieces = myReservePiecesArray.length;
+      const nbReservePieces =
+        Math.max(myReservePiecesArray.length, oppReservePiecesArray.length);
       const marginLeft =
         ((100 - nbReservePieces * (100 / reserveSquareNb)) / 2) + "%";
       if (hasReserveTop) {
diff --git a/client/src/translations/en.js b/client/src/translations/en.js
index 555606b5..eb0c0bbd 100644
--- a/client/src/translations/en.js
+++ b/client/src/translations/en.js
@@ -167,6 +167,7 @@ export const translations = {
   "64 pieces on the board": "64 pieces on the board",
   "A Clockwork Orange": "A Clockwork Orange",
   "A pawns cloud": "A pawns cloud",
+  "A story of invasion": "A story of invasion",
   "A quantum story": "A quantum story",
   "A wizard in the corner": "A wizard in the corner",
   "Absorb powers": "Absorb powers",
diff --git a/client/src/translations/es.js b/client/src/translations/es.js
index d64bda16..051978c6 100644
--- a/client/src/translations/es.js
+++ b/client/src/translations/es.js
@@ -167,6 +167,7 @@ export const translations = {
   "64 pieces on the board": "64 piezas en el tablero",
   "A Clockwork Orange": "Naranja Mecánica",
   "A pawns cloud": "Une nube de peones",
+  "A story of invasion": "Una historia de invasión",
   "A quantum story": "Una historia cuántica",
   "A wizard in the corner": "Un mago en la esquina",
   "Absorb powers": "Absorber poderes",
diff --git a/client/src/translations/fr.js b/client/src/translations/fr.js
index c516cb21..5e92206d 100644
--- a/client/src/translations/fr.js
+++ b/client/src/translations/fr.js
@@ -167,6 +167,7 @@ export const translations = {
   "64 pieces on the board": "64 pièces sur l'échiquier",
   "A Clockwork Orange": "Orange Mécanique",
   "A pawns cloud": "Une nuée de pions",
+  "A story of invasion": "Une histoire d'invasion",
   "A quantum story": "Une histoire quantique",
   "A wizard in the corner": "Un sorcier dans le coin",
   "Absorb powers": "Absorber les pouvoirs",
diff --git a/client/src/translations/rules/Shinobi/en.pug b/client/src/translations/rules/Shinobi/en.pug
new file mode 100644
index 00000000..eac9a6f2
--- /dev/null
+++ b/client/src/translations/rules/Shinobi/en.pug
@@ -0,0 +1,68 @@
+p.boxed.
+  Different armies.
+  Most white pieces start "in hand", and promote when reaching 6th rank.
+
+figure.diagram-container
+  .diagram
+    | fen:rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/3CK3:
+  figcaption Deterministic initial position.
+
+p.
+  Shinobi Chess is a chess variant designed in 2021 by Couch Tomato.
+  The chess army (the "Kingdom", black) has invaded the land of the Sakura
+  Clan ("pink"). While initially unprepared and having very few pieces
+  available, the Clan is very resourceful and can instantly recruit and
+  summon allies to defend at a minute's notice!
+
+p.
+  The Clan starts with almost all of its pieces in hand, and can drop them
+  on its side of the board (first 4 ranks) in lieu of moving a piece.
+
+p
+  | In addition to checkmate,
+  | one can win by bringing the king into the final rank ("campmate").
+  br
+  | Stalemate and repetition are both losses.
+
+h3 New pieces
+
+p.
+  There are five new units unique to the Clan: Ninja, Samurai, Lances,
+  (Wooden) Horses, and Monks. Captains are a new piece available to both
+  sides, but only the Clan starts with one on the board.
+  Ninja, Samurai, and Captains do not promote (see below).
+
+p.
+  The Clan's king is called a Kage (K) and has a different symbol, but the
+  change is purely aesthetic and thematic: it behaves like an orthodox King.
+
+ul
+  li Captain (C) &ndash; Moves like a King. Pawns promote to a Captain.
+  li Ninja (J) = Knight + Bishop.
+  li Samurai (S) = Knight + Rook.
+  li Monk (M) &ndash; One-step bishop.
+  li.
+    Horse (H) &ndash; Moves only forward two squares,
+    and then one square to the side.
+  li Lance (L) &ndash; Moves only forward, like a unidirectional rook.
+
+p.
+  Clan minor pieces are considerably weak. However, with a little
+  resourcefulness, they can trap the stronger Kingdom pieces
+  and even win the battle.
+
+h3 Promotion
+
+p.
+  Pawns promote into Captains when reaching the 6th rank.
+  All minor Clan pieces also promote upon reaching the 6th rank (or beyond):
+ul
+  li Monk into Bishop.
+  li Horse into Knight.
+  li Lance into Rook.
+
+p
+  | Clan Rooks and Bishops look different, but the changes are purely
+  | aesthetic. Note that the symbol on the Bishop is a 
+  a(href="https://handwiki.org/wiki/Sauwastika") sauwastika
+  | , not a swastika.
diff --git a/client/src/translations/rules/Shinobi/es.pug b/client/src/translations/rules/Shinobi/es.pug
new file mode 100644
index 00000000..e3d66696
--- /dev/null
+++ b/client/src/translations/rules/Shinobi/es.pug
@@ -0,0 +1,71 @@
+p.boxed.
+  Diferentes ejércitos. La mayoría de las piezas blancas comienzan
+  "en la mano", y son promovidas cuando llegan a la 6ta fila.
+
+figure.diagram-container
+  .diagram
+    | fen:rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/3CK3:
+  figcaption Posición inicial determinista.
+
+p.
+  Ajedrez Shinobi es una variación desarrollada por Couch Tomato en 2021.
+  El ejército ortodoxo (el "Reino", negras) invadió las tierras del Clan
+  Sakura ("rosa"). Aunque inicialmente no estaba preparado y con poca
+  recursos, el Clan no debe ser subestimado porque puede reclutar
+  nuevos aliados sin demora!
+
+p.
+  El Clan comienza con casi todas sus piezas en la mano y puede las
+  paracaídas en su lado del tablero (primeras 4 filas) en lugar de
+  mover una pieza.
+
+p
+  | Además del jaque mate,
+  | puedes ganar llevando al rey a la última fila ("mate de campamento").
+  br
+  | Tanto el empate como la repetición pierden.
+
+h3 Piezas nuevas
+
+p.
+  Cinco nuevas unidades son exclusivas del Clan: Ninja, Samurai, Lances,
+  Jamelgos y Monjes. Los Capitanes son una nueva pieza disponible en ambos
+  lados, pero solo el Clan comienza con uno en el tablero.
+  Los Ninja, Samuráis y Capitanes no son promovidos (ver más abajo).
+
+p.
+  El rey del Clan se llama Kage (K) y tiene un símbolo diferente, pero el
+  cambio es puramente estético y temático: se comporta como un rey ortodoxo.
+
+ul
+  li Capitán (C) &ndash; Muévese como un rey. Los peones ascienden a Capitán.
+  li Ninja (J) = Caballo + Alfil.
+  li Samurái (S) = Caballo + Torre.
+  li Monje (M) &ndash; Alfil limitado a una casilla.
+  li Jamelgo (H) &ndash; Avanza dos espacios, luego una casilla al costado.
+  li.
+    Lanza (L) &ndash; Se mueve hacia adelante,
+    como una torre de un solo sentido.
+
+p.
+  Las piezas menores del Clan son considerablemente débiles. Sin embargo, con
+  un poco astutos, pueden atrapar piezas (más fuerte) del Reino,
+  e incluso ganar la victoria.
+
+h3 Promoción
+
+p.
+  Los peones son promovidos a Capitanes cuando alcanzan la sexta fila.
+  Todas las piezas menores del Clan también se promocionan una vez en
+  la sexta fila (o más):
+ul
+  li Monje &rarr; Alfil.
+  li Jamelgo &rarr; Caballo.
+  li Lanza &rarr; Torre.
+
+p
+  | Las Torres y Alfiles del Clan son inusuales en apariencia, pero los
+  | cambios son puramente estéticos.
+  | Tenga en cuenta que el símbolo del alfil es un 
+  a(href="https://handwiki.org/wiki/Sauwastika") sauwastika
+  | , no una swastika..
diff --git a/client/src/translations/rules/Shinobi/fr.pug b/client/src/translations/rules/Shinobi/fr.pug
new file mode 100644
index 00000000..d1ba9506
--- /dev/null
+++ b/client/src/translations/rules/Shinobi/fr.pug
@@ -0,0 +1,75 @@
+p.boxed.
+  Armées différentes. La plupart des pièces blanches démarrent "en main",
+  et sont promues quand elles atteignent la 6eme rangée.
+
+figure.diagram-container
+  .diagram
+    | fen:rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/3CK3:
+  figcaption Position initiale déterministe.
+
+p.
+  Les Échecs Shinobi sont une variante élaborée par Couch Tomato en 2021.
+  L'armée orthodoxe (le "Royaume", noirs) a envahi les terres du Clan Sakura
+  ("rose"). Bien qu'initialement non préparé et ne disposant que de peu de
+  ressources, le Clan ne doit pas être sous-estimé car il peut recruter de
+  nouveaux alliés sans délais !
+
+p.
+  Le Clan démarre avec presque toutes ses pièces en main, et peut les
+  parachuter de son côté de l'échiquier (4 premières rangées) au lieu de
+  déplacer une pièce.
+
+p
+  | En plus du mat,
+  | on peut gagner en amenant le roi sur la dernière rangée ("mat de camp").
+  br
+  | Le pat et la répétition de coups perdent tous les deux.
+
+h3 Nouvelles pièces
+
+p.
+  Cinq nouvelles unités sont uniques au Clan : les Ninja, Samurai, Lances,
+  Chevaux (de Bois), et Moines. Les Capitaines sont une nouvelle pièce
+  disponible des deux côtés, mais seul le Clan démarre avec un sur l'échiquier.
+  Les Ninja, Samourai et Capitaines ne sont pas promus (voir ci-dessous).
+
+p.
+  Le roi du Clan est appelé Kage (K), et a un symbole différent, mais le
+  changement est purement esthétique et thématique : il se comporte comme
+  un roi orthodoxe.
+
+ul
+  li.
+    Capitaine (C) &ndash; Se déplace comme un Roi.
+    Les pions se promeuvent en Capitaine.
+  li Ninja (J) = Cavalier + Fou.
+  li Samurai (S) = Cavalier + Tour.
+  li Moine (M) &ndash; Fou limité à une case.
+  li.
+    Cheval (H) &ndash; Se déplace vers l'avant de deux cases,
+    puis d'une case sur le coté.
+  li.
+    Lance (L) &ndash; Se déplace vers l'avant,
+    comme une tour unidirectionnelle.
+
+p.
+  Les pièces mineures du Clan sont considérablement faibles. Cependant, avec
+  un peu de ruse, elles peuvent piéger les pièces du Royaume &ndash; pourtant
+  plus fortes &ndash;et même remporter la victoire.
+
+h3 Promotion
+
+p.
+  Les pions sont promus en Capitaines quand ils atteignent la 6eme rangée.
+  Toutes les pièces mineures du Clan se promeuvent également une fois sur
+  la 6eme rangée (ou plus loin) :
+ul
+  li Moine &rarr; Fou.
+  li Cheval &rarr; Cavalier.
+  li Lance &rarr; Tour.
+
+p
+  | Les Tours et Fous du Clan sont d'allures inhabituelles, mais les
+  changements sont purement esthétique. Notez que le symbole sur le Fou est un 
+  a(href="https://handwiki.org/wiki/Sauwastika") sauwastika
+  | , pas un swastika.
diff --git a/client/src/translations/variants/en.pug b/client/src/translations/variants/en.pug
index 6432c80e..d6c8f5ba 100644
--- a/client/src/translations/variants/en.pug
+++ b/client/src/translations/variants/en.pug
@@ -320,6 +320,7 @@ p Pieces can be drop on the board, either immediately or later in the game.
     "Madhouse",
     "Rampage",
     "Recycle",
+    "Shinobi",
     "Shogun",
     "Teleport"
   ]
diff --git a/client/src/translations/variants/es.pug b/client/src/translations/variants/es.pug
index ebf9d74c..32a83c8b 100644
--- a/client/src/translations/variants/es.pug
+++ b/client/src/translations/variants/es.pug
@@ -329,6 +329,7 @@ p.
     "Madhouse",
     "Rampage",
     "Recycle",
+    "Shinobi",
     "Shogun",
     "Teleport"
   ]
diff --git a/client/src/translations/variants/fr.pug b/client/src/translations/variants/fr.pug
index 7eb09e90..f8fcb299 100644
--- a/client/src/translations/variants/fr.pug
+++ b/client/src/translations/variants/fr.pug
@@ -328,6 +328,7 @@ p.
     "Madhouse",
     "Rampage",
     "Recycle",
+    "Shinobi",
     "Shogun",
     "Teleport"
   ]
diff --git a/client/src/variants/Shinobi.js b/client/src/variants/Shinobi.js
index 5eed0d39..8f7f11f0 100644
--- a/client/src/variants/Shinobi.js
+++ b/client/src/variants/Shinobi.js
@@ -1,15 +1,10 @@
 import { ChessRules, PiPo, Move } from "@/base_rules";
-import { ArrayFun } from "@/utils/array";
 
 export class ShinobiRules extends ChessRules {
 
-  /* Would be unused:
-  static get PawnSpecs() {
-    return Object.assign(
-      { promotions: [V.PAWN] },
-      ChessRules.PawnSpecs
-    );
-  } */
+  static get LoseOnRepetition() {
+    return true;
+  }
 
   static get CAPTAIN() {
     return 'c';
@@ -30,6 +25,11 @@ export class ShinobiRules extends ChessRules {
     return 'l';
   }
 
+  static IsGoodFlags(flags) {
+    // Only black can castle
+    return !!flags.match(/^[a-z]{2,2}$/);
+  }
+
   static get PIECES() {
     return (
       ChessRules.PIECES
@@ -46,6 +46,16 @@ export class ShinobiRules extends ChessRules {
     return "Shinobi/" + color + V.RESERVE_PIECES[index];
   }
 
+  getFlagsFen() {
+    return this.castleFlags['b'].map(V.CoordToColumn).join("");
+  }
+
+  setFlags(fenflags) {
+    this.castleFlags = { 'b': [-1, -1] };
+    for (let i = 0; i < 2; i++)
+      this.castleFlags['b'][i] = V.ColumnToCoord(fenflags.charAt(i));
+  }
+
   static IsGoodFen(fen) {
     if (!ChessRules.IsGoodFen(fen)) return false;
     const fenParsed = V.ParseFen(fen);
@@ -63,13 +73,12 @@ export class ShinobiRules extends ChessRules {
     );
   }
 
-  // In hand initially: another captain, a ninja + a samurai,
-  // and 2 x monk, horse, lance (TODO)
+  // In hand initially: captain, ninja, samurai + 2 x monk, horse, lance.
   static GenRandInitFen(randomness) {
     const baseFen = ChessRules.GenRandInitFen(Math.min(randomness, 1));
     return (
-      baseFen.substr(0, 33) + "3CK3 " +
-      "w 0 " + baseFen.substr(38, 2) + " - 111222"
+      baseFen.substr(0, 35) + "3CK3 " +
+      "w 0 " + baseFen.substr(48, 2) + " - 111222"
     );
   }
 
@@ -96,14 +105,14 @@ export class ShinobiRules extends ChessRules {
         [V.NINJA]: reserve[1],
         [V.SAMURAI]: reserve[2],
         [V.MONK]: reserve[3],
-        [V.HORSE]: reserve[4]
+        [V.HORSE]: reserve[4],
         [V.LANCE]: reserve[5]
       }
     };
   }
 
   getColor(i, j) {
-    if (i >= V.size.x) return i == V.size.x ? "w" : "b";
+    if (i >= V.size.x) return 'w';
     return this.board[i][j].charAt(0);
   }
 
@@ -112,7 +121,6 @@ export class ShinobiRules extends ChessRules {
     return this.board[i][j].charAt(1);
   }
 
-  // Ordering on reserve pieces
   static get RESERVE_PIECES() {
     return [V.CAPTAIN, V.NINJA, V.SAMURAI, V.MONK, V.HORSE, V.LANCE];
   }
@@ -130,7 +138,7 @@ export class ShinobiRules extends ChessRules {
               new PiPo({
                 x: i,
                 y: j,
-                c: color,
+                c: 'w',
                 p: p
               })
             ],
@@ -163,7 +171,8 @@ export class ShinobiRules extends ChessRules {
     // Standard moves
     const piece = this.getPiece(x, y);
     const sq = [x, y];
-    if (ChessRules.includes(piece)) return super.getPotentialMovesFrom(sq);
+    if ([V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN].includes(piece))
+      return super.getPotentialMovesFrom(sq);
     switch (piece) {
       case V.KING: return super.getPotentialKingMoves(sq);
       case V.CAPTAIN: return this.getPotentialCaptainMoves(sq);
@@ -175,6 +184,7 @@ export class ShinobiRules extends ChessRules {
       // Unpromoted
       case V.PAWN:
         moves = super.getPotentialPawnMoves(sq);
+        break;
       case V.MONK:
         moves = this.getPotentialMonkMoves(sq);
         break;
@@ -188,42 +198,50 @@ export class ShinobiRules extends ChessRules {
     const promotionZone = (this.turn == 'w' ? [0, 1, 2] : [5, 6, 7]);
     const promotedForm = V.MapUnpromoted[piece];
     moves.forEach(m => {
-      if (promotionZone.includes(m.end.x)) move.appear[0].p = promotedForm;
+      if (promotionZone.includes(m.end.x)) m.appear[0].p = promotedForm;
     });
     return moves;
   }
 
-  getPotentialCaptainMoves([x, y]) {
-  }
-
-  // TODO: adapt...
-  getPotentialNinjaMoves(sq) {
-    return super.getSlideNJumpMoves(sq, V.steps[V.BISHOP], "oneStep");
+  getPotentialKingMoves([x, y]) {
+    if (this.getColor(x, y) == 'b') return super.getPotentialKingMoves([x, y]);
+    // Clan doesn't castle:
+    return super.getSlideNJumpMoves(
+      [x, y],
+      V.steps[V.ROOK].concat(V.steps[V.BISHOP]),
+      "oneStep"
+    );
   }
 
-  getPotentialSamuraiMoves(sq) {
+  getPotentialCaptainMoves(sq) {
     const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
     return super.getSlideNJumpMoves(sq, steps, "oneStep");
   }
 
-  getPotentialMonkMoves(sq) {
+  getPotentialNinjaMoves(sq) {
+    return (
+      super.getSlideNJumpMoves(sq, V.steps[V.BISHOP])
+      .concat(super.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep"))
+    );
+  }
+
+  getPotentialSamuraiMoves(sq) {
     return (
       super.getSlideNJumpMoves(sq, V.steps[V.ROOK])
       .concat(super.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep"))
     );
   }
 
+  getPotentialMonkMoves(sq) {
+    return super.getSlideNJumpMoves(sq, V.steps[V.BISHOP], "oneStep");
+  }
+
   getPotentialHorseMoves(sq) {
-    const steps =
-      V.steps[V.BISHOP].concat(V.steps[V.ROOK]).concat(V.steps[V.KNIGHT]);
-    return super.getSlideNJumpMoves(sq, steps, "oneStep");
+    return super.getSlideNJumpMoves(sq, [ [-2, 1], [-2, -1] ], "oneStep");
   }
 
   getPotentialLanceMoves(sq) {
-    return (
-      super.getSlideNJumpMoves(sq, V.steps[V.BISHOP])
-      .concat(super.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep"))
-    );
+    return super.getSlideNJumpMoves(sq, [ [-1, 0] ]);
   }
 
   isAttacked(sq, color) {
@@ -233,8 +251,8 @@ export class ShinobiRules extends ChessRules {
     return (
       super.isAttackedByKing(sq, 'w') ||
       this.isAttackedByCaptain(sq, 'w') ||
-      this.isAttackedByNinja(sq, 'w')
-      this.isAttackedBySamurai(sq, 'w')
+      this.isAttackedByNinja(sq, 'w') ||
+      this.isAttackedBySamurai(sq, 'w') ||
       this.isAttackedByMonk(sq, 'w') ||
       this.isAttackedByHorse(sq, 'w') ||
       this.isAttackedByLance(sq, 'w') ||
@@ -247,78 +265,91 @@ export class ShinobiRules extends ChessRules {
   isAttackedByCaptain(sq, color) {
     const steps = V.steps[V.BISHOP].concat(V.steps[V.ROOK]);
     return (
-      super.isAttackedBySlideNJump(sq, color, V.DUCHESS, steps, "oneStep")
+      super.isAttackedBySlideNJump(sq, color, V.CAPTAIN, steps, "oneStep")
     );
   }
 
   isAttackedByNinja(sq, color) {
     return (
+      super.isAttackedBySlideNJump(sq, color, V.NINJA, V.steps[V.BISHOP]) ||
       super.isAttackedBySlideNJump(
-        sq, color, V.DUCHESS, V.steps[V.BISHOP], "oneStep")
+        sq, color, V.NINJA, V.steps[V.KNIGHT], "oneStep")
     );
   }
 
   isAttackedBySamurai(sq, color) {
     return (
-      super.isAttackedBySlideNJump(sq, color, V.MORTAR, V.steps[V.ROOK]) ||
+      super.isAttackedBySlideNJump(sq, color, V.SAMURAI, V.steps[V.ROOK]) ||
       super.isAttackedBySlideNJump(
-        sq, color, V.MORTAR, V.steps[V.KNIGHT], "oneStep")
+        sq, color, V.SAMURAI, V.steps[V.KNIGHT], "oneStep")
     );
   }
 
   isAttackedByMonk(sq, color) {
-    const steps =
-      V.steps[V.BISHOP].concat(V.steps[V.ROOK]).concat(V.steps[V.KNIGHT]);
     return (
-      super.isAttackedBySlideNJump(sq, color, V.GENERAL, steps, "oneStep")
+      super.isAttackedBySlideNJump(
+        sq, color, V.MONK, V.steps[V.BISHOP], "oneStep")
     );
   }
 
   isAttackedByHorse(sq, color) {
     return (
-      super.isAttackedBySlideNJump(sq, color, V.ARCHBISHOP, V.steps[V.BISHOP])
-      ||
       super.isAttackedBySlideNJump(
-        sq, color, V.ARCHBISHOP, V.steps[V.KNIGHT], "oneStep")
+        sq, color, V.HORSE, [ [2, 1], [2, -1] ], "oneStep")
     );
   }
 
   isAttackedByLance(sq, color) {
-    return (
-      super.isAttackedBySlideNJump(sq, color, V.ARCHBISHOP, V.steps[V.BISHOP])
-      ||
-      super.isAttackedBySlideNJump(
-        sq, color, V.ARCHBISHOP, V.steps[V.KNIGHT], "oneStep")
-    );
+    return super.isAttackedBySlideNJump(sq, color, V.LANCE, [ [1, 0] ]);
   }
 
   getAllValidMoves() {
     let moves = super.getAllPotentialMoves();
-    const color = this.turn;
-    for (let i = 0; i < V.RESERVE_PIECES.length; i++) {
-      moves = moves.concat(
-        this.getReserveMoves([V.size.x + (color == "w" ? 0 : 1), i])
-      );
+    if (this.turn == 'w') {
+      for (let i = 0; i < V.RESERVE_PIECES.length; i++) {
+        moves = moves.concat(
+          this.getReserveMoves([V.size.x, i])
+        );
+      }
     }
     return this.filterValid(moves);
   }
 
   atLeastOneMove() {
-    if (!super.atLeastOneMove()) {
+    if (super.atLeastOneMove()) return true;
+    if (this.turn == 'w') {
       // Search one reserve move
       for (let i = 0; i < V.RESERVE_PIECES.length; i++) {
         let moves = this.filterValid(
-          this.getReserveMoves([V.size.x + (this.turn == "w" ? 0 : 1), i])
+          this.getReserveMoves([V.size.x, i])
         );
         if (moves.length > 0) return true;
       }
-      return false;
     }
-    return true;
+    return false;
+  }
+
+  updateCastleFlags(move, piece) {
+    // Only black can castle:
+    const firstRank = 0;
+    if (piece == V.KING && move.appear[0].c == 'b')
+      this.castleFlags['b'] = [8, 8];
+    else if (
+      move.start.x == firstRank &&
+      this.castleFlags['b'].includes(move.start.y)
+    ) {
+      const flagIdx = (move.start.y == this.castleFlags['b'][0] ? 0 : 1);
+      this.castleFlags['b'][flagIdx] = 8;
+    }
+    else if (
+      move.end.x == firstRank &&
+      this.castleFlags['b'].includes(move.end.y)
+    ) {
+      const flagIdx = (move.end.y == this.castleFlags['b'][0] ? 0 : 1);
+      this.castleFlags['b'][flagIdx] = 8;
+    }
   }
 
-  // TODO: only black can castle (see Orda)
-
   postPlay(move) {
     super.postPlay(move);
     // Skip castle:
@@ -334,21 +365,30 @@ export class ShinobiRules extends ChessRules {
     if (move.vanish.length == 0) this.reserve[color][move.appear[0].p]++;
   }
 
-  /*
   static get SEARCH_DEPTH() {
     return 2;
-  } */
+  }
+
+  getCurrentScore() {
+    const color = this.turn;
+    const nodrawResult = (color == "w" ? "0-1" : "1-0");
+    const oppLastRank = (color == 'w' ? 7 : 0);
+    if (this.kingPos[V.GetOppCol(color)][0] == oppLastRank)
+      return nodrawResult;
+    if (this.atLeastOneMove()) return "*";
+    return nodrawResult;
+  }
 
-  // TODO:
   static get VALUES() {
     return (
       Object.assign(
         {
           c: 4,
-          g: 5,
-          a: 7,
-          m: 7,
-          f: 2
+          j: 7,
+          s: 8,
+          m: 2,
+          h: 2,
+          l: 2
         },
         ChessRules.VALUES
       )
@@ -357,11 +397,10 @@ export class ShinobiRules extends ChessRules {
 
   evalPosition() {
     let evaluation = super.evalPosition();
-    // Add reserves:
+    // Add reserve:
     for (let i = 0; i < V.RESERVE_PIECES.length; i++) {
       const p = V.RESERVE_PIECES[i];
       evaluation += this.reserve["w"][p] * V.VALUES[p];
-      evaluation -= this.reserve["b"][p] * V.VALUES[p];
     }
     return evaluation;
   }
diff --git a/server/db/populate.sql b/server/db/populate.sql
index 56347720..7872515f 100644
--- a/server/db/populate.sql
+++ b/server/db/populate.sql
@@ -146,6 +146,7 @@ insert or ignore into Variants (name, description) values
   ('Selfabsorb', 'Fusion pieces (v2)'),
   ('Shako', 'Non-conformism and utopia'),
   ('Shatranj', 'Ancient rules'),
+  ('Shinobi', 'A story of invasion'),
   ('Shogi', 'Japanese Chess'),
   ('Shogun', 'General''s Chess'),
   ('Sittuyin', 'Burmese Chess'),
-- 
2.44.0