Add Checkered1 + fix last move highlights
[vchess.git] / client / src / variants / Apocalypse.js
index 96431c3..4587be3 100644 (file)
@@ -29,6 +29,12 @@ export class ApocalypseRules extends ChessRules {
     return "byrow";
   }
 
+  getPPpath(m) {
+    // Show the piece taken, if any, and not multiple pawns:
+    if (m.vanish.length == 1) return "Apocalypse/empty";
+    return m.vanish[1].c + m.vanish[1].p;
+  }
+
   static get PIECES() {
     return [V.PAWN, V.KNIGHT];
   }
@@ -63,12 +69,12 @@ export class ApocalypseRules extends ChessRules {
     // 4) 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;
     }
@@ -112,7 +118,9 @@ export class ApocalypseRules extends ChessRules {
   }
 
   getFlagsFen() {
-    return this.penaltyFlags.join("");
+    return (
+      this.penaltyFlags['w'].toString() + this.penaltyFlags['b'].toString()
+    );
   }
 
   setOtherVariables(fen) {
@@ -126,7 +134,10 @@ export class ApocalypseRules extends ChessRules {
   }
 
   setFlags(fenflags) {
-    this.penaltyFlags = [0, 1].map(i => parseInt(fenflags[i]));
+    this.penaltyFlags = {
+      'w': parseInt(fenflags[0]),
+      'b': parseInt(fenflags[1])
+    };
   }
 
   getWhitemoveFen() {
@@ -135,7 +146,8 @@ export class ApocalypseRules extends ChessRules {
       start: this.whiteMove.start,
       end: this.whiteMove.end,
       appear: this.whiteMove.appear,
-      vanish: this.whiteMove.vanish
+      vanish: this.whiteMove.vanish,
+      illegal: this.whiteMove.illegal
     });
   }
 
@@ -149,13 +161,17 @@ export class ApocalypseRules extends ChessRules {
     this.turn = V.GetOppCol(color);
     const oppMoves = super.getAllValidMoves();
     this.turn = color;
-    // For each opponent's move, generate valid moves [from sq]
+    // For each opponent's move, generate valid moves [from sq if same color]
     let speculations = [];
     oppMoves.forEach(m => {
       V.PlayOnBoard(this.board, m);
       const newValidMoves =
         !!sq
-          ? super.getPotentialMovesFrom(sq)
+          ? (
+            this.getColor(sq[0], sq[1]) == color
+              ? super.getPotentialMovesFrom(sq)
+              : []
+            )
           : super.getAllValidMoves();
       newValidMoves.forEach(vm => {
         const mHash = "m" + vm.start.x + vm.start.y + vm.end.x + vm.end.y;
@@ -190,8 +206,8 @@ export class ApocalypseRules extends ChessRules {
       // If 0 or 1 horsemen, promote in knight
       let knightCounter = 0;
       let emptySquares = [];
-      for (let i=0; i<V.size.x; i++) {
-        for (let j=0; j<V.size.y; j++) {
+      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) emptySquares.push([i, j]);
           else if (
             this.getColor(i, j) == color &&
@@ -203,10 +219,22 @@ export class ApocalypseRules extends ChessRules {
       }
       if (knightCounter <= 1) finalPieces = [V.KNIGHT];
       else {
-        // Generate all possible landings
+        // Generate all possible landings, maybe capturing something on the way
+        let capture = undefined;
+        if (this.board[x2][y2] != V.EMPTY) {
+          capture = JSON.parse(JSON.stringify({
+            x: x2,
+            y: y2,
+            c: this.getColor(x2, y2),
+            p: this.getPiece(x2, y2)
+          }));
+        }
         emptySquares.forEach(sq => {
-          if (sq[0] != lastRank)
-            moves.push(this.getBasicMove([x1, y1], [sq[0], sq[1]]));
+          if (sq[0] != lastRank) {
+            let newMove = this.getBasicMove([x1, y1], [sq[0], sq[1]]);
+            if (!!capture) newMove.vanish.push(capture);
+            moves.push(newMove);
+          }
         });
         return;
       }
@@ -233,43 +261,83 @@ export class ApocalypseRules extends ChessRules {
 
   // White and black (partial) moves were played: merge
   resolveSynchroneMove(move) {
-    let m = [this.whiteMove, move];
-    for (let i of [0, 1]) {
-      if (!!m[i].illegal) {
-        // Either an anticipated capture of something which didn't move
-        // (or not to the right square), or a push through blocus.
-        if (
+    let m1 = this.whiteMove;
+    let m2 = move;
+    const movingLikeCapture = (m) => {
+      const shift = (m.vanish[0].c == 'w' ? -1 : 1);
+      return (
+        m.start.x + shift == m.end.x &&
+        Math.abs(m.end.y - m.start.y) == 1
+      );
+    };
+    const isPossible = (m, other) => {
+      return (
+        (
+          m.vanish[0].p == V.KNIGHT &&
           (
-            // Push attempt
-            m[i].start.y == m[i].end.y &&
-            (m[1-i].start.x != m[i].end.x || m[1-i].start.y != m[i].end.y)
+            m.vanish.length == 1 ||
+            m.vanish[1].c != m.vanish[0].c ||
+            // Self-capture attempt
+            (
+              !other.illegal &&
+              other.end.x == m.end.x &&
+              other.end.y == m.end.y
+            )
           )
-          ||
+        )
+        ||
+        (
+          m.vanish[0].p == V.PAWN &&
+          !other.illegal &&
           (
-            // Capture attempt
-            Math.abs(m[i].start.y - m[i].end.y) == 1 &&
-            (m[1-i].end.x != m[i].end.x || m[1-i].end.y != m[i].end.y)
+            (
+              // Promotion attempt
+              m.end.x == (m.vanish[0].c == "w" ? 0 : V.size.x - 1) &&
+              other.vanish.length == 2 &&
+              other.vanish[1].p == V.KNIGHT &&
+              other.vanish[1].c == m.vanish[0].c
+            )
+            ||
+            (
+              // Moving attempt
+              !movingLikeCapture(m) &&
+              other.start.x == m.end.x &&
+              other.start.y == m.end.y
+            )
+            ||
+            (
+              // Capture attempt
+              movingLikeCapture(m) &&
+              other.end.x == m.end.x &&
+              other.end.y == m.end.y
+            )
           )
-        ) {
-          // Just discard the move, and add a penalty point
-          this.penaltyFlags[m[i].vanish[0].c]++;
-          m[i] = null;
-        }
-      }
+        )
+      );
+    };
+    if (!!m1.illegal && !isPossible(m1, m2)) {
+      // Either an anticipated capture of something which didn't move
+      // (or not to the right square), or a push through blocus.
+      // ==> Just discard the move, and add a penalty point
+      this.penaltyFlags[m1.vanish[0].c]++;
+      m1.isNull = true;
     }
-
+    if (!!m2.illegal && !isPossible(m2, m1)) {
+      this.penaltyFlags[m2.vanish[0].c]++;
+      m2.isNull = true;
+    }
+    if (!!m1.isNull) m1 = null;
+    if (!!m2.isNull) m2 = null;
+    // If one move is illegal, just execute the other
+    if (!m1 && !!m2) return m2;
+    if (!m2 && !!m1) return m1;
     // For PlayOnBoard (no need for start / end, irrelevant)
     let smove = {
       appear: [],
       vanish: []
     };
-    const m1 = m[0],
-          m2 = m[1];
-    // If one move is illegal, just execute the other
-    if (!m1 && !!m2) return m2;
-    if (!m2 && !!m1) return m1;
     if (!m1 && !m2) return smove;
-    // Both move are now legal:
+    // Both moves are now legal or at least possible:
     smove.vanish.push(m1.vanish[0]);
     smove.vanish.push(m2.vanish[0]);
     if ((m1.end.x != m2.end.x) || (m1.end.y != m2.end.y)) {
@@ -296,15 +364,27 @@ export class ApocalypseRules extends ChessRules {
         smove.vanish.push(m2.vanish[1]);
       }
     } else {
-      // Collision: both disappear except if different kinds (knight remains)
+      // Collision: priority to the anticipated capture, if any.
+      // If ex-aequo: knight wins (higher risk), or both disappears.
+      // Then, priority to the knight vs pawn: remains.
+      // Finally: both disappears.
+      let remain = null;
       const p1 = m1.vanish[0].p;
       const p2 = m2.vanish[0].p;
-      if ([p1, p2].includes(V.KNIGHT) && [p1, p2].includes(V.PAWN)) {
+      if (!!m1.illegal && !m2.illegal) remain = { c: 'w', p: p1 };
+      else if (!!m2.illegal && !m1.illegal) remain = { c: 'b', p: p2 };
+      if (!remain) {
+        // Either both are illegal or both are legal
+        if (p1 == V.KNIGHT && p2 == V.PAWN) remain = { c: 'w', p: p1 };
+        else if (p2 == V.KNIGHT && p1 == V.PAWN) remain = { c: 'b', p: p2 };
+        // If remain is still null: same type same risk, both disappear
+      }
+      if (!!remain) {
         smove.appear.push({
           x: m1.end.x,
           y: m1.end.y,
-          p: V.KNIGHT,
-          c: (p1 == V.KNIGHT ? 'w' : 'b')
+          p: remain.p,
+          c: remain.c
         });
       }
     }
@@ -325,7 +405,6 @@ export class ApocalypseRules extends ChessRules {
       this.whiteMove = move;
       return;
     }
-
     // A full turn just ended:
     const smove = this.resolveSynchroneMove(move);
     V.PlayOnBoard(this.board, smove);
@@ -349,7 +428,7 @@ export class ApocalypseRules extends ChessRules {
     else this.whiteMove = move.whiteMove;
   }
 
-  getCheckSquares(color) {
+  getCheckSquares() {
     return [];
   }
 
@@ -373,9 +452,9 @@ export class ApocalypseRules extends ChessRules {
       return "1-0"; //fmCount['b'] == 0
     }
     // Check penaltyFlags: if a side has 2 or more, it loses
-    if (this.penaltyFlags.every(f => f == 2)) return "1/2";
-    if (this.penaltyFlags[0] == 2) return "0-1";
-    if (this.penaltyFlags[1] == 2) return "1-0";
+    if (Object.values(this.penaltyFlags).every(v => v == 2)) return "1/2";
+    if (this.penaltyFlags['w'] == 2) return "0-1";
+    if (this.penaltyFlags['b'] == 2) return "1-0";
     if (!this.atLeastOneMove('w') || !this.atLeastOneMove('b'))
       // Stalemate (should be very rare)
       return "1/2";
@@ -390,30 +469,42 @@ export class ApocalypseRules extends ChessRules {
       // TODO: this situation should not happen
       return null;
 
-    if (Math.random() < 0.5)
-      // Return a random move
-      return moves[randInt(moves.length)];
-
     // Rank moves at depth 1:
-    // try to capture something (not re-capturing)
+    let validMoves = [];
+    let illegalMoves = [];
     moves.forEach(m => {
-      V.PlayOnBoard(this.board, m);
-      m.eval = this.evalPosition();
-      V.UndoOnBoard(this.board, m);
+      // Warning: m might be illegal!
+      if (!m.illegal) {
+        V.PlayOnBoard(this.board, m);
+        m.eval = this.evalPosition();
+        V.UndoOnBoard(this.board, m);
+        validMoves.push(m);
+      } else illegalMoves.push(m);
     });
-    moves.sort((a, b) => {
+
+    const illegalRatio = illegalMoves.length / moves.length;
+    if (Math.random() < illegalRatio)
+      // Return a random illegal move
+      return illegalMoves[randInt(illegalMoves.length)];
+
+    validMoves.sort((a, b) => {
       return (color == "w" ? 1 : -1) * (b.eval - a.eval);
     });
     let candidates = [0];
-    for (let i = 1; i < moves.length && moves[i].eval == moves[0].eval; i++)
+    for (
+      let i = 1;
+      i < validMoves.length && validMoves[i].eval == moves[0].eval;
+      i++
+    ) {
       candidates.push(i);
-    return moves[candidates[randInt(candidates.length)]];
+    }
+    return validMoves[candidates[randInt(candidates.length)]];
   }
 
   getNotation(move) {
     // Basic system: piece + init + dest square
     return (
-      move.vanish[0].p.toUpperCase() +
+      (move.vanish[0].p == V.KNIGHT ? "N" : "") +
       V.CoordsToSquare(move.start) +
       V.CoordsToSquare(move.end)
     );