Fix Synchrone2 pass move
[vchess.git] / client / src / variants / Synchrone2.js
index 7f62398..dc038ec 100644 (file)
@@ -1,11 +1,11 @@
-import { ChessRules } from "@/base_rules";
+import { ChessRules, Move } from "@/base_rules";
 import { Synchrone1Rules } from "@/variants/Synchrone1";
 import { randInt } from "@/utils/alea";
 
 export class Synchrone2Rules extends Synchrone1Rules {
 
   static get CanAnalyze() {
-    return true;//false;
+    return false;
   }
 
   static get HasEnpassant() {
@@ -16,7 +16,7 @@ export class Synchrone2Rules extends Synchrone1Rules {
     if (!Synchrone1Rules.IsGoodFen(fen)) return false;
     const fenParsed = V.ParseFen(fen);
     // 5) Check initFen (not really... TODO?)
-    if (!fenParsed.initFen || fenParsed.initFen == "-") return false;
+    if (!fenParsed.initFen) return false;
     return true;
   }
 
@@ -32,18 +32,16 @@ export class Synchrone2Rules extends Synchrone1Rules {
   }
 
   getInitfenFen() {
-    if (!this.whiteMove) return "-";
-    return JSON.stringify({
-      start: this.whiteMove.start,
-      end: this.whiteMove.end,
-      appear: this.whiteMove.appear,
-      vanish: this.whiteMove.vanish
-    });
+    const L = this.initfenStack.length;
+    return L > 0 ? this.initfenStack[L-1] : "-";
   }
 
   getFen() {
     return (
-      super.getFen() + " " +
+      super.getBaseFen() + " " +
+      super.getTurnFen() + " " +
+      this.movesCount + " " +
+      super.getFlagsFen() + " " +
       this.getInitfenFen() + " " +
       this.getWhitemoveFen()
     );
@@ -52,7 +50,7 @@ export class Synchrone2Rules extends Synchrone1Rules {
   static GenRandInitFen(randomness) {
     const res = ChessRules.GenRandInitFen(randomness);
     // Add initFen field:
-    return res.slice(0, -1) + " " + res.split(' ')[1] + " -";
+    return res.slice(0, -1) + res.split(' ')[0] + " -";
   }
 
   setOtherVariables(fen) {
@@ -64,44 +62,176 @@ export class Synchrone2Rules extends Synchrone1Rules {
       parsedFen.whiteMove != "-"
         ? JSON.parse(parsedFen.whiteMove)
         : null;
-    // And initFen (not empty)
-    this.initFen = parsedFen.initFen;
+    // And initFen (could be empty)
+    this.initfenStack = [];
+    if (parsedFen.initFen != "-") this.initfenStack.push(parsedFen.initFen);
   }
 
   getPotentialMovesFrom([x, y]) {
     if (this.movesCount % 4 <= 1) return super.getPotentialMovesFrom([x, y]);
-    // TODO: either add a "blackMove' field in FEN (bof...),
-    // or write an helper function to detect from diff positions,
-    // which piece moved (if not disappeared!), which moves are valid.
-    // + do not forget pass move (king 2 king): always possible at stage 2.
-    return [];
+    // Diff current and old board to know which pieces have moved,
+    // and to deduce possible moves at stage 2.
+    const L = this.initfenStack.length;
+    let initBoard = V.GetBoard(this.initfenStack[L-1]);
+    let appeared = [];
+    const c = this.turn;
+    const oppCol = V.GetOppCol(c);
+    for (let i=0; i<8; i++) {
+      for (let j=0; j<8; j++) {
+        if (this.board[i][j] != initBoard[i][j]) {
+          if (this.board[i][j] != V.EMPTY) {
+            const color = this.board[i][j].charAt(0);
+            appeared.push({ c: color, x: i, y: j });
+            // Pawns capture in diagonal => the following fix.
+            // (Other way would be to redefine getPotentialPawnMoves()...)
+            if (color == oppCol) initBoard[i][j] = this.board[i][j];
+          }
+        }
+      }
+    }
+    const saveBoard = this.board;
+    this.board = initBoard;
+    const movesInit = super.getPotentialMovesFrom([x, y]);
+    this.board = saveBoard;
+    const target = appeared.find(a => a.c == oppCol) || { x: -1, y: -1 };
+    let movesNow = super.getPotentialMovesFrom([x, y]).filter(m => {
+      return (
+        m.end.x == target.x &&
+        m.end.y == target.y &&
+        movesInit.some(mi => mi.end.x == m.end.x && mi.end.y == m.end.y)
+      );
+    });
+    const passTarget =
+      (x != this.kingPos[c][0] || y != this.kingPos[c][1]) ? c : oppCol;
+    movesNow.push(
+      new Move({
+        start: { x: x, y: y },
+        end: {
+          x: this.kingPos[passTarget][0],
+          y: this.kingPos[passTarget][1]
+        },
+        appear: [],
+        vanish: []
+      })
+    );
+    return movesNow;
+  }
+
+  filterValid(moves) {
+    const nonEmptyMove = moves.find(m => m.vanish.length > 0);
+    if (!nonEmptyMove) return moves;
+    // filterValid can be called when it's "not our turn":
+    const color = nonEmptyMove.vanish[0].c;
+    return moves.filter(m => {
+      if (m.vanish.length == 0) return true;
+      const piece = m.vanish[0].p;
+      if (piece == V.KING) {
+        this.kingPos[color][0] = m.appear[0].x;
+        this.kingPos[color][1] = m.appear[0].y;
+      }
+      V.PlayOnBoard(this.board, m);
+      let res = !this.underCheck(color);
+      V.UndoOnBoard(this.board, m);
+      if (piece == V.KING) this.kingPos[color] = [m.start.x, m.start.y];
+      return res;
+    });
+  }
+
+  getPossibleMovesFrom([x, y]) {
+    return this.filterValid(this.getPotentialMovesFrom([x, y]));
   }
 
-  play(move) {
-    move.flags = JSON.stringify(this.aggregateFlags());
+  getAllValidMoves() {
+    const moves = this.filterValid(super.getAllPotentialMoves());
+    if (this.movesCount % 4 <= 1) return moves;
+    const emptyIdx = moves.findIndex(m => m.vanish.length == 0);
+    if (emptyIdx >= 0)
+      // Keep only one pass move (for computer play)
+      return moves.filter((m, i) => m.vanish.length > 0 || i == emptyIdx);
+    return moves;
+  }
+
+  play(move, noFlag) {
+    if (this.movesCount % 4 == 0) this.initfenStack.push(this.getBaseFen());
+    if (!noFlag) move.flags = JSON.stringify(this.aggregateFlags());
     // Do not play on board (would reveal the move...)
     this.turn = V.GetOppCol(this.turn);
     this.movesCount++;
-    this.postPlay(move);
+    if ([0, 3].includes(this.movesCount % 4)) this.postPlay(move);
+    else super.postPlay(move); //resolve synchrone move
   }
 
-  undo(move) {
-    this.disaggregateFlags(JSON.parse(move.flags));
+  postPlay(move) {
+    if (this.turn == 'b') {
+      // NOTE: whiteMove is used read-only, so no need to copy
+      this.whiteMove = move;
+      return;
+    }
+
+    // A full "deterministic" turn just ended: no need to resolve
+    const smove = {
+      appear: this.whiteMove.appear.concat(move.appear),
+      vanish: this.whiteMove.vanish.concat(move.vanish)
+    };
+    V.PlayOnBoard(this.board, smove);
+    move.whiteMove = this.whiteMove; //for undo
+    this.whiteMove = null;
+
+    // Update king position + flags
+    let kingAppear = { 'w': false, 'b': false };
+    for (let i=0; i < smove.appear.length; i++) {
+      if (smove.appear[i].p == V.KING) {
+        const c = smove.appear[i].c;
+        kingAppear[c] = true;
+        this.kingPos[c][0] = smove.appear[i].x;
+        this.kingPos[c][1] = smove.appear[i].y;
+      }
+    }
+    for (let i = 0; i < smove.vanish.length; i++) {
+      if (smove.vanish[i].p == V.KING) {
+        const c = smove.vanish[i].c;
+        if (!kingAppear[c]) {
+          this.kingPos[c][0] = -1;
+          this.kingPos[c][1] = -1;
+        }
+        break;
+      }
+    }
+    super.updateCastleFlags(smove);
+    move.smove = smove;
+  }
+
+  undo(move, noFlag) {
+    if (!noFlag) this.disaggregateFlags(JSON.parse(move.flags));
     if (this.turn == 'w')
       // Back to the middle of the move
       V.UndoOnBoard(this.board, move.smove);
     this.turn = V.GetOppCol(this.turn);
     this.movesCount--;
+    if (this.movesCount % 4 == 0) this.initfenStack.pop();
     this.postUndo(move);
   }
 
+  postUndo(move) {
+    if (this.turn == 'w') {
+      // Reset king positions: scan board (TODO: could be more efficient)
+      if (move.vanish.length > 0) this.scanKings();
+      // Also reset whiteMove
+      this.whiteMove = null;
+    }
+    else this.whiteMove = move.whiteMove;
+  }
+
   getCurrentScore() {
-    if (this.movesCount % 4 != 0)
-      // Turn (2 x white + black) not over yet
+    if (this.movesCount % 2 != 0)
+      // Turn [white + black] not over yet
       return "*";
     // Was a king captured?
     if (this.kingPos['w'][0] < 0) return "0-1";
     if (this.kingPos['b'][0] < 0) return "1-0";
+    if (this.movesCount % 4 == 2)
+      // Turn (2 x [white + black]) not over yet
+      return "*";
     const whiteCanMove = this.atLeastOneMove('w');
     const blackCanMove = this.atLeastOneMove('b');
     if (whiteCanMove && blackCanMove) return "*";
@@ -119,4 +249,15 @@ export class Synchrone2Rules extends Synchrone1Rules {
     return (whiteCanMove ? "1-0" : "0-1");
   }
 
+  getComputerMove() {
+    if (this.movesCount % 4 <= 1) return super.getComputerMove();
+    const moves = this.getAllValidMoves();
+    return moves[randInt(moves.length)];
+  }
+
+  getNotation(move) {
+    if (move.vanish.length == 0) return "pass";
+    return super.getNotation(move);
+  }
+
 };