Add Monochrome Chess
authorBenjamin Auder <benjamin.auder@somewhere>
Mon, 13 Apr 2020 10:08:41 +0000 (12:08 +0200)
committerBenjamin Auder <benjamin.auder@somewhere>
Mon, 13 Apr 2020 10:08:41 +0000 (12:08 +0200)
TODO
client/src/base_rules.js
client/src/translations/en.js
client/src/translations/es.js
client/src/translations/fr.js
client/src/translations/rules/Monochrome/en.pug [new file with mode: 0644]
client/src/translations/rules/Monochrome/es.pug [new file with mode: 0644]
client/src/translations/rules/Monochrome/fr.pug [new file with mode: 0644]
client/src/variants/Monochrome.js [new file with mode: 0644]
server/db/populate.sql

diff --git a/TODO b/TODO
index b659552..d2b81ca 100644 (file)
--- a/TODO
+++ b/TODO
@@ -1,6 +1,3 @@
-// https://vchess.club/#/game/46
-// Bug 35eme coup blanc Rx(P)e2, d2 et aussi 18eme coup blanc Rd7, Pxe6
-// --> peut-être lié à prise, ou lié à getFen(), ou inMultimove pas changé car concatène à coup précédent...
 // TODO: also fix moves played on smartphone, annoying shift...
 
 Shako, also known as UniEed Chess (Jean-Louis
 // TODO: also fix moves played on smartphone, annoying shift...
 
 Shako, also known as UniEed Chess (Jean-Louis
@@ -14,27 +11,6 @@ ERNBQKBNRE on ranks 2/9, 10xP on ranks
 
 Shogi + Makruk/Thai chess --> see on Pychess
 
 
 Shogi + Makruk/Thai chess --> see on Pychess
 
-Interesting:
-Monochrome Chess (Proprietary game,
-Looney Industries; Andrew Looney, 1996).
-Usual men and array but pieces are all of the
-same colour. A man is controlled by the player
-in whose half of the board it stands. Thus after
-e4-e5, the pawn changes sides and reverses
-direction. When you capture (by definition, in
-the opponent’s half) there can be no recapture
-as the piece has changed sides. You may not
-immediately reverse an opponent’s move. The
-king has no royal powers but can castle. The
-men are allocated points and the object is to
-have the most points (in pieces captured) when
-the game ends, which is usually when the
-players agree or when one half of the board is
-empty. Values: King=10, Queen=8, Rook=5,
-Bishop=4, Knight=3, Pawn=1. A related game
-Martian Chess is described in chapter 38.
-(Proprietor’s rule sheet, Variant Chess 39)
-
 Chakart :)
 https://www.chessvariants.com/crossover.dir/koopachess.html
 
 Chakart :)
 https://www.chessvariants.com/crossover.dir/koopachess.html
 
index 734af02..a0e64bd 100644 (file)
@@ -990,7 +990,7 @@ export const ChessRules = class ChessRules {
     const color = this.turn;
     for (let i = 0; i < V.size.x; i++) {
       for (let j = 0; j < V.size.y; j++) {
     const color = this.turn;
     for (let i = 0; i < V.size.x; i++) {
       for (let j = 0; j < V.size.y; j++) {
-        if (this.getColor(i, j) == color) {
+        if (this.board[i][j] != V.EMPTY && this.getColor(i, j) == color) {
           const moves = this.getPotentialMovesFrom([i, j]);
           if (moves.length > 0) {
             for (let k = 0; k < moves.length; k++) {
           const moves = this.getPotentialMovesFrom([i, j]);
           if (moves.length > 0) {
             for (let k = 0; k < moves.length; k++) {
index c5662f8..c2f982a 100644 (file)
@@ -162,6 +162,7 @@ export const translations = {
   "64 pieces on the board": "64 pieces on the board",
   "A pawns cloud": "A pawns cloud",
   "A wizard in the corner": "A wizard in the corner",
   "64 pieces on the board": "64 pieces on the board",
   "A pawns cloud": "A pawns cloud",
   "A wizard in the corner": "A wizard in the corner",
+  "All of the same color": "All of the same color",
   "Ancient rules": "Ancient rules",
   "Attract opposite king": "Attract opposite king",
   "Balanced sliders & leapers": "Balanced sliders & leapers",
   "Ancient rules": "Ancient rules",
   "Attract opposite king": "Attract opposite king",
   "Balanced sliders & leapers": "Balanced sliders & leapers",
index 3e0d18a..e3f3d14 100644 (file)
@@ -162,6 +162,7 @@ export const translations = {
   "64 pieces on the board": "64 piezas en el tablero",
   "A pawns cloud": "Une nube de peones",
   "A wizard in the corner": "Un mago en la esquina",
   "64 pieces on the board": "64 piezas en el tablero",
   "A pawns cloud": "Une nube de peones",
   "A wizard in the corner": "Un mago en la esquina",
+  "All of the same color": "Todo el mismo color",
   "Ancient rules": "Viejas reglas",
   "Attract opposite king": "Atraer al rey contrario",
   "Balanced sliders & leapers": "Modos de desplazamiento equilibrados",
   "Ancient rules": "Viejas reglas",
   "Attract opposite king": "Atraer al rey contrario",
   "Balanced sliders & leapers": "Modos de desplazamiento equilibrados",
index c22e129..5e0663c 100644 (file)
@@ -162,6 +162,7 @@ export const translations = {
   "64 pieces on the board": "64 pièces sur l'échiquier",
   "A pawns cloud": "Une nuée de pions",
   "A wizard in the corner": "Un sorcier dans le coin",
   "64 pieces on the board": "64 pièces sur l'échiquier",
   "A pawns cloud": "Une nuée de pions",
   "A wizard in the corner": "Un sorcier dans le coin",
+  "All of the same color": "Tout de la même couleur",
   "Ancient rules": "Règles anciennes",
   "Attract opposite king": "Attirer le roi adverse",
   "Balanced sliders & leapers": "Modes de déplacement équilibrés",
   "Ancient rules": "Règles anciennes",
   "Attract opposite king": "Attirer le roi adverse",
   "Balanced sliders & leapers": "Modes de déplacement équilibrés",
diff --git a/client/src/translations/rules/Monochrome/en.pug b/client/src/translations/rules/Monochrome/en.pug
new file mode 100644 (file)
index 0000000..639f735
--- /dev/null
@@ -0,0 +1,33 @@
+p.boxed
+  | Pieces in the first half of the board are yours. Captures are mandatory.
+
+p.
+  All pieces are of the same color (black on the website), but in fact
+  pieces just have no color: you control all
+  pieces which stand in the half-board in front of you. These pieces can
+  only capture on the other half-board.
+
+p.
+  Since captures are mandatory, pieces could make some round-trips
+  around the board, changing owner at every mid-board crossing.
+  For example on the following diagram, the sequence will be 1.Rxd7 Rxd1
+  2.Rxd8 and black would win, assuming the final rank is 8th.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:3b4/3k4/8/8/8/8/3r4/3n4:
+  .diagram.diag22
+    | fen:3b4/3r4/8/8/8/8/8/3n4:
+  figcaption Before and after Rxd7
+
+p Kings have no royal status. There are no en-passant captures.
+
+h3 Source
+
+p
+  | Strongly inspired by 
+  a(href="http://www.wunderland.com/WTS/Andy/Games/monochess.html")
+    | Monochrome Chess
+  | , but I wanted a version without points counting.
+
+p Inventor: Andrew Looney (1996)
diff --git a/client/src/translations/rules/Monochrome/es.pug b/client/src/translations/rules/Monochrome/es.pug
new file mode 100644 (file)
index 0000000..3cb4262
--- /dev/null
@@ -0,0 +1,35 @@
+p.boxed
+  | Las piezas en la primera mitad del tablero de ajedrez son tuyas.
+  | Las capturas son obligatorias.
+
+p.
+  Todas las piezas son del mismo color (negro en este sitio), pero de hecho
+  las piezas simplemente no tienen color: tú controlas todas las piezas
+  ubicado en el medio tablero de ajedrez frente a ti. Estas piezas no pueden
+  capturar que en el otro medio tablero.
+
+p.
+  Como las capturas son obligatorias, las piezas podrían ir y venir
+  alrededor del tablero de ajedrez, cambiando de propietario a
+  cada paso por el centro. Por ejemplo, en el siguiente diagrama,
+  la secuencia sería 1.Rxd7 Rxd1 2.Rxd8 y las negras ganarían, suponiendo
+  que la última fila es la 8va.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:3b4/3k4/8/8/8/8/3r4/3n4:
+  .diagram.diag22
+    | fen:3b4/3r4/8/8/8/8/8/3n4:
+  figcaption Antes y después de Rxd7
+
+p Los reyes no tienen estatus real. No hay capturas en-passant.
+
+h3 Fuente
+
+p
+  | Fuertemente inspirado por el 
+  a(href="http://www.wunderland.com/WTS/Andy/Games/monochess.html")
+    | Ajedrez Monocromo
+  | , pero quería una versión sin puntos.
+
+p Inventor: Andrew Looney (1996)
diff --git a/client/src/translations/rules/Monochrome/fr.pug b/client/src/translations/rules/Monochrome/fr.pug
new file mode 100644 (file)
index 0000000..66ac955
--- /dev/null
@@ -0,0 +1,35 @@
+p.boxed
+  | Les pièces dans la première moitié de l'échiquier sont à vous.
+  | Les captures sont obligatoires.
+
+p.
+  Toutes les pièces sont de la même couleur (noire sur ce site), mais en fait
+  les pièces n'ont juste pas de couleur : vous contrôlez toutes les pièces
+  situées dans le demi-échiquier devant vous. Ces pièces ne peuvent capturer
+  que dans l'autre demi-échiquier.
+
+p.
+  Puisque les captures sont obligatoires, les pièces pourraient effectuer
+  quelques aller-retours autour de l'échiquier, chaneant de propriétaire à
+  chaque passage au centre. Par exemple sur le diagramme suivant, la
+  séquence serait 1.Rxd7 Rxd1 2.Rxd8 et les noirs gagneraient, en supposant
+  que la dernière rangée est la 8eme.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:3b4/3k4/8/8/8/8/3r4/3n4:
+  .diagram.diag22
+    | fen:3b4/3r4/8/8/8/8/8/3n4:
+  figcaption Avant et après Rxd7
+
+p Les rois n'ont pas de statut royal. Il n'y pas de prises en passant.
+
+h3 Source
+
+p
+  | Fortement inspiré par les 
+  a(href="http://www.wunderland.com/WTS/Andy/Games/monochess.html")
+    | Échecs Monochromes
+  | , mais je voulais une version sans comptage de points.
+
+p Inventeur : Andrew Looney (1996)
diff --git a/client/src/variants/Monochrome.js b/client/src/variants/Monochrome.js
new file mode 100644 (file)
index 0000000..7264775
--- /dev/null
@@ -0,0 +1,159 @@
+import { ChessRules } from "@/base_rules";
+
+export class MonochromeRules extends ChessRules {
+  static get HasEnpassant() {
+    // Pawns would be on the same side
+    return false;
+  }
+
+  static IsGoodPosition(position) {
+    if (position.length == 0) return false;
+    const rows = position.split("/");
+    if (rows.length != V.size.x) return false;
+    for (let row of rows) {
+      let sumElts = 0;
+      for (let i = 0; i < row.length; i++) {
+        if (V.PIECES.includes(row[i])) sumElts++;
+        else {
+          const num = parseInt(row[i]);
+          if (isNaN(num)) return false;
+          sumElts += num;
+        }
+      }
+      if (sumElts != V.size.y) return false;
+    }
+    return true;
+  }
+
+  canIplay(side, [x, y]) {
+    const xBounds = side == 'w' ? [4,7] : [0,3];
+    return this.turn == side && x >= xBounds[0] && x <= xBounds[1];
+  }
+
+  canTake([x1, y1], [x2, y2]) {
+    // Capture in other half-board
+    return ((x1 <= 3 && x2 >= 4) || (x1 >= 4 && x2 <= 3));
+  }
+
+  // Trim all non-capturing moves
+  static KeepCaptures(moves) {
+    return moves.filter(m => m.vanish.length == 2 && m.appear.length == 1);
+  }
+
+  getAllPotentialMoves() {
+    const xBounds = this.turn == 'w' ? [4,7] : [0,3];
+    let potentialMoves = [];
+    for (let i = xBounds[0]; i <= xBounds[1]; i++) {
+      for (let j = 0; j < V.size.y; j++) {
+        if (this.board[i][j] != V.EMPTY) {
+          Array.prototype.push.apply(
+            potentialMoves,
+            this.getPotentialMovesFrom([i, j])
+          );
+        }
+      }
+    }
+    if (potentialMoves.some(m => m.vanish.length == 2 && m.appear.length == 1))
+      return V.KeepCaptures(potentialMoves);
+    return potentialMoves;
+  }
+
+  atLeastOneMove() {
+    const xBounds = this.turn == 'w' ? [4,7] : [0,3];
+    for (let i = xBounds[0]; i <= xBounds[1]; i++) {
+      for (let j = 0; j < V.size.y; j++) {
+        if (
+          this.board[i][j] != V.EMPTY &&
+          this.getPotentialMovesFrom([i, j]).length > 0
+        ) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  // Stop at the first capture found (if any)
+  atLeastOneCapture() {
+    const xBounds = this.turn == 'w' ? [4,7] : [0,3];
+    for (let i = xBounds[0]; i <= xBounds[1]; i++) {
+      for (let j = 0; j < V.size.y; j++) {
+        if (
+          this.board[i][j] != V.EMPTY &&
+          this.getPotentialMovesFrom([i, j]).some(m =>
+            // Warning: discard castle moves
+            m.vanish.length == 2 && m.appear.length == 1)
+        ) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  getPossibleMovesFrom(sq) {
+    let moves = this.getPotentialMovesFrom(sq);
+    const captureMoves = V.KeepCaptures(moves);
+    if (captureMoves.length > 0) return captureMoves;
+    if (this.atLeastOneCapture()) return [];
+    return moves;
+  }
+
+  filterValid(moves) {
+    return moves;
+  }
+
+  isAttacked() {
+    return false;
+  }
+
+  getCheckSquares() {
+    return [];
+  }
+
+  getCurrentScore() {
+    // Is there anything in my half board?
+    const color = V.GetOppCol(this.turn);
+    const xBounds = color == 'w' ? [4,7] : [0,3];
+    let nothingHere = true;
+    outerLoop: for (let i = xBounds[0]; i <= xBounds[1]; i++) {
+      for (let j = 0; j < V.size.y; j++) {
+        if (this.board[i][j] != V.EMPTY) {
+          nothingHere = false;
+          break outerLoop;
+        }
+      }
+    }
+    if (nothingHere) return color == 'w' ? "0-1" : "1-0";
+    if (this.atLeastOneMove()) return '*';
+    return "1/2";
+  }
+
+  static GenRandInitFen(randomness) {
+    // Remove the en-passant part of the FEN
+    const fen = ChessRules.GenRandInitFen(randomness).slice(0, -2);
+    const firstSpace = fen.indexOf(' ');
+    return (
+      fen.substr(0, firstSpace).replace(/[A-Z]/g, (c) => c.toLowerCase()) +
+      fen.substr(firstSpace)
+    );
+  }
+
+  static get SEARCH_DEPTH() {
+    return 4;
+  }
+
+  evalPosition() {
+    let evaluation = 0;
+    for (let i = 0; i < 8; i++) {
+      for (let j = 0; j < V.size.y; j++) {
+        if (this.board[i][j] != V.EMPTY) {
+          const sign = (i <= 3 ? -1 : 1);
+          // I don't think taking pieces' values into account would help
+          evaluation += sign; //* V.VALUES[this.getPiece(i, j)];
+        }
+      }
+    }
+    return evaluation;
+  }
+};
index e9cf921..a085aa1 100644 (file)
@@ -49,6 +49,7 @@ insert or ignore into Variants (name, description) values
   ('Losers', 'Get strong at self-mate'),
   ('Magnetic', 'Laws of attraction'),
   ('Maxima', 'Occupy the enemy palace'),
   ('Losers', 'Get strong at self-mate'),
   ('Magnetic', 'Laws of attraction'),
   ('Maxima', 'Occupy the enemy palace'),
+  ('Monochrome', 'All of the same color'),
   ('Monster', 'White move twice'),
   ('Omega', 'A wizard in the corner'),
   ('Orda', 'Mongolian Horde'),
   ('Monster', 'White move twice'),
   ('Omega', 'A wizard in the corner'),
   ('Orda', 'Mongolian Horde'),