From 305ede7ec3753fc669b7c86af5b5c5b2fc78a164 Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Sun, 22 Mar 2020 04:10:46 +0100 Subject: [PATCH] First draft of S-chess variant + a few fixes --- TODO | 2 - client/public/images/pieces/Schess/be.svg | 177 +++++++++++ client/public/images/pieces/Schess/bh.svg | 118 ++++++++ client/public/images/pieces/Schess/we.svg | 201 +++++++++++++ client/public/images/pieces/Schess/wh.svg | 157 ++++++++++ client/src/base_rules.js | 8 +- client/src/translations/en.js | 1 + client/src/translations/es.js | 1 + client/src/translations/fr.js | 1 + client/src/translations/rules/Ball/en.pug | 4 +- client/src/translations/rules/Ball/es.pug | 4 +- client/src/translations/rules/Ball/fr.pug | 4 +- client/src/translations/rules/Schess/en.pug | 40 +++ client/src/translations/rules/Schess/es.pug | 42 +++ client/src/translations/rules/Schess/fr.pug | 41 +++ client/src/variants/Ball.js | 2 +- client/src/variants/Cannibal.js | 15 + client/src/variants/Checkered.js | 18 +- client/src/variants/Schess.js | 308 ++++++++++++++++++++ client/src/variants/Twokings.js | 46 ++- client/src/views/Hall.vue | 1 + server/db/populate.sql | 1 + 22 files changed, 1169 insertions(+), 23 deletions(-) create mode 100644 client/public/images/pieces/Schess/be.svg create mode 100644 client/public/images/pieces/Schess/bh.svg create mode 100644 client/public/images/pieces/Schess/we.svg create mode 100644 client/public/images/pieces/Schess/wh.svg create mode 100644 client/src/translations/rules/Schess/en.pug create mode 100644 client/src/translations/rules/Schess/es.pug create mode 100644 client/src/translations/rules/Schess/fr.pug create mode 100644 client/src/variants/Schess.js diff --git a/TODO b/TODO index 4a638435..1a566ad6 100644 --- a/TODO +++ b/TODO @@ -2,8 +2,6 @@ Landing pieces from empty board: https://www.chessvariants.com/diffsetup.dir/unachess.html -S-chess https://en.wikipedia.org/wiki/Seirawan_chess - Generator variant, called "Matrix" ? Peces on first rank never move but generate new pieces. Pawn don't generate. A generator captured and replaced by a similar piece does not generate. diff --git a/client/public/images/pieces/Schess/be.svg b/client/public/images/pieces/Schess/be.svg new file mode 100644 index 00000000..5868980b --- /dev/null +++ b/client/public/images/pieces/Schess/be.svg @@ -0,0 +1,177 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Schess/bh.svg b/client/public/images/pieces/Schess/bh.svg new file mode 100644 index 00000000..da180149 --- /dev/null +++ b/client/public/images/pieces/Schess/bh.svg @@ -0,0 +1,118 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Schess/we.svg b/client/public/images/pieces/Schess/we.svg new file mode 100644 index 00000000..05f8ffa8 --- /dev/null +++ b/client/public/images/pieces/Schess/we.svg @@ -0,0 +1,201 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Schess/wh.svg b/client/public/images/pieces/Schess/wh.svg new file mode 100644 index 00000000..2b8b5e55 --- /dev/null +++ b/client/public/images/pieces/Schess/wh.svg @@ -0,0 +1,157 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/src/base_rules.js b/client/src/base_rules.js index f51e8d01..52ec2c54 100644 --- a/client/src/base_rules.js +++ b/client/src/base_rules.js @@ -191,9 +191,9 @@ export const ChessRules = class ChessRules { return V.CoordToColumn(coords.y) + (V.size.x - coords.x); } - // Path to pieces + // Path to pieces (standard ones in pieces/ folder) getPpath(b) { - return b; //usual pieces in pieces/ folder + return b; } // Path to promotion pieces (usually the same) @@ -1101,7 +1101,9 @@ export const ChessRules = class ChessRules { ) { const flagIdx = (move.start.y == this.castleFlags[c][0] ? 0 : 1); this.castleFlags[c][flagIdx] = V.size.y; - } else if ( + } + // NOTE: not "else if" because a rook could take an opposing rook + if ( move.end.x == oppFirstRank && //we took opponent rook? this.castleFlags[oppCol].includes(move.end.y) ) { diff --git a/client/src/translations/en.js b/client/src/translations/en.js index 90d7daf5..f5139ce9 100644 --- a/client/src/translations/en.js +++ b/client/src/translations/en.js @@ -193,6 +193,7 @@ export const translations = { "Reverse captures": "Reverse captures", "Run forward": "Run forward", "Score a goal": "Score a goal", + "Seirawan-Harper Chess": "Seirawan-Harper Chess", "Shared pieces": "Shared pieces", "Shoot pieces": "Shoot pieces", "Squares disappear": "Squares disappear", diff --git a/client/src/translations/es.js b/client/src/translations/es.js index 6894fcab..57e85b70 100644 --- a/client/src/translations/es.js +++ b/client/src/translations/es.js @@ -193,6 +193,7 @@ export const translations = { "Reverse captures": "Capturas invertidas", "Run forward": "Correr hacia adelante", "Score a goal": "Marcar una meta", + "Seirawan-Harper Chess": "Ajedrez Seirawan-Harper", "Shared pieces": "Piezas compartidas", "Shoot pieces": "Tirar de las piezas", "Squares disappear": "Las casillas desaparecen", diff --git a/client/src/translations/fr.js b/client/src/translations/fr.js index 02b168de..dc3329db 100644 --- a/client/src/translations/fr.js +++ b/client/src/translations/fr.js @@ -193,6 +193,7 @@ export const translations = { "Reverse captures": "Captures inversées", "Run forward": "Courir vers l'avant", "Score a goal": "Marquer un but", + "Seirawan-Harper Chess": "Échecs Seirawan-Harper", "Shared pieces": "Pièces partagées", "Shoot pieces": "Tirez sur les pièces", "Squares disappear": "Les cases disparaissent", diff --git a/client/src/translations/rules/Ball/en.pug b/client/src/translations/rules/Ball/en.pug index 475a1609..49005d8a 100644 --- a/client/src/translations/rules/Ball/en.pug +++ b/client/src/translations/rules/Ball/en.pug @@ -14,9 +14,9 @@ ul figure.diagram-container .diagram.diag12 - | fen:rnbqkwbnr/ppppp1ppp/9/5p3/4a4/9/2N3N2/PPPPPPPPP/R1BQKWB1R: + | fen:rnbqkwnbr/ppppp1ppp/9/5p3/4a4/9/2N4N1/PPPPPPPPP/R1BQKW1BR: .diagram.diag22 - | fen:rnbqkwbnr/ppppp1ppp/9/9/4s4/9/2N3N2/PPPPPPPPP/R1BQKWB1R: + | fen:rnbqkwnbr/ppppp1ppp/9/9/4s4/9/2N4N1/PPPPPPPPP/R1BQKW1BR: figcaption Left: before ...fxe5 (taking ball). Right: after ...fxe5. p. diff --git a/client/src/translations/rules/Ball/es.pug b/client/src/translations/rules/Ball/es.pug index 68940c7f..42922c47 100644 --- a/client/src/translations/rules/Ball/es.pug +++ b/client/src/translations/rules/Ball/es.pug @@ -14,9 +14,9 @@ ul figure.diagram-container .diagram.diag12 - | fen:rnbqkwbnr/ppppp1ppp/9/5p3/4a4/9/2N3N2/PPPPPPPPPP/R1BQKWB1R: + | fen:rnbqkwnbr/ppppp1ppp/9/5p3/4a4/9/2N4N1/PPPPPPPPPP/R1BQKW1BR: .diagram.diag22 - | fen:rnbqkwbnr/ppppp1ppp/9/9/4s4/9/2N3N2/PPPPPPPPPP/R1BQKWB1R: + | fen:rnbqkwnbr/ppppp1ppp/9/9/4s4/9/2N4N1/PPPPPPPPPP/R1BQKW1BR: figcaption. Izquierda: antes de ...fxe5 (tomando la pelota). Derecha: después de ...fxe5. diff --git a/client/src/translations/rules/Ball/fr.pug b/client/src/translations/rules/Ball/fr.pug index 9fae0806..070021d2 100644 --- a/client/src/translations/rules/Ball/fr.pug +++ b/client/src/translations/rules/Ball/fr.pug @@ -14,9 +14,9 @@ ul figure.diagram-container .diagram.diag12 - | fen:rnbqkwbnr/ppppp1ppp/9/5p3/4a4/9/2N3N2/PPPPPPPPP/R1BQKWB1R: + | fen:rnbqkwnbr/ppppp1ppp/9/5p3/4a4/9/2N4N1/PPPPPPPPP/R1BQKW1BR: .diagram.diag22 - | fen:rnbqkwbnr/ppppp1ppp/9/9/4s4/9/2N3N2/PPPPPPPPP/R1BQKWB1R: + | fen:rnbqkwnbr/ppppp1ppp/9/9/4s4/9/2N4N1/PPPPPPPPP/R1BQKW1BR: figcaption Gauche : avant ...fxe5 (prenant le ballon). Droite : après ...fxe5. p. diff --git a/client/src/translations/rules/Schess/en.pug b/client/src/translations/rules/Schess/en.pug new file mode 100644 index 00000000..de4435a6 --- /dev/null +++ b/client/src/translations/rules/Schess/en.pug @@ -0,0 +1,40 @@ +p.boxed + | You have two new pieces in a pocket, which can appear after any + | first piece movement. + +p. + When a piece is moved for the first time, it is possible to place a new + piece at its initial square. + You have two pieces that can enter the game like this: +ul + li The Hawk (H): moving like a bishop + knight. + li The Elephant (E): moving like a rook + knight. + +figure.diagram-container + .diagram + | fen:rnbqkbnr/pppppppp/8/8/8/2N5/PPPPPPPP/REBQKBNR: + figcaption. + Position after 1.Nc3/E. White moved his knight from b1 to c3 and placed + the elephant on b1. + +p. + After castling, a new piece can be put at the initial king square + (if not occupied), or at the initial rook square - but not both. + +p. + If a move let the king under check, then it is forbidden, + even if placing a pocket piece would have covered the check. + +h3 Source + +p + | Some explanations about the rules and a link to buy a physical S-Chess set + | on the + a(href="http://www.seirawanchess.com/") Seirawan Chess page + | . This variant is playable on + a(href="https://www.pychess.org/variant/seirawan") pychess-variants + |  with an engine available for analysis. See also the + a(href="https://en.wikipedia.org/wiki/Seirawan_chess") Wikipedia page + | . + +p Inventors: Yasser Seirawan and Bruce Harper (2008) diff --git a/client/src/translations/rules/Schess/es.pug b/client/src/translations/rules/Schess/es.pug new file mode 100644 index 00000000..ecac438a --- /dev/null +++ b/client/src/translations/rules/Schess/es.pug @@ -0,0 +1,42 @@ +p.boxed + | Tienes dos piezas en tu bolsillo, que pueden aparecer después + | cualquier movimiento inicial de una pieza. + +p. + Cuando una pieza se mueve por la primera vez, una pieza nueva + se puede colocar en su casilla inicial. + Tienes dos piezas que también pueden ingresar al juego: +ul + li El Aguila (H): se mueve como un alfil + caballo. + li El Elefante (E): se mueve como una torre + caballo. + +figure.diagram-container + .diagram + | fen:rnbqkbnr/pppppppp/8/8/8/2N5/PPPPPPPP/REBQKBNR: + figcaption. + Posición después de 1.Nc3/E. Las blancas mueven su caballo de b1 a c3 + y colocar el elefante en b1. + +p. + Después de haberse enrocado, se puede colocar una nueva pieza en la + casilla inicial del rey (si no está ocupado), o en el espacio inicial de + la torre, pero no en ambos. + +p. + Si un movimiento deja al rey en jaque, entonces está prohibido, + incluso si colocar una nueva pieza hubiera sido el jaque. + +h3 Fuente + +p + | Algunas explicaciones sobre las reglas, así como un enlace para comprar + | un conjunto de piezas de S-Chess en el + a(href="http://www.seirawanchess.com/") página Seirawan Chess + | . Esta variante se puede jugar en + a(href="https://www.pychess.org/variant/seirawan") pychess-variantes + |  con un módulo disponible para análisis. Ver también la + a(href= "https://www.pousseurdebois.fr/variantes-du-jeu-dechecs/echecsseirawan/") + a(href="https://en.wikipedia.org/wiki/Seirawan_chess") página Wikipedia + | . + +p Inventores: Yasser Seirawan y Bruce Harper (2008) diff --git a/client/src/translations/rules/Schess/fr.pug b/client/src/translations/rules/Schess/fr.pug new file mode 100644 index 00000000..ed55d9f5 --- /dev/null +++ b/client/src/translations/rules/Schess/fr.pug @@ -0,0 +1,41 @@ +p.boxed + | Vous avez deux pièces en poche, qui peuvent apparaitre après + | n'importe quel déplacement initial d'une pièce. + +p. + Quand une pièce est déplacée pour la première fois, une nouvelle pièce + peut être placée sur sa case de départ. + Vous avez deux pièces pouvant entrer dans le jeu ainsi : +ul + li L'Aigle (H) : se déplace comme un fou + cavalier. + li L'Éléphant (E) : se déplace comme une tour + cavalier. + +figure.diagram-container + .diagram + | fen:rnbqkbnr/pppppppp/8/8/8/2N5/PPPPPPPP/REBQKBNR: + figcaption. + Position après 1.Nc3/E. Les blancs déplacent leur cavalier de b1 à c3 + et placent l'éléphant en b1. + +p. + Après avoir roqué, une nouvelle pièce peut être placée sur la case + initiale du roi (si elle n'est pas occupée), ou sur la case initiale de la + tour - mais pas sur les deux. + +p. + Si un coup laisse le roi en échec, alors il est interdit, + même si le placement d'une nouvelle pièce aurait paré l'échec. + +h3 Source + +p + | Quelques explications sur les règles ainsi qu'un lien pour acheter + | un jeu de pièces de S-Chess sur la + a(href="http://www.seirawanchess.com/") page Seirawan Chess + | . Cette variante est jouable sur + a(href="https://www.pychess.org/variant/seirawan") pychess-variants + |  avec un module disponible pour l'analyse. Voir aussi + a(href="https://www.pousseurdebois.fr/variantes-du-jeu-dechecs/echecsseirawan/") + | cette page. + +p Inventeurs : Yasser Seirawan et Bruce Harper (2008) diff --git a/client/src/variants/Ball.js b/client/src/variants/Ball.js index cf50a809..5a23e9f8 100644 --- a/client/src/variants/Ball.js +++ b/client/src/variants/Ball.js @@ -129,7 +129,7 @@ export class BallRules extends ChessRules { static GenRandInitFen(randomness) { if (randomness == 0) - return "rnbqkwbnr/ppppppppp/9/9/4a4/9/9/PPPPPPPPP/RNBQKWBNR w 0 -"; + return "rnbqkwnbr/ppppppppp/9/9/4a4/9/9/PPPPPPPPP/RNBQKWNBR w 0 -"; let pieces = { w: new Array(9), b: new Array(9) }; for (let c of ["w", "b"]) { diff --git a/client/src/variants/Cannibal.js b/client/src/variants/Cannibal.js index a08f1975..b66ac382 100644 --- a/client/src/variants/Cannibal.js +++ b/client/src/variants/Cannibal.js @@ -234,4 +234,19 @@ export class CannibalRules extends ChessRules { static get SEARCH_DEPTH() { return 4; } + + getNotation(move) { + let notation = super.getNotation(move); + const lastRank = (move.appear[0].c == "w" ? 0 : 7); + if ( + move.end.x != lastRank && + this.getPiece(move.start.x, move.start.y) == V.PAWN && + move.vanish.length == 2 && + move.appear[0].p != V.PAWN + ) { + // Fix "promotion" (transform indicator) from base_rules notation + notation = notation.slice(0, -2); + } + return notation; + } }; diff --git a/client/src/variants/Checkered.js b/client/src/variants/Checkered.js index 882a32ac..799c3e54 100644 --- a/client/src/variants/Checkered.js +++ b/client/src/variants/Checkered.js @@ -71,8 +71,8 @@ export class CheckeredRules extends ChessRules { setFlags(fenflags) { super.setFlags(fenflags); //castleFlags this.pawnFlags = { - w: [...Array(8).fill(true)], //pawns can move 2 squares? - b: [...Array(8).fill(true)] + w: [...Array(8)], //pawns can move 2 squares? + b: [...Array(8)] }; const flags = fenflags.substr(4); //skip first 4 letters, for castle for (let c of ["w", "b"]) { @@ -402,19 +402,19 @@ export class CheckeredRules extends ChessRules { getFen() { const L = this.cmoves.length; - const cmoveFen = !this.cmoves[L - 1] - ? "-" - : ChessRules.CoordsToSquare(this.cmoves[L - 1].start) + - ChessRules.CoordsToSquare(this.cmoves[L - 1].end); + const cmoveFen = + !this.cmoves[L - 1] + ? "-" + : ChessRules.CoordsToSquare(this.cmoves[L - 1].start) + + ChessRules.CoordsToSquare(this.cmoves[L - 1].end); return super.getFen() + " " + cmoveFen; } getFlagsFen() { let fen = super.getFlagsFen(); // Add pawns flags - for (let c of ["w", "b"]) { - for (let i = 0; i < 8; i++) fen += this.pawnFlags[c][i] ? "1" : "0"; - } + for (let c of ["w", "b"]) + for (let i = 0; i < 8; i++) fen += (this.pawnFlags[c][i] ? "1" : "0"); return fen; } diff --git a/client/src/variants/Schess.js b/client/src/variants/Schess.js new file mode 100644 index 00000000..7cbb47a1 --- /dev/null +++ b/client/src/variants/Schess.js @@ -0,0 +1,308 @@ +import { ChessRules, PiPo } from "@/base_rules"; + +export class SchessRules extends ChessRules { + static get PawnSpecs() { + return Object.assign( + {}, + ChessRules.PawnSpecs, + { + promotions: + ChessRules.PawnSpecs.promotions.concat([V.HAWK, V.ELEPHANT]) + } + ); + } + + static get HAWK() { + return 'h'; + } + + static get ELEPHANT() { + return 'e'; + } + + static get PIECES() { + return ChessRules.PIECES.concat([V.HAWK, V.ELEPHANT]); + } + + getPpath(b) { + if ([V.HAWK, V.ELEPHANT].includes(b[1])) return "Schess/" + b; + return b; + } + + // TODO: maybe changes could be done to this method to show "empty" + // instead of a piece to not use a pocket piece... +// getPPpath(b) { } + + static IsGoodFen(fen) { + if (!ChessRules.IsGoodFen(fen)) return false; + const fenParsed = V.ParseFen(fen); + // Check pocket state + if (!fenParsed.pocket || !fenParsed.pocket.match(/^[0-1]{4,4}$/)) + return false; + return true; + } + + static IsGoodFlags(flags) { + // 4 for castle + 16 for generators + return !!flags.match(/^[a-z]{4,4}[01]{16,16}$/); + } + + setFlags(fenflags) { + super.setFlags(fenflags); //castleFlags + this.pieceFlags = { + w: [...Array(8)], //pawns can move 2 squares? + b: [...Array(8)] + }; + const flags = fenflags.substr(4); //skip first 4 letters, for castle + for (let c of ["w", "b"]) { + for (let i = 0; i < 8; i++) + this.pieceFlags[c][i] = flags.charAt((c == "w" ? 0 : 8) + i) == "1"; + } + } + + aggregateFlags() { + return [this.castleFlags, this.pieceFlags]; + } + + disaggregateFlags(flags) { + this.castleFlags = flags[0]; + this.pieceFlags = flags[1]; + } + + static ParseFen(fen) { + const fenParts = fen.split(" "); + return Object.assign( + ChessRules.ParseFen(fen), + { pocket: fenParts[5] } + ); + } + + static GenRandInitFen(randomness) { + return ( + ChessRules.GenRandInitFen(randomness).slice(0, -2) + + // Add pieceFlags + pocket + "1111111111111111 - 1111" + ); + } + + getFen() { + return ( + super.getFen() + " " + + this.getPocketFen() + ); + } + + getFenForRepeat() { + return ( + super.getFenForRepeat() + "_" + + this.getPocketFen() + ); + } + + getFlagsFen() { + let fen = super.getFlagsFen(); + // Add pieces flags + for (let c of ["w", "b"]) + for (let i = 0; i < 8; i++) fen += (this.pieceFlags[c][i] ? "1" : "0"); + return fen; + } + + getPocketFen() { + let res = ""; + for (let c of ["w", "b"]) + res += this.pocket[c][V.HAWK] + this.pocket[c][V.ELEPHANT]; + return res; + } + + setOtherVariables(fen) { + super.setOtherVariables(fen); + const fenParsed = V.ParseFen(fen); + this.pocket = { + "w": { + h: parseInt(fenParsed.pocket[0]), + e: parseInt(fenParsed.pocket[1]) + }, + "b": { + h: parseInt(fenParsed.pocket[2]), + e: parseInt(fenParsed.pocket[3]) + } + }; + } + + getPotentialMovesFrom([x, y]) { + let moves = undefined; + switch (this.getPiece(x, y)) { + case V.HAWK: + moves = this.getPotentialHawkMoves([x, y]); + break; + case V.ELEPHANT: + moves = this.getPotentialElephantMoves([x, y]); + break; + default: + moves = super.getPotentialMovesFrom([x, y]); + } + // Post-processing: add choices for hawk and elephant, + // except for moves letting the king under check. + const color = this.turn; + if (Object.values(this.pocket[color]).some(v => v > 0)) { + const firstRank = (color == "w" ? 7 : 0); + let pocketMoves = []; + moves.forEach(m => { + let inCheckAfter = false; + this.play(m); + if (this.underCheck(color)) inCheckAfter = true; + this.undo(m); + if (!inCheckAfter) { + for (let pp of ['h', 'e']) { + if (this.pocket[color][pp] > 0) { + if ( + m.start.x == firstRank && + this.pieceFlags[color][m.start.y] && + ( + m.appear.length == 1 || + // Special castle case: is initial king square free? + ![m.appear[0].y, m.appear[1].y].includes(m.vanish[0].y) + ) + ) { + let pMove = JSON.parse(JSON.stringify(m)); + // NOTE: unshift instead of push, for choices presentation + pMove.appear.unshift(new PiPo({ + p: pp, + c: color, + x: x, + y: y + })); + pocketMoves.push(pMove); + } + if ( + m.appear.length == 2 && + ![m.appear[0].y, m.appear[1].y].includes(m.vanish[1].y) + ) { + // Special castle case: rook flag was necessarily on + let pMove = JSON.parse(JSON.stringify(m)); + pMove.appear.unshift(new PiPo({ + p: pp, + c: color, + x: m.vanish[1].x, + y: m.vanish[1].y + })); + pocketMoves.push(pMove); + } + } + } + } + }); + // NOTE: the order matter, for presentation on screen + moves = moves.concat(pocketMoves); + } + return moves; + } + + getPotentialHawkMoves(sq) { + return this.getSlideNJumpMoves( + sq, + V.steps[V.BISHOP].concat(V.steps[V.KNIGHT]) + ); + } + + getPotentialElephantMoves(sq) { + return this.getSlideNJumpMoves( + sq, + V.steps[V.ROOK].concat(V.steps[V.KNIGHT]) + ); + } + + isAttacked(sq, color) { + return ( + super.isAttacked(sq, color) || + this.isAttackedByHawk(sq, color) || + this.isAttackedByElephant(sq, color) + ); + } + + isAttackedByHawk(sq, color) { + return this.isAttackedBySlideNJump( + sq, + color, + V.HAWK, + V.steps[V.BISHOP].concat(V.steps[V.KNIGHT]) + ); + } + + isAttackedByElephant(sq, color) { + return this.isAttackedBySlideNJump( + sq, + color, + V.ELEPHANT, + V.steps[V.ROOK].concat(V.steps[V.KNIGHT]) + ); + } + + prePlay(move) { + super.prePlay(move); + if (move.appear.length >= 2) { + if ([V.HAWK, V.ELEPHANT].includes(move.appear[0].p)) { + // A pocket piece is used + const color = this.turn; + this.pocket[color][move.appear[0].p] = 0; + } + } + } + + postPlay(move) { + super.postPlay(move); + const color = move.vanish[0].c; + const oppCol = V.GetOppCol(color); + const firstRank = (color == 'w' ? 7 : 0); + const oppFirstRank = 7 - firstRank; + // Does this move turn off a piece init square flag? + if (move.start.x == firstRank) { + if (this.pieceFlags[color][move.start.y]) + this.pieceFlags[color][move.start.y] = false; + // Special castle case: + if (move.appear.length >= 2) { + const L = move.appear.length; + if (move.appear[L-1].p == V.ROOK) + this.pieceFlags[color][move.vanish[1].y] = false; + } + } + if (move.end.x == oppFirstRank && this.pieceFlags[oppCol][move.end.y]) + this.pieceFlags[oppCol][move.end.y] = false; + } + + postUndo(move) { + super.postUndo(move); + if (move.appear.length >= 2) { + if ([V.HAWK, V.ELEPHANT].includes(move.appear[0].p)) { + // A pocket piece was used + const color = this.turn; + this.pocket[color][move.appear[0].p] = 1; + } + } + } + + static get SEARCH_DEPTH() { + return 2; + } + + static get VALUES() { + return Object.assign( + {}, + ChessRules.VALUES, + { 'h': 5, 'e': 7 } + ); + } + + getNotation(move) { + if ( + move.appear.length >= 2 && + [V.HAWK, V.ELEPHANT].includes(move.appear[0].p) + ) { + const suffix = "/" + move.appear[0].p.toUpperCase(); + let cmove = JSON.parse(JSON.stringify(move)); + cmove.appear.shift(); + return super.getNotation(cmove) + suffix; + } + return super.getNotation(move); + } +}; diff --git a/client/src/variants/Twokings.js b/client/src/variants/Twokings.js index 9e82a245..ce30d653 100644 --- a/client/src/variants/Twokings.js +++ b/client/src/variants/Twokings.js @@ -54,8 +54,50 @@ export class TwokingsRules extends CoregalRules { } static GenRandInitFen(randomness) { - const fen = CoregalRules.GenRandInitFen(randomness); - return fen.replace("q", "k").replace("Q", "K"); + if (randomness == 0) + return "rnqkkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNQKKBNR w 0 adehadeh -"; + + const replaceBishop = (fen, first, ch1, ch2) => { + // Remove and re-add final part: + const suffix = fen.substr(-15); + fen = fen.slice(0, -15); + if (first) fen = fen.replace(ch1, ch2); + else { + fen = + fen.split("").reverse().join("") + .replace(ch1, ch2) + .split("").reverse().join("") + } + return fen + suffix; + }; + + const sameIndexReplace = (fen) => { + const first = (Math.random() < 0.5); + return replaceBishop( + replaceBishop(fen, first, 'B', 'Q'), + first, + 'b', + 'q' + ); + }; + + const fen = + CoregalRules.GenRandInitFen(randomness) + .replace("q", "k").replace("Q", "K"); + // Now replace a bishop by the queen, + // so that bishops are of different colors: + if (randomness == 1) return sameIndexReplace(fen); + const wOdd = fen.indexOf('B') % 2; + const bOdd = fen.indexOf('b') % 2; + // Since there are 7 slashes, different oddities means symmetric + if (wOdd != bOdd) return sameIndexReplace(fen); + const wFirst = (Math.random() < 0.5); + return replaceBishop( + replaceBishop(fen, wFirst, 'B', 'Q'), + !wFirst, + 'b', + 'q' + ); } underCheck(color) { diff --git a/client/src/views/Hall.vue b/client/src/views/Hall.vue index 9bb913c4..28635469 100644 --- a/client/src/views/Hall.vue +++ b/client/src/views/Hall.vue @@ -80,6 +80,7 @@ main option(value="") option( v-for="p in Object.values(people)" + v-if="!!p.name" :value="p.name" ) | {{ p.name }} diff --git a/server/db/populate.sql b/server/db/populate.sql index a0d4c843..215a9b72 100644 --- a/server/db/populate.sql +++ b/server/db/populate.sql @@ -41,6 +41,7 @@ insert or ignore into Variants (name,description) values ('Recycle', 'Reuse pieces'), ('Royalrace', 'Kings cross the 11x11 board'), ('Rugby', 'Transform an essay'), + ('Schess', 'Seirawan-Harper Chess'), ('Shatranj', 'Ancient rules'), ('Suicide', 'Lose all pieces'), ('Suction', 'Attract opposite king'), -- 2.44.0