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
@@ -14,27 +11,6 @@ ERNBQKBNRE on ranks 2/9, 10xP on ranks
 
 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
 
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++) {
-        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++) {
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",
+  "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",
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",
+  "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",
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",
+  "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",
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'),
+  ('Monochrome', 'All of the same color'),
   ('Monster', 'White move twice'),
   ('Omega', 'A wizard in the corner'),
   ('Orda', 'Mongolian Horde'),