Draft Allmate variant
authorBenjamin Auder <benjamin.auder@somewhere>
Sun, 23 Feb 2020 13:15:15 +0000 (14:15 +0100)
committerBenjamin Auder <benjamin.auder@somewhere>
Sun, 23 Feb 2020 13:15:15 +0000 (14:15 +0100)
12 files changed:
client/src/translations/en.js
client/src/translations/es.js
client/src/translations/fr.js
client/src/translations/rules/Allmate/en.pug [new file with mode: 0644]
client/src/translations/rules/Allmate/es.pug [new file with mode: 0644]
client/src/translations/rules/Allmate/fr.pug [new file with mode: 0644]
client/src/variants/Allmate.js [new file with mode: 0644]
client/src/variants/Atomic.js
client/src/variants/Baroque.js
client/src/variants/Benedict.js
client/src/variants/Zen.js
server/db/populate.sql

index 93a0865..f2495d8 100644 (file)
@@ -141,6 +141,7 @@ export const translations = {
   "Keep antiking in check": "Keep antiking in check",
   "Laws of attraction": "Laws of attraction",
   "Lose all pieces": "Lose all pieces",
+  "Mate any piece": "Mate any piece",
   "Move twice": "Move twice",
   "Pawns move diagonally": "Pawns move diagonally",
   "Reuse pieces": "Reuse pieces",
index 792a208..d45f870 100644 (file)
@@ -141,6 +141,7 @@ export const translations = {
   "Keep antiking in check": "Mantener el antirey en jaque",
   "Laws of attraction": "Las leyes de las atracciones",
   "Lose all pieces": "Perder todas las piezas",
+  "Mate any piece": "Matar cualquier pieza",
   "Move twice": "Mover dos veces",
   "Pawns move diagonally": "Peones se mueven en diagonal",
   "Reuse pieces": "Reutilizar piezas",
index a79597b..016a2cd 100644 (file)
@@ -141,6 +141,7 @@ export const translations = {
   "Keep antiking in check": "Gardez l'antiroi en échec",
   "Laws of attraction": "Les lois de l'attraction",
   "Lose all pieces": "Perdez toutes les pièces",
+  "Mate any piece": "Mater n'importe quelle pièce",
   "Move twice": "Jouer deux coups",
   "Pawns move diagonally": "Les pions vont en diagonale",
   "Reuse pieces": "Réutiliser les pièces",
diff --git a/client/src/translations/rules/Allmate/en.pug b/client/src/translations/rules/Allmate/en.pug
new file mode 100644 (file)
index 0000000..f445290
--- /dev/null
@@ -0,0 +1,34 @@
+p.boxed
+  | If a piece is attacked and cannot move out of attack, it is captured.
+
+p.
+  Any piece that is attacked, but which cannot escape capture by moving away,
+  capturing the attacking piece, or interposing the attacking piece is
+  considered to be mated, and is removed from the board.
+  Note that such a move does not need to be played: if a piece is attacked
+  but not mated, it is not removed.
+  Thus, kings and other pieces may remain under check.
+
+p.
+  Pieces cannot be taken in the normal way, only by the "mate-capture"
+  described above.
+
+figure.diagram-container
+  .diagram
+    | fen:rnbqkbnr/p1pppppp/8/8/1P6/2N5/P1PPPPPP/RNBQKBNR:
+  figcaption After 1.b4 b5 2.Nc3X the b5 pawn is "mate-captured"
+
+p.
+  Note about moves notation: since captures do not occur on final squares,
+  an "X" mark is appended to the move to indicate a capture.
+  To keep notation short the potential list of captured squares is not written.
+
+h3 End of the game
+
+p The game ends when a king is "mate-captured".
+
+h3 Source
+
+p
+  a(href="https://www.chessvariants.com/difftaking.dir/allmate.html") Allmate chess
+  | &nbsp;on chessvariants.com.
diff --git a/client/src/translations/rules/Allmate/es.pug b/client/src/translations/rules/Allmate/es.pug
new file mode 100644 (file)
index 0000000..0762e50
--- /dev/null
@@ -0,0 +1,36 @@
+p.boxed
+  | Si una pieza es atacada y no puede escapar del ataque, se captura.
+
+p.
+  Cualquier pieza atacada que no pueda escapar de la captura moviéndose,
+  al capturar al atacante o al interponer una pieza se considera mate:
+  se elimina del juego.
+  Tenga en cuenta que tal movimiento no tiene que hacerse: cuando una pieza es
+  atacada pero sin mate, no se elimina.
+  Por lo tanto, los reyes y otras monedas pueden permanecer en jaque.
+
+p.
+  Las piezas no pueden capturar en el sentido habitual, solo a través de
+  "mate-captura" descrito anteriormente.
+
+figure.diagram-container
+  .diagram
+    | fen:rnbqkbnr/p1pppppp/8/8/1P6/2N5/P1PPPPPP/RNBQKBNR:
+  figcaption Después de 1.b4 b5 2.Nc3X el peón b5 es "mate-capturado"
+
+p.
+  Nota sobre la notación de las jugadas: ya que las capturas no se realizan
+  en la casilla de llegada, se agrega una marca "X" al movimiento para indicar la captura.
+  Para evitar las notaciones demasiado largas, la lista potencial de casillas
+  de captura no está escrita.
+
+h3 Fin de la partida
+
+p El juego termina cuando un rey es "mate-capturado".
+
+h3 Fuente
+
+p
+  | La 
+  a(href="https://www.chessvariants.com/difftaking.dir/allmate.html") variante Allmate
+  | &nbsp;en chessvariants.com.
diff --git a/client/src/translations/rules/Allmate/fr.pug b/client/src/translations/rules/Allmate/fr.pug
new file mode 100644 (file)
index 0000000..a04418d
--- /dev/null
@@ -0,0 +1,36 @@
+p.boxed
+  | Si une pièce est attaquée et ne peut se soustraire à l'attaque, elle est capturée.
+
+p.
+  Toute pièce attaquée ne pouvant échapper à la capture en se déplaçant,
+  en capturant l'attaquant ou en interposant une pièce est considérée matée :
+  elle est retirée du jeu.
+  Notez qu'un tel coup n'a pas à être joué : quand une pièce est attaquée mais
+  non matée, elle n'est pas supprimée.
+  Ainsi, les rois et autres pièces peuvent rester en échec.
+
+p.
+  Les pièces ne peuvent capturer au sens habituel, seulement via la "mat-capture"
+  décrite ci-dessus.
+
+figure.diagram-container
+  .diagram
+    | fen:rnbqkbnr/p1pppppp/8/8/1P6/2N5/P1PPPPPP/RNBQKBNR:
+  figcaption Après 1.b4 b5 2.Nc3X le pion b5 est "mat-capturé"
+
+p.
+  Note sur la notation des coups: puisque les captures ne s'effectuent pas
+  sur la case d'arrivée, une marque "X" est ajoutée au coup pour indiquer la capture.
+  Afin d'éviter les notations à rallonge la liste potentielle des cases de
+  capture n'est pas écrite.
+
+h3 Fin de la partie
+
+p La partie s'achève quand un roi est "mat-capturé".
+
+h3 Source
+
+p
+  | La 
+  a(href="https://www.chessvariants.com/difftaking.dir/allmate.html") variante Allmate
+  | &nbsp;sur chessvariants.com.
diff --git a/client/src/variants/Allmate.js b/client/src/variants/Allmate.js
new file mode 100644 (file)
index 0000000..1cfd94a
--- /dev/null
@@ -0,0 +1,223 @@
+import { ChessRules, PiPo, Move } from "@/base_rules";
+
+export const VariantRules = class AllmateRules extends ChessRules {
+  static get HasEnpassant() {
+    return false;
+  }
+
+  canTake(sq1, sq2) {
+    return false; //Captures handled differently
+  }
+
+  getCheckSquares() {
+    // No notion of check
+    return [];
+  }
+
+  static GenRandInitFen() {
+    return ChessRules.GenRandInitFen().replace(/ -$/, "");
+  }
+
+  getPotentialMovesFrom([x, y]) {
+    let moves = super.getPotentialMovesFrom([x, y]);
+
+    // Augment moves with "mate-captures":
+    // TODO: this is coded in a highly inefficient way...
+    const color = this.turn;
+    const oppCol = V.GetOppCol(this.turn);
+    moves.forEach(m => {
+      this.play(m);
+
+      // 1) What is attacked?
+      let attacked = {};
+      for (let i=0; i<V.size.x; i++) {
+        for (let j=0; j<V.size.y; j++) {
+          if (this.getColor(i,j) == oppCol && this.isAttacked([i,j], [color]))
+            attacked[i+"_"+j] = [i,j];
+        }
+      }
+
+      // 2) Among attacked pieces, which cannot escape capture?
+      // Avoid "oppMoves = this.getAllValidMoves();" => infinite recursion
+      outerLoop: for (let i=0; i<V.size.x; i++) {
+        for (let j=0; j<V.size.y; j++) {
+          if (this.getColor(i,j) == oppCol) {
+            let oppMoves = [];
+            switch (this.getPiece(i, j)) {
+              case V.PAWN:
+                oppMoves = this.getPotentialPawnMoves([i, j]);
+                break;
+              case V.ROOK:
+                oppMoves = this.getPotentialRookMoves([i, j]);
+                break;
+              case V.KNIGHT:
+                oppMoves = this.getPotentialKnightMoves([i, j]);
+                break;
+              case V.BISHOP:
+                oppMoves = this.getPotentialBishopMoves([i, j]);
+                break;
+              case V.QUEEN:
+                oppMoves = this.getPotentialQueenMoves([i, j]);
+                break;
+              case V.KING:
+                oppMoves = this.getPotentialKingMoves([i, j]);
+                break;
+            }
+            for (let om of oppMoves) {
+              V.PlayOnBoard(this.board, om);
+              Object.values(attacked).forEach(sq => {
+                const origSq = [sq[0], sq[1]];
+                if (om.start.x == sq[0] && om.start.y == sq[1])
+                  // Piece moved:
+                  sq = [om.appear[0].x, om.appear[0].y];
+                if (!this.isAttacked(sq, [color]))
+                  delete attacked[origSq[0]+"_"+origSq[1]];
+              });
+              V.UndoOnBoard(this.board, om);
+              if (Object.keys(attacked).length == 0)
+                // No need to explore more moves
+                break outerLoop;
+            }
+          }
+        }
+      }
+
+      // 3) Add mate-captures:
+      Object.values(attacked).forEach(sq => {
+        m.vanish.push(new PiPo({
+          x: sq[0],
+          y: sq[1],
+          c: oppCol,
+          p: this.getPiece(sq[0], sq[1])
+        }));
+      });
+
+      this.undo(m);
+    });
+
+    return moves;
+  }
+
+  // No "under check" conditions in castling
+  getCastleMoves([x, y]) {
+    const c = this.getColor(x, y);
+    if (x != (c == "w" ? V.size.x - 1 : 0) || y != this.INIT_COL_KING[c])
+      return []; //x isn't first rank, or king has moved (shortcut)
+
+    // Castling ?
+    const oppCol = V.GetOppCol(c);
+    let moves = [];
+    let i = 0;
+    // King, then rook:
+    const finalSquares = [
+      [2, 3],
+      [V.size.y - 2, V.size.y - 3]
+    ];
+    castlingCheck: for (
+      let castleSide = 0;
+      castleSide < 2;
+      castleSide++ //large, then small
+    ) {
+      if (!this.castleFlags[c][castleSide]) continue;
+      // If this code is reached, rooks and king are on initial position
+
+      // Nothing on the path of the king ? (and no checks)
+      const finDist = finalSquares[castleSide][0] - y;
+      let step = finDist / Math.max(1, Math.abs(finDist));
+      for (let i = y; i != finalSquares[castleSide][0]; i += step) {
+        if (
+          this.board[x][i] != V.EMPTY &&
+            // NOTE: next check is enough, because of chessboard constraints
+            (this.getColor(x, i) != c ||
+              ![V.KING, V.ROOK].includes(this.getPiece(x, i)))
+        ) {
+          continue castlingCheck;
+        }
+      }
+
+      // Nothing on the path to the rook?
+      step = castleSide == 0 ? -1 : 1;
+      for (i = y + step; i != this.INIT_COL_ROOK[c][castleSide]; i += step) {
+        if (this.board[x][i] != V.EMPTY) continue castlingCheck;
+      }
+      const rookPos = this.INIT_COL_ROOK[c][castleSide];
+
+      // 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;
+  }
+
+  filterValid(moves) {
+    return moves;
+  }
+
+  updateVariables(move) {
+    super.updateVariables(move);
+    if (move.vanish.length == 2 && move.appear.length == 1) {
+      // Did opponent king disappeared?
+      if (move.vanish[1].p == V.KING)
+        this.kingPos[this.turn] = [-1, -1];
+    }
+  }
+
+  unupdateVariables(move) {
+    super.unupdateVariables(move);
+    if (move.vanish.length == 2 && move.appear.length == 1) {
+      // Did opponent king disappeared?
+      if (move.vanish[1].p == V.KING)
+        this.kingPos[move.vanish[1].c] = [move.vanish[1].x,move.vanish[1].y];
+    }
+  }
+
+  getCurrentScore() {
+    const color = this.turn;
+    const kp = this.kingPos[color];
+    if (kp[0] < 0)
+      // King disappeared
+      return color == "w" ? "0-1" : "1-0";
+    if (this.atLeastOneMove())
+      return "*";
+    // Kings still there, no moves:
+    return "1/2";
+  }
+
+  static get SEARCH_DEPTH() {
+    return 2;
+  }
+
+  getNotation(move) {
+    let notation = super.getNotation(move);
+    // Add a capture mark (not describing what is captured...):
+    if (move.vanish.length > 1 && move.appear[0].p != V.KING)
+      notation = notation.replace("x","") + "X";
+    return notation;
+  }
+};
index 2fcb5a6..35def3f 100644 (file)
@@ -76,7 +76,7 @@ export const VariantRules = class AtomicRules extends ChessRules {
   updateVariables(move) {
     super.updateVariables(move);
     if (move.appear.length == 0) {
-      //capture
+      // Capture
       const firstRank = { w: 7, b: 0 };
       for (let c of ["w", "b"]) {
         // Did we explode king of color c ? (TODO: remove move earlier)
@@ -143,10 +143,9 @@ export const VariantRules = class AtomicRules extends ChessRules {
     const color = this.turn;
     const kp = this.kingPos[color];
     if (kp[0] < 0)
-      //king disappeared
+      // King disappeared
       return color == "w" ? "0-1" : "1-0";
     if (this.atLeastOneMove())
-      // game not over
       return "*";
     if (!this.isAttacked(kp, [V.GetOppCol(color)])) return "1/2";
     return color == "w" ? "0-1" : "1-0"; //checkmate
index 5687e22..6557b38 100644 (file)
@@ -591,7 +591,8 @@ export const VariantRules = class BaroqueRules extends ChessRules {
     } else if (move.appear[0].p == V.KING)
       notation = "K" + (move.vanish.length > 1 ? "x" : "") + finalSquare;
     else notation = move.appear[0].p.toUpperCase() + finalSquare;
-    if (move.vanish.length > 1 && move.appear[0].p != V.KING) notation += "X"; //capture mark (not describing what is captured...)
+    // Add a capture mark (not describing what is captured...):
+    if (move.vanish.length > 1 && move.appear[0].p != V.KING) notation += "X";
     return notation;
   }
 };
index 8ce7acd..e87827f 100644 (file)
@@ -132,6 +132,87 @@ export const VariantRules = class BenedictRules extends ChessRules {
     return noCaptures.concat(this.getCastleMoves(sq));
   }
 
+  // No "under check" verifications:
+  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]) continue;
+      // If this code is reached, rooks and king are on initial position
+
+      const rookPos = this.INIT_COL_ROOK[c][castleSide];
+      if (this.getColor(x, rookPos) != c)
+        // Rook is here but changed color
+        continue;
+
+      // Nothing on the path of the king ?
+      const finDist = finalSquares[castleSide][0] - y;
+      let step = finDist / Math.max(1, Math.abs(finDist));
+      for (let i = y; i != finalSquares[castleSide][0]; i += step) {
+        if (
+          this.board[x][i] != V.EMPTY &&
+            // NOTE: next check is enough, because of chessboard constraints
+            (this.getColor(x, i) != c ||
+              ![V.KING, V.ROOK].includes(this.getPiece(x, i)))
+        ) {
+          continue castlingCheck;
+        }
+      }
+
+      // Nothing on the path to the rook?
+      step = castleSide == 0 ? -1 : 1;
+      for (i = y + step; i != this.INIT_COL_ROOK[c][castleSide]; 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;
+  }
+
   // TODO: appear/vanish description of a move is too verbose for Benedict.
   // => Would need a new "flipped" array, to be passed in Game.vue...
   getPotentialMovesFrom([x, y]) {
index a6067f9..bc51e6a 100644 (file)
@@ -88,45 +88,14 @@ export const VariantRules = class ZenRules extends ChessRules {
     return moves;
   }
 
-  getPotentialPawnMoves([x, y]) {
-    const color = this.getColor(x, y);
-    let moves = [];
-    const sizeY = V.size.y;
-    const shift = color == "w" ? -1 : 1;
-    const startRank = color == "w" ? sizeY - 2 : 1;
-    const firstRank = color == "w" ? sizeY - 1 : 0;
-    const lastRank = color == "w" ? 0 : sizeY - 1;
-
-    if (x + shift != lastRank) {
-      // Normal moves
-      if (this.board[x + shift][y] == V.EMPTY) {
-        moves.push(this.getBasicMove([x, y], [x + shift, y]));
-        if (
-          [startRank, firstRank].includes(x) &&
-          this.board[x + 2 * shift][y] == V.EMPTY
-        ) {
-          // Two squares jump
-          moves.push(this.getBasicMove([x, y], [x + 2 * shift, y]));
-        }
-      }
-    }
-    else {
-      // Promotion
-      let promotionPieces = [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN];
-      promotionPieces.forEach(p => {
-        // Normal move
-        if (this.board[x + shift][y] == V.EMPTY)
-          moves.push(
-            this.getBasicMove([x, y], [x + shift, y], { c: color, p: p })
-          );
-      });
-    }
-
-    // No en passant here
+  canTake(sq1, sq2) {
+    return false; //captures handled separately
+  }
 
+  getPotentialPawnMoves([x, y]) {
+    let moves = super.getPotentialPawnMoves([x, y]);
     // Add "zen" captures
     Array.prototype.push.apply(moves, this.findCaptures([x, y]));
-
     return moves;
   }
 
index 6a847d2..80f2e0b 100644 (file)
@@ -2,6 +2,7 @@
 
 insert or ignore into Variants (name,description) values
   ('Alice', 'Both sides of the mirror'),
+  ('Allmate', 'Mate any piece'),
   ('Antiking', 'Keep antiking in check'),
   ('Antimatter', 'Dangerous collisions'),
   ('Atomic', 'Explosive captures'),