Start working on Convert (mismatch name, Chaining?)
authorBenjamin Auder <benjamin.auder@somewhere>
Tue, 25 Jul 2023 09:39:41 +0000 (11:39 +0200)
committerBenjamin Auder <benjamin.auder@somewhere>
Tue, 25 Jul 2023 09:39:41 +0000 (11:39 +0200)
base_rules.js
variants/Convert/class.js
variants/Convert/rules.html [new file with mode: 0644]
variants/Convert/style.css [new file with mode: 0644]

index 20ffbee..aea3f57 100644 (file)
@@ -1188,7 +1188,7 @@ export default class ChessRules {
   }
 
   pieces(color, x, y) {
-    const pawnShift = this.getPawnShift(color);
+    const pawnShift = this.getPawnShift(color || 'w');
     return {
       'p': {
         "class": "pawn",
index 1a006e2..566feb5 100644 (file)
@@ -4,7 +4,7 @@ import Move from "/utils/Move.js";
 
 export default class ConvertRules extends ChessRules {
 
-  // TODO
+  // TODO: options ? (balance progressive ok it seems?)
   static get Options() {
     return {
       select: C.Options.select,
@@ -23,7 +23,7 @@ export default class ConvertRules extends ChessRules {
   setOtherVariables(fenParsed, pieceArray) {
     super.setOtherVariables(fenParsed, pieceArray);
     // Stack of "last move" only for intermediate chaining
-    this.lastMoveEnd = [null];
+    this.lastMoveEnd = [];
   }
 
   genRandInitBaseFen() {
@@ -86,7 +86,195 @@ export default class ConvertRules extends ChessRules {
     return mv;
   }
 
-// TODO from here
+  getPiece(x, y) {
+    const L = this.lastMoveEnd.length;
+    if (L >= 1 && this.lastMoveEnd[L-1].x == x && this.lastMoveEnd[L-1].y == y)
+      return this.lastMoveEnd[L-1].p;
+    return super.getPiece(x, y);
+  }
+
+  getPotentialMovesFrom([x, y], color) {
+    const L = this.lastMoveEnd.length;
+    if (
+      L >= 1 &&
+      (x != this.lastMoveEnd[L-1].x || y != this.lastMoveEnd[L-1].y)
+    ) {
+      // A capture was played: wrong square
+      return [];
+    }
+    return super.getPotentialMovesFrom([x, y], color);
+  }
+
+  underAttack_aux([x, y], color, explored) {
+    if (explored.some(sq => sq[0] == x && sq[1] == y))
+      // Start of an infinite loop: exit
+      return false;
+    explored.push([x, y]);
+    if (super.underAttack([x, y], [color]))
+      return true;
+    // Maybe indirect "chaining" attack:
+    const myColor = this.turn;
+    let res = false;
+    let toCheck = []; //check all but king (no need)
+    // Pawns:
+    const shiftToPawn = (myColor == 'w' ? -1 : 1);
+    for (let yShift of [-1, 1]) {
+      const [i, j] = [x + shiftToPawn, y + yShift];
+      if (
+        this.onBoard(i, j) &&
+        this.board[i][j] != "" &&
+        // NOTE: no need to check color (no enemy pawn can take directly)
+        this.getPiece(i, j) == 'p'
+      ) {
+        toCheck.push([i, j]);
+      }
+    }
+    // Knights:
+    this.pieces()['n'].both[0].steps.forEach(s => {
+      const [i, j] = [x + s[0], y + s[1]];
+      if (
+        this.onBoard(i, j) &&
+        this.board[i][j] != "" &&
+        this.getPiece(i, j) == 'n'
+      ) {
+        toCheck.push([i, j]);
+      }
+    });
+    // Sliders:
+    this.pieces()['q'].both[0].steps.forEach(s => {
+      let [i, j] = [x + s[0], y + s[1]];
+      while (this.onBoard(i, j) && this.board[i][j] == "") {
+        i += s[0];
+        j += s[1];
+      }
+      if (!this.onBoard(i, j))
+        return;
+      const piece = this.getPiece(i, j);
+      if (
+        piece == 'q' ||
+        (piece == 'r' && (s[0] == 0 || s[1] == 0)) ||
+        (piece == 'b' && (s[0] != 0 && s[1] != 0))
+      ) {
+        toCheck.push([i, j]);
+      }
+    });
+    for (let ij of toCheck) {
+      if (this.underAttack_aux(ij, color, explored))
+        return true;
+    }
+    return false;
+  }
+
+  underAttack([x, y], color) {
+    let explored = [];
+    return this.underAttack_aux([x, y], color, explored);
+  }
+
+  filterValid(moves) {
+    // No "checks" (except to forbid castle)
+    return moves;
+  }
+
+  isLastMove(move) {
+    return (
+      super.isLastMove(move) ||
+      move.vanish.length <= 1 ||
+      move.vanish[1].c != move.vanish[0].c ||
+      move.appear.length == 2 //castle!
+    );
+  }
+
+  postPlay(move) {
+    super.postPlay(move);
+    if (!this.isLastMove(move)) {
+      this.lastMoveEnd.push({
+        x: move.end.x,
+        y: move.end.y,
+        p: move.vanish[1].p //TODO: check this
+      });
+    }
+  }
+
+};
+
+// TODO: wrong rules! mismatch Convert (taking opponent pieces) and chaining (tend to be this) taking own units (with normal initial position).
+// Initial Convert:
+
+import { ChessRules, PiPo, Move } from "@/base_rules";
+import { randInt } from "@/utils/alea";
+
+export class ConvertRules extends ChessRules {
+
+  static get HasEnpassant() {
+    return false;
+  }
+
+  setOtherVariables(fen) {
+    super.setOtherVariables(fen);
+    // Stack of "last move" only for intermediate chaining
+    this.lastMoveEnd = [null];
+  }
+
+  static GenRandInitFen(options) {
+    const baseFen = ChessRules.GenRandInitFen(options);
+    return (
+      baseFen.substr(0, 8) +
+      "/8/pppppppp/8/8/PPPPPPPP/8/" +
+      baseFen.substr(35, 17)
+    );
+  }
+
+  getBasicMove([sx, sy], [ex, ey], tr) {
+    const L = this.lastMoveEnd.length;
+    const lm = this.lastMoveEnd[L-1];
+    const piece = (!!lm ? lm.p : null);
+    const c = this.turn;
+    if (this.board[ex][ey] == V.EMPTY) {
+      if (!!piece && !tr) tr = { c: c, p: piece }
+      let mv = super.getBasicMove([sx, sy], [ex, ey], tr);
+      if (!!piece) mv.vanish.pop();
+      return mv;
+    }
+    // Capture: initial, or inside a chain
+    const initPiece = (piece || this.getPiece(sx, sy));
+    const oppCol = V.GetOppCol(c);
+    const oppPiece = this.getPiece(ex, ey);
+    let mv = new Move({
+      start: { x: sx, y: sy },
+      end: { x: ex, y: ey },
+      appear: [
+        new PiPo({
+          x: ex,
+          y: ey,
+          c: c,
+          p: (!!tr ? tr.p : initPiece)
+        })
+      ],
+      vanish: [
+        new PiPo({
+          x: ex,
+          y: ey,
+          c: oppCol,
+          p: oppPiece
+        })
+      ]
+    });
+    if (!piece) {
+      // Initial capture
+      mv.vanish.unshift(
+        new PiPo({
+          x: sx,
+          y: sy,
+          c: c,
+          p: initPiece
+        })
+      );
+    }
+    // TODO: This "converted" indication isn't needed in fact,
+    // because it can be deduced from the move itself.
+    mv.end.converted = oppPiece;
+    return mv;
+  }
 
   getPotentialMovesFrom([x, y], asA) {
     const L = this.lastMoveEnd.length;
@@ -189,6 +377,10 @@ export default class ConvertRules extends ChessRules {
     return moves;
   }
 
+  getCheckSquares() {
+    return [];
+  }
+
   prePlay(move) {
     const c = this.turn;
     // Extra conditions to avoid tracking converted kings:
@@ -221,4 +413,84 @@ export default class ConvertRules extends ChessRules {
     super.updateCastleFlags(move, move.appear[0].p, c);
   }
 
+  undo(move) {
+    this.disaggregateFlags(JSON.parse(move.flags));
+    this.lastMoveEnd.pop();
+    V.UndoOnBoard(this.board, move);
+    if (!move.end.converted) {
+      this.turn = V.GetOppCol(this.turn);
+      this.movesCount--;
+    }
+    this.postUndo(move);
+  }
+
+  postUndo(move) {
+    const c = this.getColor(move.start.x, move.start.y);
+    if (
+      move.appear[0].p == V.KING &&
+      move.vanish.length >= 1 &&
+      move.vanish[0].p == V.KING
+    ) {
+      this.kingPos[c] = [move.start.x, move.start.y];
+    }
+  }
+
+  getCurrentScore() {
+    const color = this.turn;
+    const kp = this.kingPos[color];
+    if (this.getColor(kp[0], kp[1]) != color)
+      return (color == "w" ? "0-1" : "1-0");
+    if (!super.atLeastOneMove()) return "1/2";
+    return "*";
+  }
+
+  getComputerMove() {
+    let initMoves = this.getAllValidMoves();
+    if (initMoves.length == 0) return null;
+    // Loop until valid move is found (no blocked pawn conversion...)
+    while (true) {
+      let moves = JSON.parse(JSON.stringify(initMoves));
+      let mvArray = [];
+      let mv = null;
+      // Just play random moves (for now at least. TODO?)
+      while (moves.length > 0) {
+        mv = moves[randInt(moves.length)];
+        mvArray.push(mv);
+        this.play(mv);
+        if (!!mv.end.converted)
+          // A piece was just converted
+          moves = this.getPotentialMovesFrom([mv.end.x, mv.end.y]);
+        else break;
+      }
+      for (let i = mvArray.length - 1; i >= 0; i--) this.undo(mvArray[i]);
+      if (!mv.end.converted) return (mvArray.length > 1 ? mvArray : mvArray[0]);
+    }
+    return null; //never reached
+  }
+
+  getNotation(move) {
+    if (move.appear.length == 2 && move.appear[0].p == V.KING)
+      return (move.end.y < move.start.y ? "0-0-0" : "0-0");
+    const c = this.turn;
+    const L = this.lastMoveEnd.length;
+    const lm = this.lastMoveEnd[L-1];
+    const piece = (!lm ? move.appear[0].p : lm.p);
+    // Basic move notation:
+    let notation = piece.toUpperCase();
+    if (
+      this.board[move.end.x][move.end.y] != V.EMPTY ||
+      (piece == V.PAWN && move.start.y != move.end.y)
+    ) {
+      notation += "x";
+    }
+    const finalSquare = V.CoordsToSquare(move.end);
+    notation += finalSquare;
+
+    // Add potential promotion indications:
+    const firstLastRank = (c == 'w' ? [7, 0] : [0, 7]);
+    if (move.end.x == firstLastRank[1] && piece == V.PAWN)
+      notation += "=" + move.appear[0].p.toUpperCase();
+    return notation;
+  }
+
 };
diff --git a/variants/Convert/rules.html b/variants/Convert/rules.html
new file mode 100644 (file)
index 0000000..f27cd96
--- /dev/null
@@ -0,0 +1,7 @@
+<p>
+  You can "capture" your own pieces, and then move them from the capturing
+  square in the same turn, with potential chaining if the captured unit
+  makes a self-capture too.
+</p>
+
+<p class="author">Benjamin Auder (2021).</p>
diff --git a/variants/Convert/style.css b/variants/Convert/style.css
new file mode 100644 (file)
index 0000000..a3550bc
--- /dev/null
@@ -0,0 +1 @@
+@import url("/base_pieces.css");