From 665eed903c4f294de82e7cb0ce4026b64fe64765 Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Mon, 13 Apr 2020 12:08:41 +0200 Subject: [PATCH] Add Monochrome Chess --- TODO | 24 --- client/src/base_rules.js | 2 +- client/src/translations/en.js | 1 + client/src/translations/es.js | 1 + client/src/translations/fr.js | 1 + .../src/translations/rules/Monochrome/en.pug | 33 ++++ .../src/translations/rules/Monochrome/es.pug | 35 ++++ .../src/translations/rules/Monochrome/fr.pug | 35 ++++ client/src/variants/Monochrome.js | 159 ++++++++++++++++++ server/db/populate.sql | 1 + 10 files changed, 267 insertions(+), 25 deletions(-) create mode 100644 client/src/translations/rules/Monochrome/en.pug create mode 100644 client/src/translations/rules/Monochrome/es.pug create mode 100644 client/src/translations/rules/Monochrome/fr.pug create mode 100644 client/src/variants/Monochrome.js diff --git a/TODO b/TODO index b6595522..d2b81cae 100644 --- 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 diff --git a/client/src/base_rules.js b/client/src/base_rules.js index 734af025..a0e64bd6 100644 --- a/client/src/base_rules.js +++ b/client/src/base_rules.js @@ -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++) { diff --git a/client/src/translations/en.js b/client/src/translations/en.js index c5662f80..c2f982a7 100644 --- a/client/src/translations/en.js +++ b/client/src/translations/en.js @@ -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", diff --git a/client/src/translations/es.js b/client/src/translations/es.js index 3e0d18a0..e3f3d141 100644 --- a/client/src/translations/es.js +++ b/client/src/translations/es.js @@ -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", diff --git a/client/src/translations/fr.js b/client/src/translations/fr.js index c22e129b..5e0663c0 100644 --- a/client/src/translations/fr.js +++ b/client/src/translations/fr.js @@ -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 index 00000000..639f7351 --- /dev/null +++ b/client/src/translations/rules/Monochrome/en.pug @@ -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 index 00000000..3cb4262e --- /dev/null +++ b/client/src/translations/rules/Monochrome/es.pug @@ -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 index 00000000..66ac9558 --- /dev/null +++ b/client/src/translations/rules/Monochrome/fr.pug @@ -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 index 00000000..72647752 --- /dev/null +++ b/client/src/variants/Monochrome.js @@ -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; + } +}; diff --git a/server/db/populate.sql b/server/db/populate.sql index e9cf921f..a085aa12 100644 --- a/server/db/populate.sql +++ b/server/db/populate.sql @@ -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'), -- 2.44.0