Enhance your chess performance with a cutting-edge real-time move analysis and strategy assistance system
// ==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);
}})();