Some fixes, draw lines on board, add 7 variants
[vchess.git] / client / src / variants / Sittuyin.js
index 322439d..61bab56 100644 (file)
@@ -1,4 +1,5 @@
 import { ChessRules, Move, PiPo } from "@/base_rules";
+import { randInt } from "@/utils/alea";
 
 export class SittuyinRules extends ChessRules {
   static get HasFlags() {
@@ -9,12 +10,22 @@ export class SittuyinRules extends ChessRules {
     return false;
   }
 
+  static get Monochrome() {
+    return true;
+  }
+
+  static get Lines() {
+    return ChessRules.Lines.concat([
+      [[0, 0], [8, 8]],
+      [[0, 8], [8, 0]]
+    ]);
+  }
+
   static get PawnSpecs() {
     return Object.assign(
       {},
       ChessRules.PawnSpecs,
       {
-        twoSquares: false,
         // Promotions are handled differently here
         promotions: [V.QUEEN]
       }
@@ -76,71 +87,62 @@ export class SittuyinRules extends ChessRules {
   }
 
   getPotentialMovesFrom([x, y]) {
-    if (this.movesCount <= 1) {
-      const color = this.turn;
-      const p = V.RESERVE_PIECES[y];
-      if (this.reserve[color][p] == 0) return [];
-      const iBound =
-        p != V.ROOK
-          ? (color == 'w' ? [4, 7] : [0, 3])
-          : (color == 'w' ? [7, 7] : [0, 0]);
-      const jBound = (i) => {
-        if (color == 'w' && i == 4) return [4, 7];
-        if (color == 'b' && i == 3) return [0, 3];
-        return [0, 7];
-      };
-      let moves = [];
-      for (let i = iBound[0]; i <= iBound[1]; i++) {
-        const jb = jBound(i);
-        for (let j = jb[0]; j <= jb[1]; j++) {
-          if (this.board[i][j] == V.EMPTY) {
-            let mv = new Move({
-              appear: [
-                new PiPo({
-                  x: i,
-                  y: j,
-                  c: color,
-                  p: p
-                })
-              ],
-              vanish: [],
-              start: { x: x, y: y },
-              end: { x: i, y: j }
-            });
-            moves.push(mv);
-          }
+    if (this.movesCount >= 2) return super.getPotentialMovesFrom([x, y]);
+    // Only reserve moves are allowed for now:
+    if (V.OnBoard(x, y)) return [];
+    const color = this.turn;
+    const p = V.RESERVE_PIECES[y];
+    if (this.reserve[color][p] == 0) return [];
+    const iBound =
+      p != V.ROOK
+        ? (color == 'w' ? [4, 7] : [0, 3])
+        : (color == 'w' ? [7, 7] : [0, 0]);
+    const jBound = (i) => {
+      if (color == 'w' && i == 4) return [4, 7];
+      if (color == 'b' && i == 3) return [0, 3];
+      return [0, 7];
+    };
+    let moves = [];
+    for (let i = iBound[0]; i <= iBound[1]; i++) {
+      const jb = jBound(i);
+      for (let j = jb[0]; j <= jb[1]; j++) {
+        if (this.board[i][j] == V.EMPTY) {
+          let mv = new Move({
+            appear: [
+              new PiPo({
+                x: i,
+                y: j,
+                c: color,
+                p: p
+              })
+            ],
+            vanish: [],
+            start: { x: x, y: y },
+            end: { x: i, y: j }
+          });
+          moves.push(mv);
         }
       }
-      return moves;
     }
-    return super.getPotentialMovesFrom([x, y]);
+    return moves;
   }
 
   getPotentialPawnMoves([x, y]) {
     const color = this.turn;
-    const [sizeX, sizeY] = [V.size.x, V.size.y];
     const shiftX = V.PawnSpecs.directions[color];
     let moves = [];
-    // NOTE: next condition is generally true (no pawn on last rank)
-    if (x + shiftX >= 0 && x + shiftX < sizeX) {
-      if (this.board[x + shiftX][y] == V.EMPTY) {
+    if (x + shiftX >= 0 && x + shiftX < 8) {
+      if (this.board[x + shiftX][y] == V.EMPTY)
         // One square forward
         moves.push(this.getBasicMove([x, y], [x + shiftX, y]));
-      }
       // Captures
-      if (V.PawnSpecs.canCapture) {
-        for (let shiftY of [-1, 1]) {
-          if (
-            y + shiftY >= 0 &&
-            y + shiftY < sizeY
-          ) {
-            if (
-              this.board[x + shiftX][y + shiftY] != V.EMPTY &&
-              this.canTake([x, y], [x + shiftX, y + shiftY])
-            ) {
-              moves.push(this.getBasicMove([x, y], [x + shiftX, y + shiftY]));
-            }
-          }
+      for (let shiftY of [-1, 1]) {
+        if (
+          y + shiftY >= 0 && y + shiftY < 8 &&
+          this.board[x + shiftX][y + shiftY] != V.EMPTY &&
+          this.canTake([x, y], [x + shiftX, y + shiftY])
+        ) {
+          moves.push(this.getBasicMove([x, y], [x + shiftX, y + shiftY]));
         }
       }
     }
@@ -166,30 +168,78 @@ export class SittuyinRules extends ChessRules {
         (color == 'b' && ((y >= 4 && x == y) || (y <= 3 && x == 7 - y)))
       )
     ) {
-      // Add potential promotions
       const addPromotion = ([xx, yy], moveTo) => {
-        moves.push(
-          new Move({
-            appear: [
-              new PiPo({
-                x: !!moveTo ? xx : x,
-                y: yy, //yy == y if !!moveTo
-                c: color,
-                p: V.QUEEN
-              })
-            ],
-            vanish: [
-              new PiPo({
-                x: x,
-                y: y,
-                c: color,
-                p: V.PAWN
-              })
-            ],
-            start: { x: x, y: y },
-            end: { x: xx, y: yy }
-          })
-        );
+        // The promoted pawn shouldn't attack anything,
+        // and the promotion shouldn't discover a rook attack on anything.
+        const finalSquare = (!moveTo ? [x, y] : [xx, yy]);
+        let validP = true;
+        for (let step of V.steps[V.BISHOP]) {
+          const [i, j] = [finalSquare[0] + step[0], finalSquare[1] + step[1]];
+          if (
+            V.OnBoard(i, j) &&
+            this.board[i][j] != V.EMPTY &&
+            this.getColor(i, j) != color
+          ) {
+            validP = false;
+            break;
+          }
+        }
+        if (validP && !!moveTo) {
+          // Also check rook discovered attacks on the enemy king
+          let found = {
+            "0,-1": 0,
+            "0,1": 0,
+            "1,0": 0,
+            "-1,0": 0
+          };
+          // TODO: check opposite steps one after another, which could
+          // save some time (no need to explore the other line).
+          for (let step of V.steps[V.ROOK]) {
+            let [i, j] = [x + step[0], y + step[1]];
+            while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
+              i += step[0];
+              j += step[1];
+            }
+            if (V.OnBoard(i, j)) {
+              const colIJ = this.getColor(i, j);
+              const pieceIJ = this.getPiece(i, j);
+              if (colIJ != color && pieceIJ == V.KING)
+                found[step[0] + "," + step[1]] = -1;
+              else if (colIJ == color && pieceIJ == V.ROOK)
+                found[step[0] + "," + step[1]] = 1;
+            }
+          }
+          if (
+            (found["0,-1"] * found["0,1"] < 0) ||
+            (found["-1,0"] * found["1,0"] < 0)
+          ) {
+            validP = false;
+          }
+        }
+        if (validP) {
+          moves.push(
+            new Move({
+              appear: [
+                new PiPo({
+                  x: !!moveTo ? xx : x,
+                  y: yy, //yy == y if !!moveTo
+                  c: color,
+                  p: V.QUEEN
+                })
+              ],
+              vanish: [
+                new PiPo({
+                  x: x,
+                  y: y,
+                  c: color,
+                  p: V.PAWN
+                })
+              ],
+              start: { x: x, y: y },
+              end: { x: xx, y: yy }
+            })
+          );
+        }
       };
       // In-place promotion always possible:
       addPromotion([x - shiftX, y]);
@@ -219,6 +269,18 @@ export class SittuyinRules extends ChessRules {
     );
   }
 
+  getAllValidMoves() {
+    if (this.movesCount >= 2) return super.getAllValidMoves();
+    const color = this.turn;
+    let moves = [];
+    for (let i = 0; i < V.RESERVE_PIECES.length; i++) {
+      moves = moves.concat(
+        this.getPotentialMovesFrom([V.size.x + (color == "w" ? 0 : 1), i])
+      );
+    }
+    return this.filterValid(moves);
+  }
+
   isAttackedByBishop(sq, color) {
     const forward = (this.turn == 'w' ? 1 : -1);
     return this.isAttackedBySlideNJump(
@@ -308,6 +370,20 @@ export class SittuyinRules extends ChessRules {
     };
   }
 
+  getComputerMove() {
+    if (this.movesCount >= 2) return super.getComputerMove();
+    // Play a random "initialization move"
+    let res = [];
+    for (let i=0; i<8; i++) {
+      const moves = this.getAllValidMoves();
+      const moveIdx = randInt(moves.length);
+      this.play(moves[moveIdx]);
+      res.push(moves[moveIdx]);
+    }
+    for (let i=7; i>=0; i--) this.undo(res[i]);
+    return res;
+  }
+
   getNotation(move) {
     // Do not note placement moves (complete move would be too long)
     if (move.vanish.length == 0) return "";
@@ -316,7 +392,7 @@ export class SittuyinRules extends ChessRules {
       const initSquare =
         V.CoordsToSquare({ x: move.vanish[0].x, y: move.vanish[0].y })
       const destSquare =
-        V.CoordsToSquare({ x: move.vanish[0].x, y: move.vanish[0].y })
+        V.CoordsToSquare({ x: move.appear[0].x, y: move.appear[0].y })
       const prefix = (initSquare != destSquare ? initSquare : "");
       return prefix + destSquare + "=Q";
     }