Several small improvements + integrate options + first working draft of Cwda
[vchess.git] / client / src / variants / Bario.js
index 5b5b33c..3e07665 100644 (file)
@@ -2,13 +2,15 @@ import { ChessRules, PiPo, Move } from "@/base_rules";
 import { ArrayFun } from "@/utils/array";
 import { randInt } from "@/utils/alea";
 
-// TODO: issue with undo of specialisation to cover check, subTurn decremented to 0
-
 export class BarioRules extends ChessRules {
 
+  static get Options() {
+    return null;
+  }
+
   // Does not really seem necessary (although the author mention it)
   // Instead, first move = pick a square for the king.
-  static get HasCastle() {
+  static get HasFlags() {
     return false;
   }
 
@@ -37,7 +39,7 @@ export class BarioRules extends ChessRules {
     );
   }
 
-  hoverHighlight(x, y) {
+  hoverHighlight([x, y]) {
     const c = this.turn;
     return (
       this.movesCount <= 1 &&
@@ -48,6 +50,14 @@ export class BarioRules extends ChessRules {
     );
   }
 
+  onlyClick([x, y]) {
+    return (
+      this.movesCount <= 1 ||
+      // TODO: next line theoretically shouldn't be required...
+      (this.movesCount == 2 && this.getColor(x, y) != this.turn)
+    );
+  }
+
   // Initiate the game by choosing a square for the king:
   doClick(square) {
     const c = this.turn;
@@ -64,8 +74,10 @@ export class BarioRules extends ChessRules {
       appear: [
         new PiPo({ x: square[0], y: square[1], c: c, p: V.KING })
       ],
-      vanish: [],
-      start: { x: -1, y: -1 },
+      vanish: [
+        new PiPo({ x: square[0], y: square[1], c: c, p: V.UNDEFINED })
+      ],
+      start: { x: -1, y: -1 }
     });
   }
 
@@ -111,8 +123,8 @@ export class BarioRules extends ChessRules {
   getReserveFen() {
     let counts = new Array(8);
     for (let i = 0; i < V.RESERVE_PIECES.length; i++) {
-      counts[i] = this.reserve["w"][V.PIECES[i]];
-      counts[4 + i] = this.reserve["b"][V.PIECES[i]];
+      counts[i] = this.reserve["w"][V.RESERVE_PIECES[i]];
+      counts[4 + i] = this.reserve["b"][V.RESERVE_PIECES[i]];
     }
     return counts.join("");
   }
@@ -140,7 +152,7 @@ export class BarioRules extends ChessRules {
   }
 
   static GenRandInitFen() {
-    return "8/pppppppp/8/8/8/8/PPPPPPPP/8 w 0 - 22212221 -";
+    return "uuuuuuuu/pppppppp/8/8/8/8/PPPPPPPP/UUUUUUUU w 0 - 22212221 -";
   }
 
   setOtherVariables(fen) {
@@ -194,17 +206,15 @@ export class BarioRules extends ChessRules {
     if (this.subTurn == 0) {
       const L = this.captureUndefined.length;
       const cu = this.captureUndefined[L-1];
-      return (
+      return [
+        // Nothing changes on the board, just mark start.p for reserve update
         new Move({
-          appear: [
-            new PiPo({ x: cu.x, y: cu.y, c: color, p: p })
-          ],
-          vanish: [
-            new PiPo({ x: cu.x, y: cu.y, c: color, p: V.UNDEFINED })
-          ],
-          start: { x: x, y: y }
+          appear: [],
+          vanish: [],
+          start: { x: x, y: y, p: p },
+          end: { x: cu.x, y: cu.y }
         })
-      );
+      ];
     }
     // or, subTurn == 1 => target any undefined piece that we own.
     let moves = [];
@@ -252,10 +262,25 @@ export class BarioRules extends ChessRules {
     return super.getPotentialMovesFrom([x, y]);
   }
 
-  getAllValidMoves() {
+  getAllPotentialMoves() {
+    const color = this.turn;
+    if (this.movesCount <= 1) {
+      // Just put the king on the board
+      const firstRank = (color == 'w' ? 7 : 0);
+      return [...Array(8)].map((x, j) => {
+        return new Move({
+          appear: [
+            new PiPo({ x: firstRank, y: j, c: color, p: V.KING })
+          ],
+          vanish: [
+            new PiPo({ x: firstRank, y: j, c: color, p: V.UNDEFINED })
+          ],
+          start: { x: -1, y: -1 }
+        });
+      });
+    }
     const getAllReserveMoves = () => {
       let moves = [];
-      const color = this.turn;
       for (let i = 0; i < V.RESERVE_PIECES.length; i++) {
         moves = moves.concat(
           this.getReserveMoves([V.size.x + (color == "w" ? 0 : 1), i])
@@ -271,12 +296,33 @@ export class BarioRules extends ChessRules {
   }
 
   filterValid(moves) {
+    if (this.movesCount <= 1) return moves;
     const color = this.turn;
     return moves.filter(m => {
-      if (m.vanish.length == 0) return true;
+      if (m.vanish.length == 0) {
+        // subTurn == 0: need to check if a move exists at subTurn == 1
+        this.play(m);
+        const res = this.filterValid(this.getAllPotentialMoves()).length > 0;
+        this.undo(m);
+        return res;
+      }
       const start = { x: m.vanish[0].x, y: m.vanish[0].y };
       const end = { x: m.appear[0].x, y: m.appear[0].y };
-      if (start.x == end.x && start.y == end.y) return true; //unfinished turn
+      if (start.x == end.x && start.y == end.y) {
+        // Unfinished turn: require careful check
+        this.play(m);
+        let res = false;
+        if (this.subTurn == 1)
+          // Can either play a move, or specialize a piece
+          res = this.filterValid(this.getAllPotentialMoves()).length > 0;
+        else {
+          // subTurn == 2: can only play a specialized piece
+          res = this.filterValid(
+            this.getPotentialMovesFrom([m.end.x, m.end.y])).length > 0;
+        }
+        this.undo(m);
+        return res;
+      }
       this.play(m);
       const res = !this.underCheck(color);
       this.undo(m);
@@ -294,9 +340,10 @@ export class BarioRules extends ChessRules {
       }
       return false;
     };
-    if (this.subTurn == 0) return true; //always one reserve for an undefined
-    if (!super.atLeastOneMove()) return atLeastOneReserveMove();
-    return true;
+    if (this.subTurn == 0) return atLeastOneReserveMove();
+    const canMoveSomething = super.atLeastOneMove();
+    if (this.subTurn == 2) return canMoveSomething;
+    return (canMoveSomething || atLeastOneReserveMove());
   }
 
   underCheck(color) {
@@ -326,7 +373,7 @@ export class BarioRules extends ChessRules {
       ];
       let [i, j] = [x1 + step[0], y1 + step[1]];
       while (i != x2 || j != y2) {
-        if (this.board[i][j] != V.EMPTY) return false;
+        if (!V.OnBoard(i, j) || this.board[i][j] != V.EMPTY) return false;
         i += step[0];
         j += step[1];
       }
@@ -355,7 +402,13 @@ export class BarioRules extends ChessRules {
     return false;
   }
 
+  getCheckSquares() {
+    if (this.movesCount <= 2) return [];
+    return super.getCheckSquares();
+  }
+
   play(move) {
+    move.turn = [this.turn, this.subTurn]; //easier undo (TODO?)
     const toNextPlayer = () => {
       V.PlayOnBoard(this.board, move);
       this.turn = V.GetOppCol(this.turn);
@@ -364,42 +417,37 @@ export class BarioRules extends ChessRules {
       this.movesCount++;
       this.postPlay(move);
     };
-    if (move.vanish.length == 0) {
-      toNextPlayer();
-      return;
+    if (this.movesCount <= 1) toNextPlayer();
+    else if (move.vanish.length == 0) {
+      // Removal (subTurn == 0 --> 1)
+      this.reserve[this.turn][move.start.p]--;
+      this.subTurn++;
     }
-    const start = { x: move.vanish[0].x, y: move.vanish[0].y };
-    const end = { x: move.appear[0].x, y: move.appear[0].y };
-    if (start.x == end.x && start.y == end.y) {
-      // Specialisation (subTurn == 1 before 2), or Removal (subTurn == 0).
-      // In both cases, turn not over, and a piece removed from reserve
-      this.reserve[this.turn][move.appear[0].p]--;
-      if (move.appear[0].c == move.vanish[0].c) {
-        // Specialisation: play "move" on board
+    else {
+      const start = { x: move.vanish[0].x, y: move.vanish[0].y };
+      const end = { x: move.appear[0].x, y: move.appear[0].y };
+      if (start.x == end.x && start.y == end.y) {
+        // Specialisation (subTurn == 1 before 2)
+        this.reserve[this.turn][move.appear[0].p]--;
         V.PlayOnBoard(this.board, move);
         this.definitions.push(move.end);
+        this.subTurn++;
+      }
+      else {
+        // Normal move (subTurn 1 or 2: change turn)
+        this.epSquares.push(this.getEpSquare(move));
+        toNextPlayer();
       }
-      this.subTurn++;
-    }
-    else {
-      // Normal move (subTurn 1 or 2: change turn)
-      this.epSquares.push(this.getEpSquare(move));
-      toNextPlayer();
     }
   }
 
   postPlay(move) {
     const color = V.GetOppCol(this.turn);
-    if (move.vanish.length == 0) {
-      this.kingPos[color] = [move.end.x, move.end.y];
-      const firstRank = (color == 'w' ? 7 : 0);
-      for (let j = 0; j < 8; j++) {
-        if (j != move.end.y) this.board[firstRank][j] = color + V.UNDEFINED;
-      }
-    }
+    if (this.movesCount <= 2) this.kingPos[color] = [move.end.x, move.end.y];
     else {
       if (move.vanish.length == 2 && move.vanish[1].p == V.UNDEFINED)
         this.captureUndefined.push(move.end);
+      else this.captureUndefined.push(null);
       if (move.appear[0].p == V.KING) super.postPlay(move);
       else {
         // If now all my pieces are defined, back to undefined state,
@@ -417,31 +465,55 @@ export class BarioRules extends ChessRules {
           })
         ) {
           const piecesList = [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN];
-          let myPieces = {};
+          const oppCol = this.turn;
+          let definedPieces = { w: {}, b: {} };
           for (let i=0; i<8; i++) {
             for (let j=0; j<8; j++) {
-              if (
-                this.board[i][j] != V.EMPTY &&
-                this.getColor(i, j) == color
-              ) {
+              if (this.board[i][j] != V.EMPTY) {
                 const p = this.getPiece(i, j);
-                if (piecesList.includes(p))
-                  myPieces[p] = (!myPieces[p] ? 1 : myPieces[p] + 1);
+                const c = this.getColor(i, j);
+                if (piecesList.includes(p)) {
+                  definedPieces[c][p] =
+                    (!definedPieces[c][p] ? 1 : definedPieces[c][p] + 1);
+                }
               }
             }
           }
-          const pk = Object.keys(myPieces);
-          if (pk.length >= 2) {
+          const my_pk = Object.keys(definedPieces[color]);
+          const opp_pk = Object.keys(definedPieces[oppCol]);
+          const oppRevert = (
+            opp_pk.length >= 2 ||
+            (
+              // Only one opponent's piece is defined, but
+              // at least a different piece wait in reserve:
+              opp_pk.length == 1 &&
+              Object.keys(this.reserve[oppCol]).some(k => {
+                return (k != opp_pk[0] && this.reserve[oppCol][k] >= 1);
+              })
+            )
+          );
+          if (my_pk.length >= 2 || oppRevert) {
+            // NOTE: necessary HACK... because the move is played already.
+            V.UndoOnBoard(this.board, move);
             move.position = this.getBaseFen();
-            for (let p of pk) this.reserve[color][p] = myPieces[p];
-            for (let i=0; i<8; i++) {
-              for (let j=0; j<8; j++) {
-                if (
-                  this.board[i][j] != V.EMPTY &&
-                  this.getColor(i, j) == color &&
-                  piecesList.includes(this.getPiece(i, j))
-                ) {
-                  this.board[i][j] = color + V.UNDEFINED;
+            move.reserve = JSON.parse(JSON.stringify(this.reserve));
+            V.PlayOnBoard(this.board, move);
+            for (
+              let cp of [{ c: color, pk: my_pk }, { c: oppCol, pk: opp_pk }]
+            ) {
+              if (cp.pk.length >= 2 || (cp.c == oppCol && oppRevert)) {
+                for (let p of cp.pk)
+                  this.reserve[cp.c][p] += definedPieces[cp.c][p];
+                for (let i=0; i<8; i++) {
+                  for (let j=0; j<8; j++) {
+                    if (
+                      this.board[i][j] != V.EMPTY &&
+                      this.getColor(i, j) == cp.c &&
+                      piecesList.includes(this.getPiece(i, j))
+                    ) {
+                      this.board[i][j] = cp.c + V.UNDEFINED;
+                    }
+                  }
                 }
               }
             }
@@ -454,67 +526,75 @@ export class BarioRules extends ChessRules {
   undo(move) {
     const toPrevPlayer = () => {
       V.UndoOnBoard(this.board, move);
-      this.turn = V.GetOppCol(this.turn);
+      [this.turn, this.subTurn] = move.turn;
       this.movesCount--;
       this.postUndo(move);
     };
-    if (move.vanish.length == 0) {
-      toPrevPlayer();
-      return;
+    if (this.movesCount <= 2 && move.appear[0].p == V.KING) toPrevPlayer();
+    else if (move.vanish.length == 0) {
+      this.reserve[this.turn][move.start.p]++;
+      this.subTurn = move.turn[1];
     }
-    const start = { x: move.vanish[0].x, y: move.vanish[0].y };
-    const end = { x: move.appear[0].x, y: move.appear[0].y };
-    if (start.x == end.x && start.y == end.y) {
-      this.reserve[this.turn][move.appear[0].p]++;
-      if (move.appear[0].c == move.vanish[0].c) {
+    else {
+      const start = { x: move.vanish[0].x, y: move.vanish[0].y };
+      const end = { x: move.appear[0].x, y: move.appear[0].y };
+      if (start.x == end.x && start.y == end.y) {
+        this.reserve[this.turn][move.appear[0].p]++;
         V.UndoOnBoard(this.board, move);
         this.definitions.pop();
+        this.subTurn = move.turn[1];
+      }
+      else {
+        this.epSquares.pop();
+        toPrevPlayer();
       }
-      this.subTurn--;
-    }
-    else {
-      this.epSquares.pop();
-      toPrevPlayer();
     }
   }
 
   postUndo(move) {
     const color = this.turn;
-    if (move.vanish.length == 0) {
-      this.kingPos[color] = [-1, -1];
-      const firstRank = (color == 'w' ? 7 : 0);
-      for (let j = 0; j < 8; j++) this.board[firstRank][j] = "";
-    }
+    if (this.movesCount <= 1) this.kingPos[color] = [-1, -1];
     else {
-      if (move.vanish.length == 2 && move.vanish[1].p == V.UNDEFINED)
-        this.captureUndefined.pop();
+      this.captureUndefined.pop();
       if (move.appear[0].p == V.KING) super.postUndo(move);
       else {
         if (!!move.position) {
           this.board = V.GetBoard(move.position);
-          this.reserve[color] = {
-            [V.ROOK]: 0,
-            [V.KNIGHT]: 0,
-            [V.BISHOP]: 0,
-            [V.QUEEN]: 0
-          }
+          this.reserve = move.reserve;
         }
       }
     }
   }
 
   getComputerMove() {
+    let initMoves = this.getAllValidMoves();
+    if (initMoves.length == 0) return null;
+    // Loop until valid move is found (no un-specifiable piece...)
     const color = this.turn;
-    // Just play at random for now...
-    let mvArray = [];
-    while (this.turn == color) {
-      const moves = this.getAllValidMoves();
-      const choice = moves[randInt(moves.length)];
-      mvArray.push(choice);
-      this.play(choice);
+    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 (this.turn == color) {
+          if (this.subTurn == 1) moves = this.getAllValidMoves();
+          else {
+            // subTurn == 2
+            moves = this.filterValid(
+              this.getPotentialMovesFrom([mv.end.x, mv.end.y]));
+          }
+        }
+        else break;
+      }
+      const thisIsTheEnd = (this.turn != color);
+      for (let i = mvArray.length - 1; i >= 0; i--) this.undo(mvArray[i]);
+      if (thisIsTheEnd) return (mvArray.length > 1 ? mvArray : mvArray[0]);
     }
-    for (let i = mvArray.length - 1; i >= 0; i--) this.undo(mvArray[i]);
-    return (mvArray.length == 1? mvArray[0] : mvArray);
+    return null; //never reached
   }
 
   static get VALUES() {
@@ -524,19 +604,16 @@ export class BarioRules extends ChessRules {
   // NOTE: evalPosition is wrong, but unused (random mover)
 
   getNotation(move) {
-    const end = { x: move.appear[0].x, y: move.appear[0].y };
+    const end = { x: move.end.x, y: move.end.y };
     const endSquare = V.CoordsToSquare(end);
+    if (move.appear.length == 0)
+      // Removal
+      return move.start.p.toUpperCase() + endSquare + "X";
     if (move.vanish.length == 0) return "K@" + endSquare;
     const start = { x: move.vanish[0].x, y: move.vanish[0].y };
-    if (start.x == end.x && start.y == end.y) {
-      // Something is specialized, or removed
-      const symbol = move.appear[0].p.toUpperCase();
-      if (move.appear[0].c == move.vanish[0].c)
-        // Specialisation
-        return symbol + "@" + endSquare;
-      // Removal:
-      return symbol + endSquare + "X";
-    }
+    if (start.x == end.x && start.y == end.y)
+      // Something is specialized
+      return move.appear[0].p.toUpperCase() + "@" + endSquare;
     // Normal move
     return super.getNotation(move);
   }