From 68e19a449db7a12e0a168e99cd750d985c983ba1 Mon Sep 17 00:00:00 2001 From: Benjamin Auder Date: Tue, 17 Mar 2020 01:22:41 +0100 Subject: [PATCH] Add Knightrelay1. Some fixes. Move odd 'isAttackedBy_multiple_colors' to Checkered variant only --- TODO | 11 -- client/src/base_rules.js | 88 +++++----- client/src/translations/en.js | 3 +- client/src/translations/es.js | 3 +- client/src/translations/fr.js | 3 +- .../translations/rules/Knightrelay1/en.pug | 28 ++++ .../translations/rules/Knightrelay1/es.pug | 34 ++++ .../translations/rules/Knightrelay1/fr.pug | 32 ++++ .../{Knightrelay => Knightrelay2}/en.pug | 0 .../{Knightrelay => Knightrelay2}/es.pug | 0 .../{Knightrelay => Knightrelay2}/fr.pug | 0 client/src/variants/Antiking.js | 29 ++-- client/src/variants/Atomic.js | 27 ++-- client/src/variants/Baroque.js | 46 +++--- client/src/variants/Berolina.js | 18 +-- client/src/variants/Checkered.js | 152 +++++++++++++++++- client/src/variants/Circular.js | 30 ++-- client/src/variants/Cylinder.js | 28 ++-- client/src/variants/Eightpieces.js | 54 +++---- client/src/variants/Enpassant.js | 4 +- client/src/variants/Grand.js | 20 +-- client/src/variants/Grasshopper.js | 10 +- client/src/variants/Knightmate.js | 22 +-- client/src/variants/Knightrelay1.js | 139 ++++++++++++++++ .../{Knightrelay.js => Knightrelay2.js} | 11 +- client/src/variants/Royalrace.js | 14 +- client/src/variants/Shatranj.js | 10 +- client/src/variants/Wildebeest.js | 16 +- client/src/variants/Wormhole.js | 48 +++--- client/src/views/Hall.vue | 4 +- client/src/views/MyGames.vue | 8 +- client/src/views/News.vue | 7 +- client/src/views/Problems.vue | 96 +++++++---- server/db/populate.sql | 3 +- server/models/News.js | 4 +- server/models/Problem.js | 13 +- server/routes/news.js | 2 +- server/routes/problems.js | 9 +- 38 files changed, 720 insertions(+), 306 deletions(-) create mode 100644 client/src/translations/rules/Knightrelay1/en.pug create mode 100644 client/src/translations/rules/Knightrelay1/es.pug create mode 100644 client/src/translations/rules/Knightrelay1/fr.pug rename client/src/translations/rules/{Knightrelay => Knightrelay2}/en.pug (100%) rename client/src/translations/rules/{Knightrelay => Knightrelay2}/es.pug (100%) rename client/src/translations/rules/{Knightrelay => Knightrelay2}/fr.pug (100%) create mode 100644 client/src/variants/Knightrelay1.js rename client/src/variants/{Knightrelay.js => Knightrelay2.js} (91%) diff --git a/TODO b/TODO index 86a49338..6916bdf3 100644 --- a/TODO +++ b/TODO @@ -1,14 +1,3 @@ -#Enhancements - -In Hall: at loading, if something in a tab and nothing in another (e.g. live/corr challenges), -show the other even if settings say to show the empty one. - -tabs "running" and "completed" for MyGames page (default to running if any and my turn) -"load more" option for completed games (act on corr games). - -"load more" option for problems as well: similar to news page. -Also on corr challenges. - # New variants Landing pieces from empty board: https://www.chessvariants.com/diffsetup.dir/unachess.html diff --git a/client/src/base_rules.js b/client/src/base_rules.js index 752d8ee9..fad1853a 100644 --- a/client/src/base_rules.js +++ b/client/src/base_rules.js @@ -88,7 +88,7 @@ export const ChessRules = class ChessRules { return f.charCodeAt() <= 90 ? "w" + f.toLowerCase() : "b" + f; } - // Check if FEN describe a board situation correctly + // Check if FEN describes a board situation correctly static IsGoodFen(fen) { const fenParsed = V.ParseFen(fen); // 1) Check position @@ -786,7 +786,7 @@ export const ChessRules = class ChessRules { i = y; do { if ( - this.isAttacked([x, i], [oppCol]) || + 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 || @@ -893,21 +893,21 @@ export const ChessRules = class ChessRules { return false; } - // Check if pieces of color in 'colors' are attacking (king) on square x,y - isAttacked(sq, colors) { + // Check if pieces of given color are attacking (king) on square x,y + isAttacked(sq, color) { return ( - this.isAttackedByPawn(sq, colors) || - this.isAttackedByRook(sq, colors) || - this.isAttackedByKnight(sq, colors) || - this.isAttackedByBishop(sq, colors) || - this.isAttackedByQueen(sq, colors) || - this.isAttackedByKing(sq, colors) + this.isAttackedByPawn(sq, color) || + this.isAttackedByRook(sq, color) || + this.isAttackedByKnight(sq, color) || + this.isAttackedByBishop(sq, color) || + this.isAttackedByQueen(sq, color) || + this.isAttackedByKing(sq, color) ); } // Generic method for non-pawn pieces ("sliding or jumping"): - // is x,y attacked by a piece of color in array 'colors' ? - isAttackedBySlideNJump([x, y], colors, piece, steps, oneStep) { + // is x,y attacked by a piece of given color ? + isAttackedBySlideNJump([x, y], color, piece, steps, oneStep) { for (let step of steps) { let rx = x + step[0], ry = y + step[1]; @@ -917,8 +917,8 @@ export const ChessRules = class ChessRules { } if ( V.OnBoard(rx, ry) && - this.getPiece(rx, ry) === piece && - colors.includes(this.getColor(rx, ry)) + this.getPiece(rx, ry) == piece && + this.getColor(rx, ry) == color ) { return true; } @@ -926,62 +926,60 @@ export const ChessRules = class ChessRules { return false; } - // Is square x,y attacked by 'colors' pawns ? - isAttackedByPawn([x, y], colors) { - for (let c of colors) { - const pawnShift = c == "w" ? 1 : -1; - if (x + pawnShift >= 0 && x + pawnShift < V.size.x) { - for (let i of [-1, 1]) { - if ( - y + i >= 0 && - y + i < V.size.y && - this.getPiece(x + pawnShift, y + i) == V.PAWN && - this.getColor(x + pawnShift, y + i) == c - ) { - return true; - } + // Is square x,y attacked by 'color' pawns ? + isAttackedByPawn([x, y], color) { + const pawnShift = (color == "w" ? 1 : -1); + if (x + pawnShift >= 0 && x + pawnShift < V.size.x) { + for (let i of [-1, 1]) { + if ( + y + i >= 0 && + y + i < V.size.y && + this.getPiece(x + pawnShift, y + i) == V.PAWN && + this.getColor(x + pawnShift, y + i) == color + ) { + return true; } } } return false; } - // Is square x,y attacked by 'colors' rooks ? - isAttackedByRook(sq, colors) { - return this.isAttackedBySlideNJump(sq, colors, V.ROOK, V.steps[V.ROOK]); + // Is square x,y attacked by 'color' rooks ? + isAttackedByRook(sq, color) { + return this.isAttackedBySlideNJump(sq, color, V.ROOK, V.steps[V.ROOK]); } - // Is square x,y attacked by 'colors' knights ? - isAttackedByKnight(sq, colors) { + // Is square x,y attacked by 'color' knights ? + isAttackedByKnight(sq, color) { return this.isAttackedBySlideNJump( sq, - colors, + color, V.KNIGHT, V.steps[V.KNIGHT], "oneStep" ); } - // Is square x,y attacked by 'colors' bishops ? - isAttackedByBishop(sq, colors) { - return this.isAttackedBySlideNJump(sq, colors, V.BISHOP, V.steps[V.BISHOP]); + // Is square x,y attacked by 'color' bishops ? + isAttackedByBishop(sq, color) { + return this.isAttackedBySlideNJump(sq, color, V.BISHOP, V.steps[V.BISHOP]); } - // Is square x,y attacked by 'colors' queens ? - isAttackedByQueen(sq, colors) { + // Is square x,y attacked by 'color' queens ? + isAttackedByQueen(sq, color) { return this.isAttackedBySlideNJump( sq, - colors, + color, V.QUEEN, V.steps[V.ROOK].concat(V.steps[V.BISHOP]) ); } - // Is square x,y attacked by 'colors' king(s) ? - isAttackedByKing(sq, colors) { + // Is square x,y attacked by 'color' king(s) ? + isAttackedByKing(sq, color) { return this.isAttackedBySlideNJump( sq, - colors, + color, V.KING, V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep" @@ -1100,10 +1098,10 @@ export const ChessRules = class ChessRules { // Game over const color = this.turn; // No valid move: stalemate or checkmate? - if (!this.isAttacked(this.kingPos[color], [V.GetOppCol(color)])) + if (!this.isAttacked(this.kingPos[color], V.GetOppCol(color))) return "1/2"; // OK, checkmate - return color == "w" ? "0-1" : "1-0"; + return (color == "w" ? "0-1" : "1-0"); } /////////////// diff --git a/client/src/translations/en.js b/client/src/translations/en.js index 5a14d3f7..593d68be 100644 --- a/client/src/translations/en.js +++ b/client/src/translations/en.js @@ -180,6 +180,8 @@ export const translations = { "Mate any piece (v2)": "Mate any piece (v2)", "Mate the knight": "Mate the knight", "Middle battle": "Middle battle", + "Move like a knight (v1)": "Move like a knight (v1)", + "Move like a knight (v2)": "Move like a knight (v2)", "Move twice": "Move twice", "Neverending rows": "Neverending rows", "Pawns move diagonally": "Pawns move diagonally", @@ -191,6 +193,5 @@ export const translations = { "Shoot pieces": "Shoot pieces", "Squares disappear": "Squares disappear", "Standard rules": "Standard rules", - "The knight transfers its powers": "The knight transfers its powers", "Unidentified pieces": "Unidentified pieces" }; diff --git a/client/src/translations/es.js b/client/src/translations/es.js index 34444758..84a32951 100644 --- a/client/src/translations/es.js +++ b/client/src/translations/es.js @@ -180,6 +180,8 @@ export const translations = { "Mate any piece (v2)": "Matar cualquier pieza (v2)", "Mate the knight": "Matar el caballo", "Middle battle": "Batalla media", + "Move like a knight (v1)": "Moverse como un caballo (v1)", + "Move like a knight (v2)": "Moverse como un caballo (v2)", "Move twice": "Mover dos veces", "Neverending rows": "Filas interminables", "Pawns move diagonally": "Peones se mueven en diagonal", @@ -191,6 +193,5 @@ export const translations = { "Shoot pieces": "Tirar de las piezas", "Squares disappear": "Las casillas desaparecen", "Standard rules": "Reglas estandar", - "The knight transfers its powers": "El caballo transfiere sus poderes", "Unidentified pieces": "Piezas no identificadas" }; diff --git a/client/src/translations/fr.js b/client/src/translations/fr.js index 8c1f7ae1..15575cb7 100644 --- a/client/src/translations/fr.js +++ b/client/src/translations/fr.js @@ -180,6 +180,8 @@ export const translations = { "Mate any piece (v2)": "Matez n'importe quelle pièce (v2)", "Mate the knight": "Matez le cavalier", "Middle battle": "Bataille du milieu", + "Move like a knight (v1)": "Bouger comme un cavalier (v1)", + "Move like a knight (v2)": "Bouger comme un cavalier (v2)", "Move twice": "Jouer deux coups", "Neverending rows": "Rangées sans fin", "Pawns move diagonally": "Les pions vont en diagonale", @@ -191,6 +193,5 @@ export const translations = { "Shoot pieces": "Tirez sur les pièces", "Squares disappear": "Les cases disparaissent", "Standard rules": "Règles usuelles", - "The knight transfers its powers": "Le cavalier transfère ses pouvoirs", "Unidentified pieces": "Pièces non identifiées" }; diff --git a/client/src/translations/rules/Knightrelay1/en.pug b/client/src/translations/rules/Knightrelay1/en.pug new file mode 100644 index 00000000..33ba1c2c --- /dev/null +++ b/client/src/translations/rules/Knightrelay1/en.pug @@ -0,0 +1,28 @@ +p.boxed + | Any piece guarded by a friendly knight can also move like a knight. + +p. + In addition to its normal abilities, a piece guarded by a knight can move like him. + On the following diagram, 1.Nf4 would checkmate because it guard the g6 queen. + If it is black to play, then 1...Rxe2 is forbidden because of the knight + immunity exception. Exceptions to the orthodox rules are the following: + +ul + li Knights cannot capture or be captured. + li Kings cannot be knight-relayed. + li Pawns cannot give check on last rank or promote with knight-relay. + +p These oddities excepted, orthodox rules apply. + +figure.diagram-container + .diagram + | fen:7k/8/6Q1/1n6/8/2r5/4N3/K7: + +h3 Source + +p + | These are the original N-relay or Knight-relay rules, described for example + a(href="https://www.chessvariants.com/diffmove.dir/nrelay.html") on this page + | . See also Knightrelay2. + +p Inventor: Mannis Charosh (1972) diff --git a/client/src/translations/rules/Knightrelay1/es.pug b/client/src/translations/rules/Knightrelay1/es.pug new file mode 100644 index 00000000..3ab9f8cc --- /dev/null +++ b/client/src/translations/rules/Knightrelay1/es.pug @@ -0,0 +1,34 @@ +p.boxed + | Cualquier parte protegida por un caballo también puede moverse como un caballo. + +p. + Además de sus capacidades normales, una pieza defendida por un caballo puede + muévete como él. + En el siguiente diagrama, 1.Nf4 sería jaque mate porque protege a la dama en g6. + Si son las negras para jugar, entonces 1...Rxe2 es posible gracias al caballo c8. + + Si son las negras para jugar, entonces 1...Txe2 está prohibido debido a + excepción de la inmunidad del caballo. Las excepciones a las reglas + ortodoxas son las siguientes: + +ul + li Los caballos no pueden capturar ni ser capturados. + li Los reyes no pueden ser retransmitidos por un caballo. + li. + Los peones no dan jaque en la última fila ni se promocionan + por relevo de caballo. + +figure.diagram-container + .diagram + | fen:7k/8/6Q1/1n6/8/2r5/4N3/K7: + +p Salvo estas rarezas, se aplican las reglas ortodoxas. + +h3 Fuente + +p + | Esta es la regla de origen de N-relay o Knight-relay, descrita por ejemplo + a(href="https://www.chessvariants.com/diffmove.dir/nrelay.html") en esta página + | . Ver también Knightrelay2. + +p Inventor: Mannis Charosh (1972) diff --git a/client/src/translations/rules/Knightrelay1/fr.pug b/client/src/translations/rules/Knightrelay1/fr.pug new file mode 100644 index 00000000..b9d2d8a0 --- /dev/null +++ b/client/src/translations/rules/Knightrelay1/fr.pug @@ -0,0 +1,32 @@ +p.boxed + | Toute pièce protégée par un cavalier peut aussi se déplacer comme un cavalier. + +p. + En plus de ses capacités normales, une pièce défendue par un cavalier peut se + déplacer comme lui. + Sur le diagramme suivant, 1.Nf4 ferait mat car il protège la dame en g6. + Si c'est aux noirs de jouer, alors 1...Txe2 est interdit à cause de + l'exception d'immunité du cavalier. + Les exceptions aux règles orthodoxes sont les suivantes : + +ul + li Les cavaliers ne peuvent capturer ou être capturés. + li Les rois ne peuvent pas être relayés par un cavalier. + li. + Les pions ne donnent pas échec sur la dernière rangée ni se promeuvent + par relais de cavalier. + +figure.diagram-container + .diagram + | fen:7k/8/6Q1/1n6/8/2r5/4N3/K7: + +p Ces bizarreries exceptées, les règles orthodoxes s'appliquent. + +h3 Source + +p + | Il s'agit de la règle d'origine du N-relay ou Knight-relay, décrite par exemple + a(href="https://www.chessvariants.com/diffmove.dir/nrelay.html") sur cette page + | . Voir aussi Knightrelay2. + +p Inventeur : Mannis Charosh (1972) diff --git a/client/src/translations/rules/Knightrelay/en.pug b/client/src/translations/rules/Knightrelay2/en.pug similarity index 100% rename from client/src/translations/rules/Knightrelay/en.pug rename to client/src/translations/rules/Knightrelay2/en.pug diff --git a/client/src/translations/rules/Knightrelay/es.pug b/client/src/translations/rules/Knightrelay2/es.pug similarity index 100% rename from client/src/translations/rules/Knightrelay/es.pug rename to client/src/translations/rules/Knightrelay2/es.pug diff --git a/client/src/translations/rules/Knightrelay/fr.pug b/client/src/translations/rules/Knightrelay2/fr.pug similarity index 100% rename from client/src/translations/rules/Knightrelay/fr.pug rename to client/src/translations/rules/Knightrelay2/fr.pug diff --git a/client/src/variants/Antiking.js b/client/src/variants/Antiking.js index c3fe0137..94c8af6d 100644 --- a/client/src/variants/Antiking.js +++ b/client/src/variants/Antiking.js @@ -68,28 +68,31 @@ export const VariantRules = class AntikingRules extends ChessRules { ); } - isAttacked(sq, colors) { + isAttacked(sq, color) { return ( - super.isAttacked(sq, colors) || this.isAttackedByAntiking(sq, colors) + super.isAttacked(sq, color) || + this.isAttackedByAntiking(sq, color) ); } - isAttackedByKing([x, y], colors) { - if (this.getPiece(x, y) == V.ANTIKING) return false; //antiking is not attacked by king + isAttackedByKing([x, y], color) { + // Antiking is not attacked by king: + if (this.getPiece(x, y) == V.ANTIKING) return false; return this.isAttackedBySlideNJump( [x, y], - colors, + color, V.KING, V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep" ); } - isAttackedByAntiking([x, y], colors) { - if ([V.KING, V.ANTIKING].includes(this.getPiece(x, y))) return false; //(anti)king is not attacked by antiking + isAttackedByAntiking([x, y], color) { + // (Anti)King is not attacked by antiking + if ([V.KING, V.ANTIKING].includes(this.getPiece(x, y))) return false; return this.isAttackedBySlideNJump( [x, y], - colors, + color, V.ANTIKING, V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep" @@ -99,14 +102,14 @@ export const VariantRules = class AntikingRules extends ChessRules { underCheck(color) { const oppCol = V.GetOppCol(color); let res = - this.isAttacked(this.kingPos[color], [oppCol]) || - !this.isAttacked(this.antikingPos[color], [oppCol]); + this.isAttacked(this.kingPos[color], oppCol) || + !this.isAttacked(this.antikingPos[color], oppCol); return res; } getCheckSquares(color) { let res = super.getCheckSquares(color); - if (!this.isAttacked(this.antikingPos[color], [V.GetOppCol(color)])) + if (!this.isAttacked(this.antikingPos[color], V.GetOppCol(color))) res.push(JSON.parse(JSON.stringify(this.antikingPos[color]))); return res; } @@ -136,8 +139,8 @@ export const VariantRules = class AntikingRules extends ChessRules { const color = this.turn; const oppCol = V.GetOppCol(color); if ( - !this.isAttacked(this.kingPos[color], [oppCol]) && - this.isAttacked(this.antikingPos[color], [oppCol]) + !this.isAttacked(this.kingPos[color], oppCol) && + this.isAttacked(this.antikingPos[color], oppCol) ) { return "1/2"; } diff --git a/client/src/variants/Atomic.js b/client/src/variants/Atomic.js index 51346da9..19521c2d 100644 --- a/client/src/variants/Atomic.js +++ b/client/src/variants/Atomic.js @@ -65,18 +65,21 @@ export const VariantRules = class AtomicRules extends ChessRules { return moves.concat(this.getCastleMoves([x, y])); } - isAttacked(sq, colors) { + isAttacked(sq, color) { if ( this.getPiece(sq[0], sq[1]) == V.KING && - this.isAttackedByKing(sq, colors) - ) - return false; //king cannot take... + this.isAttackedByKing(sq, color) + ) { + // A king next to the enemy king is immune to attacks + return false; + } return ( - this.isAttackedByPawn(sq, colors) || - this.isAttackedByRook(sq, colors) || - this.isAttackedByKnight(sq, colors) || - this.isAttackedByBishop(sq, colors) || - this.isAttackedByQueen(sq, colors) + this.isAttackedByPawn(sq, color) || + this.isAttackedByRook(sq, color) || + this.isAttackedByKnight(sq, color) || + this.isAttackedByBishop(sq, color) || + this.isAttackedByQueen(sq, color) + // No "attackedByKing": it cannot take ); } @@ -127,7 +130,7 @@ export const VariantRules = class AtomicRules extends ChessRules { // If opponent king disappeared, move is valid else if (this.kingPos[oppCol][0] < 0) res = false; // Otherwise, if we remain under check, move is not valid - else res = this.isAttacked(this.kingPos[color], [oppCol]); + else res = this.isAttacked(this.kingPos[color], oppCol); return res; } @@ -135,7 +138,7 @@ export const VariantRules = class AtomicRules extends ChessRules { let res = []; if ( this.kingPos[color][0] >= 0 && //king might have exploded - this.isAttacked(this.kingPos[color], [V.GetOppCol(color)]) + this.isAttacked(this.kingPos[color], V.GetOppCol(color)) ) { res = [JSON.parse(JSON.stringify(this.kingPos[color]))]; } @@ -150,7 +153,7 @@ export const VariantRules = class AtomicRules extends ChessRules { return color == "w" ? "0-1" : "1-0"; if (this.atLeastOneMove()) return "*"; - if (!this.isAttacked(kp, [V.GetOppCol(color)])) return "1/2"; + if (!this.isAttacked(kp, V.GetOppCol(color))) return "1/2"; return color == "w" ? "0-1" : "1-0"; //checkmate } }; diff --git a/client/src/variants/Baroque.js b/client/src/variants/Baroque.js index 246f5111..c7c100c6 100644 --- a/client/src/variants/Baroque.js +++ b/client/src/variants/Baroque.js @@ -362,7 +362,7 @@ export const VariantRules = class BaroqueRules extends ChessRules { // isAttacked() is OK because the immobilizer doesn't take - isAttackedByPawn([x, y], colors) { + isAttackedByPawn([x, y], color) { // Square (x,y) must be surroundable by two enemy pieces, // and one of them at least should be a pawn (moving). const dirs = [ @@ -375,12 +375,17 @@ export const VariantRules = class BaroqueRules extends ChessRules { const [i2, j2] = [x + dir[0], y + dir[1]]; //"after" if (V.OnBoard(i1, j1) && V.OnBoard(i2, j2)) { if ( - (this.board[i1][j1] != V.EMPTY && - colors.includes(this.getColor(i1, j1)) && - this.board[i2][j2] == V.EMPTY) || - (this.board[i2][j2] != V.EMPTY && - colors.includes(this.getColor(i2, j2)) && - this.board[i1][j1] == V.EMPTY) + ( + this.board[i1][j1] != V.EMPTY && + this.getColor(i1, j1) == color && + this.board[i2][j2] == V.EMPTY + ) + || + ( + this.board[i2][j2] != V.EMPTY && + this.getColor(i2, j2) == color && + this.board[i1][j1] == V.EMPTY + ) ) { // Search a movable enemy pawn landing on the empty square for (let step of steps) { @@ -392,7 +397,7 @@ export const VariantRules = class BaroqueRules extends ChessRules { } if ( V.OnBoard(i3, j3) && - colors.includes(this.getColor(i3, j3)) && + this.getColor(i3, j3) == color && this.getPiece(i3, j3) == V.PAWN && !this.isImmobilized([i3, j3]) ) { @@ -405,19 +410,18 @@ export const VariantRules = class BaroqueRules extends ChessRules { return false; } - isAttackedByRook([x, y], colors) { + isAttackedByRook([x, y], color) { // King must be on same column or row, // and a rook should be able to reach a capturing square - // colors contains only one element, giving the oppCol and thus king position - const sameRow = x == this.kingPos[colors[0]][0]; - const sameColumn = y == this.kingPos[colors[0]][1]; + const sameRow = x == this.kingPos[color][0]; + const sameColumn = y == this.kingPos[color][1]; if (sameRow || sameColumn) { // Look for the enemy rook (maximum 1) for (let i = 0; i < V.size.x; i++) { for (let j = 0; j < V.size.y; j++) { if ( this.board[i][j] != V.EMPTY && - colors.includes(this.getColor(i, j)) && + this.getColor(i, j) == color && this.getPiece(i, j) == V.ROOK ) { if (this.isImmobilized([i, j])) return false; //because only one rook @@ -438,7 +442,7 @@ export const VariantRules = class BaroqueRules extends ChessRules { return false; } - isAttackedByKnight([x, y], colors) { + isAttackedByKnight([x, y], color) { // Square (x,y) must be on same line as a knight, // and there must be empty square(s) behind. const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]); @@ -453,7 +457,7 @@ export const VariantRules = class BaroqueRules extends ChessRules { j -= step[1]; } if (V.OnBoard(i, j)) { - if (colors.includes(this.getColor(i, j))) { + if (this.getColor(i, j) == color) { if ( this.getPiece(i, j) == V.KNIGHT && !this.isImmobilized([i, j]) @@ -473,7 +477,7 @@ export const VariantRules = class BaroqueRules extends ChessRules { return false; } - isAttackedByBishop([x, y], colors) { + isAttackedByBishop([x, y], color) { // We cheat a little here: since this function is used exclusively for // the king, it's enough to check the immediate surrounding of the square. const adjacentSteps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]); @@ -482,7 +486,7 @@ export const VariantRules = class BaroqueRules extends ChessRules { if ( V.OnBoard(i, j) && this.board[i][j] != V.EMPTY && - colors.includes(this.getColor(i, j)) && + this.getColor(i, j) == color && this.getPiece(i, j) == V.BISHOP ) { return true; //bishops are never immobilized @@ -491,7 +495,7 @@ export const VariantRules = class BaroqueRules extends ChessRules { return false; } - isAttackedByQueen([x, y], colors) { + isAttackedByQueen([x, y], color) { // Square (x,y) must be adjacent to a queen, and the queen must have // some free space in the opposite direction from (x,y) const adjacentSteps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]); @@ -501,7 +505,7 @@ export const VariantRules = class BaroqueRules extends ChessRules { const sq1 = [x + step[0], y + step[1]]; if ( this.board[sq1[0]][sq1[1]] != V.EMPTY && - colors.includes(this.getColor(sq1[0], sq1[1])) && + this.getColor(sq1[0], sq1[1]) == color && this.getPiece(sq1[0], sq1[1]) == V.QUEEN && !this.isImmobilized(sq1) ) { @@ -512,7 +516,7 @@ export const VariantRules = class BaroqueRules extends ChessRules { return false; } - isAttackedByKing([x, y], colors) { + isAttackedByKing([x, y], color) { const steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]); for (let step of steps) { let rx = x + step[0], @@ -520,7 +524,7 @@ export const VariantRules = class BaroqueRules extends ChessRules { if ( V.OnBoard(rx, ry) && this.getPiece(rx, ry) === V.KING && - colors.includes(this.getColor(rx, ry)) && + this.getColor(rx, ry) == color && !this.isImmobilized([rx, ry]) ) { return true; diff --git a/client/src/variants/Berolina.js b/client/src/variants/Berolina.js index 4cdbb88b..ae150957 100644 --- a/client/src/variants/Berolina.js +++ b/client/src/variants/Berolina.js @@ -120,16 +120,14 @@ export const VariantRules = class BerolinaRules extends ChessRules { return moves; } - isAttackedByPawn([x, y], colors) { - for (let c of colors) { - let pawnShift = c == "w" ? 1 : -1; - if (x + pawnShift >= 0 && x + pawnShift < V.size.x) { - if ( - this.getPiece(x + pawnShift, y) == V.PAWN && - this.getColor(x + pawnShift, y) == c - ) { - return true; - } + isAttackedByPawn([x, y], color) { + let pawnShift = (color == "w" ? 1 : -1); + if (x + pawnShift >= 0 && x + pawnShift < V.size.x) { + if ( + this.getPiece(x + pawnShift, y) == V.PAWN && + this.getColor(x + pawnShift, y) == color + ) { + return true; } } return false; diff --git a/client/src/variants/Checkered.js b/client/src/variants/Checkered.js index 0e089f38..937b5741 100644 --- a/client/src/variants/Checkered.js +++ b/client/src/variants/Checkered.js @@ -1,4 +1,4 @@ -import { ChessRules } from "@/base_rules"; +import { ChessRules, Move, PiPo } from "@/base_rules"; export const VariantRules = class CheckeredRules extends ChessRules { static board2fen(b) { @@ -227,6 +227,86 @@ export const VariantRules = class CheckeredRules extends ChessRules { return moves; } + // Same as in base_rules but with an array given to isAttacked: + 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] >= V.size.y) 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)); + i = y; + do { + if ( + 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)))) + ) { + continue castlingCheck; + } + i += step; + } while (i != finalSquares[castleSide][0]); + + // 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; + } + canIplay(side, [x, y]) { return side == this.turn && [side, "c"].includes(this.getColor(x, y)); } @@ -293,9 +373,21 @@ export const VariantRules = class CheckeredRules extends ChessRules { return false; } + // colors: array, generally 'w' and 'c' or 'b' and 'c' + isAttacked(sq, colors) { + return ( + this.isAttackedByPawn(sq, colors) || + this.isAttackedByRook(sq, colors) || + this.isAttackedByKnight(sq, colors) || + this.isAttackedByBishop(sq, colors) || + this.isAttackedByQueen(sq, colors) || + this.isAttackedByKing(sq, colors) + ); + } + isAttackedByPawn([x, y], colors) { for (let c of colors) { - const color = c == "c" ? this.turn : c; + const color = (c == "c" ? this.turn : c); let pawnShift = color == "w" ? 1 : -1; if (x + pawnShift >= 0 && x + pawnShift < 8) { for (let i of [-1, 1]) { @@ -313,6 +405,62 @@ export const VariantRules = class CheckeredRules extends ChessRules { return false; } + isAttackedBySlideNJump([x, y], colors, piece, steps, oneStep) { + for (let step of steps) { + let rx = x + step[0], + ry = y + step[1]; + while (V.OnBoard(rx, ry) && this.board[rx][ry] == V.EMPTY && !oneStep) { + rx += step[0]; + ry += step[1]; + } + if ( + V.OnBoard(rx, ry) && + this.getPiece(rx, ry) === piece && + colors.includes(this.getColor(rx, ry)) + ) { + return true; + } + } + return false; + } + + isAttackedByRook(sq, colors) { + return this.isAttackedBySlideNJump(sq, colors, V.ROOK, V.steps[V.ROOK]); + } + + isAttackedByKnight(sq, colors) { + return this.isAttackedBySlideNJump( + sq, + colors, + V.KNIGHT, + V.steps[V.KNIGHT], + "oneStep" + ); + } + + isAttackedByBishop(sq, colors) { + return this.isAttackedBySlideNJump(sq, colors, V.BISHOP, V.steps[V.BISHOP]); + } + + isAttackedByQueen(sq, colors) { + return this.isAttackedBySlideNJump( + sq, + colors, + V.QUEEN, + V.steps[V.ROOK].concat(V.steps[V.BISHOP]) + ); + } + + isAttackedByKing(sq, colors) { + return this.isAttackedBySlideNJump( + sq, + colors, + V.KING, + V.steps[V.ROOK].concat(V.steps[V.BISHOP]), + "oneStep" + ); + } + underCheck(color) { return this.isAttacked(this.kingPos[color], [V.GetOppCol(color), "c"]); } diff --git a/client/src/variants/Circular.js b/client/src/variants/Circular.js index d6b431f6..5468dc54 100644 --- a/client/src/variants/Circular.js +++ b/client/src/variants/Circular.js @@ -180,25 +180,23 @@ export const VariantRules = class CircularRules extends ChessRules { }); } - isAttackedByPawn([x, y], colors) { - const pawnShift = 1; - const attackerRow = V.ComputeX(x + pawnShift); - for (let c of colors) { - for (let i of [-1, 1]) { - if ( - y + i >= 0 && - y + i < V.size.y && - this.getPiece(attackerRow, y + i) == V.PAWN && - this.getColor(attackerRow, y + i) == c - ) { - return true; - } + isAttackedByPawn([x, y], color) { + // pawn shift is always 1 (all pawns go the same way) + const attackerRow = V.ComputeX(x + 1); + for (let i of [-1, 1]) { + if ( + y + i >= 0 && + y + i < V.size.y && + this.getPiece(attackerRow, y + i) == V.PAWN && + this.getColor(attackerRow, y + i) == color + ) { + return true; } } return false; } - isAttackedBySlideNJump([x, y], colors, piece, steps, oneStep) { + isAttackedBySlideNJump([x, y], color, piece, steps, oneStep) { for (let step of steps) { let rx = V.ComputeX(x + step[0]), ry = y + step[1]; @@ -208,8 +206,8 @@ export const VariantRules = class CircularRules extends ChessRules { } if ( V.OnBoard(rx, ry) && - this.getPiece(rx, ry) === piece && - colors.includes(this.getColor(rx, ry)) + this.getPiece(rx, ry) == piece && + this.getColor(rx, ry) == color ) { return true; } diff --git a/client/src/variants/Cylinder.js b/client/src/variants/Cylinder.js index a7639aa6..44d1ab16 100644 --- a/client/src/variants/Cylinder.js +++ b/client/src/variants/Cylinder.js @@ -97,25 +97,23 @@ export const VariantRules = class CylinderRules extends ChessRules { return moves; } - isAttackedByPawn([x, y], colors) { - for (let c of colors) { - let pawnShift = c == "w" ? 1 : -1; - if (x + pawnShift >= 0 && x + pawnShift < V.size.x) { - for (let i of [-1, 1]) { - const nextFile = V.ComputeY(y + i); - if ( - this.getPiece(x + pawnShift, nextFile) == V.PAWN && - this.getColor(x + pawnShift, nextFile) == c - ) { - return true; - } + isAttackedByPawn([x, y], color) { + let pawnShift = (color == "w" ? 1 : -1); + if (x + pawnShift >= 0 && x + pawnShift < V.size.x) { + for (let i of [-1, 1]) { + const nextFile = V.ComputeY(y + i); + if ( + this.getPiece(x + pawnShift, nextFile) == V.PAWN && + this.getColor(x + pawnShift, nextFile) == color + ) { + return true; } } } return false; } - isAttackedBySlideNJump([x, y], colors, piece, steps, oneStep) { + isAttackedBySlideNJump([x, y], color, piece, steps, oneStep) { for (let step of steps) { let rx = x + step[0], ry = V.ComputeY(y + step[1]); @@ -125,8 +123,8 @@ export const VariantRules = class CylinderRules extends ChessRules { } if ( V.OnBoard(rx, ry) && - this.getPiece(rx, ry) === piece && - colors.includes(this.getColor(rx, ry)) + this.getPiece(rx, ry) == piece && + this.getColor(rx, ry) == color ) { return true; } diff --git a/client/src/variants/Eightpieces.js b/client/src/variants/Eightpieces.js index 76f72b0f..a0eed50d 100644 --- a/client/src/variants/Eightpieces.js +++ b/client/src/variants/Eightpieces.js @@ -635,7 +635,7 @@ export const VariantRules = class EightpiecesRules extends ChessRules { i = y; do { if ( - this.isAttacked([x, i], [oppCol]) || + 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)))) @@ -837,15 +837,15 @@ export const VariantRules = class EightpiecesRules extends ChessRules { this.sentryPush.pop(); } - isAttacked(sq, colors) { + isAttacked(sq, color) { return ( - super.isAttacked(sq, colors) || - this.isAttackedByLancer(sq, colors) || - this.isAttackedBySentry(sq, colors) + super.isAttacked(sq, color) || + this.isAttackedByLancer(sq, color) || + this.isAttackedBySentry(sq, color) ); } - isAttackedBySlideNJump([x, y], colors, piece, steps, oneStep) { + isAttackedBySlideNJump([x, y], color, piece, steps, oneStep) { for (let step of steps) { let rx = x + step[0], ry = y + step[1]; @@ -855,8 +855,8 @@ export const VariantRules = class EightpiecesRules extends ChessRules { } if ( V.OnBoard(rx, ry) && - this.getPiece(rx, ry) === piece && - colors.includes(this.getColor(rx, ry)) && + this.getPiece(rx, ry) == piece && + this.getColor(rx, ry) == color && !this.isImmobilized([rx, ry]) ) { return true; @@ -865,27 +865,25 @@ export const VariantRules = class EightpiecesRules extends ChessRules { return false; } - isAttackedByPawn([x, y], colors) { - for (let c of colors) { - const pawnShift = c == "w" ? 1 : -1; - if (x + pawnShift >= 0 && x + pawnShift < V.size.x) { - for (let i of [-1, 1]) { - if ( - y + i >= 0 && - y + i < V.size.y && - this.getPiece(x + pawnShift, y + i) == V.PAWN && - this.getColor(x + pawnShift, y + i) == c && - !this.isImmobilized([x + pawnShift, y + i]) - ) { - return true; - } + isAttackedByPawn([x, y], color) { + const pawnShift = (color == "w" ? 1 : -1); + if (x + pawnShift >= 0 && x + pawnShift < V.size.x) { + for (let i of [-1, 1]) { + if ( + y + i >= 0 && + y + i < V.size.y && + this.getPiece(x + pawnShift, y + i) == V.PAWN && + this.getColor(x + pawnShift, y + i) == color && + !this.isImmobilized([x + pawnShift, y + i]) + ) { + return true; } } } return false; } - isAttackedByLancer([x, y], colors) { + isAttackedByLancer([x, y], color) { for (let step of V.steps[V.ROOK].concat(V.steps[V.BISHOP])) { // If in this direction there are only enemy pieces and empty squares, // and we meet a lancer: can he reach us? @@ -896,7 +894,7 @@ export const VariantRules = class EightpiecesRules extends ChessRules { V.OnBoard(coord.x, coord.y) && ( this.board[coord.x][coord.y] == V.EMPTY || - colors.includes(this.getColor(coord.x, coord.y)) + this.getColor(coord.x, coord.y) == color ) ) { if ( @@ -978,17 +976,17 @@ export const VariantRules = class EightpiecesRules extends ChessRules { return false; } - isAttackedBySentry([x, y], colors) { + isAttackedBySentry([x, y], color) { // Attacked by sentry means it can self-take our king. // Just check diagonals of enemy sentry(ies), and if it reaches // one of our pieces: can I self-take? - const color = V.GetOppCol(colors[0]); + const myColor = V.GetOppCol(color); let candidates = []; for (let i=0; i m.vanish.length == 1); } - isAttackedByKnight(sq, colors) { + isAttackedByKnight(sq, color) { return this.isAttackedBySlideNJump( sq, - colors, + color, V.KNIGHT, V.steps[V.KNIGHT] ); diff --git a/client/src/variants/Grand.js b/client/src/variants/Grand.js index e6333cce..d4f620f7 100644 --- a/client/src/variants/Grand.js +++ b/client/src/variants/Grand.js @@ -255,20 +255,20 @@ export const VariantRules = class GrandRules extends ChessRules { ); } - isAttacked(sq, colors) { + isAttacked(sq, color) { return ( - super.isAttacked(sq, colors) || - this.isAttackedByMarshall(sq, colors) || - this.isAttackedByCardinal(sq, colors) + super.isAttacked(sq, color) || + this.isAttackedByMarshall(sq, color) || + this.isAttackedByCardinal(sq, color) ); } - isAttackedByMarshall(sq, colors) { + isAttackedByMarshall(sq, color) { return ( - this.isAttackedBySlideNJump(sq, colors, V.MARSHALL, V.steps[V.ROOK]) || + this.isAttackedBySlideNJump(sq, color, V.MARSHALL, V.steps[V.ROOK]) || this.isAttackedBySlideNJump( sq, - colors, + color, V.MARSHALL, V.steps[V.KNIGHT], "oneStep" @@ -276,12 +276,12 @@ export const VariantRules = class GrandRules extends ChessRules { ); } - isAttackedByCardinal(sq, colors) { + isAttackedByCardinal(sq, color) { return ( - this.isAttackedBySlideNJump(sq, colors, V.CARDINAL, V.steps[V.BISHOP]) || + this.isAttackedBySlideNJump(sq, color, V.CARDINAL, V.steps[V.BISHOP]) || this.isAttackedBySlideNJump( sq, - colors, + color, V.CARDINAL, V.steps[V.KNIGHT], "oneStep" diff --git a/client/src/variants/Grasshopper.js b/client/src/variants/Grasshopper.js index 82caa9f4..ec52d9b3 100644 --- a/client/src/variants/Grasshopper.js +++ b/client/src/variants/Grasshopper.js @@ -91,14 +91,14 @@ export const VariantRules = class GrasshopperRules extends ChessRules { return moves; } - isAttacked(sq, colors) { + isAttacked(sq, color) { return ( - super.isAttacked(sq, colors) || - this.isAttackedByGrasshopper(sq, colors) + super.isAttacked(sq, color) || + this.isAttackedByGrasshopper(sq, color) ); } - isAttackedByGrasshopper([x, y], colors) { + isAttackedByGrasshopper([x, y], color) { // Reversed process: is there an adjacent obstacle, // and a grasshopper next in the same line? for (const step of V.steps[V.ROOK].concat(V.steps[V.BISHOP])) { @@ -116,7 +116,7 @@ export const VariantRules = class GrasshopperRules extends ChessRules { if ( V.OnBoard(i, j) && this.getPiece(i, j) == V.GRASSHOPPER && - colors.includes(this.getColor(i, j)) + this.getColor(i, j) == color ) { return true; } diff --git a/client/src/variants/Knightmate.js b/client/src/variants/Knightmate.js index 0ac7ebed..a8d6dc6c 100644 --- a/client/src/variants/Knightmate.js +++ b/client/src/variants/Knightmate.js @@ -41,31 +41,31 @@ export const VariantRules = class KnightmateRules extends ChessRules { return super.getPotentialKnightMoves(sq).concat(super.getCastleMoves(sq)); } - isAttacked(sq, colors) { + isAttacked(sq, color) { return ( - this.isAttackedByCommoner(sq, colors) || - this.isAttackedByPawn(sq, colors) || - this.isAttackedByRook(sq, colors) || - this.isAttackedByBishop(sq, colors) || - this.isAttackedByQueen(sq, colors) || - this.isAttackedByKing(sq, colors) + this.isAttackedByCommoner(sq, color) || + this.isAttackedByPawn(sq, color) || + this.isAttackedByRook(sq, color) || + this.isAttackedByBishop(sq, color) || + this.isAttackedByQueen(sq, color) || + this.isAttackedByKing(sq, color) ); } - isAttackedByKing(sq, colors) { + isAttackedByKing(sq, color) { return this.isAttackedBySlideNJump( sq, - colors, + color, V.KING, V.steps[V.KNIGHT], "oneStep" ); } - isAttackedByCommoner(sq, colors) { + isAttackedByCommoner(sq, color) { return this.isAttackedBySlideNJump( sq, - colors, + color, V.COMMONER, V.steps[V.ROOK].concat(V.steps[V.BISHOP]), "oneStep" diff --git a/client/src/variants/Knightrelay1.js b/client/src/variants/Knightrelay1.js new file mode 100644 index 00000000..3e7bf38c --- /dev/null +++ b/client/src/variants/Knightrelay1.js @@ -0,0 +1,139 @@ +import { ChessRules } from "@/base_rules"; + +export const VariantRules = class Knightrelay1Rules extends ChessRules { + static get HasEnpassant() { + return false; + } + + getPotentialMovesFrom([x, y]) { + let moves = super.getPotentialMovesFrom([x, y]); + + // Expand possible moves if guarded by a knight, and is not a king: + const piece = this.getPiece(x,y); + if (![V.KNIGHT,V.KING].includes(piece)) { + const color = this.turn; + let guardedByKnight = false; + for (const step of V.steps[V.KNIGHT]) { + if ( + V.OnBoard(x+step[0],y+step[1]) && + this.getPiece(x+step[0],y+step[1]) == V.KNIGHT && + this.getColor(x+step[0],y+step[1]) == color + ) { + guardedByKnight = true; + break; + } + } + if (guardedByKnight) { + const lastRank = (color == "w" ? 0 : V.size.x - 1); + for (const step of V.steps[V.KNIGHT]) { + if ( + V.OnBoard(x+step[0],y+step[1]) && + this.getColor(x+step[0],y+step[1]) != color && + // Pawns cannot promote by knight-relay + (piece != V.PAWN || x+step[0] != lastRank) + ) { + moves.push(this.getBasicMove([x,y], [x+step[0],y+step[1]])); + } + } + } + } + + // Forbid captures of knights (invincible in this variant) + return moves.filter(m => { + return ( + m.vanish.length == 1 || + m.appear.length == 2 || + m.vanish[1].p != V.KNIGHT + ); + }); + } + + getPotentialKnightMoves(sq) { + // Knights don't capture: + return super.getPotentialKnightMoves(sq).filter(m => m.vanish.length == 1); + } + + isAttacked(sq, color) { + if (super.isAttacked(sq, color)) return true; + + // Check if a (non-knight) piece at knight distance + // is guarded by a knight (and thus attacking) + // --> Except for pawns targetting last rank. + const x = sq[0], + y = sq[1]; + // Last rank for me, that is to say oppCol of color: + const lastRank = (color == 'w' ? V.size.x - 1 : 0); + for (const step of V.steps[V.KNIGHT]) { + if ( + V.OnBoard(x+step[0],y+step[1]) && + this.getColor(x+step[0],y+step[1]) == color + ) { + const piece = this.getPiece(x+step[0],y+step[1]); + if (piece != V.KNIGHT && (piece != V.PAWN || x != lastRank)) { + for (const step2 of V.steps[V.KNIGHT]) { + const xx = x+step[0]+step2[0], + yy = y+step[1]+step2[1]; + if ( + V.OnBoard(xx,yy) && + this.getColor(xx,yy) == color && + this.getPiece(xx,yy) == V.KNIGHT + ) { + return true; + } + } + } + } + } + + return false; + } + + isAttackedByKnight(sq, color) { + // Knights don't attack + return false; + } + + static get VALUES() { + return { + p: 1, + r: 5, + n: 7, //the knight is valuable + b: 3, + q: 9, + k: 1000 + }; + } + + static get SEARCH_DEPTH() { + return 2; + } + + getNotation(move) { + if (move.appear.length == 2 && move.appear[0].p == V.KING) + // Castle + return move.end.y < move.start.y ? "0-0-0" : "0-0"; + + // Translate final and initial square + const initSquare = V.CoordsToSquare(move.start); + const finalSquare = V.CoordsToSquare(move.end); + const piece = this.getPiece(move.start.x, move.start.y); + + // Since pieces and pawns could move like knight, indicate start and end squares + let notation = + piece.toUpperCase() + + initSquare + + (move.vanish.length > move.appear.length ? "x" : "") + + finalSquare + + if ( + piece == V.PAWN && + move.appear.length > 0 && + move.appear[0].p != V.PAWN + ) { + // Promotion + notation += "=" + move.appear[0].p.toUpperCase(); + } + + return notation; + } +}; diff --git a/client/src/variants/Knightrelay.js b/client/src/variants/Knightrelay2.js similarity index 91% rename from client/src/variants/Knightrelay.js rename to client/src/variants/Knightrelay2.js index 43aef74d..9fa5dcca 100644 --- a/client/src/variants/Knightrelay.js +++ b/client/src/variants/Knightrelay2.js @@ -1,6 +1,6 @@ import { ChessRules } from "@/base_rules"; -export const VariantRules = class KnightrelayRules extends ChessRules { +export const VariantRules = class Knightrelay2Rules extends ChessRules { getPotentialMovesFrom([x, y]) { let moves = super.getPotentialMovesFrom([x, y]); @@ -46,9 +46,8 @@ export const VariantRules = class KnightrelayRules extends ChessRules { return moves; } - isAttacked(sq, colors) { - if (super.isAttacked(sq, colors)) - return true; + isAttacked(sq, color) { + if (super.isAttacked(sq, color)) return true; // Check if a (non-knight) piece at knight distance // is guarded by a knight (and thus attacking) @@ -57,7 +56,7 @@ export const VariantRules = class KnightrelayRules extends ChessRules { for (const step of V.steps[V.KNIGHT]) { if ( V.OnBoard(x+step[0],y+step[1]) && - colors.includes(this.getColor(x+step[0],y+step[1])) && + this.getColor(x+step[0],y+step[1]) == color && this.getPiece(x+step[0],y+step[1]) != V.KNIGHT ) { for (const step2 of V.steps[V.KNIGHT]) { @@ -65,7 +64,7 @@ export const VariantRules = class KnightrelayRules extends ChessRules { yy = y+step[1]+step2[1]; if ( V.OnBoard(xx,yy) && - colors.includes(this.getColor(xx,yy)) && + this.getColor(xx,yy) == color && this.getPiece(xx,yy) == V.KNIGHT ) { return true; diff --git a/client/src/variants/Royalrace.js b/client/src/variants/Royalrace.js index f5c7550a..346ca5f0 100644 --- a/client/src/variants/Royalrace.js +++ b/client/src/variants/Royalrace.js @@ -155,16 +155,16 @@ export const VariantRules = class RoyalraceRules extends ChessRules { }); } - isAttackedByPawn([x, y], colors) { - const pawnShift = 1; - if (x + pawnShift < V.size.x) { - for (let c of colors) { + isAttackedByPawn([x, y], color) { + // Pawns can capture forward and backward: + for (let pawnShift of [-1, 1]) { + if (0 < x + pawnShift && x + pawnShift < V.size.x) { for (let i of [-1, 1]) { if ( y + i >= 0 && y + i < V.size.y && this.getPiece(x + pawnShift, y + i) == V.PAWN && - this.getColor(x + pawnShift, y + i) == c + this.getColor(x + pawnShift, y + i) == color ) { return true; } @@ -174,10 +174,10 @@ export const VariantRules = class RoyalraceRules extends ChessRules { return false; } - isAttackedByKnight(sq, colors) { + isAttackedByKnight(sq, color) { return this.isAttackedBySlideNJump( sq, - colors, + color, V.KNIGHT, V.steps[V.KNIGHT] ); diff --git a/client/src/variants/Shatranj.js b/client/src/variants/Shatranj.js index c364b816..969b8197 100644 --- a/client/src/variants/Shatranj.js +++ b/client/src/variants/Shatranj.js @@ -98,20 +98,20 @@ export const VariantRules = class ShatranjRules extends ChessRules { ); } - isAttackedByBishop(sq, colors) { + isAttackedByBishop(sq, color) { return this.isAttackedBySlideNJump( sq, - colors, + color, V.BISHOP, V.ElephantSteps, "oneStep" ); } - isAttackedByQueen(sq, colors) { + isAttackedByQueen(sq, color) { return this.isAttackedBySlideNJump( sq, - colors, + color, V.QUEEN, V.steps[V.BISHOP], "oneStep" @@ -157,7 +157,7 @@ export const VariantRules = class ShatranjRules extends ChessRules { // 2 enemy units or more: I lose return getScoreLost(); // I don't have any piece, my opponent have one: can I take it? - if (this.isAttacked(piecesLeft[oppCol].square, [color])) + if (this.isAttacked(piecesLeft[oppCol].square, color)) // Yes! But I still need to take it return "*"; // No :( diff --git a/client/src/variants/Wildebeest.js b/client/src/variants/Wildebeest.js index bf5c178b..f4e3b83c 100644 --- a/client/src/variants/Wildebeest.js +++ b/client/src/variants/Wildebeest.js @@ -196,28 +196,28 @@ export const VariantRules = class WildebeestRules extends ChessRules { ); } - isAttacked(sq, colors) { + isAttacked(sq, color) { return ( - super.isAttacked(sq, colors) || - this.isAttackedByCamel(sq, colors) || - this.isAttackedByWildebeest(sq, colors) + super.isAttacked(sq, color) || + this.isAttackedByCamel(sq, color) || + this.isAttackedByWildebeest(sq, color) ); } - isAttackedByCamel(sq, colors) { + isAttackedByCamel(sq, color) { return this.isAttackedBySlideNJump( sq, - colors, + color, V.CAMEL, V.steps[V.CAMEL], "oneStep" ); } - isAttackedByWildebeest(sq, colors) { + isAttackedByWildebeest(sq, color) { return this.isAttackedBySlideNJump( sq, - colors, + color, V.WILDEBEEST, V.steps[V.KNIGHT].concat(V.steps[V.CAMEL]), "oneStep" diff --git a/client/src/variants/Wormhole.js b/client/src/variants/Wormhole.js index 462cb3bf..41c934a3 100644 --- a/client/src/variants/Wormhole.js +++ b/client/src/variants/Wormhole.js @@ -201,13 +201,13 @@ export const VariantRules = class WormholeRules extends ChessRules { return this.getJumpMoves(sq, V.steps[V.KING]); } - isAttackedByJump([x, y], colors, piece, steps) { + isAttackedByJump([x, y], color, piece, steps) { for (let step of steps) { const sq = this.getSquareAfter([x,y], step); if ( sq && - this.getPiece(sq[0], sq[1]) === piece && - colors.includes(this.getColor(sq[0], sq[1])) + this.getPiece(sq[0], sq[1]) == piece && + this.getColor(sq[0], sq[1]) == color ) { return true; } @@ -215,53 +215,51 @@ export const VariantRules = class WormholeRules extends ChessRules { return false; } - isAttackedByPawn([x, y], colors) { - for (let c of colors) { - const pawnShift = c == "w" ? 1 : -1; - for (let i of [-1, 1]) { - const sq = this.getSquareAfter([x,y], [pawnShift,i]); - if ( - sq && - this.getPiece(sq[0], sq[1]) == V.PAWN && - this.getColor(sq[0], sq[1]) == c - ) { - return true; - } + isAttackedByPawn([x, y], color) { + const pawnShift = (color == "w" ? 1 : -1); + for (let i of [-1, 1]) { + const sq = this.getSquareAfter([x,y], [pawnShift,i]); + if ( + sq && + this.getPiece(sq[0], sq[1]) == V.PAWN && + this.getColor(sq[0], sq[1]) == color + ) { + return true; } } return false; } - isAttackedByRook(sq, colors) { - return this.isAttackedByJump(sq, colors, V.ROOK, V.steps[V.ROOK]); + isAttackedByRook(sq, color) { + return this.isAttackedByJump(sq, color, V.ROOK, V.steps[V.ROOK]); } - isAttackedByKnight(sq, colors) { + isAttackedByKnight(sq, color) { // NOTE: knight attack is not symmetric in this variant: // steps order need to be reversed. return this.isAttackedByJump( sq, - colors, + color, V.KNIGHT, V.steps[V.KNIGHT].map(s => s.reverse()) ); } - isAttackedByBishop(sq, colors) { - return this.isAttackedByJump(sq, colors, V.BISHOP, V.steps[V.BISHOP]); + isAttackedByBishop(sq, color) { + return this.isAttackedByJump(sq, color, V.BISHOP, V.steps[V.BISHOP]); } - isAttackedByQueen(sq, colors) { + isAttackedByQueen(sq, color) { return this.isAttackedByJump( sq, - colors, + color, V.QUEEN, V.steps[V.ROOK].concat(V.steps[V.BISHOP]) ); } - isAttackedByKing(sq, colors) { - return this.isAttackedByJump(sq, colors, V.KING, V.steps[V.KING]); + isAttackedByKing(sq, color) { + return this.isAttackedByJump(sq, color, V.KING, V.steps[V.KING]); } // NOTE: altering move in getBasicMove doesn't work and wouldn't be logical. diff --git a/client/src/views/Hall.vue b/client/src/views/Hall.vue index 37558a93..eb8464ae 100644 --- a/client/src/views/Hall.vue +++ b/client/src/views/Hall.vue @@ -854,8 +854,8 @@ export default { cursor: this.cursor }, success: (res) => { - if (res.games.length > 0) { - const L = res.games.length; + const L = res.games.length; + if (L > 0) { this.cursor = res.games[L - 1].created; let moreGames = res.games.map(g => { const vname = this.getVname(g.vid); diff --git a/client/src/views/MyGames.vue b/client/src/views/MyGames.vue index 1f1da59e..a9e69f65 100644 --- a/client/src/views/MyGames.vue +++ b/client/src/views/MyGames.vue @@ -120,8 +120,8 @@ export default { credentials: true, data: { cursor: this.cursor }, success: (res2) => { - if (res2.games.length > 0) { - const L = res2.games.length; + const L = res2.games.length; + if (L > 0) { this.cursor = res2.games[L - 1].created; let completedGames = res2.games; completedGames.forEach(g => g.type = "corr"); @@ -295,8 +295,8 @@ export default { credentials: true, data: { cursor: this.cursor }, success: (res) => { - if (res.games.length > 0) { - const L = res.games.length; + const L = res.games.length; + if (L > 0) { this.cursor = res.games[L - 1].created; let moreGames = res.games; moreGames.forEach(g => g.type = "corr"); diff --git a/client/src/views/News.vue b/client/src/views/News.vue index 0e3c3c7a..66a52491 100644 --- a/client/src/views/News.vue +++ b/client/src/views/News.vue @@ -68,6 +68,7 @@ export default { this.newsList = res.newsList; const L = res.newsList.length; if (L > 0) this.cursor = res.newsList[L - 1].added; + else this.hasMore = false; } } ); @@ -169,10 +170,10 @@ export default { { data: { cursor: this.cursor }, success: (res) => { - if (res.newsList.length > 0) { + const L = res.newsList.length; + if (L > 0) { this.newsList = this.newsList.concat(res.newsList); - const L = res.newsList.length; - if (L > 0) this.cursor = res.newsList[L - 1].added; + this.cursor = res.newsList[L - 1].added; } else this.hasMore = false; } } diff --git a/client/src/views/Problems.vue b/client/src/views/Problems.vue index 3b53a1a0..2ea875e0 100644 --- a/client/src/views/Problems.vue +++ b/client/src/views/Problems.vue @@ -96,6 +96,11 @@ main td {{ p.vname }} td {{ firstChars(p.instruction) }} td {{ p.id }} + button#loadMoreBtn( + v-if="hasMore" + @click="loadMore()" + ) + | {{ st.tr["Load more"] }} BaseGame( ref="basegame" v-if="showOne" @@ -136,6 +141,10 @@ export default { loadedVar: 0, //corresponding to loaded V selectedVar: 0, //to filter problems based on variant problems: [], + // timestamp of oldest showed problem: + cursor: Number.MAX_SAFE_INTEGER, + // hasMore == TRUE: a priori there could be more problems to load + hasMore: true, onlyMines: false, showOne: false, infoMsg: "", @@ -150,40 +159,18 @@ export default { "/problems", "GET", { + data: { cursor: this.cursor }, success: (res) => { - // Show newest problem first: - this.problems = res.problems.sort((p1, p2) => p2.added - p1.added); - if (this.st.variants.length > 0) - this.problems.forEach(p => this.setVname(p)); - // Retrieve all problems' authors' names - let names = {}; - this.problems.forEach(p => { - if (p.uid != this.st.user.id) names[p.uid] = ""; - else p.uname = this.st.user.name; - }); + // The returned list is sorted from most recent to oldest + this.problems = res.problems; + const L = res.problems.length; + if (L > 0) this.cursor = res.problems[L - 1].added; + else this.hasMore = false; const showOneIfPid = () => { const pid = this.$route.query["id"]; if (!!pid) this.showProblem(this.problems.find(p => p.id == pid)); }; - if (Object.keys(names).length > 0) { - ajax( - "/users", - "GET", - { - data: { ids: Object.keys(names).join(",") }, - success: (res2) => { - res2.users.forEach(u => { - names[u.id] = u.name; - }); - this.problems.forEach(p => { - if (!p.uname) - p.uname = names[p.uid]; - }); - showOneIfPid(); - } - } - ); - } else showOneIfPid(); + this.decorate(this.problems, showOneIfPid); } } ); @@ -215,6 +202,36 @@ export default { setVname: function(prob) { prob.vname = this.st.variants.find(v => v.id == prob.vid).name; }, + // Add vname and user names: + decorate: function(problems, callback) { + if (this.st.variants.length > 0) + problems.forEach(p => this.setVname(p)); + // Retrieve all problems' authors' names + let names = {}; + problems.forEach(p => { + if (p.uid != this.st.user.id) names[p.uid] = ""; + else p.uname = this.st.user.name; + }); + if (Object.keys(names).length > 0) { + ajax( + "/users", + "GET", + { + data: { ids: Object.keys(names).join(",") }, + success: (res2) => { + res2.users.forEach(u => { + names[u.id] = u.name; + }); + problems.forEach(p => { + if (!p.uname) + p.uname = names[p.uid]; + }); + if (!!callback) callback(); + } + } + ); + } else if (!!callback) callback(); + }, firstChars: function(text) { let preparedText = text // Replace line jumps and
by spaces @@ -379,6 +396,23 @@ export default { } ); } + }, + loadMore: function() { + ajax( + "/problems", + "GET", + { + data: { cursor: this.cursor }, + success: (res) => { + const L = res.problems.length; + if (L > 0) { + this.decorate(res.problems); + this.problems = this.problems.concat(res.problems); + this.cursor = res.problems[L - 1].added; + } else this.hasMore = false; + } + } + ); } } }; @@ -402,6 +436,10 @@ textarea table#tProblems max-height: 100% +button#loadMoreBtn + display: block + margin: 0 auto + #controls margin: 0 width: 100% diff --git a/server/db/populate.sql b/server/db/populate.sql index 6a233f44..72dff2ec 100644 --- a/server/db/populate.sql +++ b/server/db/populate.sql @@ -26,7 +26,8 @@ insert or ignore into Variants (name,description) values ('Hidden', 'Unidentified pieces'), ('Hiddenqueen', 'Queen disguised as a pawn'), ('Knightmate', 'Mate the knight'), - ('Knightrelay', 'The knight transfers its powers'), + ('Knightrelay1', 'Move like a knight (v1)'), + ('Knightrelay2', 'Move like a knight (v2)'), ('Losers', 'Get strong at self-mate'), ('Magnetic', 'Laws of attraction'), ('Marseille', 'Move twice'), diff --git a/server/models/News.js b/server/models/News.js index 2dde566f..c10f71f2 100644 --- a/server/models/News.js +++ b/server/models/News.js @@ -33,7 +33,7 @@ const NewsModel = "WHERE added < " + cursor + " " + "ORDER BY added DESC " + "LIMIT 10"; //TODO: 10 currently hard-coded - db.all(query, (err,newsList) => { + db.all(query, (err, newsList) => { cb(err, newsList); }); }); @@ -47,7 +47,7 @@ const NewsModel = "FROM News " + "ORDER BY added DESC " + "LIMIT 1"; - db.get(query, (err,ts) => { + db.get(query, (err, ts) => { cb(err, ts); }); }); diff --git a/server/models/Problem.js b/server/models/Problem.js index 5c9af0b1..bcbefc5f 100644 --- a/server/models/Problem.js +++ b/server/models/Problem.js @@ -33,12 +33,15 @@ const ProblemModel = { }); }, - getAll: function(cb) { + getNext: function(cursor, cb) { db.serialize(function() { const query = "SELECT * " + - "FROM Problems"; - db.all(query, (err,problems) => { + "FROM Problems " + + "WHERE added < " + cursor + " " + + "ORDER BY added DESC " + + "LIMIT 20"; //TODO: 20 is arbitrary + db.all(query, (err, problems) => { cb(err, problems); }); }); @@ -50,7 +53,7 @@ const ProblemModel = { "SELECT * " + "FROM Problems " + "WHERE id = " + id; - db.get(query, (err,problem) => { + db.get(query, (err, problem) => { cb(err, problem); }); }); @@ -66,7 +69,7 @@ const ProblemModel = { "instruction = ?," + "solution = ? " + "WHERE id = " + prob.id + " AND uid = " + uid; - db.run(query, [prob.instruction,prob.solution]); + db.run(query, [prob.instruction, prob.solution]); }); }, diff --git a/server/routes/news.js b/server/routes/news.js index 4c2a74e5..51d8c824 100644 --- a/server/routes/news.js +++ b/server/routes/news.js @@ -15,7 +15,7 @@ router.post("/news", access.logged, access.ajax, (req,res) => { router.get("/news", access.ajax, (req,res) => { const cursor = req.query["cursor"]; - if (!!cursor.match(/^[0-9]+$/)) { + if (!!cursor && !!cursor.match(/^[0-9]+$/)) { NewsModel.getNext(cursor, (err, newsList) => { res.json(err || { newsList: newsList }); }); diff --git a/server/routes/problems.js b/server/routes/problems.js index 6cebb8f0..8a82462e 100644 --- a/server/routes/problems.js +++ b/server/routes/problems.js @@ -22,12 +22,13 @@ router.post("/problems", access.logged, access.ajax, (req,res) => { router.get("/problems", access.ajax, (req,res) => { const probId = req.query["pid"]; - if (probId && probId.match(/^[0-9]+$/)) { - ProblemModel.getOne(req.query["pid"], (err,problem) => { + const cursor = req.query["cursor"]; + if (!!probId && !!probId.match(/^[0-9]+$/)) { + ProblemModel.getOne(req.query["pid"], (err, problem) => { res.json(err || {problem: problem}); }); - } else { - ProblemModel.getAll((err,problems) => { + } else if (!!cursor && !!cursor.match(/^[0-9]+$/)) { + ProblemModel.getNext(cursor, (err, problems) => { res.json(err || { problems: problems }); }); } -- 2.44.0