Add Checkered1 + fix last move highlights
[vchess.git] / client / src / variants / Synchrone.js
index 372bc1e..c8f2be5 100644 (file)
@@ -1,11 +1,9 @@
-// TODO: debug, and forbid self-capture of king.
-
 import { ChessRules } from "@/base_rules";
 import { randInt } from "@/utils/alea";
 
 export class SynchroneRules extends ChessRules {
   static get CanAnalyze() {
-    return true; //false;
+    return false;
   }
 
   static get ShowMoves() {
@@ -18,12 +16,12 @@ export class SynchroneRules extends ChessRules {
     // 5) Check whiteMove
     if (
       (
-        fenParsed.turn == "w" &&
+        fenParsed.turn == "b" &&
         // NOTE: do not check really JSON stringified move...
         (!fenParsed.whiteMove || fenParsed.whiteMove == "-")
       )
       ||
-      (fenParsed.turn == "b" && fenParsed.whiteMove != "-")
+      (fenParsed.turn == "w" && fenParsed.whiteMove != "-")
     ) {
       return false;
     }
@@ -108,20 +106,20 @@ export class SynchroneRules extends ChessRules {
     });
   }
 
-  // NOTE: lazy unefficient implementation (for now. TODO?)
   getPossibleMovesFrom([x, y]) {
-    const moves = this.getAllValidMoves();
-    return moves.filter(m => {
-      return m.start.x == x && m.start.y == y;
-    });
+    let moves = this.filterValid(super.getPotentialMovesFrom([x, y]));
+    if (!this.underCheck(this.getColor(x, y)))
+      // Augment with potential recaptures, except if we are under check
+      Array.prototype.push.apply(moves, this.getRecaptures([x, y]));
+    return moves;
   }
 
-  getCaptures(x, y) {
-    const color = this.turn;
+  // Aux function used to find opponent and self captures
+  getCaptures(from, to, color) {
     const sliderAttack = (xx, yy, allowedSteps) => {
-      const deltaX = xx - x,
+      const deltaX = xx - to[0],
             absDeltaX = Math.abs(deltaX);
-      const deltaY = yy - y,
+      const deltaY = yy - to[1],
             absDeltaY = Math.abs(deltaY);
       const step = [ deltaX / absDeltaX || 0, deltaY / absDeltaY || 0 ];
       if (
@@ -131,61 +129,70 @@ export class SynchroneRules extends ChessRules {
       ) {
         return null;
       }
-      let sq = [ x + step[0], y + step[1] ];
+      let sq = [ to[0] + step[0], to[1] + step[1] ];
       while (sq[0] != xx || sq[1] != yy) {
         // NOTE: no need to check OnBoard in this special case
         if (this.board[sq[0]][sq[1]] != V.EMPTY) return null;
         sq[0] += step[0];
         sq[1] += step[1];
       }
-      return this.getBasicMove([xx, yy], [x, y]);
+      return this.getBasicMove([xx, yy], [to[0], to[1]]);
     };
-    // Can I take on the square [x, y] ?
+    // Can I take on the square 'to' ?
     // If yes, return the (list of) capturing move(s)
+    const getTargetedCaptures = ([i, j]) => {
+      let move = null;
+      // From [i, j]:
+      switch (this.getPiece(i, j)) {
+        case V.PAWN: {
+          // Pushed pawns move as enemy pawns
+          const shift = (color == 'w' ? 1 : -1);
+          if (to[0] + shift == i && Math.abs(to[1] - j) == 1)
+            move = this.getBasicMove([i, j], to);
+          break;
+        }
+        case V.KNIGHT: {
+          const deltaX = Math.abs(i - to[0]);
+          const deltaY = Math.abs(j - to[1]);
+          if (
+            deltaX + deltaY == 3 &&
+            [1, 2].includes(deltaX) &&
+            [1, 2].includes(deltaY)
+          ) {
+            move = this.getBasicMove([i, j], to);
+          }
+          break;
+        }
+        case V.KING:
+          if (Math.abs(i - to[0]) <= 1 && Math.abs(j - to[1]) <= 1)
+            move = this.getBasicMove([i, j], to);
+          break;
+        case V.ROOK: {
+          move = sliderAttack(i, j, V.steps[V.ROOK]);
+          break;
+        }
+        case V.BISHOP: {
+          move = sliderAttack(i, j, V.steps[V.BISHOP]);
+          break;
+        }
+        case V.QUEEN: {
+          move = sliderAttack(i, j, V.steps[V.ROOK].concat(V.steps[V.BISHOP]));
+          break;
+        }
+      }
+      return move;
+    };
     let moves = [];
-    for (let i=0; i<8; i++) {
-      for (let j=0; j<8; j++) {
-        if (this.getColor(i, j) == color) {
-          switch (this.getPiece(i, j)) {
-            case V.PAWN: {
-              // Pushed pawns move as enemy pawns
-              const shift = (color == 'w' ? 1 : -1);
-              if (x + shift == i && Math.abs(y - j) == 1)
-                moves.push(this.getBasicMove([i, j], [x, y]));
-              break;
-            }
-            case V.KNIGHT: {
-              const deltaX = Math.abs(i - x);
-              const deltaY = Math.abs(j - y);
-              if (
-                deltaX + deltaY == 3 &&
-                [1, 2].includes(deltaX) &&
-                [1, 2].includes(deltaY)
-              ) {
-                moves.push(this.getBasicMove([i, j], [x, y]));
-              }
-              break;
-            }
-            case V.KING:
-              if (Math.abs(i - x) <= 1 && Math.abs(j - y) <= 1)
-                moves.push(this.getBasicMove([i, j], [x, y]));
-              break;
-            case V.ROOK: {
-              const mv = sliderAttack(i, j, V.steps[V.ROOK]);
-              if (!!mv) moves.push(mv);
-              break;
-            }
-            case V.BISHOP: {
-              const mv = sliderAttack(i, j, V.steps[V.BISHOP]);
-              if (!!mv) moves.push(mv);
-              break;
-            }
-            case V.QUEEN: {
-              const mv = sliderAttack(
-                i, j, V.steps[V.ROOK].concat(V.steps[V.BISHOP]));
-              if (!!mv) moves.push(mv);
-              break;
-            }
+    if (!!from) {
+      const theMove = getTargetedCaptures(from);
+      if (!!theMove) moves.push(theMove);
+    }
+    else {
+      for (let i=0; i<8; i++) {
+        for (let j=0; j<8; j++) {
+          if (this.getColor(i, j) == color) {
+            const newMove = getTargetedCaptures([i, j]);
+            if (!!newMove) moves.push(newMove);
           }
         }
       }
@@ -193,46 +200,52 @@ export class SynchroneRules extends ChessRules {
     return this.filterValid(moves);
   }
 
-  getAllValidMoves() {
+  getRecaptures(from) {
+    // 1) Generate all opponent's capturing moves
+    let oppCaptureMoves = [];
     const color = this.turn;
-    // 0) Generate our possible moves
-    let myMoves = super.getAllValidMoves();
+    const oppCol = V.GetOppCol(color);
+    for (let i=0; i<8; i++) {
+      for (let j=0; j<8; j++) {
+        if (
+          this.getColor(i, j) == color &&
+          // Do not consider king captures: self-captures of king are forbidden
+          this.getPiece(i, j) != V.KING
+        ) {
+          Array.prototype.push.apply(
+            oppCaptureMoves,
+            this.getCaptures(null, [i, j], oppCol)
+          );
+        }
+      }
+    }
+    // 2) Play each opponent's capture, and see if back-captures are possible:
     // Lookup table to quickly decide if a move is already in list:
     let moveSet = {};
-    const getMoveHash = (move) => {
-      return (
-        "m" + move.start.x + move.start.y +
-              move.end.x + move.end.y +
-              // Also use m.appear[0].p for pawn promotions
-              move.appear[0].p
-      );
-    };
-    myMoves.forEach(m => moveSet[getMoveHash(m)] = true);
-    // 1) Generate all opponent's moves
-    this.turn = V.GetOppCol(color);
-    const oppMoves = super.getAllValidMoves();
-    this.turn = color;
-    // 2) Play each opponent's move, and see if captures are possible:
-    // --> capturing moving unit only (otherwise some issues)
-    oppMoves.forEach(m => {
-      V.PlayOnBoard(this.board, m);
-      // Can I take on [m.end.x, m.end.y] ?
-      // If yes and not already in list, add it (without the capturing part)
-      let capturingMoves = this.getCaptures(m.end.x, m.end.y);
-      capturingMoves.forEach(cm => {
-        const cmHash = getMoveHash(cm);
-        if (!moveSet[cmHash]) {
-          // The captured unit hasn't moved yet, so temporarily cancel capture
-          cm.vanish.pop();
-          // If m is itself a capturing move: then replace by self-capture
-          if (m.vanish.length == 2) cm.vanish.push(m.vanish[1]);
-          myMoves.push(cm);
-          moveSet[cmHash] = true;
-        }
-      });
-      V.UndoOnBoard(this.board, m);
+    let moves = [];
+    oppCaptureMoves.forEach(m => {
+      // If another opponent capture with same endpoint already processed, skip
+      const mHash = "m" + m.end.x + m.end.y;
+      if (!moveSet[mHash]) {
+        moveSet[mHash] = true;
+        // Just make enemy piece disappear, to clear potential path:
+        const justDisappear = {
+          appear: [],
+          vanish: [m.vanish[0]]
+        };
+        V.PlayOnBoard(this.board, justDisappear);
+        // Can I take on [m.end.x, m.end.y] ? If yes, add to list:
+        this.getCaptures(from, [m.end.x, m.end.y], color)
+          .forEach(cm => moves.push(cm));
+        V.UndoOnBoard(this.board, justDisappear);
+      }
     });
-    return myMoves;
+    return moves;
+  }
+
+  getAllValidMoves() {
+    // Return possible moves + potential recaptures
+    return super.getAllValidMoves().concat(this.getRecaptures());
   }
 
   filterValid(moves) {
@@ -278,20 +291,28 @@ export class SynchroneRules extends ChessRules {
       smove.appear.push(m1.appear[0]);
       smove.appear.push(m2.appear[0]);
       // "Captured" pieces may have moved:
-      if (
+      if (m1.appear.length == 2) {
+        // Castle
+        smove.appear.push(m1.appear[1]);
+        smove.vanish.push(m1.vanish[1]);
+      } else if (
         m1.vanish.length == 2 &&
         (
-          m2.end.x != m1.vanish[1].x ||
-          m2.end.y != m1.vanish[1].y
+          m1.vanish[1].x != m2.start.x ||
+          m1.vanish[1].y != m2.start.y
         )
       ) {
         smove.vanish.push(m1.vanish[1]);
       }
-      if (
+      if (m2.appear.length == 2) {
+        // Castle
+        smove.appear.push(m2.appear[1]);
+        smove.vanish.push(m2.vanish[1]);
+      } else if (
         m2.vanish.length == 2 &&
         (
-          m1.end.x != m2.vanish[1].x ||
-          m1.end.y != m2.vanish[1].y
+          m2.vanish[1].x != m1.start.x ||
+          m2.vanish[1].y != m1.start.y
         )
       ) {
         smove.vanish.push(m2.vanish[1]);
@@ -313,8 +334,6 @@ export class SynchroneRules extends ChessRules {
       } else {
         // One move is a self-capture and the other a normal capture:
         // only the self-capture appears
-        console.log(m1);
-        console.log(m2);
         const selfCaptureMove =
           m1.vanish[1].c == m1.vanish[0].c
             ? m1
@@ -325,6 +344,12 @@ export class SynchroneRules extends ChessRules {
           p: selfCaptureMove.appear[0].p,
           c: selfCaptureMove.vanish[0].c
         });
+        smove.vanish.push({
+          x: m1.end.x,
+          y: m1.end.y,
+          p: selfCaptureMove.vanish[1].p,
+          c: selfCaptureMove.vanish[0].c
+        });
       }
     }
     return smove;
@@ -361,6 +386,7 @@ export class SynchroneRules extends ChessRules {
     // A full turn just ended:
     const smove = this.resolveSynchroneMove(move);
     V.PlayOnBoard(this.board, smove);
+    move.whiteMove = this.whiteMove; //for undo
     this.whiteMove = null;
 
     // Update king position + flags
@@ -399,18 +425,27 @@ export class SynchroneRules extends ChessRules {
   }
 
   postUndo(move) {
-    if (this.turn == 'w')
+    if (this.turn == 'w') {
       // Reset king positions: scan board
       this.scanKings();
+      // Also reset whiteMove
+      this.whiteMove = null;
+    } else this.whiteMove = move.whiteMove;
   }
 
-  getCheckSquares(color) {
-    if (color == 'b') return [];
+  getCheckSquares() {
+    const color = this.turn;
+    if (color == 'b') {
+      // kingPos must be reset for appropriate highlighting:
+      var lastMove = JSON.parse(JSON.stringify(this.whiteMove));
+      this.undo(lastMove); //will erase whiteMove, thus saved above
+    }
     let res = [];
-    if (this.underCheck('w'))
+    if (this.kingPos['w'][0] >= 0 && this.underCheck('w'))
       res.push(JSON.parse(JSON.stringify(this.kingPos['w'])));
-    if (this.underCheck('b'))
+    if (this.kingPos['b'][0] >= 0 && this.underCheck('b'))
       res.push(JSON.parse(JSON.stringify(this.kingPos['b'])));
+    if (color == 'b') this.play(lastMove);
     return res;
   }
 
@@ -465,4 +500,16 @@ export class SynchroneRules extends ChessRules {
       candidates.push(i);
     return moves[candidates[randInt(candidates.length)]];
   }
+
+  getNotation(move) {
+    if (move.appear.length == 2 && move.appear[0].p == V.KING)
+      // Castle
+      return move.end.y < move.start.y ? "0-0-0" : "0-0";
+    // Basic system: piece + init + dest square
+    return (
+      move.vanish[0].p.toUpperCase() +
+      V.CoordsToSquare(move.start) +
+      V.CoordsToSquare(move.end)
+    );
+  }
 };