OmniChess

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

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

Advertisement:

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

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);
}})();