Add Bario draft. Small bugs to fix in Refusal and Bario
authorBenjamin Auder <benjamin.auder@somewhere>
Sat, 16 Jan 2021 02:16:51 +0000 (03:16 +0100)
committerBenjamin Auder <benjamin.auder@somewhere>
Sat, 16 Jan 2021 02:16:51 +0000 (03:16 +0100)
TODO
client/public/images/pieces/Bario/bu.svg [new symlink]
client/public/images/pieces/Bario/wu.svg [new symlink]
client/src/base_rules.js
client/src/translations/rules/Bario/en.pug
client/src/translations/rules/Refusal/en.pug
client/src/translations/rules/Refusal/es.pug
client/src/translations/rules/Refusal/fr.pug
client/src/variants/Bario.js [new file with mode: 0644]
client/src/variants/Refusal.js [new file with mode: 0644]
client/src/variants/Wormhole.js

diff --git a/TODO b/TODO
index 53cace8..7a50f28 100644 (file)
--- a/TODO
+++ b/TODO
@@ -2,14 +2,7 @@ PROBABLY WON'T FIX:
 Embedded rules language not updated when language is set (in Analyse, Game and Problems)
 If new live game starts in background, "new game" notify OK but not first move.
 
 Embedded rules language not updated when language is set (in Analyse, Game and Problems)
 If new live game starts in background, "new game" notify OK but not first move.
 
-NEW VARIANTS:
-https://www.pychess.org/variant/shogun
-https://www.chessvariants.com/incinf.dir/bario.html
-  https://www.chessvariants.com/index/listcomments.php?order=DESC&itemid=Bario
-  https://www.bario-chess-checkers-chessphotography-spaceart.de/
-  https://le-cdn.website-editor.net/20ef5f800ea646c29f6852cfc5ceda07/dms3rep/multi/opt/BAR028-e15a849c-960w.jpg
-
-Non-chess: ( won't add draughts: https://lidraughts.org/ )
+NEW VARIANTS, Non-chess ( won't add draughts: https://lidraughts.org/ )
 Gomoku, Konane
 Fanorona https://fr.wikipedia.org/wiki/Fanorona
 Yoté https://fr.wikipedia.org/wiki/Yot%C3%A9 http://www.zillionsofgames.com/cgi-bin/zilligames/submissions.cgi/92187?do=show;id=960
 Gomoku, Konane
 Fanorona https://fr.wikipedia.org/wiki/Fanorona
 Yoté https://fr.wikipedia.org/wiki/Yot%C3%A9 http://www.zillionsofgames.com/cgi-bin/zilligames/submissions.cgi/92187?do=show;id=960
diff --git a/client/public/images/pieces/Bario/bu.svg b/client/public/images/pieces/Bario/bu.svg
new file mode 120000 (symlink)
index 0000000..330db4d
--- /dev/null
@@ -0,0 +1 @@
+../Hidden/bp.svg
\ No newline at end of file
diff --git a/client/public/images/pieces/Bario/wu.svg b/client/public/images/pieces/Bario/wu.svg
new file mode 120000 (symlink)
index 0000000..f52e53d
--- /dev/null
@@ -0,0 +1 @@
+../Hidden/wp.svg
\ No newline at end of file
index 0a68127..4c90f1d 100644 (file)
@@ -662,7 +662,7 @@ export const ChessRules = class ChessRules {
       case V.QUEEN: return this.getPotentialQueenMoves(sq);
       case V.KING: return this.getPotentialKingMoves(sq);
     }
       case V.QUEEN: return this.getPotentialQueenMoves(sq);
       case V.KING: return this.getPotentialKingMoves(sq);
     }
-    return []; //never reached
+    return []; //never reached (but some variants may use it: Bario...)
   }
 
   // Build a regular move from its initial and destination squares.
   }
 
   // Build a regular move from its initial and destination squares.
index 21203ba..df8d8b6 100644 (file)
@@ -1 +1,6 @@
 p.boxed TODO
 p.boxed TODO
+
+p Some links - rules unwritten yet - variant slightly buggish for now.
+p https://www.chessvariants.com/incinf.dir/bario.html
+p https://le-cdn.website-editor.net/20ef5f800ea646c29f6852cfc5ceda07/dms3rep/multi/opt/BAR028-e15a849c-960w.jpg
+p https://www.bario-chess-checkers-chessphotography-spaceart.de/
index 3a33838..c5ad81a 100644 (file)
@@ -1,2 +1,23 @@
 p.boxed
 p.boxed
-  | TODO
+  | You can forbid the opponent to play the move he wanted.
+
+p.
+  All rules are as in normal chess, except for the following: each turn,
+  a player has the right to refuse at most one move of the opponent.
+  To refuse a move, re-play it from end to starting square.
+
+p.
+  Two moves promoting the same pawn on the same square, but to a
+  different type of piece, count as two different moves.
+
+p If a player has only one legal move, this move must be accepted.
+
+h3 More information
+
+p
+  | See the 
+  a(href="https://www.chessvariants.com/other.dir/refusal.html")
+    | chessvariants page
+  | .
+
+p Inventor: Fred Galvin (1958)
index 3a33838..c2f75fc 100644 (file)
@@ -1,2 +1,23 @@
 p.boxed
 p.boxed
-  | TODO
+  | Puedes evitar que el oponente haga cualquier movimiento que quiera.
+
+p.
+  Todo va como en el ajedrez ortodoxo, excepto por un detalle:
+  en cada turno, un jugador tiene derecho a rechazar como máximo una jugada
+  del oponente. Para rechazar un movimiento, vuelva a reproducirlo al revés.
+
+p.
+  Dos movimientos, cada uno promoviendo un peón en la misma casilla,
+  pero en dos piezas diferentes, se ven como jugadas diferentes.
+
+p Si un jugador tiene solo un movimiento legal, debe ser aceptado.
+
+h3 Más información
+
+p
+  | Ver la 
+  a(href="https://www.chessvariants.com/other.dir/refusal.html")
+    | página chessvariants
+  | .
+
+p Inventor: Fred Galvin (1958)
index 3a33838..e1b1750 100644 (file)
@@ -1,2 +1,23 @@
 p.boxed
 p.boxed
-  | TODO
+  | Vous pouvez empêcher l'adversaire de jouer le coup qu'il souhaite.
+
+p.
+  Tout se déroule comme aux échecs orthodoxes, à l'exception d'un détail :
+  à chaque tour, un joueur a le droit de refuser au plus un coup adverse.
+  Pour refuser un coup, re-jouez le à l'envers.
+
+p.
+  Deux coups promouvant chacun un pion sur la même case, mais en deux pièces
+  différentes, sont vus comme des coups différents.
+
+p Si un joueur n'a qu'un seul coup légal, celui-ci doit être accepté.
+
+h3 Plus d'information
+
+p
+  | Voir la 
+  a(href="https://www.chessvariants.com/other.dir/refusal.html")
+    | page chessvariants
+  | .
+
+p Inventeur : Fred Galvin (1958)
diff --git a/client/src/variants/Bario.js b/client/src/variants/Bario.js
new file mode 100644 (file)
index 0000000..5b5b33c
--- /dev/null
@@ -0,0 +1,544 @@
+import { ChessRules, PiPo, Move } from "@/base_rules";
+import { ArrayFun } from "@/utils/array";
+import { randInt } from "@/utils/alea";
+
+// TODO: issue with undo of specialisation to cover check, subTurn decremented to 0
+
+export class BarioRules extends ChessRules {
+
+  // Does not really seem necessary (although the author mention it)
+  // Instead, first move = pick a square for the king.
+  static get HasCastle() {
+    return false;
+  }
+
+  // Undetermined piece form:
+  static get UNDEFINED() {
+    return 'u';
+  }
+
+  static get PIECES() {
+    return ChessRules.PIECES.concat(V.UNDEFINED);
+  }
+
+  getPpath(b) {
+    if (b[1] == V.UNDEFINED) return "Bario/" + b;
+    return b;
+  }
+
+  canIplay(side, [x, y]) {
+    if (this.movesCount >= 2) return super.canIplay(side, [x, y]);
+    return (
+      this.turn == side &&
+      (
+        (side == 'w' && x == 7) ||
+        (side == 'b' && x == 0)
+      )
+    );
+  }
+
+  hoverHighlight(x, y) {
+    const c = this.turn;
+    return (
+      this.movesCount <= 1 &&
+      (
+        (c == 'w' && x == 7) ||
+        (c == 'b' && x == 0)
+      )
+    );
+  }
+
+  // Initiate the game by choosing a square for the king:
+  doClick(square) {
+    const c = this.turn;
+    if (
+      this.movesCount >= 2 ||
+      (
+        (c == 'w' && square[0] != 7) ||
+        (c == 'b' && square[0] != 0)
+      )
+    ) {
+      return null;
+    }
+    return new Move({
+      appear: [
+        new PiPo({ x: square[0], y: square[1], c: c, p: V.KING })
+      ],
+      vanish: [],
+      start: { x: -1, y: -1 },
+    });
+  }
+
+  // Do not check kings (TODO: something more subtle!)
+  static IsGoodPosition(position) {
+    if (position.length == 0) return false;
+    const rows = position.split("/");
+    if (rows.length != V.size.x) return false;
+    for (let row of rows) {
+      let sumElts = 0;
+      for (let i = 0; i < row.length; i++) {
+        if (V.PIECES.includes(row[i].toLowerCase())) sumElts++;
+        else {
+          const num = parseInt(row[i], 10);
+          if (isNaN(num) || num <= 0) return false;
+          sumElts += num;
+        }
+      }
+      if (sumElts != V.size.y) return false;
+    }
+    return true;
+  }
+
+  static IsGoodFen(fen) {
+    if (!ChessRules.IsGoodFen(fen)) return false;
+    const fenParsed = V.ParseFen(fen);
+    if (!fenParsed.reserve || !fenParsed.reserve.match(/^[0-9]{8,8}$/))
+    if (!fenParsed.capture) return false;
+    return true;
+  }
+
+  static ParseFen(fen) {
+    const fenParts = fen.split(" ");
+    return Object.assign(
+      {
+        reserve: fenParts[4],
+        capture: fenParts[5]
+      },
+      ChessRules.ParseFen(fen)
+    );
+  }
+
+  getReserveFen() {
+    let counts = new Array(8);
+    for (let i = 0; i < V.RESERVE_PIECES.length; i++) {
+      counts[i] = this.reserve["w"][V.PIECES[i]];
+      counts[4 + i] = this.reserve["b"][V.PIECES[i]];
+    }
+    return counts.join("");
+  }
+
+  getCaptureFen() {
+    const L = this.captureUndefined.length;
+    const cu = this.captureUndefined[L-1];
+    return (!!cu ? V.CoordsToSquare(cu) : "-");
+  }
+
+  getFen() {
+    return (
+      super.getFen() + " " +
+      this.getReserveFen() + " " +
+      this.getCaptureFen()
+    );
+  }
+
+  getFenForRepeat() {
+    return (
+      super.getFenForRepeat() + "_" +
+      this.getReserveFen() + "_" +
+      this.getCaptureFen()
+    );
+  }
+
+  static GenRandInitFen() {
+    return "8/pppppppp/8/8/8/8/PPPPPPPP/8 w 0 - 22212221 -";
+  }
+
+  setOtherVariables(fen) {
+    super.setOtherVariables(fen);
+    const reserve =
+      V.ParseFen(fen).reserve.split("").map(x => parseInt(x, 10));
+    this.reserve = {
+      w: {
+        [V.ROOK]: reserve[0],
+        [V.KNIGHT]: reserve[1],
+        [V.BISHOP]: reserve[2],
+        [V.QUEEN]: reserve[3]
+      },
+      b: {
+        [V.ROOK]: reserve[4],
+        [V.KNIGHT]: reserve[5],
+        [V.BISHOP]: reserve[6],
+        [V.QUEEN]: reserve[7]
+      }
+    };
+    const cu = V.ParseFen(fen).capture;
+    this.captureUndefined = [cu == '-' ? null : V.SquareToCoords(cu)];
+    this.subTurn = (cu == "-" ? 1 : 0);
+    // Local stack of pieces' definitions
+    this.definitions = [];
+  }
+
+  getColor(i, j) {
+    if (i >= V.size.x) return i == V.size.x ? "w" : "b";
+    return this.board[i][j].charAt(0);
+  }
+
+  getPiece(i, j) {
+    if (i >= V.size.x) return V.RESERVE_PIECES[j];
+    return this.board[i][j].charAt(1);
+  }
+
+  getReservePpath(index, color) {
+    return color + V.RESERVE_PIECES[index];
+  }
+
+  static get RESERVE_PIECES() {
+    return [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN];
+  }
+
+  getReserveMoves([x, y]) {
+    const color = this.turn;
+    const p = V.RESERVE_PIECES[y];
+    if (this.reserve[color][p] == 0) return [];
+    // 2 cases, subTurn == 0 => target this.captureUndefined only (one square)
+    if (this.subTurn == 0) {
+      const L = this.captureUndefined.length;
+      const cu = this.captureUndefined[L-1];
+      return (
+        new Move({
+          appear: [
+            new PiPo({ x: cu.x, y: cu.y, c: color, p: p })
+          ],
+          vanish: [
+            new PiPo({ x: cu.x, y: cu.y, c: color, p: V.UNDEFINED })
+          ],
+          start: { x: x, y: y }
+        })
+      );
+    }
+    // or, subTurn == 1 => target any undefined piece that we own.
+    let moves = [];
+    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) == color &&
+          this.getPiece(i, j) == V.UNDEFINED
+        ) {
+          let mv = new Move({
+            appear: [
+              new PiPo({ x: i, y: j, c: color, p: p })
+            ],
+            vanish: [
+              new PiPo({ x: i, y: j, c: color, p: V.UNDEFINED })
+            ],
+            start: { x: x, y: y },
+            end: { x: i, y: j }
+          });
+          moves.push(mv);
+        }
+      }
+    }
+    return moves;
+  }
+
+  getPotentialMovesFrom([x, y]) {
+    if (this.subTurn == 0) {
+      if (x < V.size.x) return [];
+      return this.getReserveMoves([x, y]);
+    }
+    if (this.subTurn == 1) {
+      // Both normal move (from defined piece) and definition allowed
+      if (x >= V.size.x) return this.getReserveMoves([x, y]);
+      if (this.getPiece(x, y) == V.UNDEFINED) return [];
+    }
+    // subTurn == 1 and we move any piece, or
+    // subTurn == 2 and we can only move the just-defined piece
+    if (this.subTurn == 2) {
+      const L = this.definitions.length; //at least 1
+      const df = this.definitions[L-1];
+      if (x != df.x || y != df.y) return [];
+    }
+    return super.getPotentialMovesFrom([x, y]);
+  }
+
+  getAllValidMoves() {
+    const getAllReserveMoves = () => {
+      let moves = [];
+      const color = this.turn;
+      for (let i = 0; i < V.RESERVE_PIECES.length; i++) {
+        moves = moves.concat(
+          this.getReserveMoves([V.size.x + (color == "w" ? 0 : 1), i])
+        );
+      }
+      return moves;
+    }
+    if (this.subTurn == 0) return getAllReserveMoves();
+    let moves = super.getAllPotentialMoves();
+    if (this.subTurn == 1)
+      moves = moves.concat(getAllReserveMoves());
+    return this.filterValid(moves);
+  }
+
+  filterValid(moves) {
+    const color = this.turn;
+    return moves.filter(m => {
+      if (m.vanish.length == 0) return true;
+      const start = { x: m.vanish[0].x, y: m.vanish[0].y };
+      const end = { x: m.appear[0].x, y: m.appear[0].y };
+      if (start.x == end.x && start.y == end.y) return true; //unfinished turn
+      this.play(m);
+      const res = !this.underCheck(color);
+      this.undo(m);
+      return res;
+    });
+  }
+
+  atLeastOneMove() {
+    const atLeastOneReserveMove = () => {
+      for (let i = 0; i < V.RESERVE_PIECES.length; i++) {
+        let moves = this.filterValid(
+          this.getReserveMoves([V.size.x + (this.turn == "w" ? 0 : 1), i])
+        );
+        if (moves.length > 0) return true;
+      }
+      return false;
+    };
+    if (this.subTurn == 0) return true; //always one reserve for an undefined
+    if (!super.atLeastOneMove()) return atLeastOneReserveMove();
+    return true;
+  }
+
+  underCheck(color) {
+    if (super.underCheck(color)) return true;
+    // Aux func for piece attack on king (no pawn)
+    const pieceAttackOn = (p, [x1, y1], [x2, y2]) => {
+      const shift = [x2 - x1, y2 - y1];
+      const absShift = shift.map(Math.abs);
+      if (
+        (
+          p == V.KNIGHT &&
+          (absShift[0] + absShift[1] != 3 || shift[0] == 0 || shift[1] == 0)
+        ) ||
+        (p == V.ROOK && shift[0] != 0 && shift[1] != 0) ||
+        (p == V.BISHOP && absShift[0] != absShift[1]) ||
+        (
+          p == V.QUEEN &&
+          shift[0] != 0 && shift[1] != 0 && absShift[0] != absShift[1]
+        )
+      ) {
+        return false;
+      }
+      // Step is compatible with piece:
+      const step = [
+        shift[0] / Math.abs(shift[0]) || 0,
+        shift[1] / Math.abs(shift[1]) || 0
+      ];
+      let [i, j] = [x1 + step[0], y1 + step[1]];
+      while (i != x2 || j != y2) {
+        if (this.board[i][j] != V.EMPTY) return false;
+        i += step[0];
+        j += step[1];
+      }
+      return true;
+    };
+    // Check potential specializations of undefined using reserve:
+    const oppCol = V.GetOppCol(color);
+    for (let i=0; i<8; i++) {
+      for (let j=0; j<8; j++) {
+        if (
+          this.board[i][j] != V.EMPTY &&
+          this.getColor(i, j) == oppCol &&
+          this.getPiece(i, j) == V.UNDEFINED
+        ) {
+          for (let p of V.RESERVE_PIECES) {
+            if (
+              this.reserve[oppCol][p] >= 1 &&
+              pieceAttackOn(p, [i, j], this.kingPos[color])
+            ) {
+              return true;
+            }
+          }
+        }
+      }
+    }
+    return false;
+  }
+
+  play(move) {
+    const toNextPlayer = () => {
+      V.PlayOnBoard(this.board, move);
+      this.turn = V.GetOppCol(this.turn);
+      this.subTurn =
+        (move.vanish.length == 2 && move.vanish[1].p == V.UNDEFINED ? 0 : 1);
+      this.movesCount++;
+      this.postPlay(move);
+    };
+    if (move.vanish.length == 0) {
+      toNextPlayer();
+      return;
+    }
+    const start = { x: move.vanish[0].x, y: move.vanish[0].y };
+    const end = { x: move.appear[0].x, y: move.appear[0].y };
+    if (start.x == end.x && start.y == end.y) {
+      // Specialisation (subTurn == 1 before 2), or Removal (subTurn == 0).
+      // In both cases, turn not over, and a piece removed from reserve
+      this.reserve[this.turn][move.appear[0].p]--;
+      if (move.appear[0].c == move.vanish[0].c) {
+        // Specialisation: play "move" on board
+        V.PlayOnBoard(this.board, move);
+        this.definitions.push(move.end);
+      }
+      this.subTurn++;
+    }
+    else {
+      // Normal move (subTurn 1 or 2: change turn)
+      this.epSquares.push(this.getEpSquare(move));
+      toNextPlayer();
+    }
+  }
+
+  postPlay(move) {
+    const color = V.GetOppCol(this.turn);
+    if (move.vanish.length == 0) {
+      this.kingPos[color] = [move.end.x, move.end.y];
+      const firstRank = (color == 'w' ? 7 : 0);
+      for (let j = 0; j < 8; j++) {
+        if (j != move.end.y) this.board[firstRank][j] = color + V.UNDEFINED;
+      }
+    }
+    else {
+      if (move.vanish.length == 2 && move.vanish[1].p == V.UNDEFINED)
+        this.captureUndefined.push(move.end);
+      if (move.appear[0].p == V.KING) super.postPlay(move);
+      else {
+        // If now all my pieces are defined, back to undefined state,
+        // only if at least two different kind of pieces on board!
+        // Store current state in move (cannot infer it after!)
+        if (
+          this.board.every(b => {
+            return b.every(cell => {
+              return (
+                cell == V.EMPTY ||
+                cell[0] != color ||
+                cell[1] != V.UNDEFINED
+              );
+            });
+          })
+        ) {
+          const piecesList = [V.ROOK, V.KNIGHT, V.BISHOP, V.QUEEN];
+          let myPieces = {};
+          for (let i=0; i<8; i++) {
+            for (let j=0; j<8; j++) {
+              if (
+                this.board[i][j] != V.EMPTY &&
+                this.getColor(i, j) == color
+              ) {
+                const p = this.getPiece(i, j);
+                if (piecesList.includes(p))
+                  myPieces[p] = (!myPieces[p] ? 1 : myPieces[p] + 1);
+              }
+            }
+          }
+          const pk = Object.keys(myPieces);
+          if (pk.length >= 2) {
+            move.position = this.getBaseFen();
+            for (let p of pk) this.reserve[color][p] = myPieces[p];
+            for (let i=0; i<8; i++) {
+              for (let j=0; j<8; j++) {
+                if (
+                  this.board[i][j] != V.EMPTY &&
+                  this.getColor(i, j) == color &&
+                  piecesList.includes(this.getPiece(i, j))
+                ) {
+                  this.board[i][j] = color + V.UNDEFINED;
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+  undo(move) {
+    const toPrevPlayer = () => {
+      V.UndoOnBoard(this.board, move);
+      this.turn = V.GetOppCol(this.turn);
+      this.movesCount--;
+      this.postUndo(move);
+    };
+    if (move.vanish.length == 0) {
+      toPrevPlayer();
+      return;
+    }
+    const start = { x: move.vanish[0].x, y: move.vanish[0].y };
+    const end = { x: move.appear[0].x, y: move.appear[0].y };
+    if (start.x == end.x && start.y == end.y) {
+      this.reserve[this.turn][move.appear[0].p]++;
+      if (move.appear[0].c == move.vanish[0].c) {
+        V.UndoOnBoard(this.board, move);
+        this.definitions.pop();
+      }
+      this.subTurn--;
+    }
+    else {
+      this.epSquares.pop();
+      toPrevPlayer();
+    }
+  }
+
+  postUndo(move) {
+    const color = this.turn;
+    if (move.vanish.length == 0) {
+      this.kingPos[color] = [-1, -1];
+      const firstRank = (color == 'w' ? 7 : 0);
+      for (let j = 0; j < 8; j++) this.board[firstRank][j] = "";
+    }
+    else {
+      if (move.vanish.length == 2 && move.vanish[1].p == V.UNDEFINED)
+        this.captureUndefined.pop();
+      if (move.appear[0].p == V.KING) super.postUndo(move);
+      else {
+        if (!!move.position) {
+          this.board = V.GetBoard(move.position);
+          this.reserve[color] = {
+            [V.ROOK]: 0,
+            [V.KNIGHT]: 0,
+            [V.BISHOP]: 0,
+            [V.QUEEN]: 0
+          }
+        }
+      }
+    }
+  }
+
+  getComputerMove() {
+    const color = this.turn;
+    // Just play at random for now...
+    let mvArray = [];
+    while (this.turn == color) {
+      const moves = this.getAllValidMoves();
+      const choice = moves[randInt(moves.length)];
+      mvArray.push(choice);
+      this.play(choice);
+    }
+    for (let i = mvArray.length - 1; i >= 0; i--) this.undo(mvArray[i]);
+    return (mvArray.length == 1? mvArray[0] : mvArray);
+  }
+
+  static get VALUES() {
+    return Object.assign({ u: 0 }, ChessRules.VALUES);
+  }
+
+  // NOTE: evalPosition is wrong, but unused (random mover)
+
+  getNotation(move) {
+    const end = { x: move.appear[0].x, y: move.appear[0].y };
+    const endSquare = V.CoordsToSquare(end);
+    if (move.vanish.length == 0) return "K@" + endSquare;
+    const start = { x: move.vanish[0].x, y: move.vanish[0].y };
+    if (start.x == end.x && start.y == end.y) {
+      // Something is specialized, or removed
+      const symbol = move.appear[0].p.toUpperCase();
+      if (move.appear[0].c == move.vanish[0].c)
+        // Specialisation
+        return symbol + "@" + endSquare;
+      // Removal:
+      return symbol + endSquare + "X";
+    }
+    // Normal move
+    return super.getNotation(move);
+  }
+
+};
diff --git a/client/src/variants/Refusal.js b/client/src/variants/Refusal.js
new file mode 100644 (file)
index 0000000..e4ec04d
--- /dev/null
@@ -0,0 +1,201 @@
+import { ChessRules } from "@/base_rules";
+import { randInt } from "@/utils/alea";
+
+// TODO: Two moves, both promoting the same pawn, but to a different type of piece, count as two different moves.
+// ==> need to accept temporarily pawn promotions even if on forbidden square, and check afterward if promoted type changed (info in lastMove)
+
+export class RefusalRules extends ChessRules {
+
+  static IsGoodFen(fen) {
+    if (!ChessRules.IsGoodFen(fen)) return false;
+    if (!V.ParseFen(fen).lastMove) return false;
+    return true;
+  }
+
+  static ParseFen(fen) {
+    return Object.assign(
+      { lastMove: fen.split(" ")[5] },
+      ChessRules.ParseFen(fen)
+    );
+  }
+
+  getFen() {
+    const L = this.lastMove.length;
+    const lm = this.lastMove[L-1];
+    return super.getFen() + " " + JSON.stringify(lm);
+  }
+
+  // NOTE: with this variant's special rule,
+  // some extra repetitions could be detected... TODO (...)
+
+  static GenRandInitFen(randomness) {
+    return ChessRules.GenRandInitFen(randomness) + " null";
+  }
+
+  setOtherVariables(fen) {
+    super.setOtherVariables(fen);
+    this.lastMove = [JSON.parse(V.ParseFen(fen).lastMove)]; //may be null
+  }
+
+  canIplay(side, [x, y]) {
+    if (super.canIplay(side, [x, y])) return true;
+    if (this.turn != side) return false;
+    // Check if playing last move, reversed:
+    const L = this.lastMove.length;
+    const lm = this.lastMove[L-1];
+    return (!!lm && !lm.noRef && x == lm.end.x && y == lm.end.y);
+  }
+
+  getPotentialMovesFrom([x, y]) {
+    if (this.getColor(x, y) != this.turn) {
+      const L = this.lastMove.length;
+      const lm = this.lastMove[L-1];
+      if (!!lm && !lm.noRef && x == lm.end.x && y == lm.end.y) {
+        let revLm = JSON.parse(JSON.stringify(lm));
+        let tmp = revLm.appear;
+        revLm.appear = revLm.vanish;
+        revLm.vanish = tmp;
+        tmp = revLm.start;
+        revLm.start = revLm.end;
+        revLm.end = tmp;
+        return [revLm];
+      }
+      return [];
+    }
+    return super.getPotentialMovesFrom([x, y]);
+  }
+
+  // NOTE: do not take refusal move into account here (two own moves)
+  atLeastTwoMoves() {
+    let movesCounter = 0;
+    const color = this.turn;
+    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) == color) {
+          const moves = this.getPotentialMovesFrom([i, j]);
+          for (let m of moves) {
+            if (m.vanish[0].c == color && this.filterValid([m]).length > 0) {
+              movesCounter++;
+              if (movesCounter >= 2) return true;
+            }
+          }
+        }
+      }
+    }
+    return false;
+  }
+
+  filterValid(moves) {
+    if (moves.length == 0) return [];
+    const color = this.turn;
+    const L = this.lastMove.length;
+    const lm = this.lastMove[L-1];
+    return moves.filter(m => {
+      if (
+        !!lm && !!lm.refusal &&
+        m.start.x == lm.end.x && m.start.y == lm.end.y &&
+        m.end.x == lm.start.x && m.end.y == lm.start.y
+      ) {
+        return false;
+      }
+      // NOTE: not using this.play()/undo() ==> infinite loop
+      V.PlayOnBoard(this.board, m);
+      if (m.appear[0].p == V.KING)
+        this.kingPos[m.appear[0].c] = [m.appear[0].x, m.appear[0].y];
+      const res = !this.underCheck(color);
+      V.UndoOnBoard(this.board, m);
+      if (m.vanish[0].p == V.KING)
+        this.kingPos[m.vanish[0].c] = [m.vanish[0].x, m.vanish[0].y];
+      return res;
+    });
+  }
+
+  prePlay(move) {
+    const L = this.lastMove.length;
+    const lm = this.lastMove[L-1];
+    if (
+      // My previous move was already refused?
+      (!!lm && this.getColor(lm.end.x, lm.end.y) == this.turn) ||
+      // I've only one move available?
+      !this.atLeastTwoMoves()
+    ) {
+      move.noRef = true;
+    }
+    // NOTE: refusal could be recomputed, but, it's easier like this
+    if (move.vanish[0].c != this.turn) move.refusal = true;
+  }
+
+  getEpSquare(move) {
+    if (!move.refusal) return super.getEpSquare(move);
+    return null; //move refusal
+  }
+
+  postPlay(move) {
+    if (!move.refusal) super.postPlay(move);
+    else {
+      const L = this.lastMove.length;
+      const lm = this.lastMove[L-1];
+      this.disaggregateFlags(JSON.parse(lm.flags));
+    }
+    // NOTE: explicitely give fields, because some are assigned in BaseGame
+    let mvInLm = {
+      start: move.start,
+      end: move.end,
+      appear: move.appear,
+      vanish: move.vanish,
+      flags: move.flags
+    };
+    if (!!move.noRef) mvInLm.noRef = true;
+    if (!!move.refusal) mvInLm.refusal = true;
+    this.lastMove.push(mvInLm);
+  }
+
+  postUndo(move) {
+    if (!move.refusal) super.postUndo(move);
+    this.lastMove.pop();
+  }
+
+  getAllPotentialMoves() {
+    const color = this.turn;
+    const L = this.lastMove.length;
+    const lm = this.lastMove[L-1];
+    let potentialMoves = [];
+    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) == color ||
+            // Add move refusal:
+            (!!lm && lm.end.x == i && lm.end.y == j)
+          )
+        ) {
+          Array.prototype.push.apply(
+            potentialMoves,
+            this.getPotentialMovesFrom([i, j])
+          );
+        }
+      }
+    }
+    return potentialMoves;
+  }
+
+  getComputerMove() {
+    // Just play at random for now... (TODO?)
+    // Refuse last move with odds 1/3.
+    const moves = this.getAllValidMoves();
+    const refusal = moves.find(m => m.vanish[0].c != this.turn);
+    if (!!refusal) {
+      if (Math.random() <= 0.33) return refusal;
+      const others = moves.filter(m => m.vanish[0].c == this.turn);
+      return others[randInt(others.length)];
+    }
+    else return moves[randInt(moves.length)];
+  }
+
+  getNotation(move) {
+    if (move.vanish[0].c != this.turn) return "Refuse";
+    return super.getNotation(move);
+  }
+
+};
index fcf63b4..05d8f2a 100644 (file)
@@ -57,7 +57,8 @@ export class WormholeRules extends ChessRules {
       // A knight
       shift1 = movement[0];
       shift2 = movement[1];
       // A knight
       shift1 = movement[0];
       shift2 = movement[1];
-    } else {
+    }
+    else {
       shift1 = movement;
       shift2 = null;
     }
       shift1 = movement;
       shift2 = null;
     }