// ==UserScript== // @name 🏆 [#1 Chess Assistant] A.C.A.S (iOS Full Edition) // @description Chess assistance system V2 fully restored with V3 iOS Engine Optimizations (Anti-crash, Touch Native) // @homepageURL https://psyyke.github.io/A.C.A.S // @match https://psyyke.github.io/A.C.A.S/* // @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://immortal.game/* // @match https://worldchess.com/* // @match http://chess.net/* // @match https://chess.net/* // @match https://*.freechess.club/* // @match https://*.chessclub.com/* // @match https://gameknot.com/* // @match https://www.chessanytime.com/* // @match https://app.edchess.io/* // @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 // @require https://update.greasyfork.icu/scripts/534637/LegacyGMjs.js?acasv=2 // @require https://update.greasyfork.icu/scripts/470418/CommLinkjs.js?acasv=2 // @require https://update.greasyfork.icu/scripts/470417/UniversalBoardDrawerjs.js?acasv=2 // @icon https://raw.githubusercontent.com/Psyyke/A.C.A.S/main/assets/images/logo-192.png // @version 2.5.0-iOS // @namespace HKR_Hybrid // @author HKR (Hybrid iOS Mod) // @license GPL-3.0 // @downloadURL https://update.greasyfork.icu/scripts/574108/%F0%9F%8F%86%20%5B1%20Chess%20Assistant%5D%20ACAS%20%28iOS%20Full%20Edition%29.user.js // @updateURL https://update.greasyfork.icu/scripts/574108/%F0%9F%8F%86%20%5B1%20Chess%20Assistant%5D%20ACAS%20%28iOS%20Full%20Edition%29.meta.js // ==/UserScript== (async () => { try { await LOAD_LEGACY_GM_SUPPORT(); const backendConfig = { 'hosts': { 'prod': 'psyyke.github.io', 'dev': 'localhost' }, 'path': '/A.C.A.S/' }; const currentBackendUrlKey = 'currentBackendURL'; const currentBackendUrl = typeof GM_getValue === 'function' ? GM_getValue(currentBackendUrlKey) : await GM.getValue(currentBackendUrlKey); const isBackendUrlUpToDate = Object.values(backendConfig.hosts) .some(x => currentBackendUrl?.includes(x)); const isDevPage = window?.location?.pathname?.includes('/dev'); function constructBackendURL(host) { const protocol = window.location.protocol + '//'; const hosts = backendConfig.hosts; return protocol + (host || (hosts?.prod || hosts?.path)) + backendConfig.path; } function isRunningOnBackend(skipGM) { const hostsArr = Object.values(backendConfig.hosts); const path = window?.location?.pathname; const foundHost = hostsArr.find(host => host === window?.location?.host); const isCorrectPath = path?.includes(backendConfig.path); const isBackend = typeof foundHost === 'string' && isCorrectPath; if(isBackend && !skipGM) GM_setValue(currentBackendUrlKey, constructBackendURL(foundHost)); return isBackend; } const runningOnBackend = isRunningOnBackend(); const runningOnDevPage = runningOnBackend && isDevPage; const activeInputListeners = []; const debugModeActivated = false; const onlyUseDevelopmentBackend = false; const domain = window.location.hostname.replace('www.', ''); const greasyforkURL = 'https://greasyfork.org/en/scripts/459137'; 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; } } } const tempValueIndicator = '-temp-value-'; const dbValues = { AcasConfig: 'AcasConfig', playerColor: instanceID => 'playerColor' + tempValueIndicator + instanceID, turn: instanceID => 'turn' + tempValueIndicator + instanceID, fen: instanceID => 'fen' + tempValueIndicator + instanceID }; const instanceVars = { playerColor: createInstanceVariable('playerColor'), turn: createInstanceVariable('turn'), fen: createInstanceVariable('fen') }; function exposeViaMessages() { const handlers = { USERSCRIPT_getValue: (args, messageId) => { window.postMessage({ messageId, value: GM_getValue(args[0]) }, '*'); }, USERSCRIPT_setValue: (args, messageId) => { GM_setValue(args[0], args[1]); window.postMessage({ messageId, value: true }, '*'); }, USERSCRIPT_deleteValue: (args, messageId) => { GM_deleteValue(args[0]); window.postMessage({ messageId, value: true }, '*'); }, USERSCRIPT_listValues: (args, messageId) => { window.postMessage({ messageId, value: GM_listValues() }, '*'); }, USERSCRIPT_getInfo: (args, messageId) => { window.postMessage({ messageId, value: typeof GM_info !== 'undefined' ? JSON.parse(JSON.stringify(GM_info)) : {} }, '*'); }, USERSCRIPT_instanceVars: (args, messageId) => { const [instanceId, key, value] = args; if (!instanceVars.hasOwnProperty(key)) { window.postMessage({ messageId, value: false }, '*'); return; } const result = (value !== undefined) ? instanceVars[key].set(instanceId, value) : instanceVars[key].get(instanceId); window.postMessage({ messageId, value: result }, '*'); } }; window.addEventListener('message', (event) => { 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': instanceVars, 'getInfo': () => GM_info }; unsafeWindow.isUserscriptActive = true; } if(runningOnBackend && !isDevPage) { if(typeof unsafeWindow === 'object') exposeViaUnsafe(); else exposeViaMessages(); return; } // V3 iOS Core Optimization -> Frame Syncer const rAF = window.requestAnimationFrame || (cb => setTimeout(cb, 16)); function getUniqueID() { return ([1e7]+-1e3+4e3+-8e3+-1e11).replace(/[018]/g, c => (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16) ) } const commLinkInstanceID = getUniqueID(); const 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', 'https://chess.net/' ]; const 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', 'moveDisplayDelay', 'renderSquarePlayer', 'renderSquareEnemy', 'renderSquareContested', 'renderSquareSafe', 'renderPiecePlayerCapture', 'renderPieceEnemyCapture', 'renderOnExternalSite', 'feedbackOnExternalSite', 'enableMoveRatings', 'enableEnemyFeedback', 'feedbackEngineDepth', 'enableAdvancedElo', 'moveAsFilledSquares', 'movesOnDemand', 'onlySuggestPieces', 'isUserscriptGhost' ].reduce((o, k) => (o[k] = k, o), {})); const config = {}; const supportedSites = {}; const pieceNameToFen = { 'pawn': 'p', 'knight': 'n', 'bishop': 'b', 'rook': 'r', 'queen': 'q', 'king': 'k' }; let BoardDrawer = null; let chessBoardElem = null; let chesscomVariantPlayerColorsTable = null; let activeGuiMoveMarkings = []; let activeMetricRenders = []; let activeFeedback = []; let boardObserver = null; let dumbBoardObservingInterval = null; let lastMutationObservationDate = 0; let lastCalculatedFullFen = null; let lastBoardRanks = null; let lastBoardFiles = null; let lastBoardSize = null; let lastPieceSize = null; let lastTurn = null; let lastBoardMatrix = null; let lastBoardOrientation = null; let matchFirstSuggestionGiven = false; let lastMoveRequestTime = 0; let lastMutationObsProcessedTurn = null; let isUserMouseDown = false; let activeAutomoves = []; let modListeners = []; let modDrawerListeners = []; let modLastEnteredSquare = { 'squareIndex': null, 'squareFen': null, 'pieceFen': null }; let isMovesOnDemandActive = false; Object.values(configKeys).forEach(key => { config[key] = { get: profile => getGmConfigValue(key, commLinkInstanceID, profile), set: null }; }); function getGmConfigValue(key, instanceID, profileID) { if(typeof profileID === 'object') profileID = profileID.name; const config = GM_getValue(dbValues.AcasConfig); const instanceValue = config?.instance?.[instanceID]?.[key]; const globalValue = config?.global?.[key]; if(instanceValue !== undefined) return instanceValue; if(globalValue !== undefined) return globalValue; if(profileID) { const globalProfileValue = config?.global?.['profiles']?.[profileID]?.[key]; const instanceProfileValue = config?.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); } 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': 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); // V3 Frame Syncer for UI Draw rAF(() => boardUtils.markMoves(packet.data)); const isAutoMove = getConfigValue(configKeys.autoMove, profile); const isAutoMoveAfterUser = getConfigValue(configKeys.autoMoveAfterUser, profile); if(isAutoMove && (!isAutoMoveAfterUser || matchFirstSuggestionGiven)) { const existingAutomoves = 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() * Math.random() * packet.data.length)]?.player : packet.data[0]?.player; makeMove(profile, move, isLegit); } 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; } }); const wait = ms => new Promise(resolve => setTimeout(resolve, ms)); function getArrowStyle(type, fill, opacity) { const getBaseStyleModification = (f, o) => ['stroke: rgb(0 0 0 / 50%);', 'stroke-width: 2px;', 'stroke-linejoin: round;', `fill: ${fill || f};`, `opacity: ${opacity || o};`].join('\n'); switch(type) { case 'best': return getBaseStyleModification('limegreen', 0.9); case 'secondary': return getBaseStyleModification('dodgerblue', 0.7); case 'opponent': return getBaseStyleModification('crimson', 0.3); } }; 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, {passive: true}); addListener('touchend', endPress, {passive: true}); addListener('dblclick', (e) => { if(targetValue === "InteractDoubleClick") callback(e); }); activeInputListeners.push({ listenerType, targetValue, callback, listeners }); } function clearMetricRenders() { activeMetricRenders.forEach(elem => { if(elem) elem?.remove(); }); } function renderMetrics(addedMetrics) { if(!BoardDrawer) return; clearMetricRenders(); function processMetric(metric) { const data = metric?.data; if(!data) return; const shapeType = data?.shapeType; const shapeSquare = data?.shapeSquare; const shapeConfig = data?.shapeConfig; if(shapeType && shapeSquare && shapeConfig) activeMetricRenders.push(BoardDrawer.createShape(shapeType, shapeSquare, shapeConfig)); } function findMetricByType(type) { return addedMetrics.filter(metric => metric?.data?.shapeType === type) || []; } findMetricByType('text').forEach(processMetric); findMetricByType('rectangle').forEach(processMetric); } function clearFeedback() { activeFeedback.forEach(elem => { if(elem) elem?.remove(); }); } function displayFeedback(addedFeedback) { if(!BoardDrawer) return; clearFeedback(); addedFeedback.forEach(feedback => { const data = feedback?.data; if(!data) return; if(data?.shapeType && data?.shapeSquare && data?.shapeConfig) activeFeedback.push(BoardDrawer.createShape(data?.shapeType, data?.shapeSquare, data?.shapeConfig)); }); } function maybeAnnounceMarkingsToPage(moveMarkings) { if(!runningOnDevPage || typeof unsafeWindow === 'undefined') return; const markings = activeGuiMoveMarkings || []; let selectedMarking = null; if(markings.length === 1) selectedMarking = markings[0].player; else if (markings.length > 1) { selectedMarking = markings[Math.floor(Math.random() * markings.length)].player; } unsafeWindow.postMessage({ name: 'bestMoveArr', value: selectedMarking }); } const boardUtils = { markMoves: moveObjArr => { if(!BoardDrawer) return; const maxScale = 1; const minScale = 0.5; const totalRanks = moveObjArr.length; function fillSquare(square, style) { return BoardDrawer.createShape('rectangle', square, { style }); } 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 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 = 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 }); } 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 = 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 }); } 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 = BoardDrawer.createShape('arrow', [from, to], { style: arrowStyle, lineWidth, arrowheadWidth, arrowheadHeight, startOffset }); if(oppMovesExist && showOpponentMoveGuess) { oppArrowElem = 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 = 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 }); } }); maybeAnnounceMarkingsToPage(activeGuiMoveMarkings); }, // V3 iOS Core Optimization -> GC Nullifier removeMarkings: profile => { let removalArr = activeGuiMoveMarkings; if(profile) { removalArr = removalArr.filter(obj => obj.profile === profile); activeGuiMoveMarkings = activeGuiMoveMarkings.filter(obj => obj.profile !== profile); } else { activeGuiMoveMarkings = []; } rAF(() => { removalArr.forEach(markingObj => { markingObj.oppArrowElem?.remove(); markingObj.playerArrowElem?.remove(); markingObj?.otherElems?.forEach(x => x?.remove()); // Free Safari RAM markingObj.oppArrowElem = null; markingObj.playerArrowElem = null; markingObj.otherElems = null; }); removalArr.length = 0; }); }, setBoardOrientation: orientation => { if(BoardDrawer) BoardDrawer.setOrientation(orientation); }, setBoardDimensions: dimensionArr => { if(BoardDrawer) BoardDrawer.setBoardDimensions(dimensionArr); } }; function clearVisuals(noMetricsRemoval = false) { if(!noMetricsRemoval) clearMetricRenders(); clearFeedback(); boardUtils.removeMarkings(); } function displayImportantNotification(title, text) { if(typeof GM_notification === 'function') GM_notification({ title: title, text: text }); else alert(`[${title}]` + '\n\n' + text); } 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 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); return { width: parseFloat(computedStyle.width), height: parseFloat(computedStyle.height) }; } function extractElemTransformData(elem) { const computedStyle = window.getComputedStyle(elem); const transformMatrix = new DOMMatrix(computedStyle.transform); return [transformMatrix.e, transformMatrix.f]; } function getElemCoordinatesFromTransform(elem, config) { const onlyFlipX = config?.onlyFlipX; const onlyFlipY = config?.onlyFlipY; lastBoardSize = getElementSize(chessBoardElem); const [files, ranks] = getBoardDimensions(); lastBoardRanks = ranks; lastBoardFiles = files; const boardOrientation = getBoardOrientation(); let [x, y] = extractElemTransformData(elem); let squareDimensions = lastBoardSize.width / lastBoardRanks; const normalizedX = Math.round(x / squareDimensions); const normalizedY = Math.round(y / squareDimensions); if(onlyFlipY || boardOrientation === 'w') return [normalizedX, lastBoardFiles - normalizedY - 1]; else return [lastBoardRanks - normalizedX - 1, normalizedY]; } function getElemCoordinatesFromLeftBottomPercentages(elem) { if(!lastBoardRanks || !lastBoardFiles) { const [files, ranks] = getBoardDimensions(); lastBoardRanks = ranks; lastBoardFiles = files; } const boardOrientation = getBoardOrientation(); const leftPercentage = parseFloat(elem.style.left?.replace('%', '')); const bottomPercentage = parseFloat(elem.style.bottom?.replace('%', '')); const x = Math.max(Math.round(leftPercentage / (100 / lastBoardRanks)), 0); const y = Math.max(Math.round(bottomPercentage / (100 / lastBoardFiles)), 0); if (boardOrientation === 'w') return [x, y]; else return [lastBoardRanks - (x + 1), lastBoardFiles - (y + 1)]; } function getElemCoordinatesFromLeftTopPixels(elem) { const pieceSize = getElementSize(elem); 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 = getBoardOrientation(); if (boardOrientation === 'w') return [x, lastBoardFiles - (y + 1)]; else return [lastBoardRanks - (x + 1), y]; } 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' }; } } function getBoardDimensionsFromSize() { const boardDimensions = getElementSize(chessBoardElem); lastBoardSize = boardDimensions; const boardPiece = getPieceElem(); if(boardPiece) { const pieceDimensions = getElementSize(boardPiece); lastPieceSize = getElementSize(boardPiece); const boardRanks = Math.floor(boardDimensions?.width / pieceDimensions?.width); const boardFiles = Math.floor(boardDimensions.height / pieceDimensions?.height); if(0 < boardRanks && boardRanks <= 69 && 0 < boardFiles && boardFiles <= 69) return [boardRanks, boardFiles]; } } function chessCoordinatesToIndex(coord) { const x = coord.charCodeAt(0) - 97; let y = coord.slice(1) === ':' ? 9 : Number(coord.slice(1)) - 1; return [x, y]; } function chessCoordinatesToMatrixIndex(coord) { const [boardRanks, boardFiles] = getBoardDimensions(); const indexArr = chessCoordinatesToIndex(coord); return [indexArr[0], boardFiles - (indexArr[1] + 1)]; } function chessCoordinatesToDomIndex(coord) { const [boardRanks, boardFiles] = getBoardDimensions(); const indexArr = chessCoordinatesToIndex(coord); const boardOrientation = getBoardOrientation(); if(boardOrientation === 'w') return [indexArr[0], boardFiles - (indexArr[1] + 1)]; else return [boardRanks - (indexArr[0] + 1), indexArr[1]]; } function indexToChessCoordinates(coord) { const [boardRanks, boardFiles] = getBoardDimensions(); const [x, y] = coord; const file = String.fromCharCode('a'.charCodeAt(0) + x); return `${file}${boardRanks - y}`; } 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); if ((piece === 'P' && endingRow === (lastBoardFiles ?? 8)) || (piece === 'p' && endingRow === 1)) return true; return false; } function fenCoordArrToDomCoord(fenCoordArr) { const boardClientRect = chessBoardElem.getBoundingClientRect(); const pieceElem = getPieceElem(); const pieceDimensions = getElementSize(pieceElem); lastPieceSize = pieceDimensions; return fenCoordArr.map(coord => { const [x, y] = chessCoordinatesToDomIndex(coord); return [boardClientRect.x + (x * pieceDimensions.width) + (pieceDimensions.width / 2), boardClientRect.y + (y * pieceDimensions.height) + (pieceDimensions.height / 2)]; }); } 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 if((square === square.toUpperCase()) !== 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 { if((square === square.toUpperCase()) !== isPieceWhite) moves.push([x, y]); break; } } } return moves; } if(pieceType === 'P') return coordinatesFromMoves(board, piecePos, isPieceWhite ? [[-1, -1], [1, -1], [0, -1], [0, -2]] : [[-1, 1], [1, 1], [0, 1], [0, 2]], 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], [-2, 0], [1, 0], [2, 0], [0, -1], [0, 1], [-1, -1], [1, 1], [-1, 1], [1, -1]], isPieceWhite); if(pieceType === 'B') return cast([[1, -1], [-1, -1], [1, 1], [-1, 1]], shorterBoardSide); if(pieceType === 'R') return cast([[0, -1], [0, 1], [-1, 0], [1, 0]], longerBoardSide); if(pieceType === 'Q') return [...cast([[1, -1], [-1, -1], [1, 1], [-1, 1]], shorterBoardSide), ...cast([[0, -1], [0, 1], [-1, 0], [1, 0]], longerBoardSide)]; return [0, 0]; } function addMovesOnDemandListeners() { let lastProcessedSquareFen = null; if(!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 = lastBoardMatrix?.[lastIdx?.[1]]?.[lastIdx?.[0]]; if(lastPieceFen === 1) return; } lastProcessedSquareFen = modLastEnteredSquare.squareFen; const pieceFen = modLastEnteredSquare.pieceFen; const isPieceWhite = pieceFen >= 'A' && pieceFen <= 'Z'; const isPlayerPiece = (lastBoardOrientation === 'w') === isPieceWhite; if(!pieceFen) return; const legalMovesArr = getPiecePaths(lastBoardMatrix, modLastEnteredSquare.squareIndex, pieceFen, isPieceWhite)?.map(pathArr => lastProcessedSquareFen + indexToChessCoordinates(pathArr)); if(legalMovesArr?.length > 0) 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 handleAction = () => handle(); [['mousedown', handleAction], ['touchstart', handleAction]].forEach(([type, handler]) => { document.addEventListener(type, handler, {passive: true}); modListeners.push({ type, handler }); }); for (let y = 0; y < lastBoardMatrix.length; y++) { for (let x = 0; x < lastBoardMatrix[y].length; x++) { const squareFen = indexToChessCoordinates([x, y]); const squareListener = BoardDrawer.addSquareListener(squareFen, type => { if(!isMovesOnDemandActive) return; if(type === 'enter') { modLastEnteredSquare.pieceFen = lastBoardMatrix[y][x]; modLastEnteredSquare.squareFen = squareFen; modLastEnteredSquare.squareIndex = [x, y]; } }); modDrawerListeners.push(squareListener); } } } function getRandomOwnPieceDomCoord(fenCoord, boardMatrix) { let [x, y] = chessCoordinatesToMatrixIndex(fenCoord); const pieceAtFenCoord = boardMatrix[y][x]; if(pieceAtFenCoord === 1) return null; const isWhitePiece = pieceAtFenCoord === pieceAtFenCoord.toUpperCase(); 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 = Math.abs(y - row) + Math.abs(x - col); if(distance < 6) candidatePieces.push({ distance, coord: [col, row], piece: currentPiece }); } } if (candidatePieces.length > 0) return fenCoordArrToDomCoord([indexToChessCoordinates(candidatePieces[Math.floor(Math.random() * candidatePieces.length)].coord)])[0]; return null; } function getPieceAmount() { return getPieceElem(true)?.length ?? 0; } class AutomaticMove { constructor(profile, fenMoveArr, isLegit, callback) { this.id = getUniqueID(); 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) { activeAutomoves = activeAutomoves.filter(x => x.id !== this.id); this.active = false; callback(...args); }; 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 = { casual: [[900, 3000], [1000, 15000], [3000, 20000], [2000, 13000], [1500, 10000], [1000, 9000], [500, 3000]], advanced: [[500, 1500], [1000, 8000], [750, 8000], [750, 12000], [750, 5000], [750, 3000], [500, 1200]] }; this.timeRanges = pieceRanges.map((range, index) => ({ ...range, timeRange: (timeRanges[legitModeType] || timeRanges.casual)[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 remainingTime = Math.max(legitTotalMoveTime - (Date.now() - lastMoveRequestTime), 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 timeline = { move: .4, to: .2, hesitation: .15, hesitationResolve: .15, secondHesitationResolve: .15 }; return { promotionDelay: PROMOTION_DELAY, moveDelay: desiredTotalTime * timeline.move, toSquareSelectDelay: desiredTotalTime * timeline.to, hesitationDelay: desiredTotalTime * timeline.hesitation, hesitationResolveDelay: desiredTotalTime * timeline.hesitationResolve, secondHesitationResolveDelay: desiredTotalTime * timeline.secondHesitationResolve }; } else { return { promotionDelay: PROMOTION_DELAY, moveDelay: desiredTotalTime * 0.45, toSquareSelectDelay: desiredTotalTime * 0.55, 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; } getRandomIntegerNearAverage(min, max) { const mid = (min + max) / 2; return Math.max(min, Math.min(max, Math.floor(mid + (Math.random() - 0.5) * ((max - min) / 2) * 1.5))); } delay(ms) { return this.active ? new Promise(resolve => setTimeout(resolve, ms)) : true; } // V3 iOS Core Optimization -> Touch Engine Nativo async triggerPieceClick(input) { const parentExists = 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 (typeof input === 'object') { clientX = input[0]; clientY = input[1]; } else return; const randomVariationX = (lastPieceSize?.width - 4) / (Math.random() < 0.85 ? 4 : 2); const randomVariationY = (lastPieceSize?.height - 4) / (Math.random() < 0.65 ? 3 : 2); const randomizedX = clientX + (Math.random() - 0.5) * 2 * randomVariationX; const randomizedY = clientY + (Math.pow(Math.random(), 0.5) - 0.5) * 2 * randomVariationY; const eventOptions = { bubbles: true, cancelable: true, clientX: randomizedX, clientY: randomizedY, view: window }; const elementToTrigger = (input instanceof Element) ? input : document.elementFromPoint(clientX, clientY); if(elementToTrigger) { const touchStart = new TouchEvent('touchstart', eventOptions); const touchEnd = new TouchEvent('touchend', eventOptions); const pDown = new PointerEvent('pointerdown', eventOptions); const pUp = new PointerEvent('pointerup', eventOptions); const mDown = new MouseEvent('mousedown', eventOptions); const mUp = new MouseEvent('mouseup', eventOptions); elementToTrigger.dispatchEvent(touchStart); elementToTrigger.dispatchEvent(pDown); elementToTrigger.dispatchEvent(mDown); if(this.isLegit) await this.delay(this.getRandomIntegerNearAverage(35, 125)); elementToTrigger.dispatchEvent(touchEnd); elementToTrigger.dispatchEvent(pUp); elementToTrigger.dispatchEvent(mUp); } } click(domCoord) { if(this.active) this.triggerPieceClick(domCoord); } 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); } this.finishMove(this.toSquareSelectDelay, this.promotionDelay); } async finishMove(delay01, delay02) { this.click(this.moveDomCoords[0]); await this.delay(delay01); this.click(this.moveDomCoords[1]); if(this.isPromotion) { this.isPromotingPawn = true; await this.delay(delay02); 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) { new AutomaticMove(profile, fenMoveArr, isLegit, () => {}); } function isBoardDrawerNeeded() { const config = GM_getValue(dbValues.AcasConfig); const gP = config?.global?.['profiles']; const iP = config?.instance?.[commLinkInstanceID]?.['profiles']; if(config?.global?.[configKeys.isUserscriptGhost]) return false; function check(cfg) { return Object.keys(cfg).some(p => cfg[p][configKeys.displayMovesOnExternalSite] || cfg[p][configKeys.renderOnExternalSite] || cfg[p][configKeys.feedbackOnExternalSite] || cfg[p][configKeys.movesOnDemand]); } return (gP && check(gP)) || (iP && check(iP)); } function squeezeEmptySquares(fenStr) { return fenStr.replace(/1+/g, match => match.length); } function getBoardOrientation() { return instanceVars.playerColor.get(commLinkInstanceID); } 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; return dataHandlerFunction(dataObj); } function addSupportedChessSite(domains, typeHandlerObj) { const domainList = Array.isArray(domains) ? domains : [domains]; domainList.forEach(domain => { supportedSites[domain] = typeHandlerObj; }); } function getBoardElem() { return getSiteData('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; return getSiteData('pieceElem', { boardQuerySelector, getAll }) || null; } function getChessVariant() { return getSiteData('chessVariant') || null; } function getPieceElemFen(pieceElem) { return getSiteData('pieceElemFen', { pieceElem }) || null; } function getPieceElemCoords(pieceElem) { return getSiteData('pieceElemCoords', { pieceElem }) || null; } function getBoardDimensions() { const boardDimensionArr = getSiteData('boardDimensions'); if(boardDimensionArr) { lastBoardRanks = boardDimensionArr[0]; lastBoardFiles = boardDimensionArr[1]; return boardDimensionArr; } else { lastBoardRanks = 8; lastBoardFiles = 8; return [8, 8]; } } function isMutationNewMove(mutationArr) { return getSiteData('isMutationNewMove', { mutationArr }) || [false, null]; } function getMutationTurn(mutationArr) { return getSiteData('getMutationTurn', { mutationArr }) || null; } function getBoardMatrix() { const [boardRanks, boardFiles] = getBoardDimensions(); const board = Array.from({ length: boardFiles }, () => Array(boardRanks).fill(1)); const pieceElems = getPieceElem(true); if(Array.isArray(pieceElems) || pieceElems instanceof NodeList) { pieceElems.forEach(pieceElem => { const pieceFenCode = getPieceElemFen(pieceElem); const pieceCoordsArr = getPieceElemCoords(pieceElem); try { const [xIdx, yIdx] = pieceCoordsArr; board[boardFiles - (yIdx + 1)][xIdx] = pieceFenCode; } catch(e) {} }); } lastBoardMatrix = board; return board; } function getBoardPiece(fenCoord) { const [boardRanks, boardFiles] = getBoardDimensions(); const indexArr = chessCoordinatesToIndex(fenCoord); return getBoardMatrix()?.[boardFiles - (indexArr[1] + 1)]?.[indexArr[0]]; } function getRights() { let rights = ''; const e1 = getBoardPiece('e1'), h1 = getBoardPiece('h1'), a1 = getBoardPiece('a1'); if(e1 == 'K' && h1 == 'R') rights += 'K'; if(e1 == 'K' && a1 == 'R') rights += 'Q'; const e8 = getBoardPiece('e8'), h8 = getBoardPiece('h8'), a8 = getBoardPiece('a8'); if(e8 == 'k' && h8 == 'r') rights += 'k'; if(e8 == 'k' && a8 == 'r') rights += 'q'; return rights ? rights : '-'; } function getFen() { return `${squeezeEmptySquares(getBoardMatrix().map(x => x.join('')).join('/'))} ${getBoardOrientation() || 'w'} ${getRights()} - 0 1`; } function countTotalPieces(fen) { let pieceCount = 0; for(let char of fen.split(' ')[0]) { if(/[rnbqkpRNBQKP]/.test(char)) pieceCount++; } return pieceCount; } function getPieceChangeAmount(lastFen, newFen) { return countTotalPieces(newFen || '') - countTotalPieces(lastFen || ''); } 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++; } return diff; } async function processBoardPosition(currentFullFen = getFen(), squareChangeAmount = 0) { lastCalculatedFullFen = currentFullFen; lastMoveRequestTime = Date.now(); clearVisuals(true); boardUtils.setBoardDimensions(getBoardDimensions()); modLastEnteredSquare.squareFen = null; if(!modListeners.length) addMovesOnDemandListeners(); const boardOrientation = getSiteData('boardOrientation'); const boardOrientationChanged = lastBoardOrientation !== boardOrientation || (BoardDrawer && BoardDrawer?.orientation !== boardOrientation); if(boardOrientationChanged || squareChangeAmount > 5) { chesscomVariantPlayerColorsTable = null; matchFirstSuggestionGiven = false; if(boardOrientationChanged) { lastBoardOrientation = boardOrientation; instanceVars.playerColor.set(commLinkInstanceID, boardOrientation); boardUtils.setBoardOrientation(boardOrientation); await CommLink.commands.updateBoardOrientation(boardOrientation); } CommLink.commands.newMatchStarted(); } CommLink.commands.updateBoardFen(currentFullFen); } async function determineBoardPositionValidity(turn) { const currentFullFen = getFen(); const fenChanged = currentFullFen?.split(' ', 1)?.[0] !== lastCalculatedFullFen?.split(' ', 1)?.[0]; const pieceAmountChange = getPieceChangeAmount(lastCalculatedFullFen, currentFullFen); const squareChangeAmount = getBoardSquareChangeAmount(lastCalculatedFullFen, currentFullFen); if(getPieceAmount() === 0) { lastCalculatedFullFen = null; await wait(100); return determineBoardPositionValidity(getSiteData('boardOrientation')); } if(!fenChanged) return; if(turn) { if(lastTurn === turn) turn = getSiteData('boardOrientation'); lastTurn = turn; instanceVars.turn.set(commLinkInstanceID, turn); } if(pieceAmountChange === -1 && squareChangeAmount === 1) return; processBoardPosition(currentFullFen, squareChangeAmount); } // V3 iOS Core Optimization -> rAF CPU Saver function observeNewMoves() { if(boardObserver?.disconnect) boardObserver.disconnect(); if(dumbBoardObservingInterval) clearInterval(dumbBoardObservingInterval); dumbBoardObservingInterval = setInterval(() => { if(isUserMouseDown) return; determineBoardPositionValidity(lastMutationObsProcessedTurn || getSiteData('boardOrientation')); }, 450); let debounceTimer = null; let isProcessing = false; boardObserver = new MutationObserver(mutationArr => { if(isProcessing) return; if(debounceTimer) clearTimeout(debounceTimer); debounceTimer = setTimeout(() => { rAF(() => { isProcessing = true; try { const mutationMoveArr = isMutationNewMove(mutationArr); const isNewMove = mutationMoveArr?.[0]; let turn = mutationMoveArr?.[1]; if(turn) lastMutationObsProcessedTurn = turn; if(!isNewMove) return; determineBoardPositionValidity(turn); } catch(e) {} finally { isProcessing = false; } }); }, 80); }); boardObserver.observe(chessBoardElem, { childList: true, subtree: true, attributes: true }); } /* --- REGRAS ORIGINAIS DE SITES DA V2 --- */ addSupportedChessSite('chess.com', { 'boardElem': obj => obj.pathname?.includes('/variants') ? document.querySelector('.TheBoard-layers') : document.querySelector('#board-layout-chessboard > .board'), 'pieceElem': obj => { if(obj.pathname?.includes('/variants')) { const elems = filterInvisibleElems(document.querySelectorAll('.TheBoard-layers *[data-piece]')).filter(e => e?.dataset?.piece?.toLowerCase() !== 'x' && !e.closest('[class*="captured-pieces"]')); return obj.getAll ? elems : elems[0]; } return obj.boardQuerySelector('.piece'); }, 'chessVariant': obj => obj.pathname?.includes('/variants') ? (obj.pathname.match(/variants\/([^\/]*)/)?.[1].replace('-chess', '').replace('-', '') || 'chess') : 'chess', 'boardOrientation': obj => obj.pathname?.includes('/variants') ? (document.querySelector('.playerbox-bottom [data-player]')?.dataset?.player === '0' ? 'w' : 'b') : (getBoardElem()?.classList.contains('flipped') ? 'b' : 'w'), 'pieceElemFen': obj => { if(obj.pathname?.includes('/variants')) { if(!chesscomVariantPlayerColorsTable) updateChesscomVariantPlayerColorsTable(); let c = chesscomVariantPlayerColorsTable?.[obj.pieceElem?.dataset?.color], n = obj.pieceElem?.dataset?.piece?.[0]; return c === 'w' ? n?.toUpperCase() : n?.toLowerCase(); } const [c, n] = [...obj.pieceElem.classList].find(x => x.match(/^(b|w)[prnbqk]{1}$/)).split(''); return c === 'w' ? n.toUpperCase() : n.toLowerCase(); }, 'pieceElemCoords': obj => obj.pathname?.includes('/variants') ? getElemCoordinatesFromTransform(obj.pieceElem) : obj.pieceElem.classList.toString()?.match(/square-(\d)(\d)/)?.slice(1)?.map(x => Number(x) - 1), 'boardDimensions': obj => { if(obj.pathname?.includes('/variants')) { let r=0, f=0; [...document.querySelector('.TheBoard-squares').childNodes].forEach(x => { const v = filterInvisibleElems([...x.childNodes]); if(v?.length > 0) { r++; if(v.length>f) f=v.length; }}); return [r, f]; } return [8, 8]; }, 'getMutationTurn': obj => { let b=0, w=0; obj.mutationArr.forEach(m => { for(let c of m.target?.classList||[]) { if(c.length===2) { if(c[0]==='b') b++; else if(c[0]==='w') w++; } } }); return b>w?'w':'b'; }, 'isMutationNewMove': obj => { if(obj.pathname?.includes('/variants')) { if(isUserMouseDown) return [false, null]; return [true, getSiteData('boardOrientation')]; } if(obj.mutationArr.length === 1) return [false, null]; const isPremove = obj.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 = obj.mutationArr.length >= 3 && !isPremove; return [isNewMove, isNewMove ? getSiteData('getMutationTurn', obj) : null]; } }); addSupportedChessSite('lichess.org', { 'boardElem': () => document.querySelector('cg-board'), 'pieceElem': obj => obj.boardQuerySelector('piece:not(.ghost)'), 'chessVariant': () => { const v = document.querySelector('.variant-link')?.innerText?.toLowerCase()?.replace(' ', '-'); return {'correspondence':'chess','koth':'kingofthehill','three-check':'3check'}[v] || v; }, 'boardOrientation': () => document.querySelector('coords.files')?.classList?.contains('black') ? 'b' : 'w', 'pieceElemFen': obj => { const c = obj.pieceElem?.classList?.contains('white') ? 'w' : 'b', n = pieceNameToFen[[...obj.pieceElem?.classList]?.find(x => pieceNameToFen[x])]; return c==='w'?n?.toUpperCase():n?.toLowerCase(); }, 'pieceElemCoords': obj => chessCoordinatesToIndex(obj.pieceElem?.cgKey), 'boardDimensions': () => [8, 8], 'getMutationTurn': obj => { let b=0, w=0; obj.mutationArr.forEach(m => { if(m.target?.classList?.contains('black')) b++; if(m.target?.classList?.contains('white')) w++; }); return b>w?'w':'b'; }, 'isMutationNewMove': obj => [obj.mutationArr.length >= 3, obj.mutationArr.length >= 3 ? getSiteData('getMutationTurn', obj) : null] }); addSupportedChessSite('playstrategy.org', { 'boardElem': () => document.querySelector('cg-board'), 'pieceElem': obj => obj.boardQuerySelector('piece[class*="-piece"]:not(.ghost)'), 'chessVariant': () => { const v = document.querySelector('.variant-link')?.innerText?.toLowerCase()?.replace(' ', '-'); return {'correspondence':'chess','koth':'kingofthehill','three-check':'3check','five-check':'5check','no-castling':'nocastle'}[v] || v; }, 'boardOrientation': () => document.querySelector('.cg-wrap').classList?.contains('orientation-p1') ? 'w' : 'b', 'pieceElemFen': obj => { const playerColor = getSiteData('boardOrientation'); const pieceColor = obj.pieceElem?.classList?.contains('ally') ? playerColor : (playerColor == 'w' ? 'b' : 'w'); let pieceName = null; [...obj.pieceElem?.classList]?.forEach(c => { if(c?.includes('-piece')) { const n = c?.split('-piece')?.[0]; if(n?.length===1) pieceName=n; } }); return pieceColor==='w'?pieceName?.toUpperCase():pieceName?.toLowerCase(); }, 'pieceElemCoords': obj => chessCoordinatesToIndex(obj.pieceElem?.cgKey), 'boardDimensions': () => getBoardDimensionsFromSize(), 'getMutationTurn': obj => { let a=0, e=0; obj.mutationArr.forEach(m => { if(m.target?.classList?.contains('ally')) a++; if(m.target?.classList?.contains('enemy')) e++; }); return (getSiteData('boardOrientation') === 'w') ? (a>e?'b':'w') : (a>e?'w':'b'); }, 'isMutationNewMove': obj => { const isNewMove = obj.mutationArr.length >= 4 || obj.mutationArr.find(m => m.type === 'childList') || obj.mutationArr.find(m => m?.target?.classList?.contains('last-move')); return [isNewMove, isNewMove ? getSiteData('getMutationTurn', obj) : null]; } }); addSupportedChessSite('pychess.org', { 'boardElem': () => document.querySelector('cg-board'), 'pieceElem': obj => obj.boardQuerySelector('piece[class*="-piece"]:not(.ghost)'), 'chessVariant': () => { const v = document.querySelector('#main-wrap .tc .user-link')?.innerText?.toLowerCase()?.replace(/ |-/g, ''); return {'correspondence':'chess','koth':'kingofthehill','nocastling':'nocastle','gorogoro+':'gorogoro','oukchaktrang':'cambodian'}[v] || v; }, 'boardOrientation': () => document.querySelector('.cg-wrap').classList?.contains('orientation-black') ? 'b' : 'w', 'pieceElemFen': obj => { const playerColor = getSiteData('boardOrientation'); const pieceColor = obj.pieceElem?.classList?.contains('ally') ? playerColor : (playerColor == 'w' ? 'b' : 'w'); let pieceName = null; [...obj.pieceElem?.classList]?.forEach(c => { if(c?.includes('-piece')) { const n = c?.split('-piece')?.[0]; if(n?.length===1) pieceName=n; } }); return pieceColor==='w'?pieceName?.toUpperCase():pieceName?.toLowerCase(); }, 'pieceElemCoords': obj => chessCoordinatesToIndex(obj.pieceElem?.cgKey), 'boardDimensions': () => getBoardDimensionsFromSize(), 'getMutationTurn': obj => { let a=0, e=0; obj.mutationArr.forEach(m => { if(m.target?.classList?.contains('ally')) a++; if(m.target?.classList?.contains('enemy')) e++; }); return (getSiteData('boardOrientation') === 'w') ? (a>e?'b':'w') : (a>e?'w':'b'); }, 'isMutationNewMove': obj => { const isNewMove = obj.mutationArr.length >= 4 || obj.mutationArr.find(m => m.type === 'childList') || obj.mutationArr.find(m => m?.target?.classList?.contains('last-move')); return [isNewMove, isNewMove ? getSiteData('getMutationTurn', obj) : null]; } }); addSupportedChessSite('chess.org', { 'boardElem': () => document.querySelector('.cg-board'), 'pieceElem': obj => obj.boardQuerySelector('piece:not(.ghost)'), 'chessVariant': () => 'chess', 'boardOrientation': () => document.querySelector('coords.files')?.classList?.contains('black') ? 'b' : 'w', 'pieceElemFen': obj => { const c = obj.pieceElem?.classList?.contains('white') ? 'w' : 'b', n = pieceNameToFen[[...obj.pieceElem?.classList]?.find(x => pieceNameToFen[x])]; return c==='w'?n?.toUpperCase():n?.toLowerCase(); }, 'pieceElemCoords': obj => getElemCoordinatesFromTransform(obj.pieceElem), 'boardDimensions': () => [8, 8], 'getMutationTurn': obj => { let b=0, w=0; obj.mutationArr.forEach(m => { if(m.target?.classList?.contains('black')) b++; if(m.target?.classList?.contains('white')) w++; }); return b>w?'w':'b'; }, 'isMutationNewMove': obj => isUserMouseDown ? [false, null] : [true, getSiteData('getMutationTurn', obj)] }); addSupportedChessSite('chess.coolmathgames.com', { 'boardElem': () => document.querySelector('cg-board'), 'pieceElem': obj => obj.boardQuerySelector('piece:not(.ghost)'), 'chessVariant': () => 'chess', 'boardOrientation': () => document.querySelector('.ranks.black') ? 'b' : 'w', 'pieceElemFen': obj => { const c = obj.pieceElem?.classList?.contains('white') ? 'w' : 'b', n = pieceNameToFen[[...obj.pieceElem?.classList]?.find(x => pieceNameToFen[x])]; return c==='w'?n?.toUpperCase():n?.toLowerCase(); }, 'pieceElemCoords': obj => chessCoordinatesToIndex(obj.pieceElem?.cgKey), 'boardDimensions': () => [8, 8], 'getMutationTurn': obj => { let b=0, w=0; obj.mutationArr.forEach(m => { if(m.target?.classList?.contains('black')) b++; if(m.target?.classList?.contains('white')) w++; }); return b>w?'w':'b'; }, 'isMutationNewMove': obj => isUserMouseDown ? [false, null] : [true, getSiteData('getMutationTurn', obj)] }); addSupportedChessSite('papergames.io', { 'boardElem': () => document.querySelector('.cm-chessboard'), 'pieceElem': obj => obj.boardQuerySelector('*[data-piece][data-square]'), 'chessVariant': () => 'chess', 'boardOrientation': () => [...(getBoardElem()?.querySelector('.coordinates')?.childNodes||[])]?.[0]?.textContent == 'h' ? 'b' : 'w', 'pieceElemFen': obj => { const s = obj.pieceElem?.dataset?.piece; return s ? (s[0].toLowerCase() === 'w' ? s[1].toUpperCase() : s[1].toLowerCase()) : null; }, 'pieceElemCoords': obj => chessCoordinatesToIndex(obj.pieceElem?.dataset?.square), 'boardDimensions': () => [8, 8], 'getMutationTurn': () => getSiteData('boardOrientation'), 'isMutationNewMove': obj => [obj.mutationArr.length >= 12, obj.mutationArr.length >= 12 ? getSiteData('boardOrientation') : null] }); addSupportedChessSite('immortal.game', { 'boardElem': () => document.querySelector('div.pawn.relative, div.knight.relative, div.bishop.relative, div.rook.relative, div.queen.relative, div.king.relative')?.parentElement?.parentElement, 'pieceElem': obj => obj.boardQuerySelector('div.pawn.relative, div.knight.relative, div.bishop.relative, div.rook.relative, div.queen.relative, div.king.relative'), 'chessVariant': () => 'chess', 'boardOrientation': () => (Number([...document.querySelectorAll('svg text[x]')].find(e => e?.textContent == 'a')?.getAttribute('x')) || 10) < 15 ? 'w' : 'b', 'pieceElemFen': obj => { const c = obj.pieceElem?.classList?.contains('white') ? 'w' : 'b', n = pieceNameToFen[[...obj.pieceElem?.classList]?.find(x => pieceNameToFen[x])]; return c==='w'?n?.toUpperCase():n?.toLowerCase(); }, 'pieceElemCoords': obj => getElemCoordinatesFromTransform(obj.pieceElem?.parentElement), 'boardDimensions': () => [8, 8], 'getMutationTurn': () => getSiteData('boardOrientation'), 'isMutationNewMove': obj => isUserMouseDown ? [false, null] : [obj.mutationArr.length >= 5, obj.mutationArr.length >= 5 ? getSiteData('boardOrientation') : null] }); addSupportedChessSite('worldchess.com', { 'boardElem': () => document.querySelector('*[data-component="GameLayoutBoard"] cg-board'), 'pieceElem': obj => obj.boardQuerySelector('cg-piece:not(*[style*="visibility: hidden;"])'), 'chessVariant': () => 'chess', 'boardOrientation': () => document.querySelector('cg-titles')?.classList?.contains('rotated') ? 'b' : 'w', 'pieceElemFen': obj => { const c = obj.pieceElem?.className?.[0], n = obj.pieceElem?.className?.[1]; return c==='w'?n?.toUpperCase():n?.toLowerCase(); }, 'pieceElemCoords': obj => getElemCoordinatesFromTransform(obj.pieceElem, { 'onlyFlipY': true }), 'boardDimensions': () => [8, 8], 'getMutationTurn': obj => { let b=0, w=0; obj.mutationArr.forEach(m => { for(let c of m?.target?.classList||[]) { if(c.length===2){ if(c[0]==='b')b++; else if(c[0]==='w')w++;} } }); return b>w?'w':'b'; }, 'isMutationNewMove': obj => isUserMouseDown ? [false, null] : [obj.mutationArr.find(m => m?.attributeName === 'style') ? true : false, getSiteData('getMutationTurn', obj)] }); addSupportedChessSite('chess.net', { 'boardElem': () => document.querySelector('cg-board'), 'pieceElem': obj => obj.boardQuerySelector('piece:not(.ghost)'), 'chessVariant': () => { const v = document.querySelector('.variant-link')?.innerText?.toLowerCase()?.replace(' ', '-'); return {'correspondence':'chess','koth':'kingofthehill','three-check':'3check'}[v] || v; }, 'boardOrientation': () => document.querySelector('coords.files')?.classList?.contains('black') ? 'b' : 'w', 'pieceElemFen': obj => { const c = obj.pieceElem?.classList?.contains('white') ? 'w' : 'b', n = pieceNameToFen[[...obj.pieceElem?.classList]?.find(x => pieceNameToFen[x])]; return c==='w'?n?.toUpperCase():n?.toLowerCase(); }, 'pieceElemCoords': obj => chessCoordinatesToIndex(obj.pieceElem?.cgKey), 'boardDimensions': () => [8, 8], 'getMutationTurn': obj => { let b=0, w=0; obj.mutationArr.forEach(m => { if(m.target?.classList?.contains('black')) b++; if(m.target?.classList?.contains('white')) w++; }); return b>w?'w':'b'; }, 'isMutationNewMove': obj => [obj.mutationArr.length >= 3, obj.mutationArr.length >= 3 ? getSiteData('getMutationTurn', obj) : null] }); addSupportedChessSite('freechess.club', { 'boardElem': () => document.querySelector('cg-board'), 'pieceElem': obj => obj.boardQuerySelector('piece:not(.ghost)'), 'chessVariant': () => 'chess', 'boardOrientation': () => document.querySelector('coords.files')?.classList?.contains('black') ? 'b' : 'w', 'pieceElemFen': obj => { const c = obj.pieceElem?.classList?.contains('white') ? 'w' : 'b', n = pieceNameToFen[[...obj.pieceElem?.classList]?.find(x => pieceNameToFen[x])]; return c==='w'?n?.toUpperCase():n?.toLowerCase(); }, 'pieceElemCoords': obj => chessCoordinatesToIndex(obj.pieceElem?.cgKey), 'boardDimensions': () => [8, 8], 'getMutationTurn': obj => { let b=0, w=0; obj.mutationArr.forEach(m => { if(m.target?.classList?.contains('black')) b++; if(m.target?.classList?.contains('white')) w++; }); return b>w?'w':'b'; }, 'isMutationNewMove': obj => [obj.mutationArr.length >= 3, obj.mutationArr.length >= 3 ? getSiteData('getMutationTurn', obj) : null] }); addSupportedChessSite('play.chessclub.com', { 'boardElem': () => document.querySelector('[data-boardid]'), 'pieceElem': obj => obj.boardQuerySelector('[data-piece]'), 'chessVariant': () => 'chess', 'boardOrientation': () => document.querySelector('[data-square]')?.dataset?.square === 'a8' ? 'w' : 'b', 'pieceElemFen': obj => { const [c, n] = (obj.pieceElem?.dataset?.piece || 'wp'); return c==='w'?n?.toUpperCase():n?.toLowerCase(); }, 'pieceElemCoords': obj => chessCoordinatesToIndex(obj.pieceElem?.parentElement?.parentElement?.dataset?.square), 'boardDimensions': () => [8, 8], 'getMutationTurn': () => getSiteData('boardOrientation'), 'isMutationNewMove': obj => [obj.mutationArr.find(m => m?.type === 'childList') ? true : false, getSiteData('boardOrientation')] }); addSupportedChessSite('gameknot.com', { 'boardElem': () => document.querySelector('#chess-board-acboard'), 'pieceElem': obj => obj.boardQuerySelector('*[class*="chess-board-piece"] > img[src*="chess56."][style*="visible"]'), 'chessVariant': () => 'chess', 'boardOrientation': () => document.querySelector('#chess-board-my-side-color .player_white') ? 'w' : 'b', 'pieceElemFen': obj => { const l = Number(obj.pieceElem.style.left.replace('px','')), t = Number(obj.pieceElem.style.top.replace('px','')); const c = l>=0?'w':'b', n = 'kqrnbp'[(t*-1)/60]; return c==='w'?n?.toUpperCase():n?.toLowerCase(); }, 'pieceElemCoords': obj => getElemCoordinatesFromLeftTopPixels(obj.pieceElem.parentElement), 'boardDimensions': () => [8, 8], 'getMutationTurn': () => getSiteData('boardOrientation'), 'isMutationNewMove': obj => { const isNewMove = obj.mutationArr.find(m => m.type === 'childList') || obj.mutationArr.find(m => m?.target?.classList?.contains('last-move')); return [isNewMove ? true : false, getSiteData('boardOrientation')]; } }); addSupportedChessSite('app.edchess.io', { 'boardElem': () => document.querySelector('*[data-boardid="chessboard"]'), 'pieceElem': obj => obj.boardQuerySelector('*[data-piece]'), 'chessVariant': () => 'chess', 'boardOrientation': () => document.querySelector('*[data-square]')?.dataset?.square == 'h1' ? 'b' : 'w', 'pieceElemFen': obj => { const [c, n] = obj.pieceElem?.dataset?.piece?.split(''); return c==='w'?n?.toUpperCase():n?.toLowerCase(); }, 'pieceElemCoords': obj => chessCoordinatesToIndex(obj.pieceElem?.parentElement?.parentElement?.dataset?.square), 'boardDimensions': () => [8, 8], 'getMutationTurn': () => getSiteData('boardOrientation'), 'isMutationNewMove': obj => [obj.mutationArr.length >= 2, obj.mutationArr.length >= 2 ? getSiteData('boardOrientation') : null] }); async function isAcasBackendReady() { return await CommLink.commands.ping() ? true : false; } async function refreshSettings() { const config = GM_getValue(dbValues.AcasConfig); const profiles = config?.global?.profiles; if(typeof profiles != 'object') return; isMovesOnDemandActive = Object.keys(profiles).some(profileName => profiles[profileName]?.movesOnDemand === true); } async function start() { await 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(BoardDrawer) BoardDrawer?.terminate(); BoardDrawer = new UniversalBoardDrawer(chessBoardElem, { 'window': 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 }, 'ignoreBodyRectLeft': domain === 'app.edchess.io' }); const waitForBoardMatrix = setInterval(() => { if(lastBoardMatrix) { clearInterval(waitForBoardMatrix); addMovesOnDemandListeners(); } }, 50); } // Check orientation and initialize logic const boardOrientationChanged = lastBoardOrientation !== boardOrientation || (BoardDrawer && BoardDrawer?.orientation !== boardOrientation); if(boardOrientationChanged) { lastBoardOrientation = boardOrientation; instanceVars.playerColor.set(commLinkInstanceID, boardOrientation); boardUtils.setBoardOrientation(boardOrientation); await CommLink.commands.updateBoardOrientation(boardOrientation); CommLink.commands.newMatchStarted(); } refreshSettings(); observeNewMoves(); CommLink.setIntervalAsync(async () => { await CommLink.commands.createInstance(commLinkInstanceID); }, 1000); } function applyAssistanceConcealment(isConcealed = false) { const BoardDrawerSvg = BoardDrawer?.boardContainerElem; if(!BoardDrawerSvg) return; if(isConcealed) BoardDrawerSvg.style.display = 'none'; else BoardDrawerSvg.style.display = 'block'; } function toggleConcealAssistance() { CommLink.commands.toggleConcealAssistance(); } function startWhenBackendReady() { let timesUrlForceOpened = 0; let i = 0; const interval = CommLink.setIntervalAsync(async () => { i++; if(await isAcasBackendReady()) { start(); interval.stop(); } else if(timesUrlForceOpened === 0 && (i % 10 === 0)) { timesUrlForceOpened++; const config = GM_getValue(dbValues.AcasConfig); const isGhost = config?.global?.[configKeys.isUserscriptGhost]; if(!isGhost) GM_openInTab(getCurrentBackendURL(), true); } }, 100); } // V3 iOS Core Optimization -> Mobile/Touch Event Fixes function initializeIfSiteReady() { const boardElem = getBoardElem(); const firstPieceElem = getPieceElem(); const bothElemsExist = boardElem && firstPieceElem; const isChessComImageBoard = domain === 'chess.com' && boardElem?.className.includes('webgl-2d'); const boardElemChanged = chessBoardElem != boardElem; if((bothElemsExist || isChessComImageBoard) && boardElemChanged) { chessBoardElem = boardElem; chessBoardElem.addEventListener('mousedown', () => { isUserMouseDown = true; }); chessBoardElem.addEventListener('mouseup', () => { isUserMouseDown = false; }); chessBoardElem.addEventListener('touchstart', () => { isUserMouseDown = true; }, {passive: true}); chessBoardElem.addEventListener('touchend', () => { isUserMouseDown = false; }, {passive: true}); chessBoardElem.addEventListener('touchcancel', () => { isUserMouseDown = false; }, {passive: true}); if(!blacklistedURLs.includes(window.location.href)) { startWhenBackendReady(); } } } if(typeof GM_registerMenuCommand === 'function') { GM_registerMenuCommand('[o] Open GUI Manually', e => { GM_openInTab(getCurrentBackendURL(), true); }, 'o'); GM_registerMenuCommand('[s] Start Manually', e => { if(chessBoardElem) start(); else displayImportantNotification('Failed to start manually', 'No chessboard element found!'); }, 's'); } setInterval(initializeIfSiteReady, 200); setInterval(refreshSettings, 2500); } catch(e) { /* Error Silenced for Prod */ }})();