Almost added TitanChess + EvolutionChess
[vchess.git] / client / src / variants / Titan.js
index 2ed5499..aadc3e7 100644 (file)
@@ -1,13 +1,15 @@
-import { ChessRules } from "@/base_rules";
+import { ChessRules, Move, PiPo } from "@/base_rules";
 
 export class TitanRules extends ChessRules {
-  // Idea: yellow = bishop, orange = knight (for white)
-  // and, red = bishop + purple = knight (black side)
-  // (avoid using a bigger board, or complicated drawings)
+
+  static get IMAGE_EXTENSION() {
+    // Temporarily, for the time SVG pieces are being designed:
+    return ".png";
+  }
 
   // Decode if normal piece, or + bishop or knight
-  getPiece(i, j) {
-    const piece = this.board[i][j].charAt(1);
+  getPiece(x, y) {
+    const piece = this.board[x][y].charAt(1);
     if (ChessRules.PIECES.includes(piece)) return piece;
     // Augmented piece:
     switch (piece) {
@@ -29,8 +31,6 @@ export class TitanRules extends ChessRules {
     }
   }
 
-  // TODO: subtelty, castle forbidden if 
-
   // Code: a/c = bishop + knight/bishop j/l for king,
   // m/o for knight, s/t for queen, u/v for rook
   static get AUGMENTED_PIECES() {
@@ -48,6 +48,10 @@ export class TitanRules extends ChessRules {
     ];
   }
 
+  getPpath(b) {
+    return "Titan/" + b;
+  }
+
   // Decode above notation into additional piece
   getExtraPiece(symbol) {
     if (['a','j','m','s','u'].includes(symbol))
@@ -55,18 +59,98 @@ export class TitanRules extends ChessRules {
     return 'b';
   }
 
+  // Inverse operation: augment piece
+  getAugmented(piece) {
+    const knight = this.movesCount <= 1;
+    switch (piece) {
+      case V.ROOK: return (knight ? 'u' : 'v');
+      case V.KNIGHT: return (knight ? 'm' : 'o');
+      case V.BISHOP: return (knight ? 'a' : 'c');
+      case V.QUEEN: return (knight ? 's' : 't');
+      case V.KING: return (knight ? 'j' : 'l');
+    }
+    return '_'; //never reached
+  }
+
+  static IsGoodPosition(position) {
+    if (position.length == 0) return false;
+    const rows = position.split("/");
+    if (rows.length != V.size.x) return false;
+    let kings = { "w": 0, "b": 0 };
+    const allPiecesCodes = V.PIECES.concat(V.AUGMENTED_PIECES);
+    const kingBlackCodes = ['j','k','l'];
+    const kingWhiteCodes = ['J','K','L'];
+    for (let row of rows) {
+      let sumElts = 0;
+      for (let i = 0; i < row.length; i++) {
+        if (kingBlackCodes.includes(row[i])) kings['b']++;
+        else if (kingWhiteCodes.includes(row[i])) kings['w']++;
+        if (allPiecesCodes.includes(row[i].toLowerCase())) sumElts++;
+        else {
+          const num = parseInt(row[i], 10);
+          if (isNaN(num)) return false;
+          sumElts += num;
+        }
+      }
+      if (sumElts != V.size.y) return false;
+    }
+    // Both kings should be on board, only one of each color:
+    if (Object.values(kings).some(v => v != 1)) return false;
+    return true;
+  }
+
+  // Kings may be augmented:
+  scanKings(fen) {
+    this.kingPos = { w: [-1, -1], b: [-1, -1] };
+    const rows = V.ParseFen(fen).position.split("/");
+    for (let i = 0; i < rows.length; i++) {
+      let k = 0; //column index on board
+      for (let j = 0; j < rows[i].length; j++) {
+        const piece = rows[i].charAt(j);
+        if (['j','k','l'].includes(piece.toLowerCase())) {
+          const color = (piece.charCodeAt(0) <= 90 ? 'w' : 'b');
+          this.kingPos[color] = [i, k];
+        }
+        else {
+          const num = parseInt(rows[i].charAt(j), 10);
+          if (!isNaN(num)) k += num - 1;
+        }
+        k++;
+      }
+    }
+  }
+
   // If piece not in usual list, bishop or knight appears.
   getPotentialMovesFrom([x, y]) {
-    let moves = super.getPotentialMovesFrom(sq);
+    if (this.movesCount <= 3) {
+      // Setup stage
+      const color = this.getColor(x, y);
+      const firstRank = (color == 'w' ? 7 : 0);
+      if (x != firstRank || V.AUGMENTED_PIECES.includes(this.board[x][y][1]))
+        return [];
+      const piece = this.getPiece(x, y);
+      const move = new Move({
+        appear: [
+          new PiPo({ x: x, y: y, c: color, p: this.getAugmented(piece) })
+        ],
+        vanish: [
+          new PiPo({ x: x, y: y, c: color, p: piece })
+        ],
+        start: { x: x, y: y },
+        end: { x: x, y: y }
+      });
+      return [move];
+    }
+    let moves = super.getPotentialMovesFrom([x, y]);
+    const initialPiece = this.getPiece(x, y);
     const color = this.turn;
-    
-// treat castle case here (both pieces appear!)
     if (
       V.AUGMENTED_PIECES.includes(this.board[x][y][1]) &&
       ((color == 'w' && x == 7) || (color == "b" && x == 0))
     ) {
       const newPiece = this.getExtraPiece(this.board[x][y][1]);
       moves.forEach(m => {
+        m.appear[0].p = initialPiece;
         m.appear.push(
           new PiPo({
             p: newPiece,
@@ -77,9 +161,129 @@ export class TitanRules extends ChessRules {
         );
       });
     }
+    moves.forEach(m => {
+      if (m.vanish.length <= 1) return;
+      const [vx, vy] = [m.vanish[1].x, m.vanish[1].y];
+      if (
+        m.appear.length >= 2 && //3 if the king was also augmented
+        m.vanish.length == 2 &&
+        m.vanish[1].c == color &&
+        V.AUGMENTED_PIECES.includes(this.board[vx][vy][1])
+      ) {
+        // Castle, rook is an "augmented piece"
+        m.appear[1].p = V.ROOK;
+        m.appear.push(
+          new PiPo({
+            p: this.getExtraPiece(this.board[vx][vy][1]),
+            c: color,
+            x: vx,
+            y: vy
+          })
+        );
+      }
+    });
     return moves;
   }
 
-  // TODO: special case of move 1 = choose squares, knight first, then bishop
-  // (just click ?)
+  // Special case of move 1 = choose squares, knight first, then bishop
+  doClick(square) {
+    if (this.movesCount >= 4) return null;
+    const color = this.turn;
+    const [x, y] = [square[0], square[1]];
+    if ((color == 'w' && x != 7) || (color == 'b' && x != 0)) return null;
+    const selectedPiece = this.board[x][y][1];
+    return new Move({
+      appear: [
+        new PiPo({
+          x: x,
+          y: y,
+          c: color,
+          p: this.getAugmented(selectedPiece)
+        })
+      ],
+      vanish: [
+        new PiPo({
+          x: x,
+          y: y,
+          c: color,
+          p: selectedPiece
+        })
+      ],
+      start: { x: x, y: y },
+      end: { x: x, y: y }
+    });
+  }
+
+  postPlay(move) {
+    if (this.movesCount > 4) super.postPlay(move);
+  }
+
+  postUndo(move) {
+    if (this.movesCount >= 4) super.postUndo(move);
+  }
+
+  evalPosition() {
+    let evaluation = 0;
+    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) {
+          const sign = this.getColor(i, j) == "w" ? 1 : -1;
+          const piece = this.getPiece(i, j);
+          evaluation += sign * V.VALUES[piece];
+          const symbol = this.board[i][j][1];
+          if (V.AUGMENTED_PIECES.includes(symbol)) {
+            const extraPiece = this.getExtraPiece(symbol);
+            evaluation += sign * V.VALUES[extraPiece]
+          }
+        }
+      }
+    }
+    return evaluation;
+  }
+
+  getNotation(move) {
+    if (
+      move.appear[0].x != move.vanish[0].x ||
+      move.appear[0].y != move.vanish[0].y
+    ) {
+      if (
+        V.AUGMENTED_PIECES.includes(move.vanish[0].p) ||
+        (
+          move.vanish.length >= 2 &&
+          V.AUGMENTED_PIECES.includes(move.vanish[1].p)
+        )
+      ) {
+        // Simplify move before calling super.getNotation()
+        let smove = JSON.parse(JSON.stringify(move));
+        if (ChessRules.PIECES.includes(move.vanish[0].p)) {
+          // Castle with an augmented rook
+          smove.appear.pop();
+          smove.vanish[1].p = smove.appear[1].p;
+        }
+        else {
+          // Moving an augmented piece
+          smove.appear.pop();
+          smove.vanish[0].p = smove.appear[0].p;
+          if (
+            smove.vanish.length == 2 &&
+            smove.vanish[0].c == smove.vanish[1].c &&
+            V.AUGMENTED_PIECES.includes(move.vanish[1].p)
+          ) {
+            // Castle with an augmented rook
+            smove.appear.pop();
+            smove.vanish[1].p = smove.appear[1].p;
+          }
+        }
+        return super.getNotation(smove);
+      }
+      // Else, more common case:
+      return super.getNotation(move);
+    }
+    // First moves in game, placements:
+    const square = V.CoordsToSquare(move.appear[0]);
+    const reserve =
+      (['a','j','m','s','u'].includes(move.appear[0].p) ? 'N' : 'B');
+    return '+' + reserve + '@' + square;
+  }
+
 };