Draft Hiddenqueen, Grasshopper and Knightmate chess (rules unwritten)
[vchess.git] / client / src / base_rules.js
index 5bf47f7..149bb4d 100644 (file)
@@ -46,16 +46,26 @@ export const ChessRules = class ChessRules {
   static get CanAnalyze() {
     return true;
   }
+  // Patch: issues with javascript OOP, objects can't access static fields.
+  get canAnalyze() {
+    return V.CanAnalyze;
+  }
 
   // Some variants show incomplete information,
   // and thus show only a partial moves list or no list at all.
   static get ShowMoves() {
     return "all";
   }
+  get showMoves() {
+    return V.ShowMoves;
+  }
 
-  // Path to pieces
-  static getPpath(b) {
-    return b; //usual pieces in pieces/ folder
+  // Some variants always show the same orientation
+  static get CanFlip() {
+    return true;
+  }
+  get canFlip() {
+    return V.CanFlip;
   }
 
   // Turn "wb" into "B" (for FEN)
@@ -160,6 +170,11 @@ export const ChessRules = class ChessRules {
     return V.CoordToColumn(coords.y) + (V.size.x - coords.x);
   }
 
+  // Path to pieces
+  getPpath(b) {
+    return b; //usual pieces in pieces/ folder
+  }
+
   // Aggregates flags into one object
   aggregateFlags() {
     return this.castleFlags;
@@ -180,17 +195,19 @@ export const ChessRules = class ChessRules {
     }
     // Argument is a move:
     const move = moveOrSquare;
-    const [sx, sy, ex] = [move.start.x, move.start.y, move.end.x];
+    const s = move.start,
+          e = move.end;
     // NOTE: next conditions are first for Atomic, and last for Checkered
     if (
       move.appear.length > 0 &&
-      Math.abs(sx - ex) == 2 &&
+      Math.abs(s.x - e.x) == 2 &&
+      s.y == e.y &&
       move.appear[0].p == V.PAWN &&
       ["w", "b"].includes(move.appear[0].c)
     ) {
       return {
-        x: (sx + ex) / 2,
-        y: sy
+        x: (s.x + e.x) / 2,
+        y: s.y
       };
     }
     return undefined; //default
@@ -267,12 +284,13 @@ export const ChessRules = class ChessRules {
       pieces[c][knight2Pos] = "n";
       pieces[c][rook2Pos] = "r";
     }
+    // Add turn + flags + enpassant
     return (
       pieces["b"].join("") +
       "/pppppppp/8/8/8/8/PPPPPPPP/" +
       pieces["w"].join("").toUpperCase() +
       " w 0 1111 -"
-    ); //add turn + flags + enpassant
+    );
   }
 
   // "Parse" FEN: just return untransformed string data
@@ -357,9 +375,9 @@ export const ChessRules = class ChessRules {
       for (let indexInRow = 0; indexInRow < rows[i].length; indexInRow++) {
         const character = rows[i][indexInRow];
         const num = parseInt(character);
+        // If num is a number, just shift j:
         if (!isNaN(num)) j += num;
-        //just shift j
-        //something at position i,j
+        // Else: something at position i,j
         else board[i][j++] = V.fen2board(character);
       }
     }
@@ -370,7 +388,6 @@ export const ChessRules = class ChessRules {
   setFlags(fenflags) {
     // white a-castle, h-castle, black a-castle, h-castle
     this.castleFlags = { w: [true, true], b: [true, true] };
-    if (!fenflags) return;
     for (let i = 0; i < 4; i++)
       this.castleFlags[i < 2 ? "w" : "b"][i % 2] = fenflags.charAt(i) == "1";
   }
@@ -379,7 +396,9 @@ export const ChessRules = class ChessRules {
   // INITIALIZATION
 
   constructor(fen) {
-    this.re_init(fen);
+    // In printDiagram() fen isn't supply because only getPpath() is used
+    if (fen)
+      this.re_init(fen);
   }
 
   // Fen string fully describes the game state
@@ -450,7 +469,7 @@ export const ChessRules = class ChessRules {
     return { x: 8, y: 8 };
   }
 
-  // Color of thing on suqare (i,j). 'undefined' if square is empty
+  // Color of thing on square (i,j). 'undefined' if square is empty
   getColor(i, j) {
     return this.board[i][j].charAt(0);
   }
@@ -526,7 +545,7 @@ export const ChessRules = class ChessRules {
   ////////////////////
   // MOVES GENERATION
 
-  // All possible moves from selected square (assumption: color is OK)
+  // All possible moves from selected square
   getPotentialMovesFrom([x, y]) {
     switch (this.getPiece(x, y)) {
       case V.PAWN:
@@ -578,6 +597,7 @@ export const ChessRules = class ChessRules {
         })
       );
     }
+
     return mv;
   }
 
@@ -590,7 +610,7 @@ export const ChessRules = class ChessRules {
       let j = y + step[1];
       while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
         moves.push(this.getBasicMove([x, y], [i, j]));
-        if (oneStep !== undefined) continue outerLoop;
+        if (oneStep) continue outerLoop;
         i += step[0];
         j += step[1];
       }
@@ -617,8 +637,8 @@ export const ChessRules = class ChessRules {
         x + shiftX == lastRank
           ? [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN]
           : [V.PAWN];
-      // One square forward
       if (this.board[x + shiftX][y] == V.EMPTY) {
+        // One square forward
         for (let piece of finalPieces) {
           moves.push(
             this.getBasicMove([x, y], [x + shiftX, y], {
@@ -816,12 +836,10 @@ export const ChessRules = class ChessRules {
   // (for engine and game end)
   getAllValidMoves() {
     const color = this.turn;
-    const oppCol = V.GetOppCol(color);
     let potentialMoves = [];
     for (let i = 0; i < V.size.x; i++) {
       for (let j = 0; j < V.size.y; j++) {
-        // Next condition "!= oppCol" to work with checkered variant
-        if (this.board[i][j] != V.EMPTY && this.getColor(i, j) != oppCol) {
+        if (this.getColor(i, j) == color) {
           Array.prototype.push.apply(
             potentialMoves,
             this.getPotentialMovesFrom([i, j])
@@ -835,10 +853,9 @@ export const ChessRules = class ChessRules {
   // Stop at the first move found
   atLeastOneMove() {
     const color = this.turn;
-    const oppCol = V.GetOppCol(color);
     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 && this.getColor(i, j) != oppCol) {
+        if (this.getColor(i, j) == color) {
           const moves = this.getPotentialMovesFrom([i, j]);
           if (moves.length > 0) {
             for (let k = 0; k < moves.length; k++) {
@@ -863,10 +880,31 @@ export const ChessRules = class ChessRules {
     );
   }
 
+  // Generic method for non-pawn pieces ("sliding or jumping"):
+  // is x,y attacked by a piece of color in array 'colors' ?
+  isAttackedBySlideNJump([x, y], colors, piece, steps, oneStep) {
+    for (let step of steps) {
+      let rx = x + step[0],
+          ry = y + step[1];
+      while (V.OnBoard(rx, ry) && this.board[rx][ry] == V.EMPTY && !oneStep) {
+        rx += step[0];
+        ry += step[1];
+      }
+      if (
+        V.OnBoard(rx, ry) &&
+        this.getPiece(rx, ry) === piece &&
+        colors.includes(this.getColor(rx, ry))
+      ) {
+        return true;
+      }
+    }
+    return false;
+  }
+
   // Is square x,y attacked by 'colors' pawns ?
   isAttackedByPawn([x, y], colors) {
     for (let c of colors) {
-      let pawnShift = c == "w" ? 1 : -1;
+      const pawnShift = c == "w" ? 1 : -1;
       if (x + pawnShift >= 0 && x + pawnShift < V.size.x) {
         for (let i of [-1, 1]) {
           if (
@@ -925,27 +963,6 @@ export const ChessRules = class ChessRules {
     );
   }
 
-  // Generic method for non-pawn pieces ("sliding or jumping"):
-  // is x,y attacked by a piece of color in array 'colors' ?
-  isAttackedBySlideNJump([x, y], colors, piece, steps, oneStep) {
-    for (let step of steps) {
-      let rx = x + step[0],
-          ry = y + step[1];
-      while (V.OnBoard(rx, ry) && this.board[rx][ry] == V.EMPTY && !oneStep) {
-        rx += step[0];
-        ry += step[1];
-      }
-      if (
-        V.OnBoard(rx, ry) &&
-        this.getPiece(rx, ry) === piece &&
-        colors.includes(this.getColor(rx, ry))
-      ) {
-        return true;
-      }
-    }
-    return false;
-  }
-
   // Is color under check after his move ?
   underCheck(color) {
     return this.isAttacked(this.kingPos[color], [V.GetOppCol(color)]);
@@ -968,6 +985,8 @@ export const ChessRules = class ChessRules {
   // After move is played, update variables + flags
   updateVariables(move) {
     let piece = undefined;
+    // TODO: update variables before move is played, and just use this.turn ?
+    // (doesn't work in general, think MarseilleChess)
     let c = undefined;
     if (move.vanish.length >= 1) {
       // Usual case, something is moved
@@ -978,9 +997,8 @@ export const ChessRules = class ChessRules {
       piece = move.appear[0].p;
       c = move.appear[0].c;
     }
-    if (c == "c") {
-      //if (!["w","b"].includes(c))
-      // 'c = move.vanish[0].c' doesn't work for Checkered
+    if (!['w','b'].includes(c)) {
+      // Checkered, for example
       c = V.GetOppCol(this.turn);
     }
     const firstRank = c == "w" ? V.size.x - 1 : 0;
@@ -1096,29 +1114,17 @@ export const ChessRules = class ChessRules {
     return 3;
   }
 
-  // NOTE: works also for extinction chess because depth is 3...
   getComputerMove() {
     const maxeval = V.INFINITY;
     const color = this.turn;
     // Some variants may show a bigger moves list to the human (Switching),
     // thus the argument "computer" below (which is generally ignored)
-    let moves1 = this.getAllValidMoves("computer");
+    let moves1 = this.getAllValidMoves();
+
     if (moves1.length == 0)
-      //TODO: this situation should not happen
+      // TODO: this situation should not happen
       return null;
 
-    // Can I mate in 1 ? (for Magnetic & Extinction)
-    for (let i of shuffle(ArrayFun.range(moves1.length))) {
-      this.play(moves1[i]);
-      let finish = Math.abs(this.evalPosition()) >= V.THRESHOLD_MATE;
-      if (!finish) {
-        const score = this.getCurrentScore();
-        if (["1-0", "0-1"].includes(score)) finish = true;
-      }
-      this.undo(moves1[i]);
-      if (finish) return moves1[i];
-    }
-
     // Rank moves using a min-max at depth 2
     for (let i = 0; i < moves1.length; i++) {
       // Initial self evaluation is very low: "I'm checkmated"
@@ -1130,7 +1136,7 @@ export const ChessRules = class ChessRules {
         // Initial enemy evaluation is very low too, for him
         eval2 = (color == "w" ? 1 : -1) * maxeval;
         // Second half-move:
-        let moves2 = this.getAllValidMoves("computer");
+        let moves2 = this.getAllValidMoves();
         for (let j = 0; j < moves2.length; j++) {
           this.play(moves2[j]);
           const score2 = this.getCurrentScore();
@@ -1166,6 +1172,7 @@ export const ChessRules = class ChessRules {
     moves1.sort((a, b) => {
       return (color == "w" ? 1 : -1) * (b.eval - a.eval);
     });
+//    console.log(moves1.map(m => { return [this.getNotation(m), m.eval]; }));
 
     let candidates = [0]; //indices of candidates moves
     for (let j = 1; j < moves1.length && moves1[j].eval == moves1[0].eval; j++)
@@ -1191,7 +1198,7 @@ export const ChessRules = class ChessRules {
         return (color == "w" ? 1 : -1) * (b.eval - a.eval);
       });
     } else return currentBest;
-    //    console.log(moves1.map(m => { return [this.getNotation(m), m.eval]; }));
+//    console.log(moves1.map(m => { return [this.getNotation(m), m.eval]; }));
 
     candidates = [0];
     for (let j = 1; j < moves1.length && moves1[j].eval == moves1[0].eval; j++)
@@ -1206,7 +1213,7 @@ export const ChessRules = class ChessRules {
     if (score != "*")
       return score == "1/2" ? 0 : (score == "1-0" ? 1 : -1) * maxeval;
     if (depth == 0) return this.evalPosition();
-    const moves = this.getAllValidMoves("computer");
+    const moves = this.getAllValidMoves();
     let v = color == "w" ? -maxeval : maxeval;
     if (color == "w") {
       for (let i = 0; i < moves.length; i++) {
@@ -1216,8 +1223,9 @@ export const ChessRules = class ChessRules {
         alpha = Math.max(alpha, v);
         if (alpha >= beta) break; //beta cutoff
       }
-    } //color=="b"
+    }
     else {
+      // color=="b"
       for (let i = 0; i < moves.length; i++) {
         this.play(moves[i]);
         v = Math.min(v, this.alphabeta(depth - 1, alpha, beta));
@@ -1251,7 +1259,7 @@ export const ChessRules = class ChessRules {
   // TODO: un-ambiguous notation (switch on piece type, check directions...)
   getNotation(move) {
     if (move.appear.length == 2 && move.appear[0].p == V.KING)
-      //castle
+      // Castle
       return move.end.y < move.start.y ? "0-0-0" : "0-0";
 
     // Translate final square
@@ -1265,10 +1273,10 @@ export const ChessRules = class ChessRules {
         // Capture
         const startColumn = V.CoordToColumn(move.start.y);
         notation = startColumn + "x" + finalSquare;
-      } //no capture
+      }
       else notation = finalSquare;
       if (move.appear.length > 0 && move.appear[0].p != V.PAWN)
-        //promotion
+        // Promotion
         notation += "=" + move.appear[0].p.toUpperCase();
       return notation;
     }