--- /dev/null
+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);
+ }
+
+};
--- /dev/null
+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);
+ }
+
+};