From a548cb4e3ad8099e977da9bb4a4184973beb56e3 Mon Sep 17 00:00:00 2001
From: Benjamin Auder <benjamin.auder@somewhere>
Date: Thu, 30 Jun 2022 17:26:07 +0200
Subject: [PATCH] Draft Antiking1/2. Changed underCheck/searchKingPos (TODO:
 impact on some other variants)

---
 base_rules.js                 | 54 ++++++++++++--------
 variants/AbstractAntiking.js  | 69 ++++++++++++++++++++++++++
 variants/Antiking1/class.js   | 93 +++++++++++++++++++++++++++++++++++
 variants/Antiking1/rules.html |  1 +
 variants/Antiking2/class.js   | 33 +++++++++++++
 variants/Antiking2/rules.html |  1 +
 6 files changed, 230 insertions(+), 21 deletions(-)
 create mode 100644 variants/AbstractAntiking.js
 create mode 100644 variants/Antiking1/class.js
 create mode 100644 variants/Antiking1/rules.html
 create mode 100644 variants/Antiking2/class.js
 create mode 100644 variants/Antiking2/rules.html

diff --git a/base_rules.js b/base_rules.js
index 54c1220..e3a34fb 100644
--- a/base_rules.js
+++ b/base_rules.js
@@ -1511,7 +1511,7 @@ export default class ChessRules {
     if (piece == "p" && this.hasEnpassant && this.epSquare)
       Array.prototype.push.apply(moves, this.getEnpassantCaptures([x, y]));
     if (
-      piece == "k" && this.hasCastle &&
+      this.isKing(0, 0, piece) && this.hasCastle &&
       this.castleFlags[color || this.turn].some(v => v < this.size.y)
     ) {
       Array.prototype.push.apply(moves, this.getCastleMoves([x, y]));
@@ -2162,21 +2162,24 @@ export default class ChessRules {
     );
   }
 
-  underCheck([x, y], oppCol) {
+  underCheck(square_s, oppCol) {
     if (this.options["taking"] || this.options["dark"])
       return false;
-    return this.underAttack([x, y], oppCol);
+    if (!Array.isArray(square_s))
+      square_s = [square_s];
+    return square_s.some(sq => this.underAttack(sq, oppCol));
   }
 
-  // Stop at first king found (TODO: multi-kings)
+  // Scan board for king(s)
   searchKingPos(color) {
+    let res = [];
     for (let i=0; i < this.size.x; i++) {
       for (let j=0; j < this.size.y; j++) {
         if (this.getColor(i, j) == color && this.isKing(i, j))
-          return [i, j];
+          res.push([i, j]);
       }
     }
-    return [-1, -1]; //king not found
+    return res;
   }
 
   // 'color' arg because some variants (e.g. Refusal) check opponent moves
@@ -2184,24 +2187,30 @@ export default class ChessRules {
     if (!color)
       color = this.turn;
     const oppCol = C.GetOppCol(color);
-    const kingPos = this.searchKingPos(color);
+    let kingPos = this.searchKingPos(color);
     let filtered = {}; //avoid re-checking similar moves (promotions...)
     return moves.filter(m => {
       const key = m.start.x + m.start.y + '.' + m.end.x + m.end.y;
       if (!filtered[key]) {
         this.playOnBoard(m);
-        let square = kingPos,
+        let newKingPP = null,
+            sqIdx = 0,
             res = true; //a priori valid
-        if (m.vanish.some(v => this.isKing(0, 0, v.p) && v.c == color)) {
+        const oldKingPP = m.vanish.find(v => this.isKing(0, 0, v.p) && v.c == color);
+        if (oldKingPP) {
           // Search king in appear array:
-          const newKingIdx =
-            m.appear.findIndex(a => this.isKing(0, 0, a.p) && a.c == color);
-          if (newKingIdx >= 0)
-            square = [m.appear[newKingIdx].x, m.appear[newKingIdx].y];
+          newKingPP =
+            m.appear.find(a => this.isKing(0, 0, a.p) && a.c == color);
+          if (newKingPP) {
+            sqIdx = kingPos.findIndex(kp => kp[0] == oldKingPP.x && kp[1] == oldKingPP[.y);
+            kingPos[sqIdx] = [newKingPP.x, newKingPP.y];
+          }
           else
-            res = false;
+            res = false; //king vanished
         }
-        res &&= !this.underCheck(square, oppCol);
+        res &&= !this.underCheck(square_s, oppCol);
+        if (oldKingPP && newKingPP)
+          kingPos[sqIdx] = [oldKingPP.x, oldKingPP.y];
         this.undoOnBoard(m);
         filtered[key] = res;
         return res;
@@ -2348,7 +2357,7 @@ export default class ChessRules {
       return false;
     const color = this.turn;
     const oppKingPos = this.searchKingPos(C.GetOppCol(color));
-    if (oppKingPos[0] < 0 || this.underCheck(oppKingPos, color))
+    if (oppKingPos.length == 0 || this.underCheck(oppKingPos, color))
       return true;
     return (
       (
@@ -2397,17 +2406,20 @@ export default class ChessRules {
   getCurrentScore(move) {
     const color = this.turn;
     const oppCol = C.GetOppCol(color);
-    const kingPos = [this.searchKingPos(color), this.searchKingPos(oppCol)];
-    if (kingPos[0][0] < 0 && kingPos[1][0] < 0)
+    const kingPos = {
+      [color]: this.searchKingPos(color),
+      [oppCol]: this.searchKingPos(oppCol)
+    };
+    if (kingPos[color].length == 0 && kingPos[oppCol].length == 0)
       return "1/2";
-    if (kingPos[0][0] < 0)
+    if (kingPos[color].length == 0)
       return (color == "w" ? "0-1" : "1-0");
-    if (kingPos[1][0] < 0)
+    if (kingPos[oppCol].length == 0)
       return (color == "w" ? "1-0" : "0-1");
     if (this.atLeastOneMove(color))
       return "*";
     // No valid move: stalemate or checkmate?
-    if (!this.underCheck(kingPos[0], oppCol))
+    if (!this.underCheck(kingPos[color], oppCol))
       return "1/2";
     // OK, checkmate
     return (color == "w" ? "0-1" : "1-0");
diff --git a/variants/AbstractAntiking.js b/variants/AbstractAntiking.js
new file mode 100644
index 0000000..7846add
--- /dev/null
+++ b/variants/AbstractAntiking.js
@@ -0,0 +1,69 @@
+import ChessRules from "/base_rules.js";
+
+export class AbstractAntikingRules extends ChessRules {
+
+  static get Options() {
+    return {
+      styles: [
+        "atomic",
+        "balance",
+        "cannibal",
+        "capture",
+        "crazyhouse",
+        "doublemove",
+        "madrasi",
+        "progressive",
+        "recycle",
+        "rifle",
+        "teleport",
+        "zen"
+      ]
+    };
+  }
+
+  pieces(color, x, y) {
+    "a": {
+      // Move like a king, no attacks
+      "class": "antiking",
+      moves: super.pieces(color, x, y)['k'].moves,
+      attack: []
+    }
+  }
+
+  isKing(x, y, p) {
+    if (!p)
+      p = this.getPiece(x, y);
+    return ['k', 'a'].includes(p);
+  }
+
+  canTake([x1, y1], [x2, y2]) {
+    const piece1 = this.getPiece(x1, y1);
+    const piece2 = this.getPiece(x2, y2);
+    const color1 = this.getColor(x1, y1);
+    const color2 = this.getColor(x2, y2);
+    return (
+      piece2 != 'a' &&
+      (
+        (piece1 != 'a' && color1 != color2) ||
+        (piece1 == 'a' && color1 == color2)
+      )
+    );
+  }
+
+  underCheck(squares, color) {
+    const oppCol = C.GetOppCol(color);
+    let res = false;
+    squares.forEach(sq => {
+      switch (this.getPiece(sq[0], sq[1])) {
+        case 'k':
+          res ||= super.underAttack(sq, oppCol);
+          break;
+        case 'a':
+          res ||= !super.underAttack(sq, oppCol);
+          break;
+      }
+    });
+    return res;
+  }
+
+};
diff --git a/variants/Antiking1/class.js b/variants/Antiking1/class.js
new file mode 100644
index 0000000..f8406b7
--- /dev/null
+++ b/variants/Antiking1/class.js
@@ -0,0 +1,93 @@
+import ChessRules from "/base_rules.js";
+import AbstractAntikingRules from "/variants/AbstractAntiking.js";
+
+export class Antiking1Rules extends AbstractAntikingRules {
+
+  static get Options() {
+    return {
+      styles: [
+        "atomic",
+        "balance",
+        "cannibal",
+        "capture",
+        "crazyhouse",
+        "doublemove",
+        "madrasi",
+        "progressive",
+        "recycle",
+        "rifle",
+        "teleport",
+        "zen"
+      ]
+    };
+  }
+
+  get hasCastle() {
+    return false;
+  }
+
+  pieces(color, x, y) {
+    const pawnShift = (color == "w" ? -1 : 1);
+    let res = super.pieces(color, x, y);
+    res['p'].moves = [
+      {
+        steps: [[pawnShift, 1], [pawnShift, -1]],
+        range: 1
+      }
+    ];
+    res['p'].attack = [
+      {
+        steps: [[pawnShift, 0]],
+        range: 1
+      }
+    ];
+    return res;
+  }
+
+  genRandInitFen() {
+    // Always deterministic setup
+    return (
+      '2prbkqA/2p1nnbr/2pppppp/8/8/PPPPPP2/RBNN1P2/aQKBRP2 w 0 ' +
+      '{"flags":"KAka"}'
+    );
+  }
+
+  // (Anti)King flags at 1 (true) if they can knight-jump
+  setFlags(fenflags) {
+    this.kingFlags = { w: {}, b: {} };
+    for (let i=0; i<fenFlags.length; i++) {
+      const white = fenFlags.charCodeAt(i) <= 90;
+      const curChar = fenFlags.charCodeAt(i).toLowerCase();
+      this.kingFlags[white ? 'w' : 'b'][curChar] = true;
+    }
+  }
+
+  getFlagsFen() {
+    return (
+      Array.prototype.concat.apply(
+        ['w', 'b'].map(c => Object.keys(this.kingFlags[c]))
+      ).join("")
+    );
+  }
+
+  getPotentialMovesFrom([x, y]) {
+    const color = this.turn;
+    let moves = super.getPotentialMovesFrom([x, y]);
+    if (this.kingFlags[color][piece]) {
+      // Allow knight jump (king or antiking)
+      const knightMoves = super.getPotentialMovesOf('n', [x, y]);
+      // Remove captures (TODO: could be done more efficiently...)
+      moves = moves.concat(knightJumps.filter(m => m.vanish.length == 1));
+    }
+    return moves;
+  }
+
+  prePlay(move) {
+    super.prePlay(move);
+    // Update king+antiking flags
+    const piece = move.vanish[0].p;
+    if (this.isKing(0, 0, piece))
+      delete this.kingFlags[move.vanish[0].c][piece];
+  }
+
+};
diff --git a/variants/Antiking1/rules.html b/variants/Antiking1/rules.html
new file mode 100644
index 0000000..fee723a
--- /dev/null
+++ b/variants/Antiking1/rules.html
@@ -0,0 +1 @@
+https://www.chessvariants.com/diffobjective.dir/anti-king-chess.html
diff --git a/variants/Antiking2/class.js b/variants/Antiking2/class.js
new file mode 100644
index 0000000..0e6a3e5
--- /dev/null
+++ b/variants/Antiking2/class.js
@@ -0,0 +1,33 @@
+import ChessRules from "/base_rules.js";
+import AbstractAntikingRules from "/variants/AbstractAntiking.js";
+import { Random } from "/utils/alea.js";
+
+export class Antiking2Rules extends AbstractAntikingRules {
+
+  static get Aliases() {
+    return Object.assign({'A': AbstractAntikingRules}, ChessRules.Aliases);
+  }
+
+  static get Options() {
+    return {
+      styles: A.Options.styles.concat("cylinder")
+    };
+  }
+
+  genRandInitFen(seed) {
+    const baseFen = super.genRandInitFen(seed);
+    // Just add an antiking on 3rd ranks
+    let akPos = [3, 3];
+    if (this.options.randomness >= 1) {
+      akPos[0] = Random.randInt(this.size.y);
+      if (this.options.randomness == 2)
+        akPos[1] = Random.randInt(this.size.y);
+      else
+        akPos[1] = akPos[0];
+    }
+    const whiteLine = (akPos[0] > 0 ? akPos[0] : "") + 'A' + (akPos < this.size.y - 1 ? ...);
+    const blackLine = ...
+    return baseFen.replace(...)
+  }
+
+};
diff --git a/variants/Antiking2/rules.html b/variants/Antiking2/rules.html
new file mode 100644
index 0000000..fee723a
--- /dev/null
+++ b/variants/Antiking2/rules.html
@@ -0,0 +1 @@
+https://www.chessvariants.com/diffobjective.dir/anti-king-chess.html
-- 
2.44.0