Add Hypnotic + Mesmer variants
authorBenjamin Auder <benjamin.auder@somewhere>
Sun, 10 Jan 2021 23:56:08 +0000 (00:56 +0100)
committerBenjamin Auder <benjamin.auder@somewhere>
Sun, 10 Jan 2021 23:56:08 +0000 (00:56 +0100)
19 files changed:
TODO
client/public/images/pieces/Mesmer/bm.svg [new symlink]
client/public/images/pieces/Mesmer/wm.svg [new symlink]
client/src/base_rules.js
client/src/translations/en.js
client/src/translations/es.js
client/src/translations/fr.js
client/src/translations/rules/Hypnotic/en.pug
client/src/translations/rules/Hypnotic/es.pug
client/src/translations/rules/Hypnotic/fr.pug
client/src/translations/rules/Mesmer/en.pug
client/src/translations/rules/Mesmer/es.pug
client/src/translations/rules/Mesmer/fr.pug
client/src/translations/variants/en.pug
client/src/translations/variants/es.pug
client/src/translations/variants/fr.pug
client/src/variants/Hypnotic.js [new file with mode: 0644]
client/src/variants/Mesmer.js [new file with mode: 0644]
server/db/populate.sql

diff --git a/TODO b/TODO
index a6d77bf..4707a60 100644 (file)
--- a/TODO
+++ b/TODO
@@ -3,8 +3,6 @@ Embedded rules language not updated when language is set (in Analyse, Game and P
 If new live game starts in background, "new game" notify OK but not first move.
 
 NEW VARIANTS:
 If new live game starts in background, "new game" notify OK but not first move.
 
 NEW VARIANTS:
-https://www.chessvariants.com/mvopponent.dir/hypnotic-chess.html
-https://www.chessvariants.com/mvopponent.dir/mesmer-chess.html
 https://www.reddit.com/r/TotemChess/comments/imi3v7/totem_rules/
 --> replaced by "Joker" chess --> start on bishop of our color (instead),
     moves like a Bishop + Dabbabah, can swap with a piece *on same color* (as a move) at anytime. hmm
 https://www.reddit.com/r/TotemChess/comments/imi3v7/totem_rules/
 --> replaced by "Joker" chess --> start on bishop of our color (instead),
     moves like a Bishop + Dabbabah, can swap with a piece *on same color* (as a move) at anytime. hmm
diff --git a/client/public/images/pieces/Mesmer/bm.svg b/client/public/images/pieces/Mesmer/bm.svg
new file mode 120000 (symlink)
index 0000000..000f539
--- /dev/null
@@ -0,0 +1 @@
+../Maxima/bg.svg
\ No newline at end of file
diff --git a/client/public/images/pieces/Mesmer/wm.svg b/client/public/images/pieces/Mesmer/wm.svg
new file mode 120000 (symlink)
index 0000000..436f84d
--- /dev/null
@@ -0,0 +1 @@
+../Maxima/wg.svg
\ No newline at end of file
index f1049b0..c849626 100644 (file)
@@ -163,7 +163,6 @@ export const ChessRules = class ChessRules {
 
   // Check if FEN describes a board situation correctly
   static IsGoodFen(fen) {
 
   // Check if FEN describes a board situation correctly
   static IsGoodFen(fen) {
-console.log("ddd");
     const fenParsed = V.ParseFen(fen);
     // 1) Check position
     if (!V.IsGoodPosition(fenParsed.position)) return false;
     const fenParsed = V.ParseFen(fen);
     // 1) Check position
     if (!V.IsGoodPosition(fenParsed.position)) return false;
index 41683bd..10ac4f8 100644 (file)
@@ -241,6 +241,8 @@ export const translations = {
   "Mate the knight": "Mate the knight",
   "Meet the Mammoth": "Meet the Mammoth",
   "Middle battle": "Middle battle",
   "Mate the knight": "Mate the knight",
   "Meet the Mammoth": "Meet the Mammoth",
   "Middle battle": "Middle battle",
+  "Mind control (v1)": "Mind control (v1)",
+  "Mind control (v2)": "Mind control (v2)",
   "Mongolian Horde (v1)": "Mongolian Horde (v1)",
   "Mongolian Horde (v2)": "Mongolian Horde (v2)",
   "Move like a knight (v1)": "Move like a knight (v1)",
   "Mongolian Horde (v1)": "Mongolian Horde (v1)",
   "Mongolian Horde (v2)": "Mongolian Horde (v2)",
   "Move like a knight (v1)": "Move like a knight (v1)",
index 14d67eb..7ae7098 100644 (file)
@@ -241,6 +241,8 @@ export const translations = {
   "Mate the knight": "Matar el caballo",
   "Meet the Mammoth": "Conoce al Mamut",
   "Middle battle": "Batalla media",
   "Mate the knight": "Matar el caballo",
   "Meet the Mammoth": "Conoce al Mamut",
   "Middle battle": "Batalla media",
+  "Mind control (v1)": "Control telepático(v1)",
+  "Mind control (v2)": "Control telepático(v2)",
   "Mongolian Horde (v1)": "Horda mongol (v1)",
   "Mongolian Horde (v2)": "Horda mongol (v2)",
   "Move like a knight (v1)": "Moverse como un caballo (v1)",
   "Mongolian Horde (v1)": "Horda mongol (v1)",
   "Mongolian Horde (v2)": "Horda mongol (v2)",
   "Move like a knight (v1)": "Moverse como un caballo (v1)",
index 255595e..f905ab3 100644 (file)
@@ -241,6 +241,8 @@ export const translations = {
   "Mate the knight": "Matez le cavalier",
   "Meet the Mammoth": "Rencontrez le Mammouth",
   "Middle battle": "Bataille du milieu",
   "Mate the knight": "Matez le cavalier",
   "Meet the Mammoth": "Rencontrez le Mammouth",
   "Middle battle": "Bataille du milieu",
+  "Mind control (v1)": "Contrôle télépathique (v1)",
+  "Mind control (v2)": "Contrôle télépathique (v2)",
   "Mongolian Horde (v1)": "Horde mongole (v1)",
   "Mongolian Horde (v2)": "Horde mongole (v2)",
   "Move like a knight (v1)": "Bouger comme un cavalier (v1)",
   "Mongolian Horde (v1)": "Horde mongole (v1)",
   "Mongolian Horde (v2)": "Horde mongole (v2)",
   "Move like a knight (v1)": "Bouger comme un cavalier (v1)",
index 21203ba..d326399 100644 (file)
@@ -1 +1,28 @@
-p.boxed TODO
+p.boxed
+  | You can control enemy pieces which are attacked.
+
+p.
+  Rather than moving one of his own pieces, a player may elect to move any
+  enemy piece that is attacked. The move must be a legal move for the enemy
+  piece, except that the (hypnotized) piece now captures its friends.
+
+p.
+  The hypnotized piece is temporarily frozen and thus cannot be moved by its
+  owner on the next turn.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:r1bqk1nr/pppp1ppp/2n5/1Bb1p3/4P3/5N2/PPPP1PPP/RNBQK2R:
+  .diagram.diag22
+    | fen:r1bnk1nr/pppp1ppp/8/1Bb1p3/4P3/5N2/PPPP1PPP/RNBQK2R:
+  figcaption Before and after Nxd8 (controlled by bishop on b5).
+
+h3 Source
+
+p
+  | Hypnotic Chess on 
+  a(href="https://www.chessvariants.com/mvopponent.dir/hypnotic-chess.html")
+    | chessvariants.com
+  | .
+
+p Inventor: W. D. Troyka (2003)
index 21203ba..e76cb56 100644 (file)
@@ -1 +1,29 @@
-p.boxed TODO
+p.boxed
+  | Puedes controlar las piezas enemigas qué son atacadas.
+
+p.
+  En lugar de mover una de sus piezas, un jugador puede decidir mover
+  una pieza del oponente que está atacando. La jugada debe ser legal al punto
+  de vista del adversario, con el detalle que la pieza (hipnotizada)
+  ahora captura sus amigos.
+
+p.
+  La pieza hipnotizada está temporalmente inmovilizada y, por lo tanto,
+  no puede movido por su dueño en el siguiente turno.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:r1bqk1nr/pppp1ppp/2n5/1Bb1p3/4P3/5N2/PPPP1PPP/RNBQK2R:
+  .diagram.diag22
+    | fen:r1bnk1nr/pppp1ppp/8/1Bb1p3/4P3/5N2/PPPP1PPP/RNBQK2R:
+  figcaption Antes y después de Nxd8 (controlado por el alfil en b5).
+
+h3 Fuente
+
+p
+  | Hypnotic Chess en 
+  a(href="https://www.chessvariants.com/mvopponent.dir/hypnotic-chess.html")
+    | chessvariants.com
+  | .
+
+p Inventor: W. D. Troyka (2003)
index 21203ba..378231b 100644 (file)
@@ -1 +1,29 @@
-p.boxed TODO
+p.boxed
+  | Vous pouvez contrôler les pièces ennemies qui sont attaquées.
+
+p.
+  Plutôt que de déplacer une de ses pièces, un joueur peut décider de
+  déplacer une pièce adverse qu'il attaque. Le coup doit être légal du point
+  de vue de l'adversaire, au détail près que la pièce (hypnotisée) capture
+  désormais ses amis.
+
+p.
+  La pièce hypnotisée est temporairement immobilisée et ne peut donc pas être
+  déplacée par son propriétaire au tour suivant.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:r1bqk1nr/pppp1ppp/2n5/1Bb1p3/4P3/5N2/PPPP1PPP/RNBQK2R:
+  .diagram.diag22
+    | fen:r1bnk1nr/pppp1ppp/8/1Bb1p3/4P3/5N2/PPPP1PPP/RNBQK2R:
+  figcaption Avant et après Nxd8 (contrôlé par le fou en b5).
+
+h3 Source
+
+p
+  | Hypnotic Chess sur 
+  a(href="https://www.chessvariants.com/mvopponent.dir/hypnotic-chess.html")
+    | chessvariants.com
+  | .
+
+p Inventeur : W. D. Troyka (2003)
index 21203ba..1f5b1bc 100644 (file)
@@ -1 +1,31 @@
-p.boxed TODO
+p.boxed
+  | Pieces attacked by the Mesmerist can be controlled.
+
+p.
+  A new piece &mdash; the Mesmerist &mdash; has the power of mind control,
+  and may elect to move any enemy piece which he observes.
+  The Mesmerist moves like an orthodox queen, but cannot capture (except
+  through mind control). His victim is said to be mesmerized.
+
+p The Mesmerist is represented by an upside-down queen for now.
+
+figure.diagram-container
+  .diagram
+    | fen:rnbqkbnr/pppppppp/3m4/8/8/3M4/PPPPPPPP/RNBQKBNR:
+  figcaption Deterministic initial position.
+
+p.
+  The mesmerized piece is temporarily frozen and thus cannot be moved by its
+  owner on the next turn.
+
+p The game is won by capturing the enemy King or Mesmerist.
+
+h3 Source
+
+p
+  | Mesmer Chess on 
+  a(href="https://www.chessvariants.com/mvopponent.dir/mesmer-chess.html")
+    | chessvariants.com
+  | .
+
+p Inventor: W. D. Troyka (2003)
index 21203ba..371ba39 100644 (file)
@@ -1 +1,31 @@
-p.boxed TODO
+p.boxed
+  | Las piezas atacadas por el Hipnotizador pueden controlarse.
+
+p.
+  Una nueva pieza &mdash; el Hipnotizador &mdash; puede elegir mudarse
+  una pieza enemiga que observa, porque luego controla su mente.
+  El Hipnotizador se mueve como una dama ortodoxa, pero no puede capturar
+  (excepto por telepatía). Se dice que su víctima está hipnotizada.
+
+p El Hipnotizador está representado por una dama al revés por ahora.
+
+figure.diagram-container
+  .diagram
+    | fen:rnbqkbnr/pppppppp/3m4/8/8/3M4/PPPPPPPP/RNBQKBNR:
+  figcaption Posición inicial determinista.
+
+p.
+  La pieza hipnotizada está temporalmente inmovilizada y, por lo tanto,
+  no puede ser movido por su dueño en el siguiente turno.
+
+p El juego se gana capturando al Rey o al Hipnotizador oponente.
+
+h3 Fuente
+
+p
+  | Mesmer Chess en 
+  a(href="https://www.chessvariants.com/mvopponent.dir/mesmer-chess.html")
+    | chessvariants.com
+  | .
+
+p Inventor: W. D. Troyka (2003)
index 21203ba..9c5255f 100644 (file)
@@ -1 +1,31 @@
-p.boxed TODO
+p.boxed
+  | Les pièces attaquées par l'Hypnotiseur peuvent être contrôlées.
+
+p.
+  Une nouvelle pièce &mdash; l'Hypnotiseur &mdash; peut choisir de déplacer
+  une pièce ennemie qu'il observe, car il contrôle alors son esprit.
+  L'Hypnotiseur se déplace comme une dame orthodoxe, mais ne peut pas capturer
+  (sauf par télépathie). Sa victime est dite hypnotisée.
+
+p L'Hypnotiseur est représenté par une dame inversé pour l'instant.
+
+figure.diagram-container
+  .diagram
+    | fen:rnbqkbnr/pppppppp/3m4/8/8/3M4/PPPPPPPP/RNBQKBNR:
+  figcaption Position initiale déterministe.
+
+p.
+  La pièce hypnotisée est temporairement immobilisée, et ne peut donc pas
+  être déplacée par son propriétaire au tour suivant.
+
+p La partie est gagnée en capturant le Roi ou l'Hypnotiseur adverse.
+
+h3 Source
+
+p
+  | Mesmer Chess sur 
+  a(href="https://www.chessvariants.com/mvopponent.dir/mesmer-chess.html")
+    | chessvariants.com
+  | .
+
+p Inventeur : W. D. Troyka (2003)
index 312df30..f77c4b1 100644 (file)
@@ -405,10 +405,12 @@ p.
     "Freecapture",
     "Gridolina",
     "Hamilton",
     "Freecapture",
     "Gridolina",
     "Hamilton",
+    "Hypnotic",
     "Isardam",
     "Kingsmaker",
     "Magnetic",
     "Maharajah",
     "Isardam",
     "Kingsmaker",
     "Magnetic",
     "Maharajah",
+    "Mesmer",
     "Otage",
     "Pacosako",
     "Parachute",
     "Otage",
     "Pacosako",
     "Parachute",
index 9d360c8..e3a287f 100644 (file)
@@ -416,10 +416,12 @@ p.
     "Freecapture",
     "Gridolina",
     "Hamilton",
     "Freecapture",
     "Gridolina",
     "Hamilton",
+    "Hypnotic",
     "Isardam",
     "Kingsmaker",
     "Magnetic",
     "Maharajah",
     "Isardam",
     "Kingsmaker",
     "Magnetic",
     "Maharajah",
+    "Mesmer",
     "Otage",
     "Pacosako",
     "Parachute",
     "Otage",
     "Pacosako",
     "Parachute",
index 411a205..cd1a6e4 100644 (file)
@@ -415,10 +415,12 @@ p.
     "Freecapture",
     "Gridolina",
     "Hamilton",
     "Freecapture",
     "Gridolina",
     "Hamilton",
+    "Hypnotic",
     "Isardam",
     "Kingsmaker",
     "Magnetic",
     "Maharajah",
     "Isardam",
     "Kingsmaker",
     "Magnetic",
     "Maharajah",
+    "Mesmer",
     "Otage",
     "Pacosako",
     "Parachute",
     "Otage",
     "Pacosako",
     "Parachute",
diff --git a/client/src/variants/Hypnotic.js b/client/src/variants/Hypnotic.js
new file mode 100644 (file)
index 0000000..130b127
--- /dev/null
@@ -0,0 +1,181 @@
+import { ChessRules } from "@/base_rules";
+
+export class HypnoticRules extends ChessRules {
+
+  static IsGoodFen(fen) {
+    if (!ChessRules.IsGoodFen(fen)) return false;
+    const fenParsed = V.ParseFen(fen);
+    // 5) Check arrival of last hypnotizing move (if any)
+    if (
+      !fenParsed.hSquare ||
+      (fenParsed.hSquare != "-" && !fenParsed.hSquare.match(/^[a-h][1-8]$/))
+    ) {
+      return false;
+    }
+    return true;
+  }
+
+  static ParseFen(fen) {
+    const fenParts = fen.split(" ");
+    return Object.assign(
+      { hSquare: fenParts[5] },
+      ChessRules.ParseFen(fen)
+    );
+  }
+
+  static GenRandInitFen(randomness) {
+    return ChessRules.GenRandInitFen(randomness) + " -";
+  }
+
+  setOtherVariables(fen) {
+    super.setOtherVariables(fen);
+    const parsedFen = V.ParseFen(fen);
+    this.hSquares = [
+      parsedFen.hSquare != "-"
+        ? V.SquareToCoords(parsedFen.hSquare)
+        : null
+    ];
+  }
+
+  getFen() {
+    const L = this.hSquares.length;
+    return (
+      super.getFen() + " " +
+      (!this.hSquares[L-1] ? "-" : V.CoordsToSquare(this.hSquares[L-1]))
+    );
+  }
+
+  canIplay(side) {
+    // Wrong, but sufficient approximation let's say
+    return this.turn == side;
+  }
+
+  canTake([x1, y1], [x2, y2]) {
+    const c = this.turn;
+    const c1 = this.getColor(x1, y1);
+    const c2 = this.getColor(x2, y2);
+    return (c == c1 && c1 != c2) || (c != c1 && c1 == c2);
+  }
+
+  getPotentialMovesFrom([x, y]) {
+    const L = this.hSquares.length;
+    const lh = this.hSquares[L-1];
+    if (!!lh && lh.x == x && lh.y == y) return [];
+    const c = this.getColor(x, y);
+    if (c == this.turn) return super.getPotentialMovesFrom([x, y]);
+    // Playing opponent's pieces: hypnotizing moves. Allowed?
+    if (!this.isAttacked([x, y], this.turn)) return [];
+    const moves =
+      this.getPiece(x, y) == V.KING
+        // No castling with enemy king (...yes, should eat it but...)
+        ? super.getSlideNJumpMoves(
+          [x, y], V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep")
+        : super.getPotentialMovesFrom([x, y]);
+    return moves;
+  }
+
+  getEnpassantCaptures([x, y], shiftX) {
+    const Lep = this.epSquares.length;
+    const epSquare = this.epSquares[Lep - 1]; //always at least one element
+    let enpassantMove = null;
+    const c = this.getColor(x, y);
+    if (
+      !!epSquare &&
+      epSquare.x == x + shiftX &&
+      Math.abs(epSquare.y - y) == 1 &&
+      // Next conditions to avoid capturing self hypnotized pawns:
+      this.board[x][epSquare.y] != V.EMPTY &&
+      this.getColor(x, epSquare.y) != c //TODO: probably redundant
+    ) {
+      enpassantMove = this.getBasicMove([x, y], [epSquare.x, epSquare.y]);
+      enpassantMove.vanish.push({
+        x: x,
+        y: epSquare.y,
+        p: this.board[x][epSquare.y].charAt(1),
+        c: this.getColor(x, epSquare.y)
+      });
+    }
+    return !!enpassantMove ? [enpassantMove] : [];
+  }
+
+  // TODO: avoid following code duplication, by using getColor()
+  // instead of this.turn at the beginning of 2 next methods
+  addPawnMoves([x1, y1], [x2, y2], moves, promotions) {
+    let finalPieces = [V.PAWN];
+    const color = this.getColor(x1, y1);
+    const lastRank = (color == "w" ? 0 : V.size.x - 1);
+    if (x2 == lastRank) finalPieces = V.PawnSpecs.promotions;
+    let tr = null;
+    for (let piece of finalPieces) {
+      tr = (piece != V.PAWN ? { c: color, p: piece } : null);
+      moves.push(this.getBasicMove([x1, y1], [x2, y2], tr));
+    }
+  }
+
+  getPotentialPawnMoves([x, y], promotions) {
+    const color = this.getColor(x, y);
+    const [sizeX, sizeY] = [V.size.x, V.size.y];
+    const forward = (color == 'w' ? -1 : 1);
+
+    let moves = [];
+    if (x + forward >= 0 && x + forward < sizeX) {
+      if (this.board[x + forward][y] == V.EMPTY) {
+        this.addPawnMoves([x, y], [x + forward, y], moves, promotions);
+        if (
+          ((color == 'w' && x == 6) || (color == 'b' && x == 1)) &&
+          this.board[x + 2 * forward][y] == V.EMPTY
+        ) {
+          moves.push(this.getBasicMove([x, y], [x + 2 * forward, y]));
+        }
+      }
+      for (let shiftY of [-1, 1]) {
+        if (
+          y + shiftY >= 0 && y + shiftY < sizeY &&
+          this.board[x + forward][y + shiftY] != V.EMPTY &&
+          this.canTake([x, y], [x + forward, y + shiftY])
+        ) {
+          this.addPawnMoves(
+            [x, y], [x + forward, y + shiftY],
+            moves, promotions
+          );
+        }
+      }
+    }
+    Array.prototype.push.apply(moves,
+                               this.getEnpassantCaptures([x, y], forward));
+    return moves;
+  }
+
+  postPlay(move) {
+    super.postPlay(move);
+    if (move.vanish[0].c == this.turn)
+      this.hSquares.push({ x: move.appear[0].x, y: move.appear[0].y });
+    else this.hSquares.push(null);
+    if (move.vanish.length == 2 && move.vanish[1].p == V.KING)
+      this.kingPos[move.vanish[1].c] = [-1, -1];
+  }
+  postUndo(move) {
+    super.postUndo(move);
+    this.hSquares.pop();
+    if (move.vanish.length == 2 && move.vanish[1].p == V.KING)
+      this.kingPos[move.vanish[1].c] = [move.vanish[1].x, move.vanish[1].y];
+  }
+
+  getCheckSquares() {
+    return [];
+  }
+  filterValid(moves) {
+    return moves;
+  }
+
+  getCurrentScore() {
+    const c = this.turn;
+    if (this.kingPos[c][0] < 0) return (c == 'w' ? "0-1" : "1-0");
+    return "*";
+  }
+
+  static get SEARCH_DEPTH() {
+    return 2;
+  }
+
+};
diff --git a/client/src/variants/Mesmer.js b/client/src/variants/Mesmer.js
new file mode 100644 (file)
index 0000000..ec40b48
--- /dev/null
@@ -0,0 +1,262 @@
+import { ChessRules } from "@/base_rules";
+import { Antiking2Rules } from "@/variants/Antiking2";
+
+export class MesmerRules extends ChessRules {
+
+  static IsGoodFen(fen) {
+    if (!ChessRules.IsGoodFen(fen)) return false;
+    const fenParsed = V.ParseFen(fen);
+    // 5) Check arrival of last hypnotizing move (if any)
+    if (
+      !fenParsed.hSquare ||
+      (fenParsed.hSquare != "-" && !fenParsed.hSquare.match(/^[a-h][1-8]$/))
+    ) {
+      return false;
+    }
+    return true;
+  }
+
+  static get MESMERIST() {
+    return 'm';
+  }
+
+  static get PIECES() {
+    return ChessRules.PIECES.concat([V.MESMERIST]);
+  }
+
+  getPpath(b) {
+    return (b.charAt(1) == 'm' ? "Mesmer/" : "") + b;
+  }
+
+  static ParseFen(fen) {
+    const fenParts = fen.split(" ");
+    return Object.assign(
+      { hSquare: fenParts[5] },
+      ChessRules.ParseFen(fen)
+    );
+  }
+
+  static GenRandInitFen(randomness) {
+    const antikingFen = Antiking2Rules.GenRandInitFen(randomness);
+    return antikingFen.replace('a', 'M').replace('A', 'm') + " -";
+  }
+
+  setOtherVariables(fen) {
+    super.setOtherVariables(fen);
+    const parsedFen = V.ParseFen(fen);
+    this.hSquares = [
+      parsedFen.hSquare != "-"
+        ? V.SquareToCoords(parsedFen.hSquare)
+        : null
+    ];
+  }
+
+  scanKings(fen) {
+    super.scanKings(fen);
+    // Squares of white and black mesmerist:
+    this.mesmerPos = { w: [-1, -1], b: [-1, -1] };
+    const fenRows = V.ParseFen(fen).position.split("/");
+    for (let i = 0; i < fenRows.length; i++) {
+      let k = 0;
+      for (let j = 0; j < fenRows[i].length; j++) {
+        switch (fenRows[i].charAt(j)) {
+          case "m":
+            this.mesmerPos["b"] = [i, k];
+            break;
+          case "M":
+            this.mesmerPos["w"] = [i, k];
+            break;
+          default: {
+            const num = parseInt(fenRows[i].charAt(j), 10);
+            if (!isNaN(num)) k += num - 1;
+          }
+        }
+        k++;
+      }
+    }
+  }
+
+  getFen() {
+    const L = this.hSquares.length;
+    return (
+      super.getFen() + " " +
+      (!this.hSquares[L-1] ? "-" : V.CoordsToSquare(this.hSquares[L-1]))
+    );
+  }
+
+  canIplay(side) {
+    // Wrong, but sufficient approximation let's say
+    return this.turn == side;
+  }
+
+  canTake([x1, y1], [x2, y2]) {
+    const c = this.turn;
+    const c1 = this.getColor(x1, y1);
+    const c2 = this.getColor(x2, y2);
+    return (c == c1 && c1 != c2) || (c != c1 && c1 == c2);
+  }
+
+  getPotentialMovesFrom([x, y]) {
+    const L = this.hSquares.length;
+    const lh = this.hSquares[L-1];
+    if (!!lh && lh.x == x && lh.y == y) return [];
+    const c = this.getColor(x, y);
+    const piece = this.getPiece(x, y);
+    if (c == this.turn) {
+      if (piece == V.MESMERIST) return this.getPotentialMesmeristMoves([x, y]);
+      return super.getPotentialMovesFrom([x, y]);
+    }
+    // Playing opponent's pieces: hypnotizing moves. Allowed?
+    if (piece == V.MESMERIST || !this.isAttackedByMesmerist([x, y], this.turn))
+      return [];
+    const moves =
+      piece == V.KING
+        // No castling with enemy king (...yes, should eat it but...)
+        ? super.getSlideNJumpMoves(
+          [x, y], V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep")
+        : super.getPotentialMovesFrom([x, y]);
+    return moves;
+  }
+
+  // Moves like a queen without capturing
+  getPotentialMesmeristMoves([x, y]) {
+    const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
+    let moves = [];
+    for (let step of steps) {
+      let i = x + step[0];
+      let j = 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];
+      }
+    }
+    return moves;
+  }
+
+  isAttackedByMesmerist(sq, color) {
+    return (
+      super.isAttackedBySlideNJump(
+        sq, color, V.MESMERIST, V.steps[V.ROOK].concat(V.steps[V.BISHOP]))
+    );
+  }
+
+  getEnpassantCaptures([x, y], shiftX) {
+    const Lep = this.epSquares.length;
+    const epSquare = this.epSquares[Lep - 1]; //always at least one element
+    let enpassantMove = null;
+    const c = this.getColor(x, y);
+    if (
+      !!epSquare &&
+      epSquare.x == x + shiftX &&
+      Math.abs(epSquare.y - y) == 1 &&
+      // Next conditions to avoid capturing self hypnotized pawns:
+      this.board[x][epSquare.y] != V.EMPTY &&
+      this.getColor(x, epSquare.y) != c //TODO: probably redundant
+    ) {
+      enpassantMove = this.getBasicMove([x, y], [epSquare.x, epSquare.y]);
+      enpassantMove.vanish.push({
+        x: x,
+        y: epSquare.y,
+        p: this.board[x][epSquare.y].charAt(1),
+        c: this.getColor(x, epSquare.y)
+      });
+    }
+    return !!enpassantMove ? [enpassantMove] : [];
+  }
+
+  // TODO: avoid following code duplication, by using getColor()
+  // instead of this.turn at the beginning of 2 next methods
+  addPawnMoves([x1, y1], [x2, y2], moves, promotions) {
+    let finalPieces = [V.PAWN];
+    const color = this.getColor(x1, y1);
+    const lastRank = (color == "w" ? 0 : V.size.x - 1);
+    if (x2 == lastRank) finalPieces = V.PawnSpecs.promotions;
+    let tr = null;
+    for (let piece of finalPieces) {
+      tr = (piece != V.PAWN ? { c: color, p: piece } : null);
+      moves.push(this.getBasicMove([x1, y1], [x2, y2], tr));
+    }
+  }
+
+  getPotentialPawnMoves([x, y], promotions) {
+    const color = this.getColor(x, y);
+    const [sizeX, sizeY] = [V.size.x, V.size.y];
+    const forward = (color == 'w' ? -1 : 1);
+
+    let moves = [];
+    if (x + forward >= 0 && x + forward < sizeX) {
+      if (this.board[x + forward][y] == V.EMPTY) {
+        this.addPawnMoves([x, y], [x + forward, y], moves, promotions);
+        if (
+          ((color == 'w' && x == 6) || (color == 'b' && x == 1)) &&
+          this.board[x + 2 * forward][y] == V.EMPTY
+        ) {
+          moves.push(this.getBasicMove([x, y], [x + 2 * forward, y]));
+        }
+      }
+      for (let shiftY of [-1, 1]) {
+        if (
+          y + shiftY >= 0 && y + shiftY < sizeY &&
+          this.board[x + forward][y + shiftY] != V.EMPTY &&
+          this.canTake([x, y], [x + forward, y + shiftY])
+        ) {
+          this.addPawnMoves(
+            [x, y], [x + forward, y + shiftY],
+            moves, promotions
+          );
+        }
+      }
+    }
+    Array.prototype.push.apply(moves,
+                               this.getEnpassantCaptures([x, y], forward));
+    return moves;
+  }
+
+  postPlay(move) {
+    super.postPlay(move);
+    if (move.vanish[0].p == V.MESMERIST)
+      this.mesmerPos[move.vanish[0].c] = [move.appear[0].x, move.appear[0].y];
+    if (move.vanish[0].c == this.turn)
+      this.hSquares.push({ x: move.appear[0].x, y: move.appear[0].y });
+    else this.hSquares.push(null);
+    if (move.vanish.length == 2) {
+      if (move.vanish[1].p == V.KING)
+        this.kingPos[move.vanish[1].c] = [-1, -1];
+      else if (move.vanish[1].p == V.MESMERIST)
+        this.mesmerPos[move.vanish[1].c] = [-1, -1]
+    }
+  }
+  postUndo(move) {
+    super.postUndo(move);
+    if (move.vanish[0].p == V.MESMERIST)
+      this.mesmerPos[move.vanish[0].c] = [move.vanish[0].x, move.vanish[0].y];
+    this.hSquares.pop();
+    if (move.vanish.length == 2) {
+      const v = move.vanish[1];
+      if (v.p == V.KING)
+        this.kingPos[v.c] = [v.x, v.y];
+      else if (v.p == V.MESMERIST)
+        this.mesmerPos[v.c] = [v.x, v.y];
+    }
+  }
+
+  getCheckSquares() {
+    return [];
+  }
+  filterValid(moves) {
+    return moves;
+  }
+
+  getCurrentScore() {
+    const c = this.turn;
+    if (this.kingPos[c][0] < 0) return (c == 'w' ? "0-1" : "1-0");
+    if (this.mesmerPos[c][0] < 0) return (c == 'w' ? "0-1" : "1-0");
+    return "*";
+  }
+
+  static get SEARCH_DEPTH() {
+    return 2;
+  }
+
+};
index 3c70353..be0f09b 100644 (file)
@@ -69,6 +69,7 @@ insert or ignore into Variants (name, description) values
   ('Hamilton', 'Walk on a graph'),
   ('Hoppelpoppel', 'Knibis and Bisknis'),
   ('Horde', 'A pawns cloud'),
   ('Hamilton', 'Walk on a graph'),
   ('Hoppelpoppel', 'Knibis and Bisknis'),
   ('Horde', 'A pawns cloud'),
+  ('Hypnotic', 'Mind control (v1)'),
   ('Interweave', 'Interweaved colorbound teams'),
   ('Isardam', 'No paralyzed pieces'),
   ('Janggi', 'Korean Chess'),
   ('Interweave', 'Interweaved colorbound teams'),
   ('Isardam', 'No paralyzed pieces'),
   ('Janggi', 'Korean Chess'),
@@ -88,6 +89,7 @@ insert or ignore into Variants (name, description) values
   ('Makpong', 'Thai Chess (v2)'),
   ('Makruk', 'Thai Chess (v1)'),
   ('Maxima', 'Occupy the enemy palace'),
   ('Makpong', 'Thai Chess (v2)'),
   ('Makruk', 'Thai Chess (v1)'),
   ('Maxima', 'Occupy the enemy palace'),
+  ('Mesmer', 'Mind control (v2)'),
   ('Minishogi', 'Shogi 5 x 5'),
   ('Minixiangqi', 'Xiangqi 7 x 7'),
   ('Monochrome', 'All of the same color'),
   ('Minishogi', 'Shogi 5 x 5'),
   ('Minixiangqi', 'Xiangqi 7 x 7'),
   ('Monochrome', 'All of the same color'),