Add Knightrelay1. Some fixes. Move odd 'isAttackedBy_multiple_colors' to Checkered...
authorBenjamin Auder <benjamin.auder@somewhere>
Tue, 17 Mar 2020 00:22:41 +0000 (01:22 +0100)
committerBenjamin Auder <benjamin.auder@somewhere>
Tue, 17 Mar 2020 00:22:41 +0000 (01:22 +0100)
38 files changed:
TODO
client/src/base_rules.js
client/src/translations/en.js
client/src/translations/es.js
client/src/translations/fr.js
client/src/translations/rules/Knightrelay1/en.pug [new file with mode: 0644]
client/src/translations/rules/Knightrelay1/es.pug [new file with mode: 0644]
client/src/translations/rules/Knightrelay1/fr.pug [new file with mode: 0644]
client/src/translations/rules/Knightrelay2/en.pug [moved from client/src/translations/rules/Knightrelay/en.pug with 100% similarity]
client/src/translations/rules/Knightrelay2/es.pug [moved from client/src/translations/rules/Knightrelay/es.pug with 100% similarity]
client/src/translations/rules/Knightrelay2/fr.pug [moved from client/src/translations/rules/Knightrelay/fr.pug with 100% similarity]
client/src/variants/Antiking.js
client/src/variants/Atomic.js
client/src/variants/Baroque.js
client/src/variants/Berolina.js
client/src/variants/Checkered.js
client/src/variants/Circular.js
client/src/variants/Cylinder.js
client/src/variants/Eightpieces.js
client/src/variants/Enpassant.js
client/src/variants/Grand.js
client/src/variants/Grasshopper.js
client/src/variants/Knightmate.js
client/src/variants/Knightrelay1.js [new file with mode: 0644]
client/src/variants/Knightrelay2.js [moved from client/src/variants/Knightrelay.js with 91% similarity]
client/src/variants/Royalrace.js
client/src/variants/Shatranj.js
client/src/variants/Wildebeest.js
client/src/variants/Wormhole.js
client/src/views/Hall.vue
client/src/views/MyGames.vue
client/src/views/News.vue
client/src/views/Problems.vue
server/db/populate.sql
server/models/News.js
server/models/Problem.js
server/routes/news.js
server/routes/problems.js

diff --git a/TODO b/TODO
index 86a4933..6916bdf 100644 (file)
--- 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
index 752d8ee..fad1853 100644 (file)
@@ -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");
   }
 
   ///////////////
index 5a14d3f..593d68b 100644 (file)
@@ -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"
 };
index 3444475..84a3295 100644 (file)
@@ -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"
 };
index 8c1f7ae..15575cb 100644 (file)
@@ -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 (file)
index 0000000..33ba1c2
--- /dev/null
@@ -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 (file)
index 0000000..3ab9f8c
--- /dev/null
@@ -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 (file)
index 0000000..b9d2d8a
--- /dev/null
@@ -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)
index c3fe013..94c8af6 100644 (file)
@@ -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";
     }
index 51346da..19521c2 100644 (file)
@@ -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
   }
 };
index 246f511..c7c100c 100644 (file)
@@ -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;
index 4cdbb88..ae15095 100644 (file)
@@ -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;
index 0e089f3..937b574 100644 (file)
@@ -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"]);
   }
index d6b431f..5468dc5 100644 (file)
@@ -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;
       }
index a7639aa..44d1ab1 100644 (file)
@@ -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;
       }
index 76f72b0..a0eed50 100644 (file)
@@ -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<V.size.x; i++) {
       for (let j=0; j<V.size.y; j++) {
         if (
           this.getPiece(i,j) == V.SENTRY &&
-          colors.includes(this.getColor(i,j)) &&
+          this.getColor(i,j) == color &&
           !this.isImmobilized([i, j])
         ) {
           for (let step of V.steps[V.BISHOP]) {
@@ -1002,7 +1000,7 @@ export const VariantRules = class EightpiecesRules extends ChessRules {
             }
             if (
               V.OnBoard(sq[0], sq[1]) &&
-              this.getColor(sq[0], sq[1]) == color
+              this.getColor(sq[0], sq[1]) == myColor
             ) {
               candidates.push([ sq[0], sq[1] ]);
             }
index 4b5ed33..dc181ca 100644 (file)
@@ -203,10 +203,10 @@ export const VariantRules = class EnpassantRules extends ChessRules {
     return filteredMoves.filter(m => m.vanish.length == 1);
   }
 
-  isAttackedByKnight(sq, colors) {
+  isAttackedByKnight(sq, color) {
     return this.isAttackedBySlideNJump(
       sq,
-      colors,
+      color,
       V.KNIGHT,
       V.steps[V.KNIGHT]
     );
index e6333cc..d4f620f 100644 (file)
@@ -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"
index 82caa9f..ec52d9b 100644 (file)
@@ -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;
         }
index 0ac7ebe..a8d6dc6 100644 (file)
@@ -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 (file)
index 0000000..3e7bf38
--- /dev/null
@@ -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;
+  }
+};
similarity index 91%
rename from client/src/variants/Knightrelay.js
rename to client/src/variants/Knightrelay2.js
index 43aef74..9fa5dcc 100644 (file)
@@ -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;
index f5c7550..346ca5f 100644 (file)
@@ -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]
     );
index c364b81..969b819 100644 (file)
@@ -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 :(
index bf5c178..f4e3b83 100644 (file)
@@ -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"
index 462cb3b..41c934a 100644 (file)
@@ -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.
index 37558a9..eb8464a 100644 (file)
@@ -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);
index 1f1da59..a9e69f6 100644 (file)
@@ -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");
index 0e3c3c7..66a5249 100644 (file)
@@ -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;
           }
         }
index 3b53a1a..2ea875e 100644 (file)
@@ -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 <br> 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%
index 6a233f4..72dff2e 100644 (file)
@@ -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'),
index 2dde566..c10f71f 100644 (file)
@@ -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);
       });
     });
index 5c9af0b..bcbefc5 100644 (file)
@@ -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]);
     });
   },
 
index 4c2a74e..51d8c82 100644 (file)
@@ -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 });
     });
index 6cebb8f..8a82462 100644 (file)
@@ -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 });
     });
   }