OmniChess

Enhance your chess performance with a cutting-edge real-time move analysis and strategy assistance system

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Advertisement:

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

Advertisement:

// ==UserScript==
// @name        OmniChess
// @description        Enhance your chess performance with a cutting-edge real-time move analysis and strategy assistance system
// @homepageURL https://quantavil.github.io/OmniChess
// @supportURL  https://github.com/quantavil/OmniChess/tree/main#why-doesnt-it-work
// @match       https://quantavil.github.io/OmniChess/*
// @match       http://localhost/*
// @match       https://www.chess.com/*
// @match       https://lichess.org/*
// @match       https://playstrategy.org/*
// @match       https://www.pychess.org/*
// @match       https://chess.org/*
// @match       https://papergames.io/*
// @match       https://chess.coolmathgames.com/*
// @match       https://www.coolmathgames.com/0-chess/*
// @match       https://worldchess.com/*
// @match       https://*.chessclub.com/*
// @match       https://gameknot.com/*
// @match       https://immortal.game/*
// @match       http://chess.net/*
// @match       https://chess.net/*
// @match       https://*.freechess.club/*
// @match       https://app.edchess.io/*
// @require     https://update.greasyfork.org/scripts/470417/UniversalBoardDrawerjs.js?omnichessv=2
// @grant       GM_getValue
// @grant       GM_setValue
// @grant       GM_deleteValue
// @grant       GM_listValues
// @grant       GM_openInTab
// @grant       GM.getValue
// @grant       GM.setValue
// @grant       GM.deleteValue
// @grant       GM.listValues
// @grant       GM.openInTab
// @grant       GM_registerMenuCommand
// @grant       GM_setClipboard
// @grant       GM_notification
// @grant       unsafeWindow
// @run-at      document-start
// @version     1.0.0
// @namespace   https://github.com/quantavil/userscript
// @license     GPL-3.0
// ==/UserScript==


(async () => { try {
// src/utils/config.js
var backendConfig = {
  hosts: { prod: "quantavil.github.io", dev: "localhost" },
  path: "/OmniChess/"
};
var domain = window.location.hostname.replace("www.", "");
var pieceNameToFen = {
  pawn: "p",
  knight: "n",
  bishop: "b",
  rook: "r",
  queen: "q",
  king: "k"
};
var configKeys = Object.freeze([
  "engineElo",
  "moveSuggestionAmount",
  "arrowOpacity",
  "displayMovesOnExternalSite",
  "showMoveGhost",
  "showOpponentMoveGuess",
  "showOpponentMoveGuessConstantly",
  "onlyShowTopMoves",
  "maxMovetime",
  "chessVariant",
  "chessEngine",
  "lc0Weight",
  "engineNodes",
  "chessFont",
  "useChess960",
  "onlyCalculateOwnTurn",
  "ttsVoiceEnabled",
  "ttsVoiceName",
  "ttsVoiceSpeed",
  "chessEngineProfile",
  "primaryArrowColorHex",
  "secondaryArrowColorHex",
  "opponentArrowColorHex",
  "reverseSide",
  "engineEnabled",
  "autoMove",
  "autoMoveLegit",
  "autoMoveRandom",
  "autoMoveAfterUser",
  "legitModeType",
  "moveMethod",
  "moveDisplayDelay",
  "renderSquarePlayer",
  "renderSquareEnemy",
  "renderSquareContested",
  "renderSquareSafe",
  "renderPiecePlayerCapture",
  "renderPieceEnemyCapture",
  "renderOnExternalSite",
  "feedbackOnExternalSite",
  "enableMoveRatings",
  "enableEnemyFeedback",
  "feedbackEngineDepth",
  "enableAdvancedElo",
  "moveAsFilledSquares",
  "movesOnDemand",
  "onlySuggestPieces",
  "isUserscriptGhost",
  "enableAdaptiveDepth",
  "adaptiveDepthMin",
  "adaptiveDepthMax"
].reduce((o, k) => (o[k] = k, o), {}));
var getUniqueID = () => crypto.randomUUID();
var commLinkInstanceID = getUniqueID();
var currentBackendUrlKey = "currentBackendURL";
var currentBackendUrl = typeof GM_getValue === "function" ? GM_getValue(currentBackendUrlKey) : await GM.getValue(currentBackendUrlKey);
var isBackendUrlUpToDate = Object.values(backendConfig.hosts).some((x) => currentBackendUrl?.includes(x));
var isDevPage = window?.location?.pathname?.includes("/dev");
function constructBackendURL(host) {
  const protocol = window.location.protocol + "//";
  const hosts = backendConfig.hosts;
  return protocol + (host || hosts?.prod) + backendConfig.path;
}
function isRunningOnBackend(skipGM) {
  const hostsArr = Object.values(backendConfig.hosts);
  const path = window?.location?.pathname;
  const foundHost = hostsArr.find((host) => {
    const cleanHost = host.split(":")[0];
    const cleanLocationHost = window?.location?.host?.split(":")[0];
    return cleanHost === cleanLocationHost;
  });
  const isCorrectPath = path?.includes(backendConfig.path);
  const isBackend = typeof foundHost === "string" && isCorrectPath;
  if (isBackend && !skipGM)
    GM_setValue(currentBackendUrlKey, constructBackendURL(foundHost));
  return isBackend;
}
var runningOnBackend = isRunningOnBackend();
var runningOnDevPage = runningOnBackend && isDevPage;
var debugModeActivated = false;
var onlyUseDevelopmentBackend = false;
function prependProtocolWhenNeeded(url) {
  if (!url.startsWith("http://") && !url.startsWith("https://")) {
    return "http://" + url;
  }
  return url;
}
function getCurrentBackendURL(skipGmStorage) {
  if (onlyUseDevelopmentBackend) {
    return constructBackendURL(backendConfig.hosts?.dev);
  }
  const gmStorageUrl = GM_getValue(currentBackendUrlKey);
  if (skipGmStorage || !gmStorageUrl) {
    return constructBackendURL();
  }
  return prependProtocolWhenNeeded(gmStorageUrl);
}
if (!isBackendUrlUpToDate) {
  GM_setValue(currentBackendUrlKey, getCurrentBackendURL(true));
}
function createInstanceVariable(dbValue) {
  return {
    set: (instanceID, value) => GM_setValue(dbValues[dbValue](instanceID), { value, date: Date.now() }),
    get: (instanceID) => {
      const data = GM_getValue(dbValues[dbValue](instanceID));
      if (data?.date) {
        data.date = Date.now();
        GM_setValue(dbValues[dbValue](instanceID), data);
      }
      return data?.value;
    }
  };
}
var tempValueIndicator = "-temp-value-";
var dbValues = {
  OmniChessConfig: "OmniChessConfig",
  playerColor: (instanceID) => "playerColor" + tempValueIndicator + instanceID,
  turn: (instanceID) => "turn" + tempValueIndicator + instanceID,
  fen: (instanceID) => "fen" + tempValueIndicator + instanceID
};
var instanceVars = {
  playerColor: createInstanceVariable("playerColor"),
  turn: createInstanceVariable("turn"),
  fen: createInstanceVariable("fen")
};
var config = {};
function setGmConfigValue(key, value, instanceID, profileID) {
  if (typeof profileID === "object") {
    profileID = profileID.name;
  }
  const configObj = GM_getValue(dbValues.OmniChessConfig) || {};
  if (profileID) {
    if (!configObj.instance)
      configObj.instance = {};
    if (!configObj.instance[instanceID])
      configObj.instance[instanceID] = {};
    if (!configObj.instance[instanceID].profiles)
      configObj.instance[instanceID].profiles = {};
    if (!configObj.instance[instanceID].profiles[profileID])
      configObj.instance[instanceID].profiles[profileID] = {};
    configObj.instance[instanceID].profiles[profileID][key] = value;
  } else {
    if (!configObj.instance)
      configObj.instance = {};
    if (!configObj.instance[instanceID])
      configObj.instance[instanceID] = {};
    configObj.instance[instanceID][key] = value;
  }
  GM_setValue(dbValues.OmniChessConfig, configObj);
}
function getGmConfigValue(key, instanceID, profileID) {
  if (typeof profileID === "object") {
    profileID = profileID.name;
  }
  const config2 = GM_getValue(dbValues.OmniChessConfig);
  const instanceValue = config2?.instance?.[instanceID]?.[key];
  const globalValue = config2?.global?.[key];
  if (instanceValue !== undefined) {
    return instanceValue;
  }
  if (globalValue !== undefined) {
    return globalValue;
  }
  if (profileID) {
    const globalProfileValue = config2?.global?.["profiles"]?.[profileID]?.[key];
    const instanceProfileValue = config2?.instance?.[instanceID]?.["profiles"]?.[profileID]?.[key];
    if (instanceProfileValue !== undefined) {
      return instanceProfileValue;
    }
    if (globalProfileValue !== undefined) {
      return globalProfileValue;
    }
  }
  return null;
}
function getConfigValue(key, profile) {
  return config[key]?.get(profile);
}
function setConfigValue(key, val) {
  return config[key]?.set(val);
}
if (!(runningOnBackend && !isDevPage)) {
  Object.values(configKeys).forEach((key) => {
    config[key] = {
      get: (profile) => getGmConfigValue(key, commLinkInstanceID, profile),
      set: (val, profile) => setGmConfigValue(key, val, commLinkInstanceID, profile)
    };
  });
}

// src/state.js
var state = {
  isUserMouseDown: false,
  lastMoveRequestTime: 0,
  activeAutomoves: [],
  chessBoardElem: null,
  BoardDrawer: null,
  activeGuiMoveMarkings: [],
  activeMetricRenders: [],
  activeFeedback: [],
  lastPieceSize: null,
  lastBoardSize: null,
  lastBoardRanks: null,
  lastBoardFiles: null,
  lastBoardMatrix: null,
  isMovesOnDemandActive: false,
  matchFirstSuggestionGiven: false,
  commLink: null,
  lastCalculatedFullFen: null,
  moveRetryCount: 0,
  getBoardDimensions: () => [8, 8],
  getPieceElem: () => null,
  getBoardOrientation: () => "w"
};

// src/utils/coordinates.js
var lastBoardRanks = null;
var lastBoardFiles = null;
var lastBoardSize = null;
var lastPieceSize = null;
var chessBoardElem = null;
function setChessBoardElem(elem) {
  chessBoardElem = elem;
  state.chessBoardElem = elem;
}
function getElementSize(elem) {
  const rect = elem.getBoundingClientRect();
  if (rect.width !== 0 && rect.height !== 0) {
    return { width: rect.width, height: rect.height };
  }
  const computedStyle = window.getComputedStyle(elem);
  const width = parseFloat(computedStyle.width);
  const height = parseFloat(computedStyle.height);
  return { width, height };
}
function extractElemTransformData(elem) {
  const computedStyle = window.getComputedStyle(elem);
  const transform = computedStyle.transform;
  if (!transform || transform === "none") {
    return [0, 0];
  }
  try {
    const transformMatrix = new DOMMatrix(transform);
    const x = transformMatrix.e;
    const y = transformMatrix.f;
    return [x, y];
  } catch (e) {
    return [0, 0];
  }
}
function getElemCoordinatesFromTransform(elem, config2) {
  const onlyFlipX = config2?.onlyFlipX;
  const onlyFlipY = config2?.onlyFlipY;
  lastBoardSize = getElementSize(state.chessBoardElem);
  state.lastBoardSize = lastBoardSize;
  const [boardRanks, boardFiles] = state.getBoardDimensions();
  lastBoardRanks = boardRanks;
  lastBoardFiles = boardFiles;
  state.lastBoardRanks = boardRanks;
  state.lastBoardFiles = boardFiles;
  const boardOrientation = state.getBoardOrientation();
  let [x, y] = extractElemTransformData(elem);
  const boardDimensions = lastBoardSize;
  let squareDimensions = boardDimensions.width / lastBoardFiles;
  const normalizedX = Math.round(x / squareDimensions);
  const normalizedY = Math.round(y / squareDimensions);
  if (onlyFlipY || boardOrientation === "w") {
    const flippedY = lastBoardRanks - normalizedY - 1;
    return [normalizedX, flippedY];
  } else {
    const flippedX = lastBoardFiles - normalizedX - 1;
    return [flippedX, normalizedY];
  }
}
function getElemCoordinatesFromLeftBottomPercentages(elem) {
  if (!lastBoardRanks || !lastBoardFiles) {
    const [boardRanks, boardFiles] = state.getBoardDimensions();
    lastBoardRanks = boardRanks;
    lastBoardFiles = boardFiles;
    state.lastBoardRanks = boardRanks;
    state.lastBoardFiles = boardFiles;
  }
  const boardOrientation = state.getBoardOrientation();
  const leftPercentage = parseFloat(elem.style.left?.replace("%", ""));
  const bottomPercentage = parseFloat(elem.style.bottom?.replace("%", ""));
  const x = Math.max(Math.round(leftPercentage / (100 / lastBoardFiles)), 0);
  const y = Math.max(Math.round(bottomPercentage / (100 / lastBoardRanks)), 0);
  if (boardOrientation === "w") {
    return [x, y];
  } else {
    const flippedX = lastBoardFiles - (x + 1);
    const flippedY = lastBoardRanks - (y + 1);
    return [flippedX, flippedY];
  }
}
function getElemCoordinatesFromLeftTopPixels(elem) {
  const pieceSize = getElementSize(elem);
  lastPieceSize = pieceSize;
  state.lastPieceSize = pieceSize;
  const leftPixels = parseFloat(elem.style.left?.replace("px", ""));
  const topPixels = parseFloat(elem.style.top?.replace("px", ""));
  const x = Math.max(Math.round(leftPixels / pieceSize.width), 0);
  const y = Math.max(Math.round(topPixels / pieceSize.width), 0);
  const boardOrientation = state.getBoardOrientation();
  if (boardOrientation === "w") {
    const flippedY = lastBoardRanks - (y + 1);
    return [x, flippedY];
  } else {
    const flippedX = lastBoardFiles - (x + 1);
    return [flippedX, y];
  }
}
function getBoardDimensionsFromSize() {
  const boardDimensions = getElementSize(state.chessBoardElem);
  lastBoardSize = boardDimensions;
  state.lastBoardSize = boardDimensions;
  const boardWidth = boardDimensions?.width;
  const boardHeight = boardDimensions.height;
  const boardPiece = state.getPieceElem();
  if (boardPiece) {
    const pieceDimensions = getElementSize(boardPiece);
    lastPieceSize = pieceDimensions;
    state.lastPieceSize = pieceDimensions;
    const boardPieceWidth = pieceDimensions?.width;
    const boardPieceHeight = pieceDimensions?.height;
    const boardRanks = Math.round(boardHeight / boardPieceHeight);
    const boardFiles = Math.round(boardWidth / boardPieceWidth);
    const ranksInAllowedRange = 0 < boardRanks && boardRanks <= 69;
    const filesInAllowedRange = 0 < boardFiles && boardFiles <= 69;
    if (ranksInAllowedRange && filesInAllowedRange) {
      return [boardRanks, boardFiles];
    }
  }
}
function chessCoordinatesToIndex(coord) {
  const x = coord.charCodeAt(0) - 97;
  const y = Number(coord.slice(1)) - 1;
  return [x, y];
}
function chessCoordinatesToMatrixIndex(coord) {
  const [boardRanks, boardFiles] = state.getBoardDimensions();
  const indexArr = chessCoordinatesToIndex(coord);
  let x, y;
  y = boardRanks - (indexArr[1] + 1);
  x = indexArr[0];
  return [x, y];
}
function chessCoordinatesToDomIndex(coord) {
  const [boardRanks, boardFiles] = state.getBoardDimensions();
  const indexArr = chessCoordinatesToIndex(coord);
  const boardOrientation = state.getBoardOrientation();
  let x, y;
  if (boardOrientation === "w") {
    x = indexArr[0];
    y = boardRanks - (indexArr[1] + 1);
  } else {
    x = boardFiles - (indexArr[0] + 1);
    y = indexArr[1];
  }
  return [x, y];
}
function indexToChessCoordinates(coord) {
  const [boardRanks, boardFiles] = state.getBoardDimensions();
  const [x, y] = coord;
  const file = String.fromCharCode(97 + x);
  let rank;
  rank = boardRanks - y;
  return `${file}${rank}`;
}
function fenCoordArrToDomCoord(fenCoordArr) {
  const boardClientRect = state.chessBoardElem.getBoundingClientRect();
  const pieceElem = state.getPieceElem();
  const pieceDimensions = getElementSize(pieceElem);
  const pieceWidth = pieceDimensions?.width;
  const pieceHeight = pieceDimensions?.height;
  lastPieceSize = pieceDimensions;
  state.lastPieceSize = pieceDimensions;
  const [boardRanks, boardFiles] = state.getBoardDimensions();
  const centerCoordinates = fenCoordArr.map((coord) => {
    const [x, y] = chessCoordinatesToDomIndex(coord);
    const centerX = boardClientRect.x + x * pieceWidth + pieceWidth / 2;
    const centerY = boardClientRect.y + y * pieceHeight + pieceHeight / 2;
    return [centerX, centerY];
  });
  return centerCoordinates;
}

// src/adapters/index.js
var supportedSites = {};
var resetCachedValues = () => {};
function setResetCachedValues(fn) {
  resetCachedValues = fn;
}
function filterInvisibleElems(elementArr, inverse) {
  return [...elementArr].filter((elem) => {
    const style = getComputedStyle(elem);
    const bounds = elem.getBoundingClientRect();
    const isHidden = style.visibility === "hidden" || style.display === "none" || style.opacity === "0" || bounds.width == 0 || bounds.height == 0;
    return inverse ? isHidden : !isHidden;
  });
}
function convertPieceStrToFen(str) {
  if (!str || str.length !== 2) {
    return null;
  }
  const firstChar = str[0].toLowerCase();
  const secondChar = str[1];
  if (firstChar === "w") {
    return secondChar.toUpperCase();
  } else if (firstChar === "b") {
    return secondChar.toLowerCase();
  }
  return null;
}
function getBoardOrientation() {
  const boardOrientation = getSiteData("boardOrientation");
  const playerColor = state.commLink ? state.commLink.commands.playerColor : null;
  return boardOrientation || playerColor || null;
}
function getBoardMatrix() {
  const [boardRanks, boardFiles] = getBoardDimensions();
  const board = Array.from({ length: boardRanks }, () => Array(boardFiles).fill(1));
  const pieceElems = getPieceElem(true);
  const isValidPieceElemsArray = Array.isArray(pieceElems) || pieceElems instanceof NodeList;
  if (isValidPieceElemsArray) {
    pieceElems.forEach((pieceElem) => {
      const pieceFenCode = getPieceElemFen(pieceElem);
      const pieceCoordsArr = getPieceElemCoords(pieceElem);
      try {
        const [xIdx, yIdx] = pieceCoordsArr;
        board[boardRanks - (yIdx + 1)][xIdx] = pieceFenCode;
      } catch (e) {
        if (state.commLink && state.commLink.debugMode)
          console.error(e);
      }
    });
  }
  state.lastBoardMatrix = board;
  return board;
}
function getBoardPiece(fenCoord) {
  const [boardRanks, boardFiles] = getBoardDimensions();
  const indexArr = chessCoordinatesToIndex(fenCoord);
  return getBoardMatrix()?.[boardRanks - (indexArr[1] + 1)]?.[indexArr[0]];
}
function isPawnPromotion(bestMove) {
  const [fenCoordFrom, fenCoordTo] = bestMove;
  const piece = getBoardPiece(fenCoordFrom);
  if (typeof piece !== "string" || piece.toLowerCase() !== "p")
    return false;
  const endingRow = parseInt(fenCoordTo[1], 10);
  const [boardRanks] = getBoardDimensions();
  if (piece === "P" && endingRow === (boardRanks ?? 8) || piece === "p" && endingRow === 1) {
    return true;
  }
  return false;
}
function getSiteData(dataType, obj) {
  const pathname = window.location.pathname;
  let dataObj = { pathname };
  if (obj && typeof obj === "object") {
    dataObj = { ...dataObj, ...obj };
  }
  const dataHandlerFunction = supportedSites[domain]?.[dataType];
  if (typeof dataHandlerFunction !== "function") {
    return null;
  }
  const result = dataHandlerFunction(dataObj);
  return result;
}
function addSupportedChessSite(domains, typeHandlerObj) {
  const domainList = Array.isArray(domains) ? domains : [domains];
  domainList.forEach((domain2) => {
    supportedSites[domain2] = typeHandlerObj;
  });
}
function getBoardElem() {
  const boardElem = getSiteData("boardElem");
  return boardElem || null;
}
function getPieceElem(getAll) {
  const boardElem = getBoardElem();
  const boardQuerySelector = getAll ? (query) => {
    const elems = boardElem?.querySelectorAll(query);
    return elems?.length ? [...elems] : null;
  } : boardElem?.querySelector?.bind(boardElem);
  if (typeof boardQuerySelector !== "function")
    return null;
  const pieceElem = getSiteData("pieceElem", { boardQuerySelector, getAll });
  return pieceElem || null;
}
function getSquareElems(element) {
  const squareElems = getSiteData("squareElems", { element });
  return squareElems || null;
}
function getChessVariant() {
  const chessVariant = getSiteData("chessVariant");
  return chessVariant || null;
}
function getPieceElemFen(pieceElem) {
  const pieceFen = getSiteData("pieceElemFen", { pieceElem });
  return pieceFen || null;
}
function getPieceElemCoords(pieceElem) {
  const pieceCoords = getSiteData("pieceElemCoords", { pieceElem });
  return pieceCoords || null;
}
function getBoardDimensions() {
  const boardDimensionArr = getSiteData("boardDimensions");
  if (boardDimensionArr) {
    return boardDimensionArr;
  } else {
    return [8, 8];
  }
}
function isMutationNewMove(mutationArr) {
  const isNewMoveArr = getSiteData("isMutationNewMove", { mutationArr });
  return isNewMoveArr || false;
}
function getMutationTurn(mutationArr) {
  const turn = getSiteData("getMutationTurn", { mutationArr });
  return turn || null;
}

// src/utils/fen.js
function getFenPieceColor(pieceFenStr) {
  return pieceFenStr == pieceFenStr.toUpperCase() ? "w" : "b";
}
function getFenPieceOppositeColor(pieceFenStr) {
  return getFenPieceColor(pieceFenStr) == "w" ? "b" : "w";
}
function getRights(boardMatrix) {
  if (!boardMatrix)
    boardMatrix = getBoardMatrix();
  let rights = "";
  const useChess960 = getConfigValue(configKeys.useChess960);
  const [boardRanks, boardFiles] = state.getBoardDimensions();
  function getPieceFromMatrix(coord) {
    const indexArr = chessCoordinatesToIndex(coord);
    return boardMatrix?.[boardRanks - (indexArr[1] + 1)]?.[indexArr[0]];
  }
  if (useChess960) {
    let whiteKingX = -1;
    let whiteRooks = [];
    for (let x = 0;x < 8; x++) {
      const piece = getPieceFromMatrix(String.fromCharCode(97 + x) + "1");
      if (piece === "K")
        whiteKingX = x;
      else if (piece === "R")
        whiteRooks.push(x);
    }
    if (whiteKingX !== -1) {
      const kSideRook = whiteRooks.filter((x) => x > whiteKingX).pop();
      if (kSideRook !== undefined) {
        rights += String.fromCharCode(65 + kSideRook);
      }
      const qSideRook = whiteRooks.filter((x) => x < whiteKingX).shift();
      if (qSideRook !== undefined) {
        rights += String.fromCharCode(65 + qSideRook);
      }
    }
    let blackKingX = -1;
    let blackRooks = [];
    for (let x = 0;x < 8; x++) {
      const piece = getPieceFromMatrix(String.fromCharCode(97 + x) + "8");
      if (piece === "k")
        blackKingX = x;
      else if (piece === "r")
        blackRooks.push(x);
    }
    if (blackKingX !== -1) {
      const kSideRook = blackRooks.filter((x) => x > blackKingX).pop();
      if (kSideRook !== undefined) {
        rights += String.fromCharCode(97 + kSideRook);
      }
      const qSideRook = blackRooks.filter((x) => x < blackKingX).shift();
      if (qSideRook !== undefined) {
        rights += String.fromCharCode(97 + qSideRook);
      }
    }
  } else {
    const e1 = getPieceFromMatrix("e1"), h1 = getPieceFromMatrix("h1"), a1 = getPieceFromMatrix("a1");
    if (e1 == "K" && h1 == "R")
      rights += "K";
    if (e1 == "K" && a1 == "R")
      rights += "Q";
    const e8 = getPieceFromMatrix("e8"), h8 = getPieceFromMatrix("h8"), a8 = getPieceFromMatrix("a8");
    if (e8 == "k" && h8 == "r")
      rights += "k";
    if (e8 == "k" && a8 == "r")
      rights += "q";
  }
  return rights ? rights : "-";
}
function squeezeEmptySquares(fenStr) {
  return fenStr.replace(/1+/g, (match) => match.length);
}
function getFen(onlyBasic) {
  const boardMatrix = getBoardMatrix();
  const basicFen = squeezeEmptySquares(boardMatrix.map((x) => x.join("")).join("/"));
  if (onlyBasic) {
    return basicFen;
  }
  const turn = instanceVars.turn.get(commLinkInstanceID) || getBoardOrientation();
  const fullFen = `${basicFen} ${turn} ${getRights(boardMatrix)} - 0 1`;
  return fullFen;
}
var countTotalPieces = (fen) => (fen.split(" ")[0].match(/[rnbqkp]/gi) || []).length;
function getPieceChangeAmount(lastFen, newFen) {
  if (!lastFen || !newFen)
    return 0;
  const lastPieceCount = countTotalPieces(lastFen);
  const newPieceCount = countTotalPieces(newFen);
  const countChange = newPieceCount - lastPieceCount;
  return countChange;
}
function getBoardSquareChangeAmount(lastFen, newFen) {
  if (!lastFen || !newFen)
    return 0;
  let board1 = lastFen.split(" ")[0].replace(/\d/g, (d) => " ".repeat(d)).split("/").join("");
  let board2 = newFen.split(" ")[0].replace(/\d/g, (d) => " ".repeat(d)).split("/").join("");
  let diff = 0;
  for (let i = 0;i < board1.length; i++) {
    if (board1[i] !== board2[i]) {
      diff += 1;
    }
  }
  return diff;
}

// src/core/autoMove.js
function getRandomOwnPieceDomCoord(fenCoord, boardMatrix) {
  let [x, y] = chessCoordinatesToMatrixIndex(fenCoord);
  const pieceAtFenCoord = boardMatrix[y][x];
  if (pieceAtFenCoord === 1) {
    return null;
  }
  const isWhitePiece = pieceAtFenCoord === pieceAtFenCoord.toUpperCase();
  const getDistance = (row1, col1, row2, col2) => {
    return Math.abs(row1 - row2) + Math.abs(col1 - col2);
  };
  let candidatePieces = [];
  for (let row = 0;row < boardMatrix.length; row++) {
    for (let col = 0;col < boardMatrix[row].length; col++) {
      const currentPiece = boardMatrix[row][col];
      if (currentPiece === 1 || isWhitePiece && currentPiece === currentPiece.toLowerCase() || !isWhitePiece && currentPiece === currentPiece.toUpperCase()) {
        continue;
      }
      const distance = getDistance(y, x, row, col);
      if (distance < 6) {
        candidatePieces.push({ distance, coord: [col, row], piece: currentPiece });
      }
    }
  }
  if (candidatePieces.length > 0) {
    const randomIndex = Math.floor(Math.random() * candidatePieces.length);
    const chosenPiece = candidatePieces[randomIndex];
    return fenCoordArrToDomCoord([indexToChessCoordinates(chosenPiece.coord)])[0];
  }
  return null;
}
function getPieceAmount() {
  return getPieceElem(true)?.length ?? 0;
}

class AutomaticMove {
  constructor(profile, fenMoveArr, isLegit, callback) {
    this.id = getUniqueID();
    state.activeAutomoves.push({ id: this.id, move: this });
    this.profile = profile;
    this.fenMoveArr = fenMoveArr;
    this.isLegit = isLegit;
    this.active = true;
    this.isPromotingPawn = false;
    this.onFinished = function(...args) {
      state.activeAutomoves = state.activeAutomoves.filter((x) => x.id !== this.id);
      this.active = false;
      callback(...args);
      if (args[0]) {
        const startingFen = state.lastCalculatedFullFen;
        setTimeout(() => {
          const checkRetry = () => {
            if (state.isUserMouseDown) {
              setTimeout(checkRetry, 100);
              return;
            }
            if (state.lastCalculatedFullFen === startingFen) {
              if (state.moveRetryCount < 3) {
                state.moveRetryCount++;
                if (debugModeActivated)
                  console.warn(`Move failed to execute. Retry attempt ${state.moveRetryCount}/3...`);
                if (state.processBoardPosition) {
                  state.processBoardPosition(getFen());
                }
              } else {
                if (debugModeActivated)
                  console.warn("Move failed 3 times. Giving up to prevent infinite loops.");
              }
            }
          };
          checkRetry();
        }, 1500);
      }
    };
    this.moveDomCoords = fenCoordArrToDomCoord(fenMoveArr);
    this.isPromotion = isPawnPromotion(fenMoveArr);
    if (this.isLegit) {
      const legitModeType = getConfigValue(configKeys.legitModeType, this.profile) ?? "casual";
      const pieceRanges = [
        { minPieces: 30, maxPieces: Infinity },
        { minPieces: 23, maxPieces: 29 },
        { minPieces: 16, maxPieces: 22 },
        { minPieces: 10, maxPieces: 15 },
        { minPieces: 6, maxPieces: 9 },
        { minPieces: 3, maxPieces: 5 },
        { minPieces: 1, maxPieces: 2 }
      ];
      const timeRanges = {
        beginner: [
          [2000, 4000],
          [3000, 15000],
          [5000, 25000],
          [4000, 30000],
          [3000, 15000],
          [2000, 1e4],
          [1000, 4000]
        ],
        casual: [
          [900, 3000],
          [1000, 15000],
          [3000, 20000],
          [2000, 13000],
          [1500, 1e4],
          [1000, 9000],
          [500, 3000]
        ],
        intermediate: [
          [750, 2000],
          [1000, 1e4],
          [2000, 15000],
          [1500, 12000],
          [1000, 8000],
          [750, 7000],
          [500, 2000]
        ],
        advanced: [
          [500, 1500],
          [1000, 8000],
          [750, 8000],
          [750, 12000],
          [750, 5000],
          [750, 3000],
          [500, 1200]
        ],
        master: [
          [333, 999],
          [400, 2000],
          [400, 3000],
          [400, 2500],
          [400, 2000],
          [400, 1500],
          [333, 750]
        ],
        professional: [
          [333, 666],
          [333, 666],
          [333, 1000],
          [333, 1500],
          [333, 1000],
          [333, 666],
          [333, 666]
        ],
        god: [
          [50, 333],
          [50, 233],
          [50, 300],
          [50, 250],
          [50, 200],
          [50, 150],
          [50, 100]
        ]
      };
      this.timeRanges = pieceRanges.map((range, index) => ({
        ...range,
        timeRange: timeRanges[legitModeType][index]
      }));
      this.shouldHesitate = this.isLegit && Math.random() < 0.15;
      this.shouldHesitateTwice = this.isLegit && Math.random() < 0.25;
      this.hesitationTypeOne = this.isLegit && Math.random() < 0.35;
      const legitTotalMoveTime = this.calculateMoveTime(getPieceAmount());
      const elapsedMoveTime = Date.now() - state.lastMoveRequestTime;
      const remainingTime = Math.max(legitTotalMoveTime - elapsedMoveTime, 500);
      const delays = this.generateDelaysForDesiredTime(remainingTime);
      for (const key of Object.keys(delays)) {
        this[key] = delays[key];
      }
    }
    this.start();
  }
  generateDelaysForDesiredTime(desiredTotalTime) {
    const PROMOTION_DELAY = this.getRandomIntegerBetween(1000, 1111);
    if (desiredTotalTime > 6000) {
      const timelines = [
        { move: 0.4, to: 0.2, hesitation: 0.15, hesitationResolve: 0.15, secondHesitationResolve: 0.15 },
        { move: 0.1, to: 0.3, hesitation: 0.25, hesitationResolve: 0.15, secondHesitationResolve: 0.2 },
        { move: 0.2, to: 0.25, hesitation: 0.2, hesitationResolve: 0.2, secondHesitationResolve: 0.15 }
      ];
      const timeline = timelines[Math.floor(Math.random() * timelines.length)];
      return {
        promotionDelay: PROMOTION_DELAY,
        moveDelay: desiredTotalTime * timeline.move,
        toSquareSelectDelay: desiredTotalTime * timeline.to,
        hesitationDelay: desiredTotalTime * timeline.hesitation,
        hesitationResolveDelay: desiredTotalTime * timeline.hesitationResolve,
        secondHesitationResolveDelay: desiredTotalTime * timeline.secondHesitationResolve
      };
    }
    if (desiredTotalTime > 3000) {
      const timelines = [
        { move: 0.3, to: 0.2, hesitation: 0.25, hesitationResolve: 0.25 },
        { move: 0.1, to: 0.3, hesitation: 0.45, hesitationResolve: 0.15 },
        { move: 0.2, to: 0.25, hesitation: 0.2, hesitationResolve: 0.35 }
      ];
      const timeline = timelines[Math.floor(Math.random() * timelines.length)];
      return {
        promotionDelay: PROMOTION_DELAY,
        moveDelay: desiredTotalTime * timeline.move,
        toSquareSelectDelay: desiredTotalTime * timeline.to,
        hesitationDelay: desiredTotalTime * timeline.hesitation,
        hesitationResolveDelay: desiredTotalTime * timeline.hesitationResolve,
        secondHesitationResolveDelay: -1
      };
    } else {
      const timelines = [
        { move: 0.9, to: 0.1 },
        { move: 0.45, to: 0.55 },
        { move: 0.6, to: 0.4 },
        { move: 0.4, to: 0.6 },
        { move: 0.1, to: 0.9 }
      ];
      const timeline = timelines[Math.floor(Math.random() * timelines.length)];
      return {
        promotionDelay: PROMOTION_DELAY,
        moveDelay: desiredTotalTime * timeline.move,
        toSquareSelectDelay: desiredTotalTime * timeline.to,
        hesitationDelay: -1,
        hesitationResolveDelay: -1,
        secondHesitationResolveDelay: -1
      };
    }
  }
  calculateMoveTime(pieceCount) {
    for (let range of this.timeRanges) {
      if (pieceCount >= range.minPieces && pieceCount <= range.maxPieces) {
        return this.getRandomIntegerBetween(range.timeRange[0], range.timeRange[1]);
      }
    }
    return 500;
  }
  getRandomIntegerBetween(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
  }
  delay(ms) {
    return this.active ? new Promise((resolve) => setTimeout(resolve, ms)) : true;
  }
  async triggerPieceClick(input) {
    const parentExists = state.activeAutomoves.find((x) => x.move === this) ? true : false;
    if (!parentExists) {
      return;
    }
    let clientX, clientY;
    if (input instanceof Element) {
      const rect = input.getBoundingClientRect();
      clientX = rect.left + rect.width / 2;
      clientY = rect.top + rect.height / 2;
    } else if (Array.isArray(input)) {
      clientX = input[0];
      clientY = input[1];
    } else {
      return;
    }
    const xDivider = Math.random() < 0.85 ? 4 : Math.random() < 0.15 ? 3 : 2;
    const yDivider = Math.random() < 0.65 ? 3 : Math.random() < 0.35 ? 2 : 4;
    const randomVariationX = (state.lastPieceSize?.width - 4) / xDivider;
    const randomVariationY = (state.lastPieceSize?.height - 4) / yDivider;
    const randomOffsetX = (Math.random() - 0.5) * 2 * randomVariationX;
    const randomOffsetY = (Math.pow(Math.random(), 0.5) - 0.5) * 2 * randomVariationY;
    const randomizedX = clientX + randomOffsetX;
    const randomizedY = clientY + randomOffsetY;
    const pointerEventOptions = {
      bubbles: true,
      cancelable: true,
      clientX: randomizedX,
      clientY: randomizedY
    };
    const overlay = state.BoardDrawer?.boardContainerElem;
    let originalPointerEvents = "";
    if (overlay) {
      originalPointerEvents = overlay.style.pointerEvents;
      overlay.style.pointerEvents = "none";
    }
    const elementToTrigger = input instanceof Element ? input : document.elementFromPoint(clientX, clientY);
    if (overlay) {
      overlay.style.pointerEvents = originalPointerEvents;
    }
    if (elementToTrigger) {
      const releaseDelay = this.isLegit ? this.getRandomIntegerBetween(40, 110) : 0;
      switch (domain) {
        case "chess.com":
          elementToTrigger.dispatchEvent(new PointerEvent("pointerdown", pointerEventOptions));
          if (releaseDelay > 0)
            await this.delay(releaseDelay);
          elementToTrigger.dispatchEvent(new PointerEvent("pointerup", pointerEventOptions));
          break;
        case "lichess.org":
        case "worldchess.com":
        default:
          elementToTrigger.dispatchEvent(new MouseEvent("mousedown", pointerEventOptions));
          if (releaseDelay > 0)
            await this.delay(releaseDelay);
          elementToTrigger.dispatchEvent(new MouseEvent("mouseup", pointerEventOptions));
          break;
      }
    }
    if (debugModeActivated) {
      const dot = document.createElement("div");
      dot.style.position = "absolute";
      dot.style.width = "7px";
      dot.style.height = "7px";
      dot.style.borderRadius = "50%";
      dot.style.backgroundColor = "lime";
      dot.style.left = `${randomizedX - 2.5}px`;
      dot.style.top = `${randomizedY - 2.5}px`;
      const container = document.createElement("div");
      container.style.position = "absolute";
      container.style.width = `${Math.round(randomVariationX * 2)}px`;
      container.style.height = `${Math.round(randomVariationY * 2)}px`;
      container.style.border = "2px dashed green";
      container.style.backgroundColor = "rgba(0, 0, 0, 0.3)";
      container.style.left = `${clientX - randomVariationX}px`;
      container.style.top = `${clientY - randomVariationY}px`;
      document.body.appendChild(container);
      document.body.appendChild(dot);
      setTimeout(() => {
        dot.remove();
        container.remove();
      }, 1000);
    }
  }
  click(domCoord) {
    if (this.active)
      this.triggerPieceClick(domCoord);
  }
  async drag(fromCoord, toCoord) {
    const parentExists = state.activeAutomoves.find((x) => x.move === this) ? true : false;
    if (!parentExists)
      return;
    const usePointer = !!window.PointerEvent;
    const [xStart, yStart] = fromCoord;
    const [xEnd, yEnd] = toCoord;
    const overlay = state.BoardDrawer?.boardContainerElem;
    let originalPointerEvents = "";
    if (overlay) {
      originalPointerEvents = overlay.style.pointerEvents;
      overlay.style.pointerEvents = "none";
    }
    const startEl = document.elementFromPoint(xStart, yStart);
    if (!startEl) {
      if (overlay)
        overlay.style.pointerEvents = originalPointerEvents;
      return;
    }
    const pointerEventOptions = (x, y, buttons) => ({
      bubbles: true,
      cancelable: true,
      composed: true,
      clientX: x,
      clientY: y,
      buttons,
      pointerId: 1,
      pointerType: "mouse",
      isPrimary: true
    });
    const downOpts = pointerEventOptions(xStart, yStart, 1);
    if (usePointer) {
      startEl.dispatchEvent(new PointerEvent("pointerdown", downOpts));
    } else {
      startEl.dispatchEvent(new MouseEvent("mousedown", downOpts));
    }
    await this.delay(20);
    let steps = 10;
    let baseStepDelay = 12;
    let delayVarianceFn = () => 0;
    let curveJitter = 0;
    let useCurve = this.isLegit;
    if (useCurve) {
      const profiles = ["fast-flicker", "slow-steady", "tired-drag"];
      const chosenProfile = profiles[Math.floor(Math.random() * profiles.length)];
      if (chosenProfile === "fast-flicker") {
        steps = this.getRandomIntegerBetween(5, 7);
        baseStepDelay = this.getRandomIntegerBetween(4, 8);
        delayVarianceFn = () => (Math.random() - 0.5) * 2;
        curveJitter = (Math.random() - 0.5) * 0.15;
      } else if (chosenProfile === "slow-steady") {
        steps = this.getRandomIntegerBetween(12, 16);
        baseStepDelay = this.getRandomIntegerBetween(10, 14);
        delayVarianceFn = () => (Math.random() - 0.5) * 1;
        curveJitter = (Math.random() - 0.5) * 0.2;
      } else if (chosenProfile === "tired-drag") {
        steps = this.getRandomIntegerBetween(15, 22);
        baseStepDelay = this.getRandomIntegerBetween(16, 24);
        delayVarianceFn = () => {
          const baseJitter = (Math.random() - 0.5) * 6;
          const pauseJitter = Math.random() < 0.2 ? 15 : 0;
          return baseJitter + pauseJitter;
        };
        curveJitter = (Math.random() - 0.5) * 0.45;
      }
    }
    const p1x = useCurve ? (xStart + xEnd) / 2 + (yStart - yEnd) * curveJitter : 0;
    const p1y = useCurve ? (yStart + yEnd) / 2 + (xEnd - xStart) * curveJitter : 0;
    for (let i = 1;i <= steps; i++) {
      const t = i / steps;
      let mx, my;
      if (useCurve) {
        const mt = 1 - t;
        mx = mt * mt * xStart + 2 * mt * t * p1x + t * t * xEnd;
        my = mt * mt * yStart + 2 * mt * t * p1y + t * t * yEnd;
      } else {
        mx = xStart + (xEnd - xStart) * t;
        my = yStart + (yEnd - yStart) * t;
      }
      const stepTarget = document.elementFromPoint(mx, my) || startEl;
      const moveOpts = pointerEventOptions(mx, my, 1);
      if (usePointer) {
        stepTarget.dispatchEvent(new PointerEvent("pointermove", moveOpts));
      } else {
        stepTarget.dispatchEvent(new MouseEvent("mousemove", moveOpts));
      }
      const stepDelay = Math.max(1, Math.round(baseStepDelay + delayVarianceFn()));
      await this.delay(stepDelay);
    }
    const endEl = document.elementFromPoint(xEnd, yEnd) || startEl;
    const upOpts = pointerEventOptions(xEnd, yEnd, 0);
    if (usePointer) {
      endEl.dispatchEvent(new PointerEvent("pointerup", upOpts));
    } else {
      endEl.dispatchEvent(new MouseEvent("mouseup", upOpts));
    }
    if (overlay) {
      overlay.style.pointerEvents = originalPointerEvents;
    }
  }
  async hesitate() {
    const hesitationPieceDomCoord = getRandomOwnPieceDomCoord(this.fenMoveArr[0], getBoardMatrix());
    if (hesitationPieceDomCoord) {
      if (this.hesitationTypeOne) {
        this.click(this.moveDomCoords[0]);
        await this.delay(this.hesitationDelay);
      }
      this.click(hesitationPieceDomCoord);
      await this.delay(this.hesitationResolveDelay);
      if (this.shouldHesitateTwice && this.secondHesitationResolveDelay !== -1) {
        const secondHesitationPieceDomCoord = getRandomOwnPieceDomCoord(this.fenMoveArr[0], getBoardMatrix());
        if (secondHesitationPieceDomCoord) {
          this.click(secondHesitationPieceDomCoord);
          await this.delay(this.secondHesitationResolveDelay);
        }
      }
    }
    this.finishMove(this.toSquareSelectDelay, this.promotionDelay);
  }
  async finishMove(delay01, delay02) {
    let moveMethod = getConfigValue("moveMethod", this.profile) || "click";
    if (moveMethod === "natural") {
      moveMethod = Math.random() < 0.5 ? "click" : "drag";
    }
    if (moveMethod === "drag") {
      await this.delay(delay01);
      await this.drag(this.moveDomCoords[0], this.moveDomCoords[1]);
    } else {
      this.click(this.moveDomCoords[0]);
      await this.delay(delay01);
      this.click(this.moveDomCoords[1]);
    }
    if (this.isPromotion) {
      this.isPromotingPawn = true;
      await this.delay(delay02);
      const promoPiece = this.fenMoveArr[1]?.[2]?.toLowerCase();
      let promoClicked = false;
      if (promoPiece) {
        const selectors = [
          `.promotion-menu .${promoPiece}`,
          `#promotion-choice .${promoPiece}`,
          `.promotion-piece.${promoPiece}`,
          `[data-promotion="${promoPiece}"]`,
          `.promotion-menu [data-piece*="${promoPiece}"]`,
          `#promotion-choice [data-piece*="${promoPiece}"]`
        ];
        for (const selector of selectors) {
          const el = document.querySelector(selector);
          if (el) {
            this.click(el);
            promoClicked = true;
            break;
          }
        }
      }
      if (!promoClicked) {
        this.click(this.moveDomCoords[1]);
      }
      this.isPromotingPawn = false;
    }
    this.onFinished(true);
  }
  async playLegit() {
    await this.delay(this.moveDelay);
    if (this.shouldHesitate && this.hesitationDelay !== -1)
      this.hesitate();
    else
      this.finishMove(this.toSquareSelectDelay, this.promotionDelay);
  }
  async start() {
    if (this.isLegit) {
      this.playLegit();
    } else {
      this.finishMove(5, 1111);
    }
  }
  async stop() {
    if (this.isPromotingPawn) {
      this.click(this.moveDomCoords[1]);
    }
    this.onFinished(false);
  }
}
async function makeMove(profile, fenMoveArr, isLegit) {
  const move = new AutomaticMove(profile, fenMoveArr, isLegit, (e) => {
    if (debugModeActivated)
      console.warn("Move", fenMoveArr, move.id, "finished", "for profile:", profile);
  });
}

// src/drawing/drawing.js
var BoardDrawer = null;
var activeGuiMoveMarkings = state.activeGuiMoveMarkings;
var activeMetricRenders = state.activeMetricRenders;
var activeFeedback = state.activeFeedback;
function setBoardDrawer(val) {
  BoardDrawer = val;
  state.BoardDrawer = val;
}
var arrowDefaults = { best: ["limegreen", 0.9], secondary: ["dodgerblue", 0.7], opponent: ["crimson", 0.3] };
function getArrowStyle(type, fill, opacity) {
  const [f, o] = arrowDefaults[type] || [];
  return `stroke: rgb(0 0 0 / 50%);
stroke-width: 2px;
stroke-linejoin: round;
fill: ${fill || f};
opacity: ${opacity || o};`;
}
function renderShapes(items, bucket) {
  if (!state.BoardDrawer || !Array.isArray(items))
    return;
  for (const item of items) {
    const data = item?.data;
    if (!data)
      continue;
    const shapeType = data?.shapeType;
    const shapeSquare = data?.shapeSquare;
    const shapeConfig = data?.shapeConfig;
    if (shapeType && shapeSquare && shapeConfig) {
      const shape = state.BoardDrawer.createShape(shapeType, shapeSquare, shapeConfig);
      bucket.push(shape);
    }
  }
}
function clearMetricRenders() {
  state.activeMetricRenders.forEach((elem) => {
    if (elem)
      elem.remove();
  });
  state.activeMetricRenders.length = 0;
}
function renderMetrics(addedMetrics) {
  clearMetricRenders();
  if (!addedMetrics)
    return;
  const texts = addedMetrics.filter((metric) => metric?.data?.shapeType === "text");
  const rects = addedMetrics.filter((metric) => metric?.data?.shapeType === "rectangle");
  renderShapes(texts, state.activeMetricRenders);
  renderShapes(rects, state.activeMetricRenders);
}
function clearFeedback() {
  state.activeFeedback.forEach((elem) => {
    if (elem)
      elem.remove();
  });
  state.activeFeedback.length = 0;
}
function displayFeedback(addedFeedback) {
  clearFeedback();
  renderShapes(addedFeedback, state.activeFeedback);
}
function maybeAnnounceMarkingsToPage(moveMarkings) {
  if (!runningOnDevPage || typeof unsafeWindow === "undefined")
    return;
  const markings = moveMarkings || [];
  let selectedMarking = null;
  if (markings.length === 1) {
    selectedMarking = markings[0].player;
  } else if (markings.length > 1) {
    const randomIndex = Math.floor(Math.random() * markings.length);
    selectedMarking = markings[randomIndex].player;
  }
  unsafeWindow.postMessage({ name: "bestMoveArr", value: selectedMarking });
}
var boardUtils = {
  markMoves: (moveObjArr) => {
    if (!state.BoardDrawer)
      return;
    const maxScale = 1;
    const minScale = 0.5;
    const totalRanks = moveObjArr.length;
    function fillSquare(square, style) {
      const shapeType = "rectangle";
      const shapeConfig = { style };
      const rect = state.BoardDrawer.createShape(shapeType, square, shapeConfig);
      return rect;
    }
    const markedSquares = { 0: [], 1: [] };
    moveObjArr.forEach((markingObj, idx) => {
      const profile = markingObj.profile;
      const [from, to] = markingObj.player;
      const [oppFrom, oppTo] = markingObj.opponent;
      const oppMovesExist = oppFrom && oppTo;
      const rank = idx + 1;
      const cp = markingObj?.cp;
      const showOpponentMoveGuess = getConfigValue(configKeys.showOpponentMoveGuess, profile);
      const showOpponentMoveGuessConstantly = getConfigValue(configKeys.showOpponentMoveGuessConstantly, profile);
      const arrowOpacity = getConfigValue(configKeys.arrowOpacity, profile) / 100;
      const primaryArrowColorHex = getConfigValue(configKeys.primaryArrowColorHex, profile);
      const secondaryArrowColorHex = getConfigValue(configKeys.secondaryArrowColorHex, profile);
      const opponentArrowColorHex = getConfigValue(configKeys.opponentArrowColorHex, profile);
      const moveAsFilledSquares = getConfigValue(configKeys.moveAsFilledSquares, profile);
      const onlySuggestPieces = getConfigValue(configKeys.onlySuggestPieces, profile);
      const movesOnDemand = getConfigValue(configKeys.movesOnDemand, profile);
      if (onlySuggestPieces && !movesOnDemand) {
        const fillType = idx === 0 ? 1 : 0, fillColor = fillType ? primaryArrowColorHex : secondaryArrowColorHex;
        const fromSquareMarking = fillSquare(from, `opacity: ${arrowOpacity}; stroke-width: 5; stroke: black; rx: 2; ry: 2; fill: ${fillColor};`);
        let markedSquareElems = [fromSquareMarking];
        if (oppFrom) {
          const oppFromSquareMarking = fillSquare(oppFrom, `opacity: ${arrowOpacity}; stroke-width: 5; stroke: black; rx: 2; ry: 2; display: none; fill: ${opponentArrowColorHex};`);
          const squareListener = state.BoardDrawer.addSquareListener(from, (type) => {
            if (!oppFromSquareMarking)
              squareListener.remove();
            switch (type) {
              case "enter":
                oppFromSquareMarking.style.display = "inherit";
                break;
              case "leave":
                oppFromSquareMarking.style.display = "none";
                break;
            }
          });
          markedSquareElems.push(oppFromSquareMarking);
        }
        activeGuiMoveMarkings.push({ otherElems: markedSquareElems, profile });
        state.activeGuiMoveMarkings.push({ otherElems: markedSquareElems, profile });
      } else if (moveAsFilledSquares) {
        const fillType = idx === 0 ? 1 : 0, fillColor = fillType ? primaryArrowColorHex : secondaryArrowColorHex, styling = `opacity: ${arrowOpacity}; stroke-width: 5; stroke: black; rx: 2; ry: 2; fill: ${fillColor};`, skipFromSquare = markedSquares[fillType].find((x) => x === from) ? "opacity: 0;" : "", skipToSquare = markedSquares[fillType].find((x) => x === to) ? "opacity: 0;" : "";
        const fromSquareStyle = `${styling} ${skipFromSquare}`;
        const toSquareStyle = `filter: brightness(1.5); stroke-dasharray: 4 4; ${styling} ${skipToSquare}`;
        const fromSquareFill = fillSquare(from, fromSquareStyle);
        const toSquareFill = fillSquare(to, toSquareStyle);
        const markedSquareFens = [from, to];
        const markedSquareElems = [fromSquareFill, toSquareFill];
        if (oppMovesExist && showOpponentMoveGuess) {
          const oppFromSquareFill = fillSquare(oppFrom, fromSquareStyle + ` fill: ${opponentArrowColorHex};`);
          const oppToSquareFill = fillSquare(oppTo, toSquareStyle + ` fill: ${opponentArrowColorHex};`);
          markedSquareElems.push(oppFromSquareFill, oppToSquareFill);
          if (showOpponentMoveGuessConstantly) {
            oppFromSquareFill.style.display = "block";
            oppToSquareFill.style.display = "block";
          } else {
            oppFromSquareFill.style.display = "none";
            oppToSquareFill.style.display = "none";
            const squareListener = state.BoardDrawer.addSquareListener(from, (type) => {
              if (!oppFromSquareFill || !oppToSquareFill) {
                squareListener.remove();
              }
              switch (type) {
                case "enter":
                  oppFromSquareFill.style.display = "inherit";
                  oppToSquareFill.style.display = "inherit";
                  break;
                case "leave":
                  oppFromSquareFill.style.display = "none";
                  oppToSquareFill.style.display = "none";
                  break;
              }
            });
          }
        }
        markedSquares[fillType].push(...markedSquareFens);
        activeGuiMoveMarkings.push({ otherElems: markedSquareElems, profile });
        state.activeGuiMoveMarkings.push({ otherElems: markedSquareElems, profile });
      } else {
        let playerArrowElem = null;
        let oppArrowElem = null;
        let arrowStyle = getArrowStyle("best", primaryArrowColorHex, arrowOpacity);
        let lineWidth = 30;
        let arrowheadWidth = 80;
        let arrowheadHeight = 60;
        let startOffset = 30;
        if (idx !== 0) {
          arrowStyle = getArrowStyle("secondary", secondaryArrowColorHex, arrowOpacity);
          const arrowScale = totalRanks === 2 ? 0.75 : maxScale - (maxScale - minScale) * ((rank - 1) / (totalRanks - 1));
          lineWidth = lineWidth * arrowScale;
          arrowheadWidth = arrowheadWidth * arrowScale;
          arrowheadHeight = arrowheadHeight * arrowScale;
        }
        playerArrowElem = state.BoardDrawer.createShape("arrow", [from, to], {
          style: arrowStyle,
          lineWidth,
          arrowheadWidth,
          arrowheadHeight,
          startOffset
        });
        if (oppMovesExist && showOpponentMoveGuess) {
          oppArrowElem = state.BoardDrawer.createShape("arrow", [oppFrom, oppTo], {
            style: getArrowStyle("opponent", opponentArrowColorHex, arrowOpacity),
            lineWidth,
            arrowheadWidth,
            arrowheadHeight,
            startOffset
          });
          if (showOpponentMoveGuessConstantly) {
            oppArrowElem.style.display = "block";
          } else {
            oppArrowElem.style.display = "none";
            const squareListener = state.BoardDrawer.addSquareListener(from, (type) => {
              if (!oppArrowElem) {
                squareListener.remove();
              }
              switch (type) {
                case "enter":
                  oppArrowElem.style.display = "inherit";
                  break;
                case "leave":
                  oppArrowElem.style.display = "none";
                  break;
              }
            });
          }
        }
        if (idx === 0 && playerArrowElem) {
          const parentElem = playerArrowElem.parentElement;
          parentElem.appendChild(playerArrowElem);
          if (oppArrowElem) {
            parentElem.appendChild(oppArrowElem);
          }
        }
        activeGuiMoveMarkings.push({ ...markingObj, playerArrowElem, oppArrowElem, profile });
        state.activeGuiMoveMarkings.push({ ...markingObj, playerArrowElem, oppArrowElem, profile });
      }
    });
    maybeAnnounceMarkingsToPage(moveObjArr);
  },
  removeMarkings: (profile) => {
    let removalArr = [...state.activeGuiMoveMarkings];
    if (profile) {
      removalArr = removalArr.filter((obj) => obj.profile === profile);
      const filtered = state.activeGuiMoveMarkings.filter((obj) => obj.profile !== profile);
      state.activeGuiMoveMarkings.length = 0;
      state.activeGuiMoveMarkings.push(...filtered);
    } else {
      state.activeGuiMoveMarkings.length = 0;
    }
    removalArr.forEach((markingObj) => {
      markingObj.oppArrowElem?.remove();
      markingObj.playerArrowElem?.remove();
      markingObj?.otherElems?.forEach((x) => x?.remove());
    });
  },
  setBoardOrientation: (orientation) => {
    if (state.BoardDrawer) {
      if (debugModeActivated)
        console.warn("setBoardOrientation", orientation);
      state.BoardDrawer.setOrientation(orientation);
    }
  },
  setBoardDimensions: (dimensionArr) => {
    if (state.BoardDrawer) {
      if (debugModeActivated)
        console.warn("setBoardDimensions", dimensionArr);
      state.BoardDrawer.setBoardDimensions(dimensionArr);
    }
  }
};
function clearVisuals(noMetricsRemoval = false) {
  if (!noMetricsRemoval)
    clearMetricRenders();
  clearFeedback();
  boardUtils.removeMarkings();
}
function getCanvasPixelColor(canvas, [xPercentage, yPercentage], debug) {
  const ctx = canvas.getContext("2d");
  const x = xPercentage * canvas.width;
  const y = yPercentage * canvas.height;
  const imageData = ctx.getImageData(x, y, 1, 1);
  const pixel = imageData.data;
  const brightness = (pixel[0] + pixel[1] + pixel[2]) / 3;
  return brightness < 128 ? "b" : "w";
}
function canvasHasPixelAt(canvas, [xPercentage, yPercentage], debug) {
  xPercentage = Math.min(Math.max(xPercentage, 0), 100);
  yPercentage = Math.min(Math.max(yPercentage, 0), 100);
  const ctx = canvas.getContext("2d");
  const x = xPercentage * canvas.width;
  const y = yPercentage * canvas.height;
  const imageData = ctx.getImageData(x, y, 1, 1);
  const pixel = imageData.data;
  return pixel[3] !== 0;
}

// src/utils/sandbox.js
function exposeViaMessages() {
  const handlers = {
    USERSCRIPT_getValue: (args, messageId) => {
      const [key] = args;
      const value = GM_getValue(key);
      window.postMessage({ messageId, value }, window.location.origin);
    },
    USERSCRIPT_setValue: (args, messageId) => {
      const [key, value] = args;
      GM_setValue(key, value);
      window.postMessage({ messageId, value: true }, window.location.origin);
    },
    USERSCRIPT_deleteValue: (args, messageId) => {
      const [key] = args;
      GM_deleteValue(key);
      window.postMessage({ messageId, value: true }, window.location.origin);
    },
    USERSCRIPT_listValues: (args, messageId) => {
      const value = GM_listValues();
      window.postMessage({ messageId, value }, window.location.origin);
    },
    USERSCRIPT_getInfo: (args, messageId) => {
      const value = typeof GM_info !== "undefined" ? JSON.parse(JSON.stringify(GM_info)) : {};
      window.postMessage({ messageId, value }, window.location.origin);
    },
    USERSCRIPT_instanceVars: (args, messageId) => {
      const [instanceId, key, value] = args;
      if (!instanceVars.hasOwnProperty(key)) {
        window.postMessage({ messageId, value: false }, window.location.origin);
        return;
      }
      const result = value !== undefined ? instanceVars[key].set(instanceId, value) : instanceVars[key].get(instanceId);
      window.postMessage({ messageId, value: result }, window.location.origin);
    }
  };
  window.addEventListener("message", (event) => {
    try {
      const originUrl = new URL(event.origin);
      const allowedHosts = ["quantavil.github.io", "localhost"];
      const cleanOriginHost = originUrl.hostname;
      const isAllowed = allowedHosts.some((host) => {
        const cleanHost = host.split(":")[0];
        return cleanHost === cleanOriginHost;
      });
      if (!isAllowed)
        return;
    } catch (e) {
      return;
    }
    const handler = handlers[event.data?.type];
    if (handler)
      handler(event.data.args, event.data.messageId);
  });
  const script = document.createElement("script");
  script.innerHTML = "window.isUserscriptActive = true;";
  document.head.appendChild(script);
}
function exposeViaUnsafe() {
  if (typeof unsafeWindow !== "object")
    return;
  unsafeWindow.USERSCRIPT = {
    getValue: (val) => GM_getValue(val),
    setValue: (val, data) => GM_setValue(val, data),
    deleteValue: (val) => GM_deleteValue(val),
    listValues: (val) => GM_listValues(val),
    instanceVars,
    getInfo: () => GM_info
  };
  unsafeWindow.isUserscriptActive = true;
}
function checkAndExposeSandbox() {
  if (runningOnBackend && !isDevPage) {
    if (typeof unsafeWindow === "object")
      exposeViaUnsafe();
    else
      exposeViaMessages();
  }
}

// src/utils/input.js
var activeInputListeners = [];
function createInputListener(listenerType, targetValue, callback) {
  if (typeof listenerType !== "string" || typeof targetValue !== "string" || !callback)
    return;
  const existingIndex = activeInputListeners.findIndex((l) => l.listenerType === listenerType);
  if (existingIndex !== -1) {
    const existing = activeInputListeners[existingIndex];
    if (existing.targetValue === targetValue)
      return;
    existing.listeners.forEach(({ type, fn }) => document.removeEventListener(type, fn));
    activeInputListeners.splice(existingIndex, 1);
  }
  let holdTimer = null;
  let lastTapTime = 0;
  const dblTapThreshold = 300;
  const listeners = [];
  const addListener = (type, fn) => {
    document.addEventListener(type, fn);
    listeners.push({ type, fn });
  };
  addListener("keydown", (e) => {
    if (!targetValue.startsWith("Interact") && e.code === targetValue)
      callback(e);
  });
  const startPress = (e) => {
    if (!targetValue.startsWith("Interact"))
      return;
    const match = targetValue.match(/^InteractLongPress(\d+)$/);
    if (match)
      holdTimer = setTimeout(() => {
        callback(e);
        holdTimer = null;
      }, parseInt(match[1], 10) * 1000);
    if (targetValue === "InteractDoubleClick" && e.type.startsWith("touch")) {
      const now = performance.now();
      if (now - lastTapTime < dblTapThreshold) {
        callback(e);
        lastTapTime = 0;
      } else
        lastTapTime = now;
    }
  };
  const endPress = () => {
    if (holdTimer) {
      clearTimeout(holdTimer);
      holdTimer = null;
    }
  };
  addListener("mousedown", startPress);
  addListener("mouseup", endPress);
  addListener("touchstart", startPress);
  addListener("touchend", endPress);
  addListener("dblclick", (e) => {
    if (targetValue === "InteractDoubleClick")
      callback(e);
  });
  activeInputListeners.push({
    listenerType,
    targetValue,
    callback,
    listeners
  });
}
function applyAssistanceConcealment(isConcealed = false) {
  const BoardDrawerSvg = state.BoardDrawer?.boardContainerElem;
  if (!BoardDrawerSvg)
    return;
  if (isConcealed)
    BoardDrawerSvg.style.display = "none";
  else
    BoardDrawerSvg.style.display = "block";
}
function toggleConcealAssistance() {
  if (state.commLink) {
    state.commLink.commands.toggleConcealAssistance();
  }
}

// src/utils/pathfinding.js
var modListeners = [];
var modDrawerListeners = [];
var modLastEnteredSquare = { squareIndex: null, squareFen: null, pieceFen: null };
function coordinatesFromMoves(board, piecePos, moves, isPieceWhite) {
  const result = [];
  for (let i = 0;i < moves.length; i++) {
    const x = piecePos[0] + moves[i][0];
    const y = piecePos[1] + moves[i][1];
    const square = board?.[y]?.[x];
    if (!square)
      continue;
    if (square === 1) {
      result.push([x, y]);
    } else {
      const squareIsWhite = square === square.toUpperCase();
      if (squareIsWhite !== isPieceWhite)
        result.push([x, y]);
    }
  }
  return result;
}
function getPiecePaths(board, piecePos, pieceFen, isPieceWhite) {
  const [xPos, yPos] = piecePos;
  if (!pieceFen || typeof pieceFen !== "string")
    return;
  const pieceType = pieceFen.toUpperCase();
  const boardHeight = board.length;
  const boardWidth = board[0]?.length || 0;
  const longerBoardSide = Math.max(boardWidth, boardHeight);
  const shorterBoardSide = Math.min(boardWidth, boardHeight);
  function cast(directions, length) {
    const moves = [];
    for (let direction of directions) {
      for (let i = 1;i < length; i++) {
        const x = xPos + direction[0] * i;
        const y = yPos + direction[1] * i;
        const square = board?.[y]?.[x];
        if (!square)
          break;
        if (square === 1) {
          moves.push([x, y]);
        } else {
          const squareIsWhite = square === square.toUpperCase();
          if (squareIsWhite !== isPieceWhite) {
            moves.push([x, y]);
            break;
          } else
            break;
        }
      }
    }
    return moves;
  }
  function castDiagonal() {
    return cast([
      [1, -1],
      [-1, -1],
      [1, 1],
      [-1, 1]
    ], shorterBoardSide);
  }
  function castStraight() {
    return cast([
      [0, -1],
      [0, 1],
      [-1, 0],
      [1, 0]
    ], longerBoardSide);
  }
  if (pieceType === "P") {
    const direction = isPieceWhite ? [[-1, -1], [1, -1], [0, -1], [0, -2]] : [[-1, 1], [1, 1], [0, 1], [0, 2]];
    return coordinatesFromMoves(board, piecePos, direction, isPieceWhite);
  }
  if (pieceType === "N") {
    return coordinatesFromMoves(board, piecePos, [
      [-2, -1],
      [-2, 1],
      [2, -1],
      [2, 1],
      [-1, -2],
      [-1, 2],
      [1, -2],
      [1, 2]
    ], isPieceWhite);
  }
  if (pieceType === "K") {
    return coordinatesFromMoves(board, piecePos, [
      [-1, 0],
      [1, 0],
      [0, -1],
      [0, 1],
      [-1, -1],
      [1, 1],
      [-1, 1],
      [1, -1]
    ], isPieceWhite);
  }
  if (pieceType === "B")
    return castDiagonal();
  if (pieceType === "R")
    return castStraight();
  if (pieceType === "Q")
    return [...castDiagonal(), ...castStraight()];
  return [];
}
function addMovesOnDemandListeners() {
  let lastProcessedSquareFen = null;
  if (!state.BoardDrawer)
    return;
  function handle() {
    if (lastProcessedSquareFen !== modLastEnteredSquare.squareFen || !modLastEnteredSquare.squareFen) {
      const lastIdx = modLastEnteredSquare.squareIndex;
      if (!modLastEnteredSquare.squareFen && lastIdx) {
        const lastPieceFen = modLastEnteredSquare.pieceFen;
        modLastEnteredSquare.squareFen = indexToChessCoordinates(lastIdx);
        modLastEnteredSquare.pieceFen = state.lastBoardMatrix?.[lastIdx?.[1]]?.[lastIdx?.[0]];
        if (lastPieceFen === 1)
          return;
      }
      lastProcessedSquareFen = modLastEnteredSquare.squareFen;
      const pieceFen = modLastEnteredSquare.pieceFen;
      const isPieceWhite = pieceFen >= "A" && pieceFen <= "Z";
      const isPlayerPiece = state.lastBoardOrientation === "w" === isPieceWhite;
      if (!pieceFen)
        return;
      const legalMovesArr = getPiecePaths(state.lastBoardMatrix, modLastEnteredSquare.squareIndex, pieceFen, isPieceWhite)?.map((pathArr) => lastProcessedSquareFen + indexToChessCoordinates(pathArr));
      if (legalMovesArr?.length > 0 && state.commLink)
        state.commLink.commands.calculateSpecificMoves({ moves: legalMovesArr, isOpponent: !isPlayerPiece });
    }
  }
  modListeners.forEach(({ type, handler }) => {
    document.removeEventListener(type, handler);
  });
  modListeners.length = 0;
  modDrawerListeners.forEach((x) => x?.remove());
  modDrawerListeners.length = 0;
  const mouseDownHandler = () => handle(true);
  const touchStartHandler = () => handle(true);
  [
    ["mousedown", mouseDownHandler],
    ["touchstart", touchStartHandler]
  ].forEach(([type, handler]) => {
    document.addEventListener(type, handler);
    modListeners.push({ type, handler });
  });
  for (let y = 0;y < state.lastBoardMatrix.length; y++)
    for (let x = 0;x < state.lastBoardMatrix[y].length; x++) {
      const squareFen = indexToChessCoordinates([x, y]);
      const squareListener = state.BoardDrawer.addSquareListener(squareFen, (type) => {
        if (!state.isMovesOnDemandActive)
          return;
        switch (type) {
          case "enter":
            modLastEnteredSquare.pieceFen = state.lastBoardMatrix[y][x];
            modLastEnteredSquare.squareFen = squareFen;
            modLastEnteredSquare.squareIndex = [x, y];
            break;
        }
      });
      modDrawerListeners.push(squareListener);
    }
}

// src/core/comm.js
class CommLinkHandler {
  constructor(commlinkID, configObj) {
    this.commlinkID = commlinkID;
    this.singlePacketResponseWaitTime = configObj?.singlePacketResponseWaitTime || 1500;
    this.maxSendAttempts = configObj?.maxSendAttempts || 3;
    this.statusCheckInterval = configObj?.statusCheckInterval || 25;
    this.silentMode = configObj?.silentMode || false;
    this.commlinkValueIndicator = "commlink-packet-";
    this.commands = {};
    this.listeners = [];
    this.greasy = typeof GM === "object" ? GM : {};
    const getValueMethod = typeof GM_getValue === "function" ? GM_getValue : this.greasy?.getValue || configObj?.functions?.getValue;
    const setValueMethod = typeof GM_setValue === "function" ? GM_setValue : this.greasy?.setValue || configObj?.functions?.setValue;
    const deleteValueMethod = typeof GM_deleteValue === "function" ? GM_deleteValue : this.greasy?.deleteValue || configObj?.functions?.deleteValue;
    const listValuesMethod = typeof GM_listValues === "function" ? GM_listValues : this.greasy?.listValues || configObj?.functions?.listValues;
    this.storage = {
      getValue: async (key) => await getValueMethod(key),
      setValue: (key, value) => setValueMethod(key, value),
      deleteValue: (key) => deleteValueMethod(key),
      listValues: async () => await listValuesMethod()
    };
    this.removeOldPackets();
    this.receiveInterval = this.setIntervalAsync(async () => {
      await this.receivePackets();
    }, this.statusCheckInterval);
  }
  async removeOldPackets() {
    const packets = await this.getStoredPackets();
    packets.filter((packet) => Date.now() - packet?.date > 20000).forEach((packet) => this.removePacketByID(packet.id));
  }
  setIntervalAsync(callback, interval = this.statusCheckInterval) {
    let running = true;
    (async () => {
      while (running) {
        try {
          await callback();
        } catch (e) {}
        await new Promise((resolve) => setTimeout(resolve, interval));
      }
    })();
    return { stop: () => running = false };
  }
  getCommKey(packetID) {
    return this.commlinkValueIndicator + packetID;
  }
  async getStoredPackets() {
    const keys = await this.storage.listValues();
    const packets = await Promise.all(keys.filter((k) => k.includes(this.commlinkValueIndicator)).map((k) => this.storage.getValue(k)));
    return packets.filter(Boolean);
  }
  addPacket(packet) {
    this.storage.setValue(this.getCommKey(packet.id), packet);
  }
  removePacketByID(packetID) {
    this.storage.deleteValue(this.getCommKey(packetID));
  }
  async findPacketByID(packetID) {
    return await this.storage.getValue(this.getCommKey(packetID));
  }
  editPacket(newPacket) {
    this.storage.setValue(this.getCommKey(newPacket.id), newPacket);
  }
  async send(platform, cmd, d) {
    for (let attempts = 1;attempts <= this.maxSendAttempts; attempts++) {
      const packetID = crypto.randomUUID();
      const attemptStartDate = Date.now();
      const packet = { sender: platform, id: packetID, command: cmd, data: d, date: attemptStartDate };
      if (!this.silentMode)
        console.log(`[CommLink Sender] Sending packet! (#${attempts} attempt):`, packet);
      this.addPacket(packet);
      while (Date.now() - attemptStartDate <= this.singlePacketResponseWaitTime) {
        const poolPacket = await this.findPacketByID(packetID);
        if (poolPacket?.result !== undefined && poolPacket.result !== null) {
          if (!this.silentMode)
            console.log(`[CommLink Sender] Got result for a packet (${packetID}):`, poolPacket.result);
          this.removePacketByID(packetID);
          return poolPacket.result;
        }
        await new Promise((res) => setTimeout(res, this.statusCheckInterval));
      }
      this.removePacketByID(packetID);
    }
    return null;
  }
  registerSendCommand(name, obj) {
    this.commands[name] = async (data) => await this.send(obj?.commlinkID || this.commlinkID, name, obj?.data || data);
  }
  registerListener(sender, commandHandler) {
    const listener = {
      sender,
      commandHandler
    };
    this.listeners.push(listener);
  }
  async receivePackets() {
    const packets = await this.getStoredPackets();
    for (const packet of packets) {
      for (const listener of this.listeners) {
        if (packet.sender === listener.sender && !packet.hasOwnProperty("result")) {
          try {
            const result = await listener.commandHandler(packet);
            packet.result = result;
            this.editPacket(packet);
            if (!this.silentMode) {
              if (packet.result == null)
                console.log("[CommLink Receiver] Possibly failed to handle packet:", packet);
              else
                console.log("[CommLink Receiver] Successfully handled a packet:", packet);
            }
          } catch (error) {
            console.error("[CommLink Receiver] Error handling packet:", error);
          }
        }
      }
    }
  }
  kill() {
    this.receiveInterval?.stop();
  }
}
function setupCommLink() {
  const CommLink = new CommLinkHandler(`frontend_${commLinkInstanceID}`, {
    singlePacketResponseWaitTime: 250,
    maxSendAttempts: 3,
    statusCheckInterval: 1,
    silentMode: true
  });
  CommLink.commands["createInstance"] = async () => {
    return await CommLink.send("mum", "createInstance", {
      domain,
      instanceID: commLinkInstanceID,
      chessVariant: getChessVariant(),
      playerColor: getBoardOrientation()
    });
  };
  CommLink.registerSendCommand("ping", { commlinkID: "mum", data: "ping" });
  CommLink.registerSendCommand("pingInstance", { data: "ping" });
  CommLink.registerSendCommand("log");
  CommLink.registerSendCommand("updateBoardOrientation");
  CommLink.registerSendCommand("updateBoardFen");
  CommLink.registerSendCommand("newMatchStarted");
  CommLink.registerSendCommand("calculateBestMoves");
  CommLink.registerSendCommand("calculateSpecificMoves");
  CommLink.registerSendCommand("forceInstanceRestart");
  CommLink.registerSendCommand("toggleConcealAssistance");
  CommLink.registerListener(`backend_${commLinkInstanceID}`, (packet) => {
    try {
      switch (packet.command) {
        case "ping":
          return `pong (took ${Date.now() - packet.date}ms)`;
        case "getFen":
          return getFen();
        case "removeSiteMoveMarkings":
          boardUtils.removeMarkings();
          return true;
        case "markMoveToSite":
          const profile = packet.data?.[0]?.profile;
          boardUtils.removeMarkings(profile);
          boardUtils.markMoves(packet.data);
          const isAutoMove = getConfigValue(configKeys.autoMove, profile);
          const isAutoMoveAfterUser = getConfigValue(configKeys.autoMoveAfterUser, profile);
          const turn = instanceVars.turn.get(commLinkInstanceID) || getBoardOrientation();
          const isMyTurn = turn === getBoardOrientation();
          if (isAutoMove && isMyTurn && (!isAutoMoveAfterUser || state.matchFirstSuggestionGiven)) {
            const existingAutomoves = state.activeAutomoves.filter((x) => x.move.active);
            for (const x of existingAutomoves) {
              x.move.stop();
            }
            const isLegit = getConfigValue(configKeys.autoMoveLegit, profile);
            const isRandom = getConfigValue(configKeys.autoMoveRandom, profile);
            const move = isRandom ? packet.data[Math.floor(Math.random() * packet.data.length)]?.player : packet.data[0]?.player;
            makeMove(profile, move, isLegit);
          }
          state.matchFirstSuggestionGiven = true;
          return true;
        case "renderMetricsToSite":
          renderMetrics(packet.data);
          return true;
        case "feedbackToSite":
          displayFeedback(packet.data);
          return true;
        case "updateRestartListener":
          createInputListener("instanceRestart", packet.data, () => {
            CommLink.commands.forceInstanceRestart();
          });
          return true;
        case "updateConcealAssistanceListener":
          createInputListener("concealAssistance", packet.data, toggleConcealAssistance);
          return true;
        case "applyAssistanceConcealment":
          applyAssistanceConcealment(packet.data);
          return true;
      }
    } catch (e) {
      return null;
    }
  });
  state.commLink = CommLink;
}

// src/core/index.js
state.getBoardDimensions = getBoardDimensions;
state.getPieceElem = getPieceElem;
state.getBoardOrientation = getBoardOrientation;
checkAndExposeSandbox();
var blacklistedURLs = [
  constructBackendURL(backendConfig?.hosts?.prod),
  constructBackendURL(backendConfig?.hosts?.dev),
  "https://www.chess.com/play",
  "https://lichess.org/",
  "https://chess.org/",
  "https://papergames.io/en/chess",
  "https://playstrategy.org/",
  "https://www.pychess.org/",
  "https://www.coolmathgames.com/0-chess"
];
var boardObserver = null;
var dumbBoardObservingInterval = null;
var lastMutationObservationDate = 0;
var lastTurn = null;
var lastBoardOrientation = null;
var lastMutationObsProcessedTurn = null;
var isCheckingBackendReady = false;
var wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
function displayImportantNotification(title, text) {
  if (typeof GM_notification === "function") {
    GM_notification({ title, text });
  } else {
    alert(`[${title}]` + `

` + text);
  }
}
async function processBoardPosition(currentFullFen = getFen(), squareChangeAmount = 0) {
  state.lastCalculatedFullFen = currentFullFen;
  state.lastMoveRequestTime = Date.now();
  clearVisuals(true);
  boardUtils.setBoardDimensions(getBoardDimensions());
  if (!state.commLink)
    return;
  const didBoardOrientationChange = await checkBoardOrientationChange();
  if (didBoardOrientationChange || squareChangeAmount > 5) {
    resetCachedValues();
    state.matchFirstSuggestionGiven = false;
    state.commLink.commands.newMatchStarted();
  }
  state.commLink.commands.updateBoardFen(currentFullFen);
}
state.processBoardPosition = processBoardPosition;
function getTurnFromFenChange(lastFen, newFen) {
  if (!lastFen || !newFen)
    return null;
  const board1 = lastFen.split(" ", 1)[0].replace(/\d/g, (d) => " ".repeat(Number(d))).split("/").join("");
  const board2 = newFen.split(" ", 1)[0].replace(/\d/g, (d) => " ".repeat(Number(d))).split("/").join("");
  for (let i = 0;i < board2.length; i++) {
    if (board2[i] !== " " && board2[i] !== board1[i]) {
      const piece = board2[i];
      return piece === piece.toUpperCase() ? "b" : "w";
    }
  }
  return null;
}
async function determineBoardPositionValidity(turn) {
  const currentFullFen = getFen();
  const basicFen = currentFullFen?.split(" ", 1)?.[0];
  const fenChanged = basicFen !== state.lastCalculatedFullFen?.split(" ", 1)?.[0];
  const pieceAmountChange = getPieceChangeAmount(state.lastCalculatedFullFen, currentFullFen);
  const squareChangeAmount = getBoardSquareChangeAmount(state.lastCalculatedFullFen, currentFullFen);
  const pieceAmount = getPieceAmount();
  if (pieceAmount === 0) {
    state.lastCalculatedFullFen = null;
    return;
  }
  if (!fenChanged)
    return;
  const wasCalculatedFenNull = state.lastCalculatedFullFen === null;
  const previousCalculatedFen = state.lastCalculatedFullFen;
  state.lastCalculatedFullFen = currentFullFen;
  if (!wasCalculatedFenNull) {
    state.moveRetryCount = 0;
  }
  if (turn) {
    if (basicFen && basicFen.includes("/pppppppp/8/8/8/8/PPPPPPPP/")) {
      turn = "w";
    } else {
      const calculatedTurn = getTurnFromFenChange(previousCalculatedFen, currentFullFen);
      if (calculatedTurn) {
        turn = calculatedTurn;
      } else if (lastTurn === turn) {
        turn = getBoardOrientation();
      }
    }
    lastTurn = turn;
    instanceVars.turn.set(commLinkInstanceID, turn);
  }
  if (pieceAmountChange === -1) {
    if (squareChangeAmount === 1) {
      return;
    }
  }
  processBoardPosition(getFen(), squareChangeAmount);
}
function observeNewMoves() {
  if (boardObserver?.disconnect)
    boardObserver.disconnect();
  if (dumbBoardObservingInterval)
    clearInterval(dumbBoardObservingInterval);
  dumbBoardObservingInterval = setInterval(() => {
    if (state.isUserMouseDown)
      return;
    determineBoardPositionValidity(lastMutationObsProcessedTurn || getBoardOrientation());
  }, 1000);
  boardObserver = new MutationObserver((mutationArr) => {
    if (state.isUserMouseDown)
      return;
    try {
      lastMutationObservationDate = Date.now();
      const mutationMoveArr = isMutationNewMove(mutationArr);
      const isNewMove = mutationMoveArr?.[0];
      let turn = mutationMoveArr?.[1];
      if (turn)
        lastMutationObsProcessedTurn = turn;
      if (!isNewMove)
        return;
      determineBoardPositionValidity(turn);
    } catch (e) {
      if (debugModeActivated)
        console.error(e);
    }
  });
  boardObserver.observe(state.chessBoardElem, { childList: true, subtree: true, attributes: true });
}
async function checkBoardOrientationChange() {
  const boardOrientation = getBoardOrientation();
  const boardOrientationChanged = lastBoardOrientation !== boardOrientation;
  const boardOrientationDiffers = state.BoardDrawer && state.BoardDrawer?.orientation !== boardOrientation;
  if (boardOrientationChanged || boardOrientationDiffers) {
    lastBoardOrientation = boardOrientation;
    instanceVars.playerColor.set(commLinkInstanceID, boardOrientation);
    boardUtils.setBoardOrientation(boardOrientation);
    await state.commLink.commands.updateBoardOrientation(boardOrientation);
  }
  return boardOrientationChanged;
}
async function isOmniChessBackendReady() {
  const res = await state.commLink.commands.ping();
  return res ? true : false;
}
async function refreshSettings() {
  const config2 = GM_getValue(dbValues.OmniChessConfig);
  const profiles = config2?.global?.profiles;
  if (typeof profiles != "object")
    return;
  state.isMovesOnDemandActive = Object.keys(profiles).some((profileName) => profiles[profileName]?.movesOnDemand === true);
}
function isBoardDrawerNeeded() {
  const config2 = GM_getValue(dbValues.OmniChessConfig);
  const gP = config2?.global?.profiles;
  const iP = config2?.instance?.[commLinkInstanceID]?.profiles;
  const isGhost = config2?.global?.[configKeys.isUserscriptGhost];
  if (isGhost)
    return false;
  const check = (cfg) => Object.values(cfg || {}).some((p) => p[configKeys.displayMovesOnExternalSite] || p[configKeys.renderOnExternalSite] || p[configKeys.feedbackOnExternalSite] || p[configKeys.movesOnDemand]);
  if (gP && check(gP))
    return true;
  if (iP && check(iP))
    return true;
  return false;
}
async function start() {
  await state.commLink.commands.createInstance(commLinkInstanceID);
  const pathname = window.location.pathname;
  const boardOrientation = getBoardOrientation();
  instanceVars.playerColor.set(commLinkInstanceID, boardOrientation);
  instanceVars.fen.set(commLinkInstanceID, getFen());
  if (isBoardDrawerNeeded()) {
    if (state.BoardDrawer)
      state.BoardDrawer?.terminate();
    setBoardDrawer(new UniversalBoardDrawer(state.chessBoardElem, {
      window,
      boardDimensions: getBoardDimensions(),
      playerColor: getBoardOrientation(),
      zIndex: Math.floor(Math.random() * (99 - 10 + 1)) + 10,
      prepend: true,
      debugMode: debugModeActivated,
      adjustSizeByDimensions: domain === "chess.com" && pathname?.includes("/variants"),
      adjustSizeConfig: {
        noLeftAdjustment: true
      }
    }));
    let boardMatrixAttempts = 0;
    const waitForBoardMatrix = setInterval(() => {
      boardMatrixAttempts++;
      if (state.lastBoardMatrix || boardMatrixAttempts > 100) {
        clearInterval(waitForBoardMatrix);
        if (state.lastBoardMatrix) {
          addMovesOnDemandListeners();
        }
      }
    }, 50);
  }
  await checkBoardOrientationChange();
  refreshSettings();
  observeNewMoves();
  state.commLink.setIntervalAsync(async () => {
    await state.commLink.commands.createInstance(commLinkInstanceID);
  }, 1000);
  createInputListener("concealAssistance", await getGmConfigValue("concealAssistanceTriggerCode"), toggleConcealAssistance);
  createInputListener("instanceRestart", await getGmConfigValue("instanceRestartTriggerCode"), () => {
    state.commLink.commands.forceInstanceRestart();
  });
}
function startWhenBackendReady() {
  if (isCheckingBackendReady)
    return;
  isCheckingBackendReady = true;
  let timesUrlForceOpened = 0;
  let i = 0;
  const interval = state.commLink.setIntervalAsync(async () => {
    i++;
    if (await isOmniChessBackendReady()) {
      start();
      isCheckingBackendReady = false;
      interval.stop();
    } else if (timesUrlForceOpened === 0 && i % 10 === 0) {
      timesUrlForceOpened++;
      const config2 = GM_getValue(dbValues.OmniChessConfig);
      const isGhost = config2?.global?.[configKeys.isUserscriptGhost];
      const lastForceOpen = GM_getValue("lastForceOpenTime") || 0;
      const now = Date.now();
      if (!isGhost && now - lastForceOpen > 1e4) {
        GM_setValue("lastForceOpenTime", now);
        const url = getCurrentBackendURL();
        const finalUrl = url.endsWith("/") ? url + "app/" : url + "/app/";
        GM_openInTab(finalUrl, true);
      }
    }
  }, 100);
}
function initializeIfSiteReady() {
  const boardElem = getBoardElem();
  const firstPieceElem = getPieceElem();
  const bothElemsExist = boardElem && firstPieceElem;
  const isChessComImageBoard = domain === "chess.com" && boardElem?.className.includes("webgl-2d");
  const boardElemChanged = state.chessBoardElem != boardElem;
  if ((bothElemsExist || isChessComImageBoard) && boardElemChanged) {
    setChessBoardElem(boardElem);
    state.chessBoardElem.addEventListener("pointerdown", (e) => {
      if (e.isTrusted)
        state.isUserMouseDown = true;
    });
    state.chessBoardElem.addEventListener("mousedown", (e) => {
      if (e.isTrusted)
        state.isUserMouseDown = true;
    });
    state.chessBoardElem.addEventListener("touchstart", (e) => {
      if (e.isTrusted)
        state.isUserMouseDown = true;
    });
    if (!blacklistedURLs.includes(window.location.href)) {
      startWhenBackendReady();
    }
  }
}
if (!(runningOnBackend && !isDevPage)) {
  let runInitializeLoop = function() {
    initializeIfSiteReady();
    const boardAttached = state.chessBoardElem && document.body.contains(state.chessBoardElem);
    const desiredInterval = boardAttached ? 1000 : 100;
    if (desiredInterval !== checkIntervalTime) {
      checkIntervalTime = desiredInterval;
      clearInterval(initInterval);
      initInterval = setInterval(runInitializeLoop, checkIntervalTime);
    }
  };
  window.addEventListener("pointerup", () => {
    state.isUserMouseDown = false;
  });
  window.addEventListener("mouseup", () => {
    state.isUserMouseDown = false;
  });
  window.addEventListener("touchend", () => {
    state.isUserMouseDown = false;
  });
  setupCommLink();
  if (typeof GM_registerMenuCommand === "function") {
    GM_registerMenuCommand("[o] Open GUI Manually", (e) => {
      const url = getCurrentBackendURL();
      const finalUrl = url.endsWith("/") ? url + "app/" : url + "/app/";
      GM_openInTab(finalUrl, true);
    }, "o");
    GM_registerMenuCommand("[s] Start Manually", (e) => {
      if (state.chessBoardElem) {
        start();
      } else {
        displayImportantNotification("Failed to start manually", "No chessboard element found!");
      }
    }, "s");
    GM_registerMenuCommand("[g] Get Moves Manually", (e) => {
      if (state.chessBoardElem) {
        processBoardPosition();
      } else {
        displayImportantNotification("Failed to get moves", "No chessboard element found!");
      }
    }, "g");
    GM_registerMenuCommand("[r] Render BoardDrawer Manually", (e) => {
      if (typeof state.BoardDrawer?.updateDimensions === "function") {
        state.BoardDrawer.updateDimensions();
      } else {
        displayImportantNotification("Failed to render BoardDrawer", "BoardDrawer not initialized or something else went wrong!");
      }
    }, "r");
    if (typeof GM_setClipboard === "function") {
      GM_registerMenuCommand("[c] Copy FEN to Clipboard", (e) => {
        if (state.chessBoardElem) {
          GM_setClipboard(getFen());
        } else {
          displayImportantNotification("Failed to get FEN", "No chessboard element found!");
        }
      }, "c");
    }
  }
  let checkIntervalTime = 100;
  let initInterval = null;
  initInterval = setInterval(runInitializeLoop, checkIntervalTime);
  setInterval(refreshSettings, 2500);
}

// src/adapters/chesscom.js
var chesscomVariantPlayerColorsTable = null;
function resetChesscomCachedValues() {
  chesscomVariantPlayerColorsTable = null;
}
setResetCachedValues(resetChesscomCachedValues);
function updateChesscomVariantPlayerColorsTable() {
  let colors = [];
  document.querySelectorAll("*[data-color]").forEach((pieceElem) => {
    const colorCode = Number(pieceElem?.dataset?.color);
    if (!colors?.includes(colorCode)) {
      colors.push(colorCode);
    }
  });
  if (colors?.length > 1) {
    colors = colors.sort((a, b) => a - b);
    chesscomVariantPlayerColorsTable = { [colors[0]]: "w", [colors[1]]: "b" };
  }
}
addSupportedChessSite("chess.com", {
  boardElem: (obj) => {
    const pathname = obj.pathname;
    if (pathname?.includes("/variants")) {
      return document.querySelector(".TheBoard-layers");
    }
    return document.querySelector("#board-layout-chessboard > .board");
  },
  pieceElem: (obj) => {
    const pathname = obj.pathname;
    const getAll = obj.getAll;
    if (pathname?.includes("/variants")) {
      const filteredPieceElems = filterInvisibleElems(document.querySelectorAll(".TheBoard-layers *[data-piece]")).filter((elem) => {
        if (elem?.dataset?.piece?.toLowerCase() === "x")
          return false;
        return !elem.closest('[class*="captured-pieces"]');
      });
      return getAll ? filteredPieceElems : filteredPieceElems[0];
    }
    return obj.boardQuerySelector(".piece");
  },
  squareElems: (obj) => {
    const pathname = obj.pathname;
    const element = obj.element;
    if (pathname?.includes("/variants")) {
      return [...element.querySelectorAll(".square")];
    }
  },
  chessVariant: (obj) => {
    const pathname = obj.pathname;
    if (pathname?.includes("/variants")) {
      const variant = pathname.match(/variants\/([^\/]*)/)?.[1].replaceAll("-chess", "").replaceAll("-", "");
      const replacementTable = {
        "doubles-bughouse": "bughouse",
        "paradigm-chess30": "paradigm"
      };
      return replacementTable[variant] || variant;
    }
  },
  boardOrientation: (obj) => {
    const pathname = obj.pathname;
    if (pathname?.includes("/variants")) {
      const playerNumberStr = document.querySelector(".playerbox-bottom [data-player]")?.dataset?.player;
      if (!playerNumberStr)
        return "w";
      return playerNumberStr === "0" ? "w" : "b";
    }
    const boardElem = getBoardElem();
    return boardElem?.classList.contains("flipped") ? "b" : "w";
  },
  pieceElemFen: (obj) => {
    const pathname = obj.pathname;
    const pieceElem = obj.pieceElem;
    let pieceColor = null;
    let pieceName = null;
    if (pathname?.includes("/variants")) {
      if (!chesscomVariantPlayerColorsTable) {
        updateChesscomVariantPlayerColorsTable();
      }
      pieceColor = chesscomVariantPlayerColorsTable?.[pieceElem?.dataset?.color];
      pieceName = pieceElem?.dataset?.piece;
      if (pieceName?.length > 1) {
        pieceName = pieceName[0];
      }
    } else {
      const pieceStr = [...pieceElem.classList].find((x) => x.match(/^(b|w)[prnbqk]{1}$/));
      if (!pieceStr)
        return null;
      [pieceColor, pieceName] = pieceStr.split("");
    }
    return pieceColor == "w" ? pieceName.toUpperCase() : pieceName.toLowerCase();
  },
  pieceElemCoords: (obj) => {
    const pathname = obj.pathname;
    const pieceElem = obj.pieceElem;
    if (pathname?.includes("/variants")) {
      const coords = getElemCoordinatesFromTransform(pieceElem);
      return coords;
    }
    return pieceElem.classList.toString()?.match(/square-(\d)(\d)/)?.slice(1)?.map((x) => Number(x) - 1);
  },
  boardDimensions: (obj) => {
    const pathname = obj.pathname;
    if (pathname?.includes("/variants")) {
      const squaresContainerElem = document.querySelector(".TheBoard-squares");
      let ranks = 0;
      let files = 0;
      [...squaresContainerElem.childNodes].forEach((x, i) => {
        const visibleChildElems = filterInvisibleElems([...x.childNodes]);
        if (visibleChildElems?.length > 0) {
          ranks = ranks + 1;
          if (visibleChildElems.length > files) {
            files = visibleChildElems.length;
          }
        }
      });
      return [ranks, files];
    } else {
      return [8, 8];
    }
  },
  getMutationTurn: (obj) => {
    const mutationArr = obj.mutationArr;
    let blacks = 0;
    let whites = 0;
    mutationArr.forEach((mutation) => {
      const classList = mutation.target?.classList;
      if (!classList)
        return;
      for (let i = 0;i < classList.length; i++) {
        const cls = classList[i];
        if (cls.match(/^(b|w)[prnbqk]{1}$/)) {
          if (cls[0] === "b")
            blacks = blacks + 1;
          if (cls[0] === "w")
            whites = whites + 1;
        }
      }
    });
    const turn = blacks > whites ? "w" : "b";
    return turn || null;
  },
  isMutationNewMove: (obj) => {
    const pathname = obj.pathname;
    const mutationArr = obj.mutationArr;
    if (pathname?.includes("/variants")) {
      if (state.isUserMouseDown)
        return [false, null];
      return [true, getBoardOrientation()];
    }
    if (mutationArr.length === 1)
      return [false, null];
    const isPremove = mutationArr.filter((m) => m?.target?.classList?.contains("highlight")).map((x) => x?.target?.style?.["background-color"]).find((x) => x === "rgb(244, 42, 50)") ? true : false;
    const isNewMove = mutationArr.length >= 3 && !isPremove;
    if (isNewMove)
      return [isNewMove, getMutationTurn(mutationArr)];
    return [isNewMove, null];
  }
});

// src/adapters/lichess.js
addSupportedChessSite("lichess.org", {
  boardElem: (obj) => {
    return document.querySelector("cg-board");
  },
  pieceElem: (obj) => {
    return obj.boardQuerySelector("piece:not(.ghost)");
  },
  chessVariant: (obj) => {
    const variantLinkElem = document.querySelector(".variant-link");
    if (variantLinkElem) {
      let variant = variantLinkElem?.innerText?.toLowerCase()?.replaceAll(" ", "-");
      const replacementTable = {
        correspondence: "chess",
        koth: "kingofthehill",
        "three-check": "3check"
      };
      return replacementTable[variant] || variant;
    }
  },
  boardOrientation: (obj) => {
    const filesElem = document.querySelector("coords.files");
    return filesElem?.classList?.contains("black") ? "b" : "w";
  },
  pieceElemFen: (obj) => {
    const pieceElem = obj.pieceElem;
    const pieceColor = pieceElem?.classList?.contains("white") ? "w" : "b";
    const elemPieceName = [...pieceElem?.classList]?.find((className) => Object.keys(pieceNameToFen).includes(className));
    if (pieceColor && elemPieceName) {
      const pieceName = pieceNameToFen[elemPieceName];
      return pieceColor == "w" ? pieceName.toUpperCase() : pieceName.toLowerCase();
    }
  },
  pieceElemCoords: (obj) => {
    const pieceElem = obj.pieceElem;
    const key = pieceElem?.cgKey;
    if (key) {
      return chessCoordinatesToIndex(key);
    }
  },
  boardDimensions: (obj) => {
    return [8, 8];
  },
  getMutationTurn: (obj) => {
    const mutationArr = obj.mutationArr;
    let blacks = 0;
    let whites = 0;
    mutationArr.forEach((mutation) => {
      const classList = mutation.target?.classList;
      if (classList?.contains("black"))
        blacks += 1;
      if (classList?.contains("white"))
        whites += 1;
    });
    const turn = blacks > whites ? "w" : "b";
    return turn || null;
  },
  isMutationNewMove: (obj) => {
    const mutationArr = obj.mutationArr;
    const isNewMove = mutationArr.length >= 3;
    if (isNewMove)
      return [isNewMove, getMutationTurn(mutationArr)];
    return [isNewMove, null];
  }
});

// src/adapters/playstrategy.js
addSupportedChessSite("playstrategy.org", {
  boardElem: (obj) => {
    return document.querySelector("cg-board");
  },
  pieceElem: (obj) => {
    return obj.boardQuerySelector('piece[class*="-piece"]:not(.ghost)');
  },
  chessVariant: (obj) => {
    const variantLinkElem = document.querySelector(".variant-link");
    if (variantLinkElem) {
      let variant = variantLinkElem?.innerText?.toLowerCase()?.replaceAll(" ", "-");
      const replacementTable = {
        correspondence: "chess",
        koth: "kingofthehill",
        "three-check": "3check",
        "five-check": "5check",
        "no-castling": "nocastle"
      };
      return replacementTable[variant] || variant;
    }
  },
  boardOrientation: (obj) => {
    const cgWrapElem = document.querySelector(".cg-wrap");
    return cgWrapElem?.classList?.contains("orientation-p1") ? "w" : "b";
  },
  pieceElemFen: (obj) => {
    const pieceElem = obj.pieceElem;
    const playerColor = getBoardOrientation();
    const pieceColor = pieceElem?.classList?.contains("ally") ? playerColor : playerColor == "w" ? "b" : "w";
    let pieceName = null;
    [...pieceElem?.classList]?.forEach((className) => {
      if (className?.includes("-piece")) {
        const elemPieceName = className?.split("-piece")?.[0];
        if (elemPieceName && elemPieceName?.length === 1) {
          pieceName = elemPieceName;
        }
      }
    });
    if (pieceColor && pieceName) {
      return pieceColor == "w" ? pieceName.toUpperCase() : pieceName.toLowerCase();
    }
  },
  pieceElemCoords: (obj) => {
    const pieceElem = obj.pieceElem;
    const key = pieceElem?.cgKey;
    if (key) {
      return chessCoordinatesToIndex(key);
    }
  },
  boardDimensions: (obj) => {
    return getBoardDimensionsFromSize();
  },
  getMutationTurn: (obj) => {
    const mutationArr = obj.mutationArr;
    let ally = 0;
    let enemy = 0;
    const boardOrientation = getBoardOrientation();
    const isPlayerWhite = boardOrientation === "w";
    mutationArr.forEach((mutation) => {
      const classList = mutation.target?.classList;
      if (classList?.contains("ally"))
        ally += 1;
      if (classList?.contains("enemy"))
        enemy += 1;
    });
    const turn = isPlayerWhite ? ally > enemy ? "b" : "w" : ally > enemy ? "w" : "b";
    return turn || null;
  },
  isMutationNewMove: (obj) => {
    const mutationArr = obj.mutationArr;
    const isNewMove = mutationArr.length >= 4 || mutationArr.some((m) => m.type === "childList") || mutationArr.some((m) => m?.target?.classList?.contains("last-move"));
    if (isNewMove)
      return [isNewMove, getMutationTurn(mutationArr)];
    return [isNewMove, null];
  }
});

// src/adapters/pychess.js
addSupportedChessSite("pychess.org", {
  boardElem: (obj) => {
    return document.querySelector("cg-board");
  },
  pieceElem: (obj) => {
    return obj.boardQuerySelector('piece[class*="-piece"]:not(.ghost)');
  },
  chessVariant: (obj) => {
    const variantLinkElem = document.querySelector("#main-wrap .tc .user-link");
    if (variantLinkElem) {
      let variant = variantLinkElem?.innerText?.toLowerCase()?.replaceAll(" ", "")?.replaceAll("-", "");
      const replacementTable = {
        correspondence: "chess",
        koth: "kingofthehill",
        nocastling: "nocastle",
        "gorogoro+": "gorogoro",
        oukchaktrang: "cambodian"
      };
      return replacementTable[variant] || variant;
    }
  },
  boardOrientation: (obj) => {
    const cgWrapElem = document.querySelector(".cg-wrap");
    return cgWrapElem?.classList?.contains("orientation-black") ? "b" : "w";
  },
  pieceElemFen: (obj) => {
    const pieceElem = obj.pieceElem;
    const playerColor = getBoardOrientation();
    const pieceColor = pieceElem?.classList?.contains("ally") ? playerColor : playerColor == "w" ? "b" : "w";
    let pieceName = null;
    [...pieceElem?.classList]?.forEach((className) => {
      if (className?.includes("-piece")) {
        const elemPieceName = className?.split("-piece")?.[0];
        if (elemPieceName && elemPieceName?.length === 1) {
          pieceName = elemPieceName;
        }
      }
    });
    if (pieceColor && pieceName) {
      return pieceColor == "w" ? pieceName.toUpperCase() : pieceName.toLowerCase();
    }
  },
  pieceElemCoords: (obj) => {
    const pieceElem = obj.pieceElem;
    const key = pieceElem?.cgKey;
    if (key) {
      return chessCoordinatesToIndex(key);
    }
    return getElemCoordinatesFromTransform(pieceElem);
  },
  boardDimensions: (obj) => {
    return getBoardDimensionsFromSize();
  },
  getMutationTurn: (obj) => {
    const mutationArr = obj.mutationArr;
    let ally = 0;
    let enemy = 0;
    const boardOrientation = getBoardOrientation();
    const isPlayerWhite = boardOrientation === "w";
    mutationArr.forEach((mutation) => {
      const classList = mutation.target?.classList;
      if (classList?.contains("ally"))
        ally += 1;
      if (classList?.contains("enemy"))
        enemy += 1;
    });
    const turn = isPlayerWhite ? ally > enemy ? "b" : "w" : ally > enemy ? "w" : "b";
    return turn || null;
  },
  isMutationNewMove: (obj) => {
    const mutationArr = obj.mutationArr;
    const isNewMove = mutationArr.length >= 4 || mutationArr.some((m) => m.type === "childList") || mutationArr.some((m) => m?.target?.classList?.contains("last-move"));
    if (isNewMove)
      return [isNewMove, getMutationTurn(mutationArr)];
    return [isNewMove, null];
  }
});

// src/adapters/worldchess.js
addSupportedChessSite("worldchess.com", {
  boardElem: (obj) => {
    return document.querySelector('*[data-component="GameBoard"] cg-board');
  },
  pieceElem: (obj) => {
    return obj.boardQuerySelector('cg-piece:not(*[style*="visibility: hidden;"])');
  },
  chessVariant: (obj) => {
    return "chess";
  },
  boardOrientation: (obj) => {
    const titlesElem = document.querySelector("cg-titles");
    return titlesElem?.classList?.contains("rotated") ? "b" : "w";
  },
  pieceElemFen: (obj) => {
    const pieceElem = obj.pieceElem;
    const pieceColor = pieceElem?.className?.[0];
    const elemPieceName = pieceElem?.className?.[1];
    if (pieceColor && elemPieceName) {
      const pieceName = elemPieceName;
      return pieceColor === "w" ? pieceName.toUpperCase() : pieceName.toLowerCase();
    }
  },
  pieceElemCoords: (obj) => {
    const pieceElem = obj.pieceElem;
    return getElemCoordinatesFromTransform(pieceElem, { onlyFlipY: true });
  },
  boardDimensions: (obj) => {
    return [8, 8];
  },
  getMutationTurn: (obj) => {
    const mutationArr = obj.mutationArr;
    let blacks = 0;
    let whites = 0;
    mutationArr.forEach((mutation) => {
      const classList = mutation?.target?.classList;
      if (!classList)
        return;
      for (let i = 0;i < classList.length; i++) {
        const cls = classList[i];
        if (cls.length === 2) {
          const prefix = cls[0];
          if (prefix === "b")
            blacks++;
          else if (prefix === "w")
            whites++;
        }
      }
    });
    const turn = blacks > whites ? "w" : "b";
    return turn || null;
  },
  isMutationNewMove: (obj) => {
    const mutationArr = obj.mutationArr;
    if (state.isUserMouseDown) {
      return [false, null];
    }
    const isNewMove = mutationArr.find((m) => m?.attributeName === "style") ? true : false;
    if (isNewMove)
      return [isNewMove, getMutationTurn(mutationArr)];
    return [isNewMove, null];
  }
});

// src/adapters/gameknot.js
addSupportedChessSite("gameknot.com", {
  boardElem: (obj) => {
    return document.querySelector("#chess-board-acboard");
  },
  pieceElem: (obj) => {
    return obj.boardQuerySelector('*[class*="chess-board-piece"] > img[src*="chess56."][style*="visible"]');
  },
  chessVariant: (obj) => {
    return "chess";
  },
  boardOrientation: (obj) => {
    return document.querySelector("#chess-board-my-side-color .player_white") ? "w" : "b";
  },
  pieceElemFen: (obj) => {
    const pieceElem = obj.pieceElem;
    const left = Number(pieceElem.style.left.replace("px", ""));
    const top = Number(pieceElem.style.top.replace("px", ""));
    const pieceColor = left >= 0 ? "w" : "b";
    const pieceName = "kqrnbp"[top * -1 / 60];
    return pieceColor === "w" ? pieceName.toUpperCase() : pieceName.toLowerCase();
  },
  pieceElemCoords: (obj) => {
    const pieceElem = obj.pieceElem;
    return getElemCoordinatesFromLeftTopPixels(pieceElem.parentElement);
  },
  boardDimensions: (obj) => {
    return [8, 8];
  },
  getMutationTurn: (obj) => {
    const mutationArr = obj.mutationArr;
    return getBoardOrientation() || null;
  },
  isMutationNewMove: (obj) => {
    const mutationArr = obj.mutationArr;
    const isNewMove = mutationArr.some((m) => m.type === "childList") || mutationArr.some((m) => m?.target?.classList?.contains("last-move"));
    if (isNewMove)
      return [isNewMove, getMutationTurn(mutationArr)];
    return [isNewMove, null];
  }
});

// src/adapters/others.js
addSupportedChessSite("chess.org", {
  boardElem: (obj) => {
    return document.querySelector(".cg-board");
  },
  pieceElem: (obj) => {
    return obj.boardQuerySelector("piece:not(.ghost)");
  },
  chessVariant: (obj) => {
    return "chess";
  },
  boardOrientation: (obj) => {
    const filesElem = document.querySelector("coords.files");
    return filesElem?.classList?.contains("black") ? "b" : "w";
  },
  pieceElemFen: (obj) => {
    const pieceElem = obj.pieceElem;
    const pieceColor = pieceElem?.classList?.contains("white") ? "w" : "b";
    const elemPieceName = [...pieceElem?.classList]?.find((className) => Object.keys(pieceNameToFen).includes(className));
    if (pieceColor && elemPieceName) {
      const pieceName = pieceNameToFen[elemPieceName];
      return pieceColor == "w" ? pieceName.toUpperCase() : pieceName.toLowerCase();
    }
  },
  pieceElemCoords: (obj) => {
    const pieceElem = obj.pieceElem;
    return getElemCoordinatesFromTransform(pieceElem);
  },
  boardDimensions: (obj) => {
    return [8, 8];
  },
  getMutationTurn: (obj) => {
    const mutationArr = obj.mutationArr;
    let blacks = 0;
    let whites = 0;
    mutationArr.forEach((mutation) => {
      const classList = mutation.target?.classList;
      if (classList?.contains("black"))
        blacks += 1;
      if (classList?.contains("white"))
        whites += 1;
    });
    const turn = blacks > whites ? "w" : "b";
    return turn || null;
  },
  isMutationNewMove: (obj) => {
    const mutationArr = obj.mutationArr;
    if (state.isUserMouseDown) {
      return [false, null];
    }
    return [true, getMutationTurn(mutationArr)];
  }
});
addSupportedChessSite(["chess.coolmathgames.com", "coolmathgames.com"], {
  boardElem: (obj) => {
    return document.querySelector("cg-board");
  },
  pieceElem: (obj) => {
    return obj.boardQuerySelector("piece:not(.ghost)");
  },
  chessVariant: (obj) => {
    return "chess";
  },
  boardOrientation: (obj) => {
    return document.querySelector(".ranks.black") ? "b" : "w";
  },
  pieceElemFen: (obj) => {
    const pieceElem = obj.pieceElem;
    const pieceColor = pieceElem?.classList?.contains("white") ? "w" : "b";
    const elemPieceName = [...pieceElem?.classList]?.find((className) => Object.keys(pieceNameToFen).includes(className));
    if (pieceColor && elemPieceName) {
      const pieceName = pieceNameToFen[elemPieceName];
      return pieceColor == "w" ? pieceName.toUpperCase() : pieceName.toLowerCase();
    }
  },
  pieceElemCoords: (obj) => {
    const pieceElem = obj.pieceElem;
    const key = pieceElem?.cgKey;
    if (key) {
      return chessCoordinatesToIndex(key);
    }
  },
  boardDimensions: (obj) => {
    return [8, 8];
  },
  getMutationTurn: (obj) => {
    const mutationArr = obj.mutationArr;
    let blacks = 0;
    let whites = 0;
    mutationArr.forEach((mutation) => {
      const classList = mutation.target?.classList;
      if (classList?.contains("black"))
        blacks += 1;
      if (classList?.contains("white"))
        whites += 1;
    });
    const turn = blacks > whites ? "w" : "b";
    return turn || null;
  },
  isMutationNewMove: (obj) => {
    const mutationArr = obj.mutationArr;
    if (state.isUserMouseDown) {
      return [false, null];
    }
    return [true, getMutationTurn(mutationArr)];
  }
});
addSupportedChessSite("papergames.io", {
  boardElem: (obj) => {
    return document.querySelector(".cm-chessboard");
  },
  pieceElem: (obj) => {
    return obj.boardQuerySelector("*[data-piece][data-square]");
  },
  chessVariant: (obj) => {
    return "chess";
  },
  boardOrientation: (obj) => {
    const boardElem = document.querySelector(".cm-chessboard");
    if (boardElem) {
      const coords = boardElem.querySelector(".coordinates");
      const firstRankText = coords ? [...coords.childNodes]?.[0]?.textContent : null;
      return firstRankText == "h" ? "b" : "w";
    }
  },
  pieceElemFen: (obj) => {
    const pieceElem = obj.pieceElem;
    return convertPieceStrToFen(pieceElem?.dataset?.piece);
  },
  pieceElemCoords: (obj) => {
    const pieceElem = obj.pieceElem;
    const key = pieceElem?.dataset?.square;
    if (key) {
      return chessCoordinatesToIndex(key);
    }
  },
  boardDimensions: (obj) => {
    return [8, 8];
  },
  getMutationTurn: (obj) => {
    const mutationArr = obj.mutationArr;
    const playerColor = getBoardOrientation();
    return playerColor || null;
  },
  isMutationNewMove: (obj) => {
    const mutationArr = obj.mutationArr;
    const isNewMove = mutationArr.length >= 12;
    if (isNewMove)
      return [isNewMove, getMutationTurn(mutationArr)];
    return [isNewMove, null];
  }
});
addSupportedChessSite("immortal.game", {
  boardElem: (obj) => {
    return document.querySelector("div.pawn.relative, div.knight.relative, div.bishop.relative, div.rook.relative, div.queen.relative, div.king.relative")?.parentElement?.parentElement;
  },
  pieceElem: (obj) => {
    return obj.boardQuerySelector("div.pawn.relative, div.knight.relative, div.bishop.relative, div.rook.relative, div.queen.relative, div.king.relative");
  },
  chessVariant: (obj) => {
    return "chess";
  },
  boardOrientation: (obj) => {
    const coordA = [...document.querySelectorAll("svg text[x]")].find((elem) => elem?.textContent == "a");
    const coordAX = Number(coordA?.getAttribute("x")) || 10;
    return coordAX < 15 ? "w" : "b";
  },
  pieceElemFen: (obj) => {
    const pieceElem = obj.pieceElem;
    const pieceColor = pieceElem?.classList?.contains("white") ? "w" : "b";
    const elemPieceName = [...pieceElem?.classList]?.find((className) => Object.keys(pieceNameToFen).includes(className));
    if (pieceColor && elemPieceName) {
      const pieceName = pieceNameToFen[elemPieceName];
      return pieceColor === "w" ? pieceName.toUpperCase() : pieceName.toLowerCase();
    }
  },
  pieceElemCoords: (obj) => {
    const pieceElem = obj.pieceElem;
    return getElemCoordinatesFromTransform(pieceElem?.parentElement);
  },
  boardDimensions: (obj) => {
    return [8, 8];
  },
  getMutationTurn: (obj) => {
    const mutationArr = obj.mutationArr;
    const playerColor = getBoardOrientation();
    return playerColor || null;
  },
  isMutationNewMove: (obj) => {
    const mutationArr = obj.mutationArr;
    if (state.isUserMouseDown) {
      return [false, null];
    }
    const isNewMove = mutationArr.length >= 5;
    if (isNewMove)
      return [isNewMove, getMutationTurn(mutationArr)];
    return [isNewMove, null];
  }
});
addSupportedChessSite("chess.net", {
  boardElem: (obj) => {
    return document.querySelector("cg-board");
  },
  pieceElem: (obj) => {
    return obj.boardQuerySelector("piece:not(.ghost)");
  },
  chessVariant: (obj) => {
    const variantLinkElem = document.querySelector(".variant-link");
    if (variantLinkElem) {
      let variant = variantLinkElem?.innerText?.toLowerCase()?.replaceAll(" ", "-");
      const replacementTable = {
        correspondence: "chess",
        koth: "kingofthehill",
        "three-check": "3check"
      };
      return replacementTable[variant] || variant;
    }
  },
  boardOrientation: (obj) => {
    const filesElem = document.querySelector("coords.files");
    return filesElem?.classList?.contains("black") ? "b" : "w";
  },
  pieceElemFen: (obj) => {
    const pieceElem = obj.pieceElem;
    const pieceColor = pieceElem?.classList?.contains("white") ? "w" : "b";
    const elemPieceName = [...pieceElem?.classList]?.find((className) => Object.keys(pieceNameToFen).includes(className));
    if (pieceColor && elemPieceName) {
      const pieceName = pieceNameToFen[elemPieceName];
      return pieceColor == "w" ? pieceName.toUpperCase() : pieceName.toLowerCase();
    }
  },
  pieceElemCoords: (obj) => {
    const pieceElem = obj.pieceElem;
    const key = pieceElem?.cgKey;
    if (key) {
      return chessCoordinatesToIndex(key);
    }
  },
  boardDimensions: (obj) => {
    return [8, 8];
  },
  getMutationTurn: (obj) => {
    const mutationArr = obj.mutationArr;
    let blacks = 0;
    let whites = 0;
    mutationArr.forEach((mutation) => {
      const classList = mutation.target?.classList;
      if (classList?.contains("black"))
        blacks += 1;
      if (classList?.contains("white"))
        whites += 1;
    });
    const turn = blacks > whites ? "w" : "b";
    return turn || null;
  },
  isMutationNewMove: (obj) => {
    const mutationArr = obj.mutationArr;
    const isNewMove = mutationArr.length >= 3;
    if (isNewMove)
      return [isNewMove, getMutationTurn(mutationArr)];
    return [isNewMove, null];
  }
});
addSupportedChessSite("freechess.club", {
  boardElem: (obj) => {
    return document.querySelector("cg-board");
  },
  pieceElem: (obj) => {
    return obj.boardQuerySelector("piece:not(.ghost)");
  },
  chessVariant: (obj) => {
    return "chess";
  },
  boardOrientation: (obj) => {
    const filesElem = document.querySelector("coords.files");
    return filesElem?.classList?.contains("black") ? "b" : "w";
  },
  pieceElemFen: (obj) => {
    const pieceElem = obj.pieceElem;
    const pieceColor = pieceElem?.classList?.contains("white") ? "w" : "b";
    const elemPieceName = [...pieceElem?.classList]?.find((className) => Object.keys(pieceNameToFen).includes(className));
    if (pieceColor && elemPieceName) {
      const pieceName = pieceNameToFen[elemPieceName];
      return pieceColor == "w" ? pieceName.toUpperCase() : pieceName.toLowerCase();
    }
  },
  pieceElemCoords: (obj) => {
    const pieceElem = obj.pieceElem;
    const key = pieceElem?.cgKey;
    if (key) {
      return chessCoordinatesToIndex(key);
    }
  },
  boardDimensions: (obj) => {
    return [8, 8];
  },
  getMutationTurn: (obj) => {
    const mutationArr = obj.mutationArr;
    let blacks = 0;
    let whites = 0;
    mutationArr.forEach((mutation) => {
      const classList = mutation.target?.classList;
      if (classList?.contains("black"))
        blacks += 1;
      if (classList?.contains("white"))
        whites += 1;
    });
    const turn = blacks > whites ? "w" : "b";
    return turn || null;
  },
  isMutationNewMove: (obj) => {
    const mutationArr = obj.mutationArr;
    const isNewMove = mutationArr.length >= 3;
    if (isNewMove)
      return [isNewMove, getMutationTurn(mutationArr)];
    return [isNewMove, null];
  }
});
addSupportedChessSite("play.chessclub.com", {
  boardElem: (obj) => {
    return document.querySelector("[data-boardid]");
  },
  pieceElem: (obj) => {
    return obj.boardQuerySelector("[data-piece]");
  },
  chessVariant: (obj) => {
    return "chess";
  },
  boardOrientation: (obj) => {
    return document.querySelector("[data-square]")?.dataset?.square === "a8" ? "w" : "b";
  },
  pieceElemFen: (obj) => {
    const pieceElem = obj.pieceElem;
    const [pieceColor, pieceName] = pieceElem?.dataset?.piece || "wp";
    if (pieceColor && pieceName) {
      return pieceColor === "w" ? pieceName.toUpperCase() : pieceName.toLowerCase();
    }
  },
  pieceElemCoords: (obj) => {
    const pieceElem = obj.pieceElem;
    const parentParent = pieceElem?.parentElement?.parentElement;
    if (parentParent) {
      return chessCoordinatesToIndex(parentParent?.dataset?.square);
    }
  },
  boardDimensions: (obj) => {
    return [8, 8];
  },
  getMutationTurn: (obj) => {
    const mutationArr = obj.mutationArr;
    return getBoardOrientation() || null;
  },
  isMutationNewMove: (obj) => {
    const mutationArr = obj.mutationArr;
    const isNewMove = mutationArr.find((mutation) => mutation?.type === "childList") ? true : false;
    if (isNewMove)
      return [isNewMove, getMutationTurn(mutationArr)];
    return [isNewMove, null];
  }
});
addSupportedChessSite("app.edchess.io", {
  boardElem: (obj) => {
    return document.querySelector('*[data-boardid="chessboard"]');
  },
  pieceElem: (obj) => {
    return obj.boardQuerySelector("*[data-piece]");
  },
  chessVariant: (obj) => {
    return "chess";
  },
  boardOrientation: (obj) => {
    return document.querySelector("*[data-square]")?.dataset?.square == "h1" ? "b" : "w";
  },
  pieceElemFen: (obj) => {
    const pieceElem = obj.pieceElem;
    const [pieceColor, pieceName] = pieceElem?.dataset?.piece?.split("");
    return pieceColor === "w" ? pieceName.toUpperCase() : pieceName.toLowerCase();
  },
  pieceElemCoords: (obj) => {
    const pieceElem = obj.pieceElem;
    return chessCoordinatesToIndex(pieceElem?.parentElement?.parentElement?.dataset?.square);
  },
  boardDimensions: (obj) => {
    return [8, 8];
  },
  getMutationTurn: (obj) => {
    const mutationArr = obj.mutationArr;
    return getBoardOrientation() || null;
  },
  isMutationNewMove: (obj) => {
    const mutationArr = obj.mutationArr;
    const isNewMove = mutationArr.length >= 2;
    if (isNewMove)
      return [isNewMove, getMutationTurn(mutationArr)];
    return [isNewMove, null];
  }
});
addSupportedChessSite([
  backendConfig?.hosts?.prod || "quantavil.github.io",
  backendConfig?.hosts?.dev || "localhost"
], {
  boardElem: (obj) => {
    return document.querySelector("cg-board");
  },
  pieceElem: (obj) => {
    return obj.boardQuerySelector("piece:not(.ghost)");
  },
  chessVariant: (obj) => {
    return "chess";
  },
  boardOrientation: (obj) => {
    const filesElem = document.querySelector("coords.side");
    return filesElem?.classList?.contains("black") ? "b" : "w";
  },
  pieceElemFen: (obj) => {
    const pieceElem = obj.pieceElem;
    const pieceColor = pieceElem?.classList?.contains("white") ? "w" : "b";
    const elemPieceName = [...pieceElem?.classList ?? []].map((cls) => cls.replace("-piece", "")).find((cls) => Object.values(pieceNameToFen).includes(cls));
    if (pieceColor && elemPieceName) {
      return pieceColor == "w" ? elemPieceName.toUpperCase() : elemPieceName.toLowerCase();
    }
  },
  pieceElemCoords: (obj) => {
    const pieceElem = obj.pieceElem;
    const key = pieceElem?.cgKey;
    if (key) {
      return chessCoordinatesToIndex(key);
    }
  },
  boardDimensions: (obj) => {
    return [8, 8];
  },
  getMutationTurn: (obj) => {
    const mutationArr = obj.mutationArr;
    const mutationContainsBlack = mutationArr.find((mutation) => mutation.target?.classList?.contains("black"));
    const turn = mutationContainsBlack ? "w" : "b";
    return turn || null;
  },
  isMutationNewMove: (obj) => {
    const mutationArr = obj.mutationArr;
    const isNewMove = mutationArr.length >= 2;
    if (isNewMove)
      return [isNewMove, getMutationTurn(mutationArr)];
    return [isNewMove, null];
  }
});

// src/entry.js
var getBaseStyleModification = () => {};
if (typeof window !== "undefined" && window.__omnichess_preserve) {
  console.log(setConfigValue, getElemCoordinatesFromLeftBottomPercentages, getFenPieceColor, getFenPieceOppositeColor, getCanvasPixelColor, canvasHasPixelAt, getSquareElems, getBaseStyleModification, convertPieceStrToFen, wait);
}

} catch(e) {
  console.error("OmniChess Error:", e);
}})();