From a68362420a3a92099dfaacea10f6cbd579161183 Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Thu, 19 Mar 2020 20:43:17 +0100 Subject: [PATCH] Add Perfect Chess, fix a bug in BaseGame when moving while choosing a promotion, eliminate some redundant castling code --- client/public/images/pieces/Perfect/ba.svg | 232 +++++++++++++++++++ client/public/images/pieces/Perfect/be.svg | 205 ++++++++++++++++ client/public/images/pieces/Perfect/bs.svg | 156 +++++++++++++ client/public/images/pieces/Perfect/wa.svg | 25 ++ client/public/images/pieces/Perfect/we.svg | 175 ++++++++++++++ client/public/images/pieces/Perfect/ws.svg | 152 ++++++++++++ client/src/base_rules.js | 13 +- client/src/components/BaseGame.vue | 9 + client/src/translations/en.js | 1 + client/src/translations/es.js | 1 + client/src/translations/fr.js | 1 + client/src/translations/rules/Perfect/en.pug | 31 +++ client/src/translations/rules/Perfect/es.pug | 32 +++ client/src/translations/rules/Perfect/fr.pug | 32 +++ client/src/variants/Allmate1.js | 76 +----- client/src/variants/Allmate2.js | 76 +----- client/src/variants/Eightpieces.js | 85 +------ client/src/variants/Grand.js | 7 +- client/src/variants/Perfect.js | 202 ++++++++++++++++ server/db/populate.sql | 1 + 20 files changed, 1273 insertions(+), 239 deletions(-) create mode 100644 client/public/images/pieces/Perfect/ba.svg create mode 100644 client/public/images/pieces/Perfect/be.svg create mode 100644 client/public/images/pieces/Perfect/bs.svg create mode 100644 client/public/images/pieces/Perfect/wa.svg create mode 100644 client/public/images/pieces/Perfect/we.svg create mode 100644 client/public/images/pieces/Perfect/ws.svg create mode 100644 client/src/translations/rules/Perfect/en.pug create mode 100644 client/src/translations/rules/Perfect/es.pug create mode 100644 client/src/translations/rules/Perfect/fr.pug create mode 100644 client/src/variants/Perfect.js diff --git a/client/public/images/pieces/Perfect/ba.svg b/client/public/images/pieces/Perfect/ba.svg new file mode 100644 index 00000000..0215b879 --- /dev/null +++ b/client/public/images/pieces/Perfect/ba.svg @@ -0,0 +1,232 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Perfect/be.svg b/client/public/images/pieces/Perfect/be.svg new file mode 100644 index 00000000..fd548016 --- /dev/null +++ b/client/public/images/pieces/Perfect/be.svg @@ -0,0 +1,205 @@ + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Perfect/bs.svg b/client/public/images/pieces/Perfect/bs.svg new file mode 100644 index 00000000..afc27f05 --- /dev/null +++ b/client/public/images/pieces/Perfect/bs.svg @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Perfect/wa.svg b/client/public/images/pieces/Perfect/wa.svg new file mode 100644 index 00000000..beeaaea1 --- /dev/null +++ b/client/public/images/pieces/Perfect/wa.svg @@ -0,0 +1,25 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Perfect/we.svg b/client/public/images/pieces/Perfect/we.svg new file mode 100644 index 00000000..fd0288db --- /dev/null +++ b/client/public/images/pieces/Perfect/we.svg @@ -0,0 +1,175 @@ + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/public/images/pieces/Perfect/ws.svg b/client/public/images/pieces/Perfect/ws.svg new file mode 100644 index 00000000..b45ea509 --- /dev/null +++ b/client/public/images/pieces/Perfect/ws.svg @@ -0,0 +1,152 @@ + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + diff --git a/client/src/base_rules.js b/client/src/base_rules.js index cd4bb778..267b2335 100644 --- a/client/src/base_rules.js +++ b/client/src/base_rules.js @@ -810,7 +810,8 @@ export const ChessRules = class ChessRules { return moves; } - getCastleMoves([x, y]) { + // "castleInCheck" arg to let some variants castle under check + getCastleMoves([x, y], castleInCheck) { const c = this.getColor(x, y); if (x != (c == "w" ? V.size.x - 1 : 0) || y != this.INIT_COL_KING[c]) return []; //x isn't first rank, or king has moved (shortcut) @@ -832,7 +833,9 @@ export const ChessRules = class ChessRules { if (this.castleFlags[c][castleSide] >= V.size.y) continue; // If this code is reached, rooks and king are on initial position + // NOTE: in some variants this is not a rook, but let's keep variable name const rookPos = this.castleFlags[c][castleSide]; + const castlingPiece = this.getPiece(x, rookPos); if (this.getColor(x, rookPos) != c) // Rook is here but changed color (see Benedict) continue; @@ -843,11 +846,11 @@ export const ChessRules = class ChessRules { i = y; do { if ( - this.isAttacked([x, i], oppCol) || + (!castleInCheck && this.isAttacked([x, i], oppCol)) || (this.board[x][i] != V.EMPTY && // NOTE: next check is enough, because of chessboard constraints (this.getColor(x, i) != c || - ![V.KING, V.ROOK].includes(this.getPiece(x, i)))) + ![V.KING, castlingPiece].includes(this.getPiece(x, i)))) ) { continue castlingCheck; } @@ -876,11 +879,11 @@ export const ChessRules = class ChessRules { new Move({ appear: [ new PiPo({ x: x, y: finalSquares[castleSide][0], p: V.KING, c: c }), - new PiPo({ x: x, y: finalSquares[castleSide][1], p: V.ROOK, c: c }) + new PiPo({ x: x, y: finalSquares[castleSide][1], p: castlingPiece, c: c }) ], vanish: [ new PiPo({ x: x, y: y, p: V.KING, c: c }), - new PiPo({ x: x, y: rookPos, p: V.ROOK, c: c }) + new PiPo({ x: x, y: rookPos, p: castlingPiece, c: c }) ], end: Math.abs(y - rookPos) <= 2 diff --git a/client/src/components/BaseGame.vue b/client/src/components/BaseGame.vue index 3446a036..65e96cf6 100644 --- a/client/src/components/BaseGame.vue +++ b/client/src/components/BaseGame.vue @@ -11,6 +11,7 @@ div#baseGame #gameContainer #boardContainer Board( + ref="board" :vr="vr" :last-move="lastMove" :analyze="game.mode=='analyze'" @@ -306,6 +307,8 @@ export default { }, // "light": if gotoMove() or gotoEnd() play: function(move, received, light, noemit) { + // Freeze while choices are shown: + if (this.$refs["board"].choices.length > 0) return; if (!!noemit) { if (this.inPlay) { // Received moves in observed games can arrive too fast: @@ -445,6 +448,8 @@ export default { }, // "light": if gotoMove() or gotoBegin() undo: function(move, light) { + // Freeze while choices are shown: + if (this.$refs["board"].choices.length > 0) return; if (this.inMultimove) { this.cancelCurrentMultimove(); this.incheck = this.vr.getCheckSquares(this.vr.turn); @@ -467,6 +472,7 @@ export default { } }, gotoMove: function(index) { + if (this.$refs["board"].choices.length > 0) return; if (this.inMultimove) this.cancelCurrentMultimove(); if (index == this.cursor) return; if (index < this.cursor) { @@ -484,6 +490,7 @@ export default { this.emitFenIfAnalyze(); }, gotoBegin: function() { + if (this.$refs["board"].choices.length > 0) return; if (this.inMultimove) this.cancelCurrentMultimove(); const minCursor = this.moves.length > 0 && this.moves[0].notation == "..." @@ -495,11 +502,13 @@ export default { this.emitFenIfAnalyze(); }, gotoEnd: function() { + if (this.$refs["board"].choices.length > 0) return; if (this.cursor == this.moves.length - 1) return; this.gotoMove(this.moves.length - 1); this.emitFenIfAnalyze(); }, flip: function() { + if (this.$refs["board"].choices.length > 0) return; this.orientation = V.GetOppCol(this.orientation); } } diff --git a/client/src/translations/en.js b/client/src/translations/en.js index 328f0895..5d093742 100644 --- a/client/src/translations/en.js +++ b/client/src/translations/en.js @@ -187,6 +187,7 @@ export const translations = { "Move twice": "Move twice", "Neverending rows": "Neverending rows", "Pawns move diagonally": "Pawns move diagonally", + "Powerful pieces": "Powerful pieces", "Queen disguised as a pawn": "Queen disguised as a pawn", "Reuse pieces": "Reuse pieces", "Reverse captures": "Reverse captures", diff --git a/client/src/translations/es.js b/client/src/translations/es.js index 78c0b054..877f8587 100644 --- a/client/src/translations/es.js +++ b/client/src/translations/es.js @@ -187,6 +187,7 @@ export const translations = { "Move twice": "Mover dos veces", "Neverending rows": "Filas interminables", "Pawns move diagonally": "Peones se mueven en diagonal", + "Powerful pieces": "Piezas poderosas", "Queen disguised as a pawn": "Reina disfrazada de peón", "Reuse pieces": "Reutilizar piezas", "Reverse captures": "Capturas invertidas", diff --git a/client/src/translations/fr.js b/client/src/translations/fr.js index 8c782953..cf32f6e4 100644 --- a/client/src/translations/fr.js +++ b/client/src/translations/fr.js @@ -187,6 +187,7 @@ export const translations = { "Move twice": "Jouer deux coups", "Neverending rows": "Rangées sans fin", "Pawns move diagonally": "Les pions vont en diagonale", + "Powerful pieces": "Pièces puissantes", "Queen disguised as a pawn": "Reine déguisée en pion", "Reuse pieces": "Réutiliser les pièces", "Reverse captures": "Captures inversées", diff --git a/client/src/translations/rules/Perfect/en.pug b/client/src/translations/rules/Perfect/en.pug new file mode 100644 index 00000000..5973aa8f --- /dev/null +++ b/client/src/translations/rules/Perfect/en.pug @@ -0,0 +1,31 @@ +p.boxed + | Three new pieces appear, combining the movements of the knight + | with those of the queen, rook or bishop. + +figure.diagram-container + .diagram + | fen:esqakbnr/pppppppp/8/8/8/8/PPPPPPPP/ESQAKBNR: + figcaption. + Standard initial position: empresses, princesses and amazons + respectively on a, b and d files. + +p As the pictures suggest, +ul + li The Amazon (A) moves like a queen + knight, + li The Empress (E) moves like a rook + knight, + li the Princess (S) moves like a bishop + knight. + +p. + Note: in the original version of the rules the fairy pieces + are called General, Chancellor and Minister. + +h3 Source + +p + a(href="https://www.chessvariants.com/diffmove.dir/perfectchess.html") Perfect chess + |  on chessvariants.com. + | You can also play this variant + a(href="https://greenchess.net/rules.php?v=perfect") on greenchess + | . + +p Inventor: Köksal Karakus (2000) diff --git a/client/src/translations/rules/Perfect/es.pug b/client/src/translations/rules/Perfect/es.pug new file mode 100644 index 00000000..f5a5240e --- /dev/null +++ b/client/src/translations/rules/Perfect/es.pug @@ -0,0 +1,32 @@ +p.boxed + | Aparecen tres piezas nuevas que combinan los movimientos del caballo + | con los de la dama, la torre o el loco. + +figure.diagram-container + .diagram + | fen:esqakbnr/pppppppp/8/8/8/8/PPPPPPPP/ESQAKBNR: + figcaption. + Posición inicial estándar: emperatriz, princesas y amazonas. + están respectivamente en las columnas a, b y d. + +p Como sugiere la imagen, +ul + li The Amazon (A) se mueve como una dama + caballo, + li La emperatriz (E) se mueve como una torre + caballo, + li La princesa (S) se mueve como un alfil + caballo. + +p. + Nota: en la versión original de estas reglas, las piezas mágicas son + llamado General, Canciller y Ministro. + +h3 Fuente + +p + | La + a(href="https://www.chessvariants.com/diffmove.dir/perfectchess.html") variante Perfect + |  en chessvariants.com. + | Esta variante también es jugable + a(href="https://greenchess.net/rules.php?v=perfect") en greenchess + | . + +p Inventor: Köksal Karakus (2000) diff --git a/client/src/translations/rules/Perfect/fr.pug b/client/src/translations/rules/Perfect/fr.pug new file mode 100644 index 00000000..07e3b38a --- /dev/null +++ b/client/src/translations/rules/Perfect/fr.pug @@ -0,0 +1,32 @@ +p.boxed + | Trois nouvelles pièces apparaissent, combinant les déplacements du cavalier + | avec ceux de la dame, de la tour ou du fou. + +figure.diagram-container + .diagram + | fen:esqakbnr/pppppppp/8/8/8/8/PPPPPPPP/ESQAKBNR: + figcaption. + Position de départ standard : les impératrices, princesses et amazones + sont respectivement sur les colonnes a, b et d. + +p Comme suggéré par l'image, +ul + li L'Amazone (A) se déplace comme une dame + cavalier, + li L'Impératrice (E) se déplace comme une tour + cavalier, + li La Princesse (S) se déplace comme un fou + cavalier. + +p. + Note : dans la version d'ortigine de ces règles, les pièces féériques sont + appelées Général, Chancelier et Ministre. + +h3 Source + +p + | La + a(href="https://www.chessvariants.com/diffmove.dir/perfectchess.html") variante Perfect + |  sur chessvariants.com. + | Cette variante est également jouable + a(href="https://greenchess.net/rules.php?v=perfect") sur greenchess + | . + +p Inventeur : Köksal Karakus (2000) diff --git a/client/src/variants/Allmate1.js b/client/src/variants/Allmate1.js index 65e20b79..3d843005 100644 --- a/client/src/variants/Allmate1.js +++ b/client/src/variants/Allmate1.js @@ -99,80 +99,8 @@ export class Allmate1Rules extends ChessRules { } // No "under check" conditions in castling - getCastleMoves([x, y]) { - const c = this.getColor(x, y); - if (x != (c == "w" ? V.size.x - 1 : 0) || y != this.INIT_COL_KING[c]) - return []; //x isn't first rank, or king has moved (shortcut) - - // Castling ? - const oppCol = V.GetOppCol(c); - let moves = []; - let i = 0; - // King, then rook: - const finalSquares = [ - [2, 3], - [V.size.y - 2, V.size.y - 3] - ]; - castlingCheck: for ( - let castleSide = 0; - castleSide < 2; - castleSide++ //large, then small - ) { - if (this.castleFlags[c][castleSide] >= 8) continue; - // If this code is reached, rooks and king are on initial position - - // Nothing on the path of the king ? (and no checks) - const finDist = finalSquares[castleSide][0] - y; - let step = finDist / Math.max(1, Math.abs(finDist)); - for (let i = y; i != finalSquares[castleSide][0]; i += step) { - if ( - this.board[x][i] != V.EMPTY && - // NOTE: next check is enough, because of chessboard constraints - (this.getColor(x, i) != c || - ![V.KING, V.ROOK].includes(this.getPiece(x, i))) - ) { - continue castlingCheck; - } - } - - // Nothing on the path to the rook? - step = castleSide == 0 ? -1 : 1; - const rookPos = this.castleFlags[c][castleSide]; - for (i = y + step; i != rookPos; i += step) { - if (this.board[x][i] != V.EMPTY) continue castlingCheck; - } - - // Nothing on final squares, except maybe king and castling rook? - for (i = 0; i < 2; i++) { - if ( - this.board[x][finalSquares[castleSide][i]] != V.EMPTY && - this.getPiece(x, finalSquares[castleSide][i]) != V.KING && - finalSquares[castleSide][i] != rookPos - ) { - continue castlingCheck; - } - } - - // If this code is reached, castle is valid - moves.push( - new Move({ - appear: [ - new PiPo({ x: x, y: finalSquares[castleSide][0], p: V.KING, c: c }), - new PiPo({ x: x, y: finalSquares[castleSide][1], p: V.ROOK, c: c }) - ], - vanish: [ - new PiPo({ x: x, y: y, p: V.KING, c: c }), - new PiPo({ x: x, y: rookPos, p: V.ROOK, c: c }) - ], - end: - Math.abs(y - rookPos) <= 2 - ? { x: x, y: rookPos } - : { x: x, y: y + 2 * (castleSide == 0 ? -1 : 1) } - }) - ); - } - - return moves; + getCastleMoves(sq) { + return super.getCastleMoves(sq, "castleInCheck"); } // TODO: allow pieces to "commit suicide"? (Currently yes except king) diff --git a/client/src/variants/Allmate2.js b/client/src/variants/Allmate2.js index 386bf697..424ff61b 100644 --- a/client/src/variants/Allmate2.js +++ b/client/src/variants/Allmate2.js @@ -103,80 +103,8 @@ export class Allmate2Rules extends ChessRules { } // No "under check" conditions in castling - getCastleMoves([x, y]) { - const c = this.getColor(x, y); - if (x != (c == "w" ? V.size.x - 1 : 0) || y != this.INIT_COL_KING[c]) - return []; //x isn't first rank, or king has moved (shortcut) - - // Castling ? - const oppCol = V.GetOppCol(c); - let moves = []; - let i = 0; - // King, then rook: - const finalSquares = [ - [2, 3], - [V.size.y - 2, V.size.y - 3] - ]; - castlingCheck: for ( - let castleSide = 0; - castleSide < 2; - castleSide++ //large, then small - ) { - if (this.castleFlags[c][castleSide] >= 8) continue; - // If this code is reached, rooks and king are on initial position - - // Nothing on the path of the king ? (and no checks) - const finDist = finalSquares[castleSide][0] - y; - let step = finDist / Math.max(1, Math.abs(finDist)); - for (let i = y; i != finalSquares[castleSide][0]; i += step) { - if ( - this.board[x][i] != V.EMPTY && - // NOTE: next check is enough, because of chessboard constraints - (this.getColor(x, i) != c || - ![V.KING, V.ROOK].includes(this.getPiece(x, i))) - ) { - continue castlingCheck; - } - } - - // Nothing on the path to the rook? - step = castleSide == 0 ? -1 : 1; - const rookPos = this.castleFlags[c][castleSide]; - for (i = y + step; i != rookPos; i += step) { - if (this.board[x][i] != V.EMPTY) continue castlingCheck; - } - - // Nothing on final squares, except maybe king and castling rook? - for (i = 0; i < 2; i++) { - if ( - this.board[x][finalSquares[castleSide][i]] != V.EMPTY && - this.getPiece(x, finalSquares[castleSide][i]) != V.KING && - finalSquares[castleSide][i] != rookPos - ) { - continue castlingCheck; - } - } - - // If this code is reached, castle is valid - moves.push( - new Move({ - appear: [ - new PiPo({ x: x, y: finalSquares[castleSide][0], p: V.KING, c: c }), - new PiPo({ x: x, y: finalSquares[castleSide][1], p: V.ROOK, c: c }) - ], - vanish: [ - new PiPo({ x: x, y: y, p: V.KING, c: c }), - new PiPo({ x: x, y: rookPos, p: V.ROOK, c: c }) - ], - end: - Math.abs(y - rookPos) <= 2 - ? { x: x, y: rookPos } - : { x: x, y: y + 2 * (castleSide == 0 ? -1 : 1) } - }) - ); - } - - return moves; + getCastleMoves(sq) { + return super.getCastleMoves(sq, "castleInCheck"); } // TODO: allow pieces to "commit suicide"? (Currently yes except king) diff --git a/client/src/variants/Eightpieces.js b/client/src/variants/Eightpieces.js index bea261a3..81d69d81 100644 --- a/client/src/variants/Eightpieces.js +++ b/client/src/variants/Eightpieces.js @@ -195,7 +195,7 @@ export class EightpiecesRules extends ChessRules { positions.splice(randIndex, 1); // Rook, jailer and king positions are now almost fixed, - // only the ordering rook-> jailer or jailer->rook must be decided. + // only the ordering rook->jailer or jailer->rook must be decided. let rookPos = positions[0]; let jailerPos = positions[2]; const kingPos = positions[1]; @@ -610,81 +610,6 @@ export class EightpiecesRules extends ChessRules { ); } - // Adapted: castle with jailer possible - getCastleMoves([x, y]) { - const c = this.getColor(x, y); - const firstRank = (c == "w" ? V.size.x - 1 : 0); - if (x != firstRank || y != this.INIT_COL_KING[c]) - return []; - - const oppCol = V.GetOppCol(c); - let moves = []; - let i = 0; - // King, then rook or jailer: - const finalSquares = [ - [2, 3], - [V.size.y - 2, V.size.y - 3] - ]; - castlingCheck: for ( - let castleSide = 0; - castleSide < 2; - castleSide++ - ) { - if (this.castleFlags[c][castleSide] >= 8) continue; - // Rook (or jailer) and king are on initial position - const finDist = finalSquares[castleSide][0] - y; - let step = finDist / Math.max(1, Math.abs(finDist)); - i = y; - do { - if ( - this.isAttacked([x, i], oppCol) || - (this.board[x][i] != V.EMPTY && - (this.getColor(x, i) != c || - ![V.KING, V.ROOK, V.JAILER].includes(this.getPiece(x, i)))) - ) { - continue castlingCheck; - } - i += step; - } while (i != finalSquares[castleSide][0]); - step = castleSide == 0 ? -1 : 1; - const rookOrJailerPos = this.castleFlags[c][castleSide]; - for (i = y + step; i != rookOrJailerPos; i += step) - if (this.board[x][i] != V.EMPTY) continue castlingCheck; - - // Nothing on final squares, except maybe king and castling rook or jailer? - for (i = 0; i < 2; i++) { - if ( - this.board[x][finalSquares[castleSide][i]] != V.EMPTY && - this.getPiece(x, finalSquares[castleSide][i]) != V.KING && - finalSquares[castleSide][i] != rookOrJailerPos - ) { - continue castlingCheck; - } - } - - // If this code is reached, castle is valid - const castlingPiece = this.getPiece(firstRank, rookOrJailerPos); - moves.push( - new Move({ - appear: [ - new PiPo({ x: x, y: finalSquares[castleSide][0], p: V.KING, c: c }), - new PiPo({ x: x, y: finalSquares[castleSide][1], p: castlingPiece, c: c }) - ], - vanish: [ - new PiPo({ x: x, y: y, p: V.KING, c: c }), - new PiPo({ x: x, y: rookOrJailerPos, p: castlingPiece, c: c }) - ], - end: - Math.abs(y - rookOrJailerPos) <= 2 - ? { x: x, y: rookOrJailerPos } - : { x: x, y: y + 2 * (castleSide == 0 ? -1 : 1) } - }) - ); - } - - return moves; - } - atLeastOneMove() { // If in second-half of a move, we already know that a move is possible if (this.subTurn == 2) return true; @@ -764,10 +689,6 @@ export class EightpiecesRules extends ChessRules { } play(move) { -// if (!this.states) this.states = []; -// const stateFen = this.getFen(); -// this.states.push(stateFen); - this.prePlay(move); move.flags = JSON.stringify(this.aggregateFlags()); this.epSquares.push(this.getEpSquare(move)); @@ -828,10 +749,6 @@ export class EightpiecesRules extends ChessRules { if (move.sentryPush) this.subTurn = 2; } this.postUndo(move); - -// const stateFen = this.getFen(); -// if (stateFen != this.states[this.states.length-1]) debugger; -// this.states.pop(); } postUndo(move) { diff --git a/client/src/variants/Grand.js b/client/src/variants/Grand.js index 5523244b..f4a96ce1 100644 --- a/client/src/variants/Grand.js +++ b/client/src/variants/Grand.js @@ -87,12 +87,15 @@ export class GrandRules extends ChessRules { return { x: 10, y: 10 }; } + // Rook + knight: static get MARSHALL() { return "m"; - } //rook+knight + } + + // Bishop + knight static get CARDINAL() { return "c"; - } //bishop+knight + } static get PIECES() { return ChessRules.PIECES.concat([V.MARSHALL, V.CARDINAL]); diff --git a/client/src/variants/Perfect.js b/client/src/variants/Perfect.js new file mode 100644 index 00000000..a0c75c57 --- /dev/null +++ b/client/src/variants/Perfect.js @@ -0,0 +1,202 @@ +import { ChessRules } from "@/base_rules"; +import { ArrayFun } from "@/utils/array"; +import { randInt } from "@/utils/alea"; + +export class PerfectRules extends ChessRules { + static get PawnSpecs() { + return Object.assign( + {}, + ChessRules.PawnSpecs, + { + promotions: + ChessRules.PawnSpecs.promotions + .concat([V.AMAZON, V.EMPRESS, V.PRINCESS]) + } + ); + } + + getPpath(b) { + return ( + [V.AMAZON, V.EMPRESS, V.PRINCESS].includes(b[1]) + ? "Perfect/" + : "" + ) + b; + } + + // Queen + knight + static get AMAZON() { + return "a"; + } + + // Rook + knight + static get EMPRESS() { + return "e"; + } + + // Bishop + knight + static get PRINCESS() { + return "s"; + } + + static get PIECES() { + return ChessRules.PIECES.concat([V.AMAZON, V.EMPRESS, V.PRINCESS]); + } + + getPotentialMovesFrom([x, y]) { + switch (this.getPiece(x, y)) { + case V.AMAZON: + return this.getPotentialAmazonMoves([x, y]); + case V.EMPRESS: + return this.getPotentialEmpressMoves([x, y]); + case V.PRINCESS: + return this.getPotentialPrincessMoves([x, y]); + default: + return super.getPotentialMovesFrom([x, y]); + } + } + + getPotentialAmazonMoves(sq) { + return super.getPotentialQueenMoves(sq).concat( + this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep") + ); + } + + getPotentialEmpressMoves(sq) { + return this.getSlideNJumpMoves(sq, V.steps[V.ROOK]).concat( + this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep") + ); + } + + getPotentialPrincessMoves(sq) { + return this.getSlideNJumpMoves(sq, V.steps[V.BISHOP]).concat( + this.getSlideNJumpMoves(sq, V.steps[V.KNIGHT], "oneStep") + ); + } + + isAttacked(sq, color) { + return ( + super.isAttacked(sq, color) || + this.isAttackedByAmazon(sq, color) || + this.isAttackedByEmpress(sq, color) || + this.isAttackedByPrincess(sq, color) + ); + } + + isAttackedByAmazon(sq, color) { + return ( + super.isAttackedByQueen(sq, color) || + this.isAttackedBySlideNJump( + sq, + color, + V.MARSHALL, + V.steps[V.KNIGHT], + "oneStep" + ) + ); + } + + isAttackedByEmpress(sq, color) { + return ( + this.isAttackedBySlideNJump(sq, color, V.MARSHALL, V.steps[V.ROOK]) || + this.isAttackedBySlideNJump( + sq, + color, + V.MARSHALL, + V.steps[V.KNIGHT], + "oneStep" + ) + ); + } + + isAttackedByPrincess(sq, color) { + return ( + this.isAttackedBySlideNJump(sq, color, V.CARDINAL, V.steps[V.BISHOP]) || + this.isAttackedBySlideNJump( + sq, + color, + V.CARDINAL, + V.steps[V.KNIGHT], + "oneStep" + ) + ); + } + + static get VALUES() { + return Object.assign( + { a: 12, e: 7, s: 5 }, //experimental + ChessRules.VALUES + ); + } + + static get SEARCH_DEPTH() { + return 2; + } + + static GenRandInitFen(randomness) { + if (randomness == 0) + return "esqakbnr/pppppppp/8/8/8/8/PPPPPPPP/ESQAKBNR w 0 ahah -"; + + let pieces = { w: new Array(8), b: new Array(8) }; + let flags = ""; + let whiteBishopPos = -1; + for (let c of ["w", "b"]) { + if (c == 'b' && randomness == 1) { + pieces['b'] = pieces['w']; + flags += flags; + break; + } + + let positions = ArrayFun.range(8); + + // Get random squares for bishop: if black, pick a different color + // than where the white one stands. + let randIndex = + c == 'w' + ? randInt(8) + : 2 * randInt(4) + (1 - whiteBishopPos % 2); + if (c == 'w') whiteBishopPos = randIndex; + const bishopPos = positions[randIndex]; + positions.splice(randIndex, 1); + + randIndex = randInt(7); + const knightPos = positions[randIndex]; + positions.splice(randIndex, 1); + + randIndex = randInt(6); + const queenPos = positions[randIndex]; + positions.splice(randIndex, 1); + + randIndex = randInt(5); + const amazonPos = positions[randIndex]; + positions.splice(randIndex, 1); + + randIndex = randInt(4); + const princessPos = positions[randIndex]; + positions.splice(randIndex, 1); + + // Rook, empress and king positions are now almost fixed, + // only the ordering rook->empress or empress->rook must be decided. + let rookPos = positions[0]; + let empressPos = positions[2]; + const kingPos = positions[1]; + flags += V.CoordToColumn(rookPos) + V.CoordToColumn(empressPos); + if (Math.random() < 0.5) [rookPos, empressPos] = [empressPos, rookPos]; + + pieces[c][rookPos] = "r"; + pieces[c][knightPos] = "n"; + pieces[c][bishopPos] = "b"; + pieces[c][queenPos] = "q"; + pieces[c][kingPos] = "k"; + pieces[c][amazonPos] = "a"; + pieces[c][princessPos] = "s"; + pieces[c][empressPos] = "e"; + } + // Add turn + flags + enpassant + return ( + pieces["b"].join("") + + "/pppppppp/8/8/8/8/PPPPPPPP/" + + pieces["w"].join("").toUpperCase() + + " w 0 " + flags + " -" + ); + } +}; diff --git a/server/db/populate.sql b/server/db/populate.sql index cee29708..2f1dcf58 100644 --- a/server/db/populate.sql +++ b/server/db/populate.sql @@ -33,6 +33,7 @@ insert or ignore into Variants (name,description) values ('Losers', 'Get strong at self-mate'), ('Magnetic', 'Laws of attraction'), ('Marseille', 'Move twice'), + ('Perfect', 'Powerful pieces'), ('Racingkings', 'Kings cross the 8x8 board'), ('Rifle', 'Shoot pieces'), ('Recycle', 'Reuse pieces'), -- 2.44.0