🏆 [#1 Chess Assistant] A.C.A.S(高级国际象棋辅助系统)

利用尖端实时走法分析和策略辅助系统,提升您的国际象棋水平

  1. // ==UserScript==
  2. // @name 🏆 [#1 Chess Assistant] A.C.A.S (Advanced Chess Assistance System)
  3. // @name:en 🏆 [#1 Chess Assistant] A.C.A.S (Advanced Chess Assistance System)
  4. // @name:fi 🏆 [#1 Chess Assistant] A.C.A.S (Edistynyt shakkiavustusjärjestelmä)
  5. // @name:sw 🏆 [#1 Chess Assistant] A.C.A.S (Advanserad Schack Assitant System)
  6. // @name:zh-CN 🏆 [#1 Chess Assistant] A.C.A.S(高级国际象棋辅助系统)
  7. // @name:es 🏆 [#1 Chess Assistant] A.C.A.S (Sistema Avanzado de Asistencia al Ajedrez)
  8. // @name:hi 🏆 [#1 Chess Assistant] A.C.A.S (उन्नत शतरंज सहायता प्रणाली)
  9. // @name:ar 🏆 [#1 Chess Assistant] A.C.A.S (نظام المساعدة المتقدم في الشطرنج)
  10. // @name:pt 🏆 [#1 Chess Assistant] A.C.A.S (Sistema Avançado de Assistência ao Xadrez)
  11. // @name:ja 🏆 [#1 Chess Assistant] A.C.A.S(先進的なチェス支援システム)
  12. // @name:de 🏆 [#1 Chess Assistant] A.C.A.S (Fortgeschrittenes Schach-Hilfesystem)
  13. // @name:fr 🏆 [#1 Chess Assistant] A.C.A.S (Système Avancé d'Assistance aux Échecs)
  14. // @name:it 🏆 [#1 Chess Assistant] A.C.A.S (Sistema Avanzato di Assistenza agli Scacchi)
  15. // @name:ko 🏆 [#1 Chess Assistant] A.C.A.S (고급 체스 보조 시스템)
  16. // @name:nl 🏆 [#1 Chess Assistant] A.C.A.S (Geavanceerd Schaakondersteuningssysteem)
  17. // @name:pl 🏆 [#1 Chess Assistant] A.C.A.S (Zaawansowany System Pomocy Szachowej)
  18. // @name:tr 🏆 [#1 Chess Assistant] A.C.A.S (Gelişmiş Satranç Yardım Sistemi)
  19. // @name:vi 🏆 [#1 Chess Assistant] A.C.A.S (Hệ Thống Hỗ Trợ Cờ Vua Nâng Cao)
  20. // @name:uk 🏆 [#1 Chess Assistant] A.C.A.S (Система передової допомоги в шахах)
  21. // @name:ru 🏆 [#1 Chess Assistant] A.C.A.S (Система расширенной помощи в шахматах)
  22. // @description Enhance your chess performance with a cutting-edge real-time move analysis and strategy assistance system
  23. // @description:en Enhance your chess performance with a cutting-edge real-time move analysis and strategy assistance system
  24. // @description:fi Paranna shakkipelisi suorituskykyä huippuluokan reaaliaikaisen siirtoanalyysin ja strategisen avustusjärjestelmän avulla
  25. // @description:sw Förbättra dina schackprestationer med ett banbrytande rörelseanalys i realtid och strategiassistans
  26. // @description:zh-CN 利用尖端实时走法分析和策略辅助系统,提升您的国际象棋水平
  27. // @description:es Mejora tu rendimiento en ajedrez con un sistema de análisis de movimientos en tiempo real y asistencia estratégica de vanguardia
  28. // @description:hi अपने शतरंज प्रदर्शन को उन्नत करें, एक कटिंग-एज रियल-टाइम मूव विश्लेषण और रणनीति सहायता प्रणाली के साथ
  29. // @description:ar قم بتحسين أداءك في الشطرنج مع تحليل حركات اللعب في الوقت الحقيقي ونظام مساعدة استراتيجية حديث
  30. // @description:pt Melhore seu desempenho no xadrez com uma análise de movimentos em tempo real e um sistema avançado de assistência estratégica
  31. // @description:ja 最新のリアルタイムのムーブ分析と戦略支援システムでチェスのパフォーマンスを向上させましょう
  32. // @description:de Verbessern Sie Ihre Schachleistung mit einer hochmodernen Echtzeitzug-Analyse- und Strategiehilfe-System
  33. // @description:fr Améliorez vos performances aux échecs avec une analyse de mouvement en temps réel de pointe et un système d'assistance stratégique
  34. // @description:it Migliora le tue prestazioni agli scacchi con un sistema all'avanguardia di analisi dei movimenti in tempo reale e assistenza strategica
  35. // @description:ko 최첨단 실시간 움직임 분석 및 전략 지원 시스템으로 체스 성과 향상
  36. // @description:nl Verbeter je schaakprestaties met een geavanceerd systeem voor realtime zetanalyse en strategische ondersteuning
  37. // @description:pl Popraw swoje osiągnięcia w szachach dzięki zaawansowanemu systemowi analizy ruchów w czasie rzeczywistym i wsparciu strategicznemu
  38. // @description:tr Keskinleşmiş gerçek zamanlı hareket analizi ve strateji yardım sistemiyle satranç performansınızı artırın
  39. // @description:vi Nâng cao hiệu suất cờ vua của bạn với hệ thống phân tích nước đi và hỗ trợ chiến thuật hiện đại
  40. // @description:uk Покращуйте свою шахову гру з використанням передової системи аналізу ходів в режимі реального часу та стратегічної підтримки
  41. // @description:ru Слава Украине
  42. // @homepageURL https://psyyke.github.io/A.C.A.S
  43. // @supportURL https://github.com/Psyyke/A.C.A.S/tree/main#why-doesnt-it-work
  44. // @match https://psyyke.github.io/A.C.A.S/*
  45. // @match http://localhost/*
  46. // @match https://www.chess.com/*
  47. // @match https://lichess.org/*
  48. // @match https://playstrategy.org/*
  49. // @match https://www.pychess.org/*
  50. // @match https://chess.org/*
  51. // @match https://papergames.io/*
  52. // @match https://vole.wtf/kilobytes-gambit/
  53. // @match https://chess.coolmathgames.com/*
  54. // @match https://www.coolmathgames.com/0-chess/*
  55. // @match https://immortal.game/*
  56. // @match https://chessarena.com/*
  57. // @match http://chess.net/*
  58. // @match https://chess.net/*
  59. // @match https://www.freechess.club/*
  60. // @match https://*.chessclub.com/*
  61. // @match https://gameknot.com/*
  62. // @match https://chesstempo.com/*
  63. // @match https://www.redhotpawn.com/*
  64. // @match https://www.chessanytime.com/*
  65. // @match https://www.simplechess.com/*
  66. // @match https://chessworld.net/*
  67. // @match https://app.edchess.io/*
  68. // @grant GM_getValue
  69. // @grant GM_setValue
  70. // @grant GM_deleteValue
  71. // @grant GM_listValues
  72. // @grant GM_openInTab
  73. // @grant GM.getValue
  74. // @grant GM.setValue
  75. // @grant GM.deleteValue
  76. // @grant GM.listValues
  77. // @grant GM.openInTab
  78. // @grant GM_registerMenuCommand
  79. // @grant GM_setClipboard
  80. // @grant GM_notification
  81. // @grant unsafeWindow
  82. // @run-at document-start
  83. // @require https://update.greasyfork.org/scripts/534637/LegacyGMjs.js?acasv=2
  84. // @require https://update.greasyfork.org/scripts/470418/CommLinkjs.js?acasv=2
  85. // @require https://update.greasyfork.org/scripts/470417/UniversalBoardDrawerjs.js?acasv=1
  86. // @icon https://raw.githubusercontent.com/Psyyke/A.C.A.S/main/assets/images/grey-logo.png
  87. // @version 2.3.1
  88. // @namespace HKR
  89. // @author HKR
  90. // @license GPL-3.0
  91. // ==/UserScript==
  92.  
  93. /*
  94. e e88~-_ e ,d88~~\
  95. d8b d888 \ d8b 8888
  96. /Y88b 8888 /Y88b `Y88b
  97. / Y88b 8888 / Y88b `Y88b,
  98. /____Y88b d88b Y888 / d88b /____Y88b d88b 8888
  99. / Y88b Y88P "88_-~ Y88P / Y88b Y88P \__88P'
  100.  
  101. Advanced Chess Assistance System (A.C.A.S) v2 | Q3 2023
  102.  
  103. [WARNING]
  104. - Please be advised that the use of A.C.A.S may violate the rules and lead to disqualification or banning from tournaments and online platforms.
  105. - The developers of A.C.A.S and related systems will NOT be held accountable for any consequences resulting from its use.
  106. - We strongly advise to use A.C.A.S only in a controlled environment ethically.
  107.  
  108. [ADDITIONAL]
  109. - Big fonts created with: https://www.patorjk.com/software/taag/ (Cyberlarge)
  110.  
  111. JOIN THE DISCUSSION ABOUT USERSCRIPTS IN GENERAL @ https://hakorr.github.io/Userscripts/community/invite ("Userscript Hub")
  112.  
  113. DANGER ZONE - DO NOT PROCEED IF YOU DON'T KNOW WHAT YOU'RE DOING*\
  114. \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
  115. //////////////////////////////////////////////////////////////////
  116. DANGER ZONE - DO NOT PROCEED IF YOU DON'T KNOW WHAT YOU'RE DOING*/
  117.  
  118. (async () => { await LOAD_LEGACY_GM_SUPPORT();
  119. /*
  120. ______ _____ ______ _______
  121. | ____ | | | |_____] |_____| |
  122. |_____| |_____ |_____| |_____] | | |_____
  123.  
  124.  
  125. Code below this point runs on any site, including the GUI.
  126. */
  127.  
  128. const backendConfig = {
  129. 'hosts': { 'prod': 'psyyke.github.io', 'dev': 'localhost' },
  130. 'path': '/A.C.A.S/'
  131. };
  132.  
  133. const currentBackendUrlKey = 'currentBackendURL';
  134. const currentBackendUrl = typeof GM_getValue === 'function'
  135. ? GM_getValue(currentBackendUrlKey)
  136. : await GM.getValue(currentBackendUrlKey);
  137. const isBackendUrlUpToDate = Object.values(backendConfig.hosts).some(x => currentBackendUrl?.includes(x));
  138.  
  139. function constructBackendURL(host) {
  140. const protocol = window.location.protocol + '//';
  141. const hosts = backendConfig.hosts;
  142.  
  143. return protocol + (host || (hosts?.prod || hosts?.path)) + backendConfig.path;
  144. }
  145.  
  146. function isRunningOnBackend(skipGM) {
  147. const hostsArr = Object.values(backendConfig.hosts);
  148.  
  149. const foundHost = hostsArr.find(host => host === window?.location?.host);
  150. const isCorrectPath = window?.location?.pathname?.includes(backendConfig.path);
  151.  
  152. const isBackend = typeof foundHost === 'string' && isCorrectPath;
  153.  
  154. if(isBackend && !skipGM)
  155. GM_setValue(currentBackendUrlKey, constructBackendURL(foundHost));
  156.  
  157. return isBackend;
  158. }
  159.  
  160. // KEEP THESE AS FALSE ON PRODUCTION
  161. const debugModeActivated = false;
  162. const onlyUseDevelopmentBackend = false;
  163.  
  164. const domain = window.location.hostname.replace('www.', '');
  165. const greasyforkURL = 'https://greasyfork.org/en/scripts/459137';
  166.  
  167. function prependProtocolWhenNeeded(url) {
  168. if(!url.startsWith('http://') && !url.startsWith('https://')) {
  169. return 'http://' + url;
  170. }
  171.  
  172. return url;
  173. }
  174.  
  175. function getCurrentBackendURL(skipGmStorage) {
  176. if(onlyUseDevelopmentBackend) {
  177. return constructBackendURL(backendConfig.hosts?.dev);
  178. }
  179.  
  180. const gmStorageUrl = GM_getValue(currentBackendUrlKey);
  181.  
  182. if(skipGmStorage || !gmStorageUrl) {
  183. return constructBackendURL();
  184. }
  185.  
  186. return prependProtocolWhenNeeded(gmStorageUrl);
  187. }
  188.  
  189. if(!isBackendUrlUpToDate) {
  190. GM_setValue(currentBackendUrlKey, getCurrentBackendURL(true));
  191. }
  192.  
  193. function createInstanceVariable(dbValue) {
  194. return {
  195. set: (instanceID, value) => GM_setValue(dbValues[dbValue](instanceID), { value, 'date': Date.now() }),
  196. get: instanceID => {
  197. const data = GM_getValue(dbValues[dbValue](instanceID));
  198.  
  199. if(data?.date) {
  200. data.date = Date.now();
  201.  
  202. GM_setValue(dbValues[dbValue](instanceID), data);
  203. }
  204.  
  205. return data?.value;
  206. }
  207. }
  208. }
  209.  
  210. // If you modify tempValueIndicator or AcasConfig key,
  211. // then modify them on the GUI (acas-globals.js) as well
  212. const tempValueIndicator = '-temp-value-';
  213. const dbValues = {
  214. AcasConfig: 'AcasConfig',
  215. playerColor: instanceID => 'playerColor' + tempValueIndicator + instanceID,
  216. turn: instanceID => 'turn' + tempValueIndicator + instanceID,
  217. fen: instanceID => 'fen' + tempValueIndicator + instanceID
  218. };
  219. // Add them to acas-userscript-bridge.js as well if you,
  220. // decide to add more variables here
  221. const instanceVars = {
  222. playerColor: createInstanceVariable('playerColor'),
  223. fen: createInstanceVariable('fen')
  224. };
  225.  
  226. function exposeViaMessages() {
  227. const handlers = {
  228. USERSCRIPT_getValue: (args, messageId) => {
  229. const [key] = args;
  230. const value = GM_getValue(key);
  231. window.postMessage({ messageId, value }, '*');
  232. },
  233. USERSCRIPT_setValue: (args, messageId) => {
  234. const [key, value] = args;
  235. GM_setValue(key, value);
  236. window.postMessage({ messageId, value: true }, '*');
  237. },
  238. USERSCRIPT_deleteValue: (args, messageId) => {
  239. const [key] = args;
  240. GM_deleteValue(key);
  241. window.postMessage({ messageId, value: true }, '*');
  242. },
  243. USERSCRIPT_listValues: (args, messageId) => {
  244. const value = GM_listValues();
  245. window.postMessage({ messageId, value }, '*');
  246. },
  247. USERSCRIPT_getInfo: (args, messageId) => {
  248. const value = typeof GM_info !== 'undefined' ? JSON.parse(JSON.stringify(GM_info)) : {};
  249. window.postMessage({ messageId, value }, '*');
  250. },
  251. USERSCRIPT_instanceVars: (args, messageId) => {
  252. const [instanceId, key, value] = args;
  253.  
  254. if (!instanceVars.hasOwnProperty(key)) {
  255. window.postMessage({ messageId, value: false }, '*');
  256. return;
  257. }
  258.  
  259. const result = (value !== undefined)
  260. ? instanceVars[key].set(instanceId, value)
  261. : instanceVars[key].get(instanceId);
  262.  
  263. window.postMessage({ messageId, value: result }, '*');
  264. }
  265. };
  266.  
  267. window.addEventListener('message', (event) => {
  268. const handler = handlers[event.data?.type];
  269. if(handler) handler(event.data.args, event.data.messageId);
  270. });
  271.  
  272. const script = document.createElement('script');
  273. script.innerHTML = 'window.isUserscriptActive = true;';
  274.  
  275. document.head.appendChild(script);
  276. }
  277.  
  278. function exposeViaUnsafe() {
  279. if(typeof unsafeWindow !== 'object') return;
  280.  
  281. unsafeWindow.USERSCRIPT = {
  282. 'getValue': val => GM_getValue(val),
  283. 'setValue': (val, data) => GM_setValue(val, data),
  284. 'deleteValue': val => GM_deleteValue(val),
  285. 'listValues': val => GM_listValues(val),
  286. 'instanceVars': instanceVars,
  287. 'getInfo': () => GM_info
  288. };
  289.  
  290. unsafeWindow.isUserscriptActive = true;
  291. }
  292.  
  293. if(isRunningOnBackend()) {
  294. if(typeof unsafeWindow === 'object')
  295. exposeViaUnsafe();
  296. else
  297. exposeViaMessages();
  298.  
  299. return;
  300. }
  301.  
  302. /*
  303. _______ _ _ _______ _______ _______ _______ _____ _______ _______ _______
  304. | |_____| |______ |______ |______ |______ | | |______ |______
  305. |_____ | | |______ ______| ______| ______| __|__ | |______ ______|
  306.  
  307.  
  308. Code below this point only runs on chess sites, not on the GUI itself.
  309. */
  310.  
  311. function getUniqueID() {
  312. return ([1e7]+-1e3+4e3+-8e3+-1e11).replace(/[018]/g, c =>
  313. (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
  314. )
  315. }
  316.  
  317. const commLinkInstanceID = getUniqueID();
  318.  
  319. const blacklistedURLs = [
  320. constructBackendURL(backendConfig?.hosts?.prod),
  321. constructBackendURL(backendConfig?.hosts?.dev),
  322. 'https://www.chess.com/play',
  323. 'https://lichess.org/',
  324. 'https://chess.org/',
  325. 'https://papergames.io/en/chess',
  326. 'https://playstrategy.org/',
  327. 'https://www.pychess.org/',
  328. 'https://www.coolmathgames.com/0-chess',
  329. 'https://chess.net/'
  330. ];
  331.  
  332. const configKeys = {
  333. 'engineElo': 'engineElo',
  334. 'moveSuggestionAmount': 'moveSuggestionAmount',
  335. 'arrowOpacity': 'arrowOpacity',
  336. 'displayMovesOnExternalSite': 'displayMovesOnExternalSite',
  337. 'showMoveGhost': 'showMoveGhost',
  338. 'showOpponentMoveGuess': 'showOpponentMoveGuess',
  339. 'showOpponentMoveGuessConstantly': 'showOpponentMoveGuessConstantly',
  340. 'onlyShowTopMoves': 'onlyShowTopMoves',
  341. 'maxMovetime': 'maxMovetime',
  342. 'chessVariant': 'chessVariant',
  343. 'chessEngine': 'chessEngine',
  344. 'lc0Weight': 'lc0Weight',
  345. 'engineNodes': 'engineNodes',
  346. 'chessFont': 'chessFont',
  347. 'useChess960': 'useChess960',
  348. 'onlyCalculateOwnTurn': 'onlyCalculateOwnTurn',
  349. 'ttsVoiceEnabled': 'ttsVoiceEnabled',
  350. 'ttsVoiceName': 'ttsVoiceName',
  351. 'ttsVoiceSpeed': 'ttsVoiceSpeed',
  352. 'chessEngineProfile': 'chessEngineProfile',
  353. 'primaryArrowColorHex': 'primaryArrowColorHex',
  354. 'secondaryArrowColorHex': 'secondaryArrowColorHex',
  355. 'opponentArrowColorHex': 'opponentArrowColorHex',
  356. 'reverseSide': 'reverseSide',
  357. 'autoMove': 'autoMove',
  358. 'autoMoveLegit': 'autoMoveLegit',
  359. 'autoMoveRandom': 'autoMoveRandom',
  360. 'autoMoveAfterUser': 'autoMoveAfterUser',
  361. 'legitModeType': 'legitModeType',
  362. 'moveDisplayDelay': 'moveDisplayDelay',
  363. 'renderOnExternalSite': 'renderOnExternalSite',
  364. 'feedbackOnExternalSite': 'feedbackOnExternalSite'
  365. };
  366.  
  367. const config = {};
  368.  
  369. Object.values(configKeys).forEach(key => {
  370. config[key] = {
  371. get: profile => getGmConfigValue(key, commLinkInstanceID, profile),
  372. set: null
  373. };
  374. });
  375.  
  376. let BoardDrawer = null;
  377. let chessBoardElem = null;
  378. let chesscomVariantPlayerColorsTable = null;
  379. let activeGuiMoveMarkings = [];
  380. let activeMetricRenders = [];
  381. let activeFeedback = [];
  382.  
  383. let lastBoardRanks = null;
  384. let lastBoardFiles = null;
  385.  
  386. let lastBoardSize = null;
  387. let lastPieceSize = null;
  388.  
  389. let lastBoardMatrix = null;
  390. let lastBoardOrientation = null;
  391.  
  392. let matchFirstSuggestionGiven = false;
  393.  
  394. let lastMoveRequestTime = 0;
  395. let lastPieceAmount = 0;
  396.  
  397. let isUserMouseDown = false;
  398. let activeAutomoves = [];
  399.  
  400. const supportedSites = {};
  401.  
  402. const pieceNameToFen = {
  403. 'pawn': 'p',
  404. 'knight': 'n',
  405. 'bishop': 'b',
  406. 'rook': 'r',
  407. 'queen': 'q',
  408. 'king': 'k'
  409. };
  410.  
  411. function getArrowStyle(type, fill, opacity) {
  412. const baseStyleArr = [
  413. 'stroke: rgb(0 0 0 / 50%);',
  414. 'stroke-width: 2px;',
  415. 'stroke-linejoin: round;'
  416. ];
  417.  
  418. switch(type) {
  419. case 'best':
  420. return [
  421. `fill: ${fill || 'limegreen'};`,
  422. `opacity: ${opacity || 0.9};`,
  423. ...baseStyleArr
  424. ].join('\n');
  425. case 'secondary':
  426. return [
  427. ...baseStyleArr,
  428. `fill: ${fill ? fill : 'dodgerblue'};`,
  429. `opacity: ${opacity || 0.7};`,
  430. ].join('\n');
  431. case 'opponent':
  432. return [
  433. ...baseStyleArr,
  434. `fill: ${fill ? fill : 'crimson'};`,
  435. `opacity: ${opacity || 0.3};`,
  436. ].join('\n');
  437. }
  438. };
  439.  
  440. const CommLink = new CommLinkHandler(`frontend_${commLinkInstanceID}`, {
  441. 'singlePacketResponseWaitTime': 1500,
  442. 'maxSendAttempts': 3,
  443. 'statusCheckInterval': 1,
  444. 'silentMode': true
  445. });
  446.  
  447. // manually register a command so that the variables are dynamic
  448. CommLink.commands['createInstance'] = async () => {
  449. return await CommLink.send('mum', 'createInstance', {
  450. 'domain': domain,
  451. 'instanceID': commLinkInstanceID,
  452. 'chessVariant': getChessVariant(),
  453. 'playerColor': getPlayerColorVariable()
  454. });
  455. }
  456.  
  457. CommLink.registerSendCommand('ping', { commlinkID: 'mum', data: 'ping' });
  458. CommLink.registerSendCommand('pingInstance', { data: 'ping' });
  459. CommLink.registerSendCommand('log');
  460. CommLink.registerSendCommand('updateBoardOrientation');
  461. CommLink.registerSendCommand('updateBoardFen');
  462. CommLink.registerSendCommand('calculateBestMoves');
  463.  
  464. CommLink.registerListener(`backend_${commLinkInstanceID}`, packet => {
  465. try {
  466. switch(packet.command) {
  467. case 'ping':
  468. return `pong (took ${Date.now() - packet.date}ms)`;
  469. case 'getFen':
  470. return getFen();
  471. case 'removeSiteMoveMarkings':
  472. boardUtils.removeMarkings();
  473. return true;
  474. case 'markMoveToSite':
  475. boardUtils.markMoves(packet.data);
  476.  
  477. const profile = packet.data?.[0]?.profile;
  478. const isAutoMove = getConfigValue(configKeys.autoMove, profile);
  479. const isAutoMoveAfterUser = getConfigValue(configKeys.autoMoveAfterUser, profile);
  480.  
  481. if (isAutoMove && (!isAutoMoveAfterUser || matchFirstSuggestionGiven)) {
  482. const existingAutomoves = activeAutomoves.filter(x => x.move.active);
  483.  
  484. // Stop all existing automoves
  485. for(const x of existingAutomoves) {
  486. x.move.stop();
  487. }
  488.  
  489. const isLegit = getConfigValue(configKeys.autoMoveLegit, profile);
  490. const isRandom = getConfigValue(configKeys.autoMoveRandom, profile);
  491.  
  492. const move = isRandom
  493. ? packet.data[Math.floor(Math.random() * Math.random() * packet.data.length)]?.player
  494. : packet.data[0]?.player;
  495.  
  496. makeMove(profile, move, isLegit);
  497. }
  498.  
  499. matchFirstSuggestionGiven = true;
  500.  
  501. return true;
  502. case 'renderMetricsToSite':
  503. renderMetrics(packet.data);
  504. return true;
  505. case 'feedbackToSite':
  506. displayFeedback(packet.data);
  507. return true;
  508. }
  509. } catch(e) {
  510. return null;
  511. }
  512. });
  513.  
  514. function clearMetricRenders() {
  515. activeMetricRenders.forEach(elem => {
  516. if(elem) elem?.remove();
  517. });
  518. }
  519.  
  520. function renderMetrics(addedMetrics) {
  521. clearMetricRenders();
  522.  
  523. function processMetric(metric) {
  524. const data = metric?.data;
  525.  
  526. if(!data) return;
  527.  
  528. const shapeType = data?.shapeType;
  529. const shapeSquare = data?.shapeSquare;
  530. const shapeConfig = data?.shapeConfig;
  531.  
  532. if(shapeType && shapeSquare && shapeConfig) {
  533. const shape = BoardDrawer.createShape(shapeType, shapeSquare, shapeConfig);
  534.  
  535. activeMetricRenders.push(shape);
  536. }
  537. }
  538.  
  539. function findMetricByType(type) {
  540. return addedMetrics.filter(metric => metric?.data?.shapeType === type) || [];
  541. }
  542.  
  543. findMetricByType('text')
  544. .forEach(processMetric);
  545.  
  546. findMetricByType('rectangle')
  547. .forEach(processMetric);
  548. }
  549.  
  550. function clearFeedback() {
  551. activeFeedback.forEach(elem => {
  552. if(elem) elem?.remove();
  553. });
  554. }
  555.  
  556.  
  557. function displayFeedback(addedFeedback) {
  558. clearFeedback();
  559.  
  560. function processFeedback(feedback) {
  561. const data = feedback?.data;
  562.  
  563. if(!data) return;
  564.  
  565. const shapeType = data?.shapeType;
  566. const shapeSquare = data?.shapeSquare;
  567. const shapeConfig = data?.shapeConfig;
  568.  
  569. if(shapeType && shapeSquare && shapeConfig) {
  570. const shape = BoardDrawer.createShape(shapeType, shapeSquare, shapeConfig);
  571.  
  572. activeFeedback.push(shape);
  573. }
  574. }
  575.  
  576. addedFeedback.forEach(processFeedback);
  577. }
  578.  
  579. const boardUtils = {
  580. markMoves: moveObjArr => {
  581. const maxScale = 1;
  582. const minScale = 0.5;
  583. const totalRanks = moveObjArr.length;
  584.  
  585. moveObjArr.forEach((markingObj, idx) => {
  586. const profile = markingObj.profile;
  587.  
  588. if(idx === 0)
  589. boardUtils.removeMarkings(profile);
  590.  
  591. const [from, to] = markingObj.player;
  592. const [oppFrom, oppTo] = markingObj.opponent;
  593. const oppMovesExist = oppFrom && oppTo;
  594. const rank = idx + 1;
  595.  
  596. const showOpponentMoveGuess = getConfigValue(configKeys.showOpponentMoveGuess, profile);
  597. const showOpponentMoveGuessConstantly = getConfigValue(configKeys.showOpponentMoveGuessConstantly, profile);
  598.  
  599. const arrowOpacity = getConfigValue(configKeys.arrowOpacity, profile) / 100;
  600. const primaryArrowColorHex = getConfigValue(configKeys.primaryArrowColorHex, profile);
  601. const secondaryArrowColorHex = getConfigValue(configKeys.secondaryArrowColorHex, profile);
  602. const opponentArrowColorHex = getConfigValue(configKeys.opponentArrowColorHex, profile);
  603.  
  604. let playerArrowElem = null;
  605. let oppArrowElem = null;
  606. let arrowStyle = getArrowStyle('best', primaryArrowColorHex, arrowOpacity);
  607. let lineWidth = 30;
  608. let arrowheadWidth = 80;
  609. let arrowheadHeight = 60;
  610. let startOffset = 30;
  611.  
  612. if(idx !== 0) {
  613. arrowStyle = getArrowStyle('secondary', secondaryArrowColorHex, arrowOpacity);
  614.  
  615. const arrowScale = totalRanks === 2
  616. ? 0.75
  617. : maxScale - (maxScale - minScale) * ((rank - 1) / (totalRanks - 1));
  618.  
  619. lineWidth = lineWidth * arrowScale;
  620. arrowheadWidth = arrowheadWidth * arrowScale;
  621. arrowheadHeight = arrowheadHeight * arrowScale;
  622. startOffset = startOffset;
  623. }
  624.  
  625. playerArrowElem = BoardDrawer.createShape('arrow', [from, to],
  626. {
  627. style: arrowStyle,
  628. lineWidth, arrowheadWidth, arrowheadHeight, startOffset
  629. }
  630. );
  631.  
  632. if(oppMovesExist && showOpponentMoveGuess) {
  633. oppArrowElem = BoardDrawer.createShape('arrow', [oppFrom, oppTo],
  634. {
  635. style: getArrowStyle('opponent', opponentArrowColorHex, arrowOpacity),
  636. lineWidth, arrowheadWidth, arrowheadHeight, startOffset
  637. }
  638. );
  639.  
  640. if(showOpponentMoveGuessConstantly) {
  641. oppArrowElem.style.display = 'block';
  642. } else {
  643. const squareListener = BoardDrawer.addSquareListener(from, type => {
  644. if(!oppArrowElem) {
  645. squareListener.remove();
  646. }
  647.  
  648. switch(type) {
  649. case 'enter':
  650. oppArrowElem.style.display = 'inherit';
  651. break;
  652. case 'leave':
  653. oppArrowElem.style.display = 'none';
  654. break;
  655. }
  656. });
  657. }
  658. }
  659.  
  660. if(idx === 0 && playerArrowElem) {
  661. const parentElem = playerArrowElem.parentElement;
  662.  
  663. // move best arrow element on top (multiple same moves can hide the best move)
  664. parentElem.appendChild(playerArrowElem);
  665.  
  666. if(oppArrowElem) {
  667. parentElem.appendChild(oppArrowElem);
  668. }
  669. }
  670.  
  671. activeGuiMoveMarkings.push({ ...markingObj, playerArrowElem, oppArrowElem, profile });
  672. });
  673. },
  674. removeMarkings: profile => {
  675. let removalArr = activeGuiMoveMarkings;
  676.  
  677. if(profile) {
  678. removalArr = removalArr.filter(obj => obj.profile === profile);
  679.  
  680. activeGuiMoveMarkings = activeGuiMoveMarkings.filter(obj => obj.profile !== profile);
  681. } else {
  682. activeGuiMoveMarkings = [];
  683. }
  684.  
  685. removalArr.forEach(markingObj => {
  686. markingObj.oppArrowElem?.remove();
  687. markingObj.playerArrowElem?.remove();
  688. });
  689. },
  690. setBoardOrientation: orientation => {
  691. if(BoardDrawer) {
  692. if(debugModeActivated) console.warn('setBoardOrientation', orientation);
  693.  
  694. BoardDrawer.setOrientation(orientation);
  695. }
  696. },
  697. setBoardDimensions: dimensionArr => {
  698. if(BoardDrawer) {
  699. if(debugModeActivated) console.warn('setBoardDimensions', dimensionArr);
  700.  
  701. BoardDrawer.setBoardDimensions(dimensionArr);
  702. }
  703. }
  704. };
  705.  
  706. function displayImportantNotification(title, text) {
  707. if(typeof GM_notification === 'function') {
  708. GM_notification({ title: title, text: text });
  709. } else {
  710. alert(`[${title}]` + '\n\n' + text);
  711. }
  712. }
  713.  
  714. function filterInvisibleElems(elementArr, inverse) {
  715. return [...elementArr].filter(elem => {
  716. const style = getComputedStyle(elem);
  717. const bounds = elem.getBoundingClientRect();
  718.  
  719. const isHidden =
  720. style.visibility === 'hidden' ||
  721. style.display === 'none' ||
  722. style.opacity === '0' ||
  723. bounds.width == 0 ||
  724. bounds.height == 0;
  725.  
  726. return inverse ? isHidden : !isHidden;
  727. });
  728. }
  729.  
  730. function getElementSize(elem) {
  731. const rect = elem.getBoundingClientRect();
  732.  
  733. if(rect.width !== 0 && rect.height !== 0) {
  734. return { width: rect.width, height: rect.height };
  735. }
  736.  
  737. const computedStyle = window.getComputedStyle(elem);
  738. const width = parseFloat(computedStyle.width);
  739. const height = parseFloat(computedStyle.height);
  740.  
  741. return { width, height };
  742. }
  743.  
  744. function extractElemTransformData(elem) {
  745. const computedStyle = window.getComputedStyle(elem);
  746. const transformMatrix = new DOMMatrix(computedStyle.transform);
  747.  
  748. const x = transformMatrix.e;
  749. const y = transformMatrix.f;
  750.  
  751. return [x, y];
  752. }
  753.  
  754. function getElemCoordinatesFromTransform(elem, config) {
  755. const onlyFlipX = config?.onlyFlipX;
  756. const onlyFlipY = config?.onlyFlipY;
  757.  
  758. lastBoardSize = getElementSize(chessBoardElem);
  759.  
  760. const [files, ranks] = getBoardDimensions();
  761.  
  762. lastBoardRanks = ranks;
  763. lastBoardFiles = files;
  764.  
  765. const boardOrientation = getPlayerColorVariable();
  766.  
  767. let [x, y] = extractElemTransformData(elem);
  768.  
  769. const boardDimensions = lastBoardSize;
  770. let squareDimensions = boardDimensions.width / lastBoardRanks;
  771.  
  772. const normalizedX = Math.round(x / squareDimensions);
  773. const normalizedY = Math.round(y / squareDimensions);
  774.  
  775. if(onlyFlipY || boardOrientation === 'w') {
  776. const flippedY = lastBoardFiles - normalizedY - 1;
  777.  
  778. return [normalizedX, flippedY];
  779. } else {
  780. const flippedX = lastBoardRanks - normalizedX - 1;
  781.  
  782. return [flippedX, normalizedY];
  783. }
  784. }
  785.  
  786. function getElemCoordinatesFromLeftBottomPercentages(elem) {
  787. if(!lastBoardRanks || !lastBoardFiles) {
  788. const [files, ranks] = getBoardDimensions();
  789.  
  790. lastBoardRanks = ranks;
  791. lastBoardFiles = files;
  792. }
  793.  
  794. const boardOrientation = getPlayerColorVariable();
  795.  
  796. const leftPercentage = parseFloat(elem.style.left?.replace('%', ''));
  797. const bottomPercentage = parseFloat(elem.style.bottom?.replace('%', ''));
  798.  
  799. const x = Math.max(Math.round(leftPercentage / (100 / lastBoardRanks)), 0);
  800. const y = Math.max(Math.round(bottomPercentage / (100 / lastBoardFiles)), 0);
  801.  
  802. if (boardOrientation === 'w') {
  803. return [x, y];
  804. } else {
  805. const flippedX = lastBoardRanks - (x + 1);
  806. const flippedY = lastBoardFiles - (y + 1);
  807.  
  808. return [flippedX, flippedY];
  809. }
  810. }
  811.  
  812. function getElemCoordinatesFromLeftTopPixels(elem) {
  813. const pieceSize = getElementSize(elem);
  814.  
  815. lastPieceSize = pieceSize;
  816.  
  817. const leftPixels = parseFloat(elem.style.left?.replace('px', ''));
  818. const topPixels = parseFloat(elem.style.top?.replace('px', ''));
  819.  
  820. const x = Math.max(Math.round(leftPixels / pieceSize.width), 0);
  821. const y = Math.max(Math.round(topPixels / pieceSize.width), 0);
  822.  
  823. const boardOrientation = getPlayerColorVariable();
  824.  
  825. if (boardOrientation === 'w') {
  826. const flippedY = lastBoardFiles - (y + 1);
  827.  
  828. return [x, flippedY];
  829. } else {
  830. const flippedX = lastBoardRanks - (x + 1);
  831.  
  832. return [flippedX, y];
  833. }
  834. }
  835.  
  836. function updateChesscomVariantPlayerColorsTable() {
  837. let colors = [];
  838.  
  839. document.querySelectorAll('*[data-color]').forEach(pieceElem => {
  840. const colorCode = Number(pieceElem?.dataset?.color);
  841.  
  842. if(!colors?.includes(colorCode)) {
  843. colors.push(colorCode);
  844. }
  845. });
  846.  
  847. if(colors?.length > 1) {
  848. colors = colors.sort((a, b) => a - b);
  849.  
  850. chesscomVariantPlayerColorsTable = { [colors[0]]: 'w', [colors[1]]: 'b' };
  851. }
  852. }
  853.  
  854. function getBoardDimensionsFromSize() {
  855. const boardDimensions = getElementSize(chessBoardElem);
  856.  
  857. lastBoardSize = boardDimensions;
  858.  
  859. const boardWidth = boardDimensions?.width;
  860. const boardHeight = boardDimensions.height;
  861.  
  862. const boardPiece = getPieceElem();
  863.  
  864. if(boardPiece) {
  865. const pieceDimensions = getElementSize(boardPiece);
  866.  
  867. lastPieceSize = getElementSize(boardPiece);
  868.  
  869. const boardPieceWidth = pieceDimensions?.width;
  870. const boardPieceHeight = pieceDimensions?.height;
  871.  
  872. const boardRanks = Math.floor(boardWidth / boardPieceWidth);
  873. const boardFiles = Math.floor(boardHeight / boardPieceHeight);
  874.  
  875. const ranksInAllowedRange = 0 < boardRanks && boardRanks <= 69;
  876. const filesInAllowedRange = 0 < boardFiles && boardFiles <= 69;
  877.  
  878. if(ranksInAllowedRange && filesInAllowedRange) {
  879. return [boardRanks, boardFiles];
  880. }
  881. }
  882. }
  883.  
  884. function chessCoordinatesToIndex(coord) {
  885. const x = coord.charCodeAt(0) - 97;
  886. let y = null;
  887.  
  888. const lastHalf = coord.slice(1);
  889.  
  890. if(lastHalf === ':') {
  891. y = 9;
  892. } else {
  893. y = Number(coord.slice(1)) - 1;
  894. }
  895.  
  896. return [x, y];
  897. }
  898.  
  899. /* Need to make the board matricies more cohesive, right now it's really confusing flipping them
  900. * differently for each function. I just can't be bothered right now so please don't make fun of it.
  901. * Thanks, Haka
  902. * */
  903.  
  904. function chessCoordinatesToMatrixIndex(coord) {
  905. const [boardRanks, boardFiles] = getBoardDimensions();
  906. const indexArr = chessCoordinatesToIndex(coord);
  907.  
  908. let x, y;
  909.  
  910. y = boardFiles - (indexArr[1] + 1);
  911. x = indexArr[0];
  912.  
  913. return [x, y];
  914. }
  915.  
  916. function chessCoordinatesToDomIndex(coord) {
  917. const [boardRanks, boardFiles] = getBoardDimensions();
  918. const indexArr = chessCoordinatesToIndex(coord);
  919. const boardOrientation = getBoardOrientation();
  920.  
  921. let x, y;
  922.  
  923. if(boardOrientation === 'w') {
  924. x = indexArr[0];
  925. y = boardFiles - (indexArr[1] + 1);
  926. } else {
  927. x = boardRanks - (indexArr[0] + 1);
  928. y = indexArr[1];
  929. }
  930.  
  931. return [x, y];
  932. }
  933.  
  934. function indexToChessCoordinates(coord) {
  935. const [boardRanks, boardFiles] = getBoardDimensions();
  936. const boardOrientation = getBoardOrientation();
  937.  
  938. const [x, y] = coord;
  939. const file = String.fromCharCode('a'.charCodeAt(0) + x);
  940.  
  941. let rank;
  942.  
  943. if (boardOrientation === 'w') {
  944. rank = boardRanks - y;
  945. } else {
  946. rank = boardRanks - y;
  947. }
  948.  
  949. return `${file}${rank}`;
  950. }
  951.  
  952. function isPawnPromotion(bestMove) {
  953. const [fenCoordFrom, fenCoordTo] = bestMove;
  954. const piece = getBoardPiece(fenCoordFrom);
  955.  
  956. if(typeof piece !== 'string' || piece.toLowerCase() !== 'p')
  957. return false;
  958.  
  959. // Determine the row from the ending coordinate (assumes standard algebraic notation, e.g., 'e8')
  960. const endingRow = parseInt(fenCoordTo[1], 10);
  961.  
  962. // Check if the pawn reaches the promotion row
  963. if ((piece === 'P' && endingRow === (lastBoardFiles ?? 8)) || (piece === 'p' && endingRow === 1)) {
  964. return true;
  965. }
  966.  
  967. return false;
  968. }
  969.  
  970.  
  971. function fenCoordArrToDomCoord(fenCoordArr) {
  972. // fenCoordArr e.g. ["e6", "e5"]
  973.  
  974. const boardClientRect = chessBoardElem.getBoundingClientRect();
  975.  
  976. const pieceElem = getPieceElem();
  977. const pieceDimensions = getElementSize(pieceElem);
  978. const pieceWidth = pieceDimensions?.width;
  979. const pieceHeight = pieceDimensions?.height;
  980.  
  981. lastPieceSize = pieceDimensions;
  982.  
  983. const [boardRanks, boardFiles] = getBoardDimensions();
  984.  
  985. // Array to hold the center coordinates of each square
  986. const centerCoordinates = fenCoordArr.map(coord => {
  987. const [x, y] = chessCoordinatesToDomIndex(coord);
  988.  
  989. const centerX = boardClientRect.x + (x * pieceWidth) + (pieceWidth / 2);
  990. const centerY = boardClientRect.y + (y * pieceHeight) + (pieceHeight / 2);
  991.  
  992. return [centerX, centerY];
  993. });
  994.  
  995. return centerCoordinates;
  996. }
  997.  
  998. function getRandomOwnPieceDomCoord(fenCoord, boardMatrix) {
  999. const boardOrientation = getBoardOrientation();
  1000. let [x, y] = chessCoordinatesToMatrixIndex(fenCoord);
  1001.  
  1002. const pieceAtFenCoord = boardMatrix[y][x];
  1003.  
  1004. if(pieceAtFenCoord === 1) {
  1005. return null;
  1006. }
  1007.  
  1008. const isWhitePiece = pieceAtFenCoord === pieceAtFenCoord.toUpperCase();
  1009.  
  1010. const getDistance = (row1, col1, row2, col2) => {
  1011. return Math.abs(row1 - row2) + Math.abs(col1 - col2);
  1012. };
  1013.  
  1014. let candidatePieces = [];
  1015.  
  1016. // Loop through the board matrix to find all close own pieces
  1017. for(let row = 0; row < boardMatrix.length; row++) {
  1018. for(let col = 0; col < boardMatrix[row].length; col++) {
  1019. const currentPiece = boardMatrix[row][col];
  1020.  
  1021. // Skip if no piece is found or if the piece is of the wrong color
  1022. if(currentPiece === 1 || (isWhitePiece && currentPiece === currentPiece.toLowerCase()) || (!isWhitePiece && currentPiece === currentPiece.toUpperCase())) {
  1023. continue;
  1024. }
  1025.  
  1026. const distance = getDistance(y, x, row, col);
  1027.  
  1028. if(distance < 6) {
  1029. candidatePieces.push({ distance, coord: [col, row], piece: currentPiece });
  1030. }
  1031. }
  1032. }
  1033.  
  1034. if (candidatePieces.length > 0) {
  1035. // Choose a random piece from the candidates
  1036. const randomIndex = Math.floor(Math.random() * candidatePieces.length);
  1037. const chosenPiece = candidatePieces[randomIndex];
  1038.  
  1039. return fenCoordArrToDomCoord([indexToChessCoordinates(chosenPiece.coord)])[0];
  1040. }
  1041.  
  1042. return null;
  1043. }
  1044.  
  1045. function getPieceAmount() {
  1046. return getPieceElem(true)?.length ?? 0;
  1047. }
  1048.  
  1049. class AutomaticMove {
  1050. constructor(profile, fenMoveArr, isLegit, callback) {
  1051. this.id = getUniqueID();
  1052.  
  1053. // activeAutomoves is an external variable, not a child of AutomaticMove
  1054. activeAutomoves.push({ 'id': this.id, 'move': this });
  1055.  
  1056. this.profile = profile;
  1057. this.fenMoveArr = fenMoveArr;
  1058. this.isLegit = isLegit;
  1059.  
  1060. this.active = true;
  1061. this.isPromotingPawn = false;
  1062.  
  1063. this.onFinished = function(...args) {
  1064. activeAutomoves.filter(x => x.id !== this.id); // remove the move from the active automove list
  1065.  
  1066. this.active = false;
  1067.  
  1068. callback(...args);
  1069. };
  1070.  
  1071. this.moveDomCoords = fenCoordArrToDomCoord(fenMoveArr);
  1072. this.isPromotion = isPawnPromotion(fenMoveArr);
  1073.  
  1074. if(this.isLegit) {
  1075. const legitModeType = getConfigValue(configKeys.legitModeType, this.profile) ?? 'casual';
  1076.  
  1077. const pieceRanges = [
  1078. { minPieces: 30, maxPieces: Infinity }, // Opening (60+ pieces)
  1079. { minPieces: 23, maxPieces: 29 }, // Early Middlegame (48 to 64 pieces)
  1080. { minPieces: 16, maxPieces: 22 }, // Mid Middlegame (32 to 48 pieces)
  1081. { minPieces: 10, maxPieces: 15 }, // Late Middlegame (16 to 32 pieces)
  1082. { minPieces: 6, maxPieces: 9 }, // Endgame (8 to 16 pieces)
  1083. { minPieces: 3, maxPieces: 5 }, // Very Endgame (2 to 8 pieces)
  1084. { minPieces: 1, maxPieces: 2 }, // Extremely Few Pieces (1 piece)
  1085. ];
  1086.  
  1087. const timeRanges = {
  1088. beginner: [
  1089. [2000, 4000],
  1090. [3000, 15000],
  1091. [5000, 25000],
  1092. [4000, 30000],
  1093. [3000, 15000],
  1094. [2000, 10000],
  1095. [1000, 4000],
  1096. ],
  1097. casual: [
  1098. [900, 3000], // Opening
  1099. [1000, 15000], // Early Middlegame
  1100. [3000, 20000], // Mid Middlegame
  1101. [2000, 13000], // Late Middlegame
  1102. [1500, 10000], // Endgame
  1103. [1000, 9000], // Very Endgame
  1104. [500, 3000], // Extremely Few Pieces
  1105. ],
  1106. intermediate: [
  1107. [750, 2000],
  1108. [1000, 10000],
  1109. [2000, 15000],
  1110. [1500, 12000],
  1111. [1000, 8000],
  1112. [750, 7000],
  1113. [500, 2000],
  1114. ],
  1115. advanced: [
  1116. [500, 1500],
  1117. [1000, 8000],
  1118. [750, 8000],
  1119. [750, 12000],
  1120. [750, 5000],
  1121. [750, 3000],
  1122. [500, 1200],
  1123. ],
  1124. master: [
  1125. [333, 999],
  1126. [400, 2000],
  1127. [400, 3000],
  1128. [400, 2500],
  1129. [400, 2000],
  1130. [400, 1500],
  1131. [333, 750],
  1132. ],
  1133. professional: [
  1134. [333, 666],
  1135. [333, 666],
  1136. [333, 1000],
  1137. [333, 1500],
  1138. [333, 1000],
  1139. [333, 666],
  1140. [333, 666],
  1141. ],
  1142. god: [
  1143. [50, 333],
  1144. [50, 233],
  1145. [50, 300],
  1146. [50, 250],
  1147. [50, 200],
  1148. [50, 150],
  1149. [50, 100],
  1150. ]
  1151. };
  1152.  
  1153. this.timeRanges = pieceRanges.map((range, index) => ({
  1154. ...range,
  1155. timeRange: timeRanges[legitModeType][index],
  1156. }));
  1157.  
  1158. this.shouldHesitate = this.isLegit && Math.random() < 0.15;
  1159. this.shouldHesitateTwice = this.isLegit && Math.random() < 0.25;
  1160. this.hesitationTypeOne = this.isLegit && Math.random() < 0.35;
  1161.  
  1162. const legitTotalMoveTime = this.calculateMoveTime(getPieceAmount());
  1163. const elapsedMoveTime = (Date.now() - lastMoveRequestTime); // How long did it take for the engine to calculate the move
  1164. const remainingTime = Math.max(legitTotalMoveTime - elapsedMoveTime, 500);
  1165.  
  1166. const delays = this.generateDelaysForDesiredTime(remainingTime);
  1167.  
  1168. for(const key of Object.keys(delays)) {
  1169. this[key] = delays[key];
  1170. }
  1171. }
  1172.  
  1173. this.start();
  1174. }
  1175.  
  1176. generateDelaysForDesiredTime(desiredTotalTime) {
  1177. // Fixed minimum values
  1178. const PROMOTION_DELAY = this.getRandomIntegerBetween(1000, 1111); // just can't be done very fast for some reason at least on Chess.com
  1179.  
  1180. if(desiredTotalTime > 6000) {
  1181. const timelines = [
  1182. { move: .4, to: .2, hesitation: .15, hesitationResolve: .15, secondHesitationResolve: .15 },
  1183. { move: .1, to: .3, hesitation: .25, hesitationResolve: .15, secondHesitationResolve: .2 },
  1184. { move: .2, to: .25, hesitation: .2, hesitationResolve: .2, secondHesitationResolve: .15 }
  1185. ];
  1186.  
  1187. const timeline = timelines[Math.floor(Math.random() * timelines.length)];
  1188.  
  1189. return {
  1190. promotionDelay: PROMOTION_DELAY,
  1191. moveDelay: desiredTotalTime * timeline.move,
  1192. toSquareSelectDelay: desiredTotalTime * timeline.to,
  1193. hesitationDelay: desiredTotalTime * timeline.hesitation,
  1194. hesitationResolveDelay: desiredTotalTime * timeline.hesitationResolve,
  1195. secondHesitationResolveDelay: desiredTotalTime * timeline.secondHesitationResolve
  1196. };
  1197. }
  1198. // There is time for one hesitation
  1199. if(desiredTotalTime > 3000) {
  1200. const timelines = [
  1201. { move: .3, to: .2, hesitation: .25, hesitationResolve: .25 },
  1202. { move: .1, to: .3, hesitation: .45, hesitationResolve: .15 },
  1203. { move: .2, to: .25, hesitation: .2, hesitationResolve: .35 }
  1204. ];
  1205.  
  1206. const timeline = timelines[Math.floor(Math.random() * timelines.length)];
  1207.  
  1208. return {
  1209. promotionDelay: PROMOTION_DELAY,
  1210. moveDelay: desiredTotalTime * timeline.move,
  1211. toSquareSelectDelay: desiredTotalTime * timeline.to,
  1212. hesitationDelay: desiredTotalTime * timeline.hesitation,
  1213. hesitationResolveDelay: desiredTotalTime * timeline.hesitationResolve,
  1214. secondHesitationResolveDelay: -1
  1215. };
  1216. }
  1217. // There is not enough time to hesitate
  1218. else {
  1219. const timelines = [
  1220. { move: .9, to: .1 },
  1221. { move: .45, to: .55 },
  1222. { move: .6, to: .4 },
  1223. { move: .4, to: .6 },
  1224. { move: .1, to: .9 }
  1225. ];
  1226.  
  1227. const timeline = timelines[Math.floor(Math.random() * timelines.length)];
  1228.  
  1229. return {
  1230. promotionDelay: PROMOTION_DELAY,
  1231. moveDelay: desiredTotalTime * timeline.move,
  1232. toSquareSelectDelay: desiredTotalTime * timeline.to,
  1233. hesitationDelay: -1, hesitationResolveDelay: -1, secondHesitationResolveDelay: -1
  1234. };
  1235. }
  1236. }
  1237.  
  1238. calculateMoveTime(pieceCount) {
  1239. for(let range of this.timeRanges) {
  1240. if(pieceCount >= range.minPieces && pieceCount <= range.maxPieces) {
  1241. return this.getRandomIntegerBetween(range.timeRange[0], range.timeRange[1]);
  1242. }
  1243. }
  1244.  
  1245. return 500;
  1246. }
  1247.  
  1248. getRandomIntegerBetween(min, max) {
  1249. return Math.floor(Math.random() * (max - min + 1)) + min;
  1250. }
  1251.  
  1252. getRandomIntegerNearAverage(min, max) {
  1253. const mid = (min + max) / 2;
  1254. const range = (max - min) / 2;
  1255.  
  1256. let value = Math.floor(mid + (Math.random() - 0.5) * range * 1.5);
  1257.  
  1258. return Math.max(min, Math.min(max, value));
  1259. }
  1260.  
  1261. delay(ms) {
  1262. return this.active ? new Promise(resolve => setTimeout(resolve, ms)) : true;
  1263. }
  1264.  
  1265. async triggerPieceClick(input) {
  1266. const parentExists = activeAutomoves.find(x => x.move === this) ? true : false;
  1267.  
  1268. if(!parentExists) {
  1269. return;
  1270. }
  1271.  
  1272. let clientX, clientY;
  1273.  
  1274. if(input instanceof Element) {
  1275. const rect = input.getBoundingClientRect();
  1276. clientX = rect.left + rect.width / 2;
  1277. clientY = rect.top + rect.height / 2;
  1278. } else if (typeof input === 'object') {
  1279. clientX = input[0];
  1280. clientY = input[1];
  1281. } else {
  1282. return;
  1283. }
  1284.  
  1285. const xDivider = Math.random() < 0.85 ? 4 : Math.random() < 0.15 ? 3 : 2;
  1286. const yDivider = Math.random() < 0.65 ? 3 : Math.random() < 0.35 ? 2 : 4;
  1287.  
  1288. const randomVariationX = (lastPieceSize?.width - 4) / xDivider;
  1289. const randomVariationY = (lastPieceSize?.height - 4) / yDivider;
  1290.  
  1291. const randomOffsetX = (Math.random() - 0.5) * 2 * randomVariationX;
  1292. const randomOffsetY = (Math.pow(Math.random(), 0.5) - 0.5) * 2 * randomVariationY;
  1293.  
  1294. const randomizedX = clientX + randomOffsetX;
  1295. const randomizedY = clientY + randomOffsetY;
  1296.  
  1297. const pointerEventOptions = {
  1298. bubbles: true,
  1299. cancelable: true,
  1300. clientX: randomizedX,
  1301. clientY: randomizedY,
  1302. };
  1303.  
  1304. const elementToTrigger = (input instanceof Element) ? input : document.elementFromPoint(clientX, clientY);
  1305.  
  1306. if(elementToTrigger) {
  1307. switch(domain) {
  1308. case 'chess.com':
  1309. elementToTrigger.dispatchEvent(new PointerEvent('pointerdown', pointerEventOptions));
  1310.  
  1311. if(this.isLegit) await this.delay(this.getRandomIntegerNearAverage(35, 125));
  1312.  
  1313. elementToTrigger.dispatchEvent(new PointerEvent('pointerup', pointerEventOptions));
  1314.  
  1315. break;
  1316. case 'lichess.org':
  1317. elementToTrigger.dispatchEvent(new MouseEvent('mousedown', pointerEventOptions));
  1318.  
  1319. if(this.isLegit) await this.delay(this.getRandomIntegerNearAverage(35, 125));
  1320.  
  1321. elementToTrigger.dispatchEvent(new MouseEvent('mouseup', pointerEventOptions));
  1322.  
  1323. break;
  1324. case 'chessarena.com':
  1325. elementToTrigger.dispatchEvent(new MouseEvent('mousedown', pointerEventOptions));
  1326.  
  1327. if(this.isLegit) await this.delay(this.getRandomIntegerNearAverage(35, 125));
  1328.  
  1329. elementToTrigger.dispatchEvent(new MouseEvent('mouseup', pointerEventOptions));
  1330.  
  1331. break;
  1332. }
  1333. }
  1334.  
  1335. if(debugModeActivated) {
  1336. const dot = document.createElement('div');
  1337. dot.style.position = 'absolute';
  1338. dot.style.width = '7px';
  1339. dot.style.height = '7px';
  1340. dot.style.borderRadius = '50%';
  1341. dot.style.backgroundColor = 'lime';
  1342. dot.style.left = `${randomizedX - 2.5}px`;
  1343. dot.style.top = `${randomizedY - 2.5}px`;
  1344.  
  1345. const container = document.createElement('div');
  1346. container.style.position = 'absolute';
  1347. container.style.width = `${Math.round(randomVariationX * 2)}px`;
  1348. container.style.height = `${Math.round(randomVariationY * 2)}px`;
  1349. container.style.border = '2px dashed green';
  1350. container.style.backgroundColor = 'rgba(0, 0, 0, 0.3)';
  1351. container.style.left = `${clientX - randomVariationX}px`;
  1352. container.style.top = `${clientY - randomVariationY}px`;
  1353.  
  1354. document.body.appendChild(container);
  1355. document.body.appendChild(dot);
  1356.  
  1357. setTimeout(() => {
  1358. dot.remove();
  1359. container.remove();
  1360. }, 1000);
  1361. }
  1362. }
  1363.  
  1364. click(domCoord) {
  1365. if(this.active)
  1366. this.triggerPieceClick(domCoord);
  1367. }
  1368.  
  1369. async hesitate() {
  1370. const hesitationPieceDomCoord = getRandomOwnPieceDomCoord(this.fenMoveArr[0], getBoardMatrix());
  1371.  
  1372. if(hesitationPieceDomCoord) {
  1373. if(this.hesitationTypeOne) {
  1374. this.click(this.moveDomCoords[0]);
  1375. await this.delay(this.hesitationDelay);
  1376. }
  1377.  
  1378. this.click(hesitationPieceDomCoord);
  1379.  
  1380. await this.delay(this.hesitationResolveDelay);
  1381.  
  1382. if(this.shouldHesitateTwice && this.secondHesitationResolveDelay !== -1) {
  1383. const secondHesitationPieceDomCoord = getRandomOwnPieceDomCoord(this.fenMoveArr[0], getBoardMatrix());
  1384. this.click(secondHesitationPieceDomCoord);
  1385. await this.delay(this.secondHesitationResolveDelay);
  1386. }
  1387. }
  1388.  
  1389. this.finishMove(this.toSquareSelectDelay, this.promotionDelay);
  1390. }
  1391.  
  1392.  
  1393. async finishMove(delay01, delay02) {
  1394. this.click(this.moveDomCoords[0]);
  1395.  
  1396. await this.delay(delay01);
  1397.  
  1398. this.click(this.moveDomCoords[1]);
  1399.  
  1400. // Handle promotion click if necessary
  1401. if(this.isPromotion) {
  1402. this.isPromotingPawn = true;
  1403.  
  1404. await this.delay(delay02);
  1405.  
  1406. this.click(this.moveDomCoords[1]);
  1407.  
  1408. this.isPromotingPawn = false;
  1409. }
  1410.  
  1411. this.onFinished(true);
  1412. }
  1413.  
  1414. async playLegit() {
  1415. await this.delay(this.moveDelay);
  1416.  
  1417. if(this.shouldHesitate && this.hesitationDelay !== -1)
  1418. this.hesitate();
  1419. else
  1420. this.finishMove(this.toSquareSelectDelay, this.promotionDelay);
  1421. }
  1422.  
  1423. async start() {
  1424. if(this.isLegit) {
  1425. this.playLegit();
  1426. } else {
  1427. this.finishMove(5, 1111);
  1428. }
  1429. }
  1430.  
  1431. async stop() {
  1432. if(this.isPromotingPawn) {
  1433. // Attempt to promote the pawn before closing
  1434. this.click(this.moveDomCoords[1]);
  1435. }
  1436.  
  1437. this.onFinished(false);
  1438. }
  1439. }
  1440.  
  1441. async function makeMove(profile, fenMoveArr, isLegit) {
  1442. const move = new AutomaticMove(profile, fenMoveArr, isLegit, e => {
  1443. // This is ran when the move finished
  1444.  
  1445. if(debugModeActivated) console.warn('Move', fenMoveArr, move.id, 'finished', 'for profile:', profile);
  1446. });
  1447. }
  1448.  
  1449. function getGmConfigValue(key, instanceID, profileID) {
  1450. if(typeof profileID === 'object') {
  1451. profileID = profileID.name;
  1452. }
  1453.  
  1454. const config = GM_getValue(dbValues.AcasConfig);
  1455.  
  1456. const instanceValue = config?.instance?.[instanceID]?.[key];
  1457. const globalValue = config?.global?.[key];
  1458.  
  1459. if(instanceValue !== undefined) {
  1460. return instanceValue;
  1461. }
  1462.  
  1463. if(globalValue !== undefined) {
  1464. return globalValue;
  1465. }
  1466.  
  1467. if(profileID) {
  1468. const globalProfileValue = config?.global?.['profiles']?.[profileID]?.[key];
  1469. const instanceProfileValue = config?.instance?.[instanceID]?.['profiles']?.[profileID]?.[key];
  1470.  
  1471. if(instanceProfileValue !== undefined) {
  1472. return instanceProfileValue;
  1473. }
  1474.  
  1475. if(globalProfileValue !== undefined) {
  1476. return globalProfileValue;
  1477. }
  1478. }
  1479.  
  1480. return null;
  1481. }
  1482.  
  1483. function isBoardDrawerNeeded() {
  1484. const config = GM_getValue(dbValues.AcasConfig);
  1485.  
  1486. const gP = config?.global?.['profiles'];
  1487. const iP = config?.instance?.[commLinkInstanceID]?.['profiles'];
  1488.  
  1489. if(gP) {
  1490. const globalProfiles = Object.keys(gP);
  1491.  
  1492. for(profileName of globalProfiles) {
  1493. const externalMoves = gP[profileName][configKeys.displayMovesOnExternalSite];
  1494. const externalRenders = gP[profileName][configKeys.renderOnExternalSite];
  1495. const externalFeedback = gP[profileName][configKeys.feedbackOnExternalSite];
  1496.  
  1497.  
  1498. if(externalMoves || externalRenders || externalFeedback) {
  1499. return true;
  1500. }
  1501. }
  1502. }
  1503.  
  1504. if(iP) {
  1505. const instanceProfiles = Object.keys(iP);
  1506.  
  1507. for(profileName of instanceProfiles) {
  1508. const externalMoves = iP[profileName][configKeys.displayMovesOnExternalSite];
  1509. const externalRenders = gP[profileName][configKeys.renderOnExternalSite];
  1510. const externalFeedback = gP[profileName][configKeys.feedbackOnExternalSite];
  1511.  
  1512. if(externalMoves || externalRenders || externalFeedback) {
  1513. return true;
  1514. }
  1515. }
  1516. }
  1517.  
  1518. return false;
  1519. }
  1520.  
  1521. function getConfigValue(key, profile) {
  1522. return config[key]?.get(profile);
  1523. }
  1524.  
  1525. function setConfigValue(key, val) {
  1526. return config[key]?.set(val);
  1527. }
  1528.  
  1529. function squeezeEmptySquares(fenStr) {
  1530. return fenStr.replace(/1+/g, match => match.length);
  1531. }
  1532.  
  1533. function getPlayerColorVariable() {
  1534. return instanceVars.playerColor.get(commLinkInstanceID);
  1535. }
  1536.  
  1537. function getFenPieceColor(pieceFenStr) {
  1538. return pieceFenStr == pieceFenStr.toUpperCase() ? 'w' : 'b';
  1539. }
  1540.  
  1541. function getFenPieceOppositeColor(pieceFenStr) {
  1542. return getFenPieceColor(pieceFenStr) == 'w' ? 'b' : 'w';
  1543. }
  1544.  
  1545. function convertPieceStrToFen(str) {
  1546. if(!str || str.length !== 2) {
  1547. return null;
  1548. }
  1549.  
  1550. const firstChar = str[0].toLowerCase();
  1551. const secondChar = str[1];
  1552.  
  1553. if(firstChar === 'w') {
  1554. return secondChar.toUpperCase();
  1555. } else if (firstChar === 'b') {
  1556. return secondChar.toLowerCase();
  1557. }
  1558.  
  1559. return null;
  1560. }
  1561.  
  1562. function getCanvasPixelColor(canvas, [xPercentage, yPercentage], debug) {
  1563. const ctx = canvas.getContext('2d');
  1564.  
  1565. const x = xPercentage * canvas.width;
  1566. const y = yPercentage * canvas.height;
  1567.  
  1568. const imageData = ctx.getImageData(x, y, 1, 1);
  1569. const pixel = imageData.data;
  1570. const brightness = (pixel[0] + pixel[1] + pixel[2]) / 3;
  1571.  
  1572. if(debug) {
  1573. const clonedCanvas = document.createElement('canvas');
  1574. clonedCanvas.width = canvas.width;
  1575. clonedCanvas.height = canvas.height;
  1576.  
  1577. const clonedCtx = clonedCanvas.getContext('2d');
  1578. clonedCtx.drawImage(canvas, 0, 0);
  1579.  
  1580. clonedCtx.fillStyle = 'red';
  1581. clonedCtx.beginPath();
  1582. clonedCtx.arc(x, y, 1, 0, Math.PI * 2);
  1583. clonedCtx.fill();
  1584.  
  1585. const dataURL = clonedCanvas.toDataURL();
  1586.  
  1587. console.log(canvas, pixel, dataURL);
  1588. }
  1589.  
  1590. return brightness < 128 ? 'b' : 'w';
  1591. }
  1592.  
  1593. function canvasHasPixelAt(canvas, [xPercentage, yPercentage], debug) {
  1594. xPercentage = Math.min(Math.max(xPercentage, 0), 100);
  1595. yPercentage = Math.min(Math.max(yPercentage, 0), 100);
  1596.  
  1597. const ctx = canvas.getContext('2d');
  1598. const x = xPercentage * canvas.width;
  1599. const y = yPercentage * canvas.height;
  1600.  
  1601. const imageData = ctx.getImageData(x, y, 1, 1);
  1602. const pixel = imageData.data;
  1603.  
  1604. if(debug) {
  1605. const clonedCanvas = document.createElement('canvas');
  1606. clonedCanvas.width = canvas.width;
  1607. clonedCanvas.height = canvas.height;
  1608.  
  1609. const clonedCtx = clonedCanvas.getContext('2d');
  1610. clonedCtx.drawImage(canvas, 0, 0);
  1611.  
  1612. clonedCtx.fillStyle = 'red';
  1613. clonedCtx.beginPath();
  1614. clonedCtx.arc(x, y, 1, 0, Math.PI * 2);
  1615. clonedCtx.fill();
  1616.  
  1617. const dataURL = clonedCanvas.toDataURL();
  1618.  
  1619. console.log(canvas, pixel, dataURL);
  1620. }
  1621.  
  1622. return pixel[3] !== 0;
  1623. }
  1624.  
  1625. function getSiteData(dataType, obj) {
  1626. const pathname = window.location.pathname;
  1627.  
  1628. let dataObj = { pathname };
  1629.  
  1630. if(obj && typeof obj === 'object') {
  1631. dataObj = { ...dataObj, ...obj };
  1632. }
  1633.  
  1634. const dataHandlerFunction = supportedSites[domain]?.[dataType];
  1635.  
  1636. if(typeof dataHandlerFunction !== 'function') {
  1637. return null;
  1638. }
  1639.  
  1640. const result = dataHandlerFunction(dataObj);
  1641.  
  1642. return result;
  1643. }
  1644.  
  1645. function addSupportedChessSite(domain, typeHandlerObj) {
  1646. supportedSites[domain] = typeHandlerObj;
  1647. }
  1648.  
  1649. function getBoardElem() {
  1650. const boardElem = getSiteData('boardElem');
  1651.  
  1652. return boardElem || null;
  1653. }
  1654.  
  1655. function getPieceElem(getAll) {
  1656. const boardElem = getBoardElem();
  1657.  
  1658. const boardQuerySelector = (getAll ? query => [...boardElem?.querySelectorAll(query)] : boardElem?.querySelector?.bind(boardElem));
  1659.  
  1660. if(typeof boardQuerySelector !== 'function')
  1661. return null;
  1662.  
  1663. const pieceElem = getSiteData('pieceElem', { boardQuerySelector, getAll });
  1664.  
  1665. return pieceElem || null;
  1666. }
  1667.  
  1668. function getSquareElems(element) {
  1669. const squareElems = getSiteData('squareElems', { element });
  1670.  
  1671. return squareElems || null;
  1672. }
  1673.  
  1674. function getChessVariant() {
  1675. const chessVariant = getSiteData('chessVariant');
  1676.  
  1677. return chessVariant || null;
  1678. }
  1679.  
  1680. function getBoardOrientation() {
  1681. const boardOrientation = getSiteData('boardOrientation');
  1682.  
  1683. return boardOrientation || null;
  1684. }
  1685.  
  1686. function getPieceElemFen(pieceElem) {
  1687. const pieceFen = getSiteData('pieceElemFen', { pieceElem });
  1688.  
  1689. return pieceFen || null;
  1690. }
  1691.  
  1692. // this function gets called a lot, needs to be optimized
  1693. function getPieceElemCoords(pieceElem) {
  1694. const pieceCoords = getSiteData('pieceElemCoords', { pieceElem });
  1695.  
  1696. return pieceCoords || null;
  1697. }
  1698.  
  1699. function getBoardDimensions() {
  1700. const boardDimensionArr = getSiteData('boardDimensions');
  1701.  
  1702. if(boardDimensionArr) {
  1703. lastBoardRanks = boardDimensionArr[0];
  1704. lastBoardFiles = boardDimensionArr[1];
  1705.  
  1706. return boardDimensionArr;
  1707. } else {
  1708. lastBoardRanks = 8;
  1709. lastBoardFiles = 8;
  1710.  
  1711. return [8, 8];
  1712. }
  1713. }
  1714.  
  1715. function isMutationNewMove(mutationArr) {
  1716. const isNewMove = getSiteData('isMutationNewMove', { mutationArr });
  1717.  
  1718. return isNewMove || false;
  1719. }
  1720.  
  1721. function getBoardMatrix() {
  1722. const [boardRanks, boardFiles] = getBoardDimensions();
  1723.  
  1724. const board = Array.from({ length: boardFiles }, () => Array(boardRanks).fill(1));
  1725. const pieceElems = getPieceElem(true);
  1726. const isValidPieceElemsArray = Array.isArray(pieceElems) || pieceElems instanceof NodeList;
  1727.  
  1728. if(isValidPieceElemsArray) {
  1729. pieceElems.forEach(pieceElem => {
  1730. const pieceFenCode = getPieceElemFen(pieceElem);
  1731. const pieceCoordsArr = getPieceElemCoords(pieceElem);
  1732.  
  1733. //if(debugModeActivated) console.warn('pieceElem', pieceElem, 'pieceFenCode', pieceFenCode, 'pieceCoordsArr', pieceCoordsArr);
  1734.  
  1735. try {
  1736. const [xIdx, yIdx] = pieceCoordsArr;
  1737.  
  1738. board[boardFiles - (yIdx + 1)][xIdx] = pieceFenCode;
  1739. } catch(e) {
  1740. if(debugModeActivated) console.error(e);
  1741. }
  1742. });
  1743. }
  1744.  
  1745. lastBoardMatrix = board;
  1746.  
  1747. return board;
  1748. }
  1749.  
  1750. function getBoardPiece(fenCoord) {
  1751. const [boardRanks, boardFiles] = getBoardDimensions();
  1752. const indexArr = chessCoordinatesToIndex(fenCoord);
  1753.  
  1754. return getBoardMatrix()?.[boardFiles - (indexArr[1] + 1)]?.[indexArr[0]];
  1755. }
  1756.  
  1757. // Works on 8x8 boards only
  1758. function getRights() {
  1759. let rights = '';
  1760.  
  1761. // check for white
  1762. const e1 = getBoardPiece('e1'),
  1763. h1 = getBoardPiece('h1'),
  1764. a1 = getBoardPiece('a1');
  1765.  
  1766. if(e1 == 'K' && h1 == 'R') rights += 'K';
  1767. if(e1 == 'K' && a1 == 'R') rights += 'Q';
  1768.  
  1769. //check for black
  1770. const e8 = getBoardPiece('e8'),
  1771. h8 = getBoardPiece('h8'),
  1772. a8 = getBoardPiece('a8');
  1773.  
  1774. if(e8 == 'k' && h8 == 'r') rights += 'k';
  1775. if(e8 == 'k' && a8 == 'r') rights += 'q';
  1776.  
  1777. return rights ? rights : '-';
  1778. }
  1779.  
  1780. function getBasicFen() {
  1781. const boardMatrix = getBoardMatrix();
  1782.  
  1783. return squeezeEmptySquares(boardMatrix.map(x => x.join('')).join('/'));
  1784. }
  1785.  
  1786. function getFen(onlyBasic) {
  1787. const basicFen = getBasicFen();
  1788.  
  1789. if(debugModeActivated) console.warn('basicFen', basicFen);
  1790.  
  1791. if(onlyBasic) {
  1792. return basicFen;
  1793. }
  1794.  
  1795. // FEN structure: [fen] [player color] [castling rights] [en passant targets] [halfmove clock] [fullmove clock]
  1796. const fullFen = `${basicFen} ${getPlayerColorVariable()} ${getRights()} - 0 1`;
  1797.  
  1798. return fullFen;
  1799. }
  1800.  
  1801. function resetCachedValues() {
  1802. chesscomVariantPlayerColorsTable = null;
  1803. }
  1804.  
  1805. function fenToArray(fen) {
  1806. const rows = fen.split('/');
  1807. const board = [];
  1808.  
  1809. for (let row of rows) {
  1810. const boardRow = [];
  1811. for (let char of row) {
  1812. if (isNaN(char)) {
  1813. boardRow.push(char);
  1814. } else {
  1815. boardRow.push(...Array(parseInt(char)).fill(''));
  1816. }
  1817. }
  1818. board.push(boardRow);
  1819. }
  1820.  
  1821. return board;
  1822. }
  1823.  
  1824. function onNewMove(mutationArr, bypassFenChangeDetection) {
  1825. const currentFullFen = getFen();
  1826. const lastFullFen = instanceVars.fen.get(commLinkInstanceID);
  1827.  
  1828. const fenChanged = currentFullFen !== lastFullFen;
  1829.  
  1830. if((fenChanged || bypassFenChangeDetection)) {
  1831. if(debugModeActivated) console.warn('NEW MOVE DETECTED!');
  1832.  
  1833. const pieceAmount = getPieceAmount();
  1834. const pieceAmountChange = Math.abs(pieceAmount - lastPieceAmount);
  1835.  
  1836. // Possibly new match due to large piece amount change
  1837. if(pieceAmountChange > 7) {
  1838. matchFirstSuggestionGiven = false;
  1839. }
  1840.  
  1841. resetCachedValues();
  1842.  
  1843. boardUtils.setBoardDimensions(getBoardDimensions());
  1844.  
  1845. const lastPlayerColor = getPlayerColorVariable();
  1846.  
  1847. updatePlayerColor();
  1848.  
  1849. const playerColor = getPlayerColorVariable();
  1850. const orientationChanged = playerColor != lastPlayerColor;
  1851.  
  1852. if(orientationChanged) {
  1853. CommLink.commands.log(`Player color (e.g. board orientation) changed from ${lastPlayerColor} to ${playerColor}!`);
  1854.  
  1855. resetCachedValues();
  1856.  
  1857. matchFirstSuggestionGiven = false;
  1858.  
  1859. CommLink.commands.log(`Turn updated to ${playerColor}!`);
  1860. }
  1861.  
  1862. boardUtils.removeMarkings();
  1863.  
  1864. CommLink.commands.updateBoardFen(currentFullFen);
  1865.  
  1866. lastMoveRequestTime = Date.now();
  1867. lastPieceAmount = pieceAmount;
  1868.  
  1869. if(orientationChanged) {
  1870. CommLink.commands.calculateBestMoves(currentFullFen);
  1871. }
  1872. }
  1873. }
  1874.  
  1875. function observeNewMoves() {
  1876. const boardObserver = new MutationObserver(mutationArr => {
  1877. if(debugModeActivated) console.log(mutationArr);
  1878.  
  1879. if(isMutationNewMove(mutationArr))
  1880. {
  1881. if(debugModeActivated) console.warn('Mutation is a new move:', mutationArr);
  1882.  
  1883. try {
  1884. if(domain === 'chess.org' || domain === 'chessarena.com')
  1885. {
  1886. setTimeout(() => onNewMove(mutationArr), 250);
  1887. }
  1888. else
  1889. {
  1890. onNewMove(mutationArr);
  1891. }
  1892. } catch(e) {
  1893. if(debugModeActivated) console.error('Error while running onNewMove:', e);
  1894. }
  1895. }
  1896. });
  1897.  
  1898. boardObserver.observe(chessBoardElem, { childList: true, subtree: true, attributes: true });
  1899. }
  1900.  
  1901. async function updatePlayerColor() {
  1902. const boardOrientation = getBoardOrientation();
  1903.  
  1904. const boardOrientationChanged = lastBoardOrientation !== boardOrientation;
  1905. const boardOrientationDiffers = BoardDrawer && BoardDrawer?.orientation !== boardOrientation;
  1906.  
  1907. if(boardOrientationChanged || boardOrientationDiffers) {
  1908. lastBoardOrientation = boardOrientation;
  1909.  
  1910. instanceVars.playerColor.set(commLinkInstanceID, boardOrientation);
  1911.  
  1912. boardUtils.setBoardOrientation(boardOrientation);
  1913.  
  1914. await CommLink.commands.updateBoardOrientation(boardOrientation);
  1915. }
  1916. }
  1917.  
  1918.  
  1919.  
  1920.  
  1921.  
  1922.  
  1923. /*
  1924. _______ _____ _______ _______ _______ _____ _______ _______ _____ _______ _____ _______
  1925. |______ | | |______ |______ |_____] |______ | | |______ | |
  1926. ______| __|__ | |______ ______| | |______ |_____ __|__ | __|__ |_____
  1927.  
  1928. Code below this point handles chess site specific things. (e.g. which element is the board or the pieces)
  1929. */
  1930.  
  1931. addSupportedChessSite('chess.com', {
  1932. 'boardElem': obj => {
  1933. const pathname = obj.pathname;
  1934.  
  1935. if(pathname?.includes('/variants')) {
  1936. return document.querySelector('.TheBoard-layers');
  1937. }
  1938.  
  1939. return document.querySelector('#board-layout-chessboard > .board');
  1940. },
  1941.  
  1942. 'pieceElem': obj => {
  1943. const pathname = obj.pathname;
  1944. const getAll = obj.getAll;
  1945.  
  1946. if(pathname?.includes('/variants')) {
  1947. const filteredPieceElems = filterInvisibleElems(document.querySelectorAll('.TheBoard-layers *[data-piece]'))
  1948. .filter(elem => elem?.dataset?.piece?.toLowerCase() !== 'x');
  1949.  
  1950. return getAll ? filteredPieceElems : filteredPieceElems[0];
  1951. }
  1952.  
  1953. return obj.boardQuerySelector('.piece');
  1954. },
  1955.  
  1956. 'squareElems': obj => {
  1957. const pathname = obj.pathname;
  1958. const element = obj.element;
  1959.  
  1960. if(pathname?.includes('/variants')) {
  1961. return [...element.querySelectorAll('.square')];
  1962. }
  1963. },
  1964.  
  1965. 'chessVariant': obj => {
  1966. const pathname = obj.pathname;
  1967.  
  1968. if(pathname?.includes('/variants')) {
  1969. const variant = pathname.match(/variants\/([^\/]*)/)?.[1]
  1970. .replaceAll('-chess', '')
  1971. .replaceAll('-', '');
  1972.  
  1973. const replacementTable = {
  1974. 'doubles-bughouse': 'bughouse',
  1975. 'paradigm-chess30': 'paradigm'
  1976. };
  1977.  
  1978. return replacementTable[variant] || variant;
  1979. }
  1980. },
  1981.  
  1982. 'boardOrientation': obj => {
  1983. const pathname = obj.pathname;
  1984.  
  1985. if(pathname?.includes('/variants')) {
  1986. const playerNumberStr = document.querySelector('.playerbox-bottom [data-player]')?.dataset?.player;
  1987.  
  1988. if(!playerNumberStr)
  1989. return 'w';
  1990.  
  1991. return playerNumberStr === '0' ? 'w' : 'b';
  1992. }
  1993.  
  1994. const boardElem = getBoardElem();
  1995.  
  1996. return boardElem?.classList.contains('flipped') ? 'b' : 'w';
  1997. },
  1998.  
  1999. 'pieceElemFen': obj => {
  2000. const pathname = obj.pathname;
  2001. const pieceElem = obj.pieceElem;
  2002.  
  2003. let pieceColor = null;
  2004. let pieceName = null;
  2005.  
  2006. if(pathname?.includes('/variants')) {
  2007. if(!chesscomVariantPlayerColorsTable) {
  2008. updateChesscomVariantPlayerColorsTable();
  2009. }
  2010.  
  2011. const pieceFenStr = pieceElem?.dataset?.piece;
  2012.  
  2013. pieceColor = chesscomVariantPlayerColorsTable?.[pieceElem?.dataset?.color];
  2014. pieceName = pieceElem?.dataset?.piece;
  2015.  
  2016. if(pieceName?.length > 1) {
  2017. pieceName = pieceName[0];
  2018. }
  2019. } else {
  2020. const pieceStr = [...pieceElem.classList].find(x => x.match(/^(b|w)[prnbqk]{1}$/));
  2021.  
  2022. [pieceColor, pieceName] = pieceStr.split('');
  2023. }
  2024.  
  2025. return pieceColor == 'w' ? pieceName.toUpperCase() : pieceName.toLowerCase();
  2026. },
  2027.  
  2028. 'pieceElemCoords': obj => {
  2029. const pathname = obj.pathname;
  2030. const pieceElem = obj.pieceElem;
  2031.  
  2032. if(pathname?.includes('/variants')) {
  2033. const coords = getElemCoordinatesFromTransform(pieceElem);
  2034.  
  2035. return coords;
  2036. }
  2037.  
  2038. return pieceElem.classList.toString()
  2039. ?.match(/square-(\d)(\d)/)
  2040. ?.slice(1)
  2041. ?.map(x => Number(x) - 1);
  2042. },
  2043.  
  2044. 'boardDimensions': obj => {
  2045. const pathname = obj.pathname;
  2046.  
  2047. if(pathname?.includes('/variants')) {
  2048. const squaresContainerElem = document.querySelector('.TheBoard-squares');
  2049.  
  2050. let ranks = 0;
  2051. let files = 0;
  2052.  
  2053. [...squaresContainerElem.childNodes].forEach((x, i) => {
  2054. const visibleChildElems = filterInvisibleElems([...x.childNodes]);
  2055.  
  2056. if(visibleChildElems?.length > 0) {
  2057. ranks = ranks + 1;
  2058.  
  2059. if(visibleChildElems.length > files) {
  2060. files = visibleChildElems.length;
  2061. }
  2062. }
  2063. });
  2064.  
  2065. return [ranks, files];
  2066. } else {
  2067. return [8, 8];
  2068. }
  2069. },
  2070.  
  2071. 'isMutationNewMove': obj => {
  2072. const pathname = obj.pathname;
  2073. const mutationArr = obj.mutationArr;
  2074.  
  2075. if(pathname?.includes('/variants')) {
  2076. return mutationArr.find(m => m.type === 'childList') ? true : false;
  2077. }
  2078.  
  2079. if(mutationArr.length == 1)
  2080. return false;
  2081.  
  2082. const modifiedHoverSquare = mutationArr.find(m => m?.target?.classList?.contains('hover-square')) ? true : false;
  2083. const modifiedHighlight = mutationArr.find(m => m?.target?.classList?.contains('highlight')) ? true : false;
  2084. const modifiedElemPool = mutationArr.find(m => m?.target?.classList?.contains('element-pool')) ? true : false;
  2085.  
  2086. const isPremove = mutationArr.filter(m => m?.target?.classList?.contains('highlight'))
  2087. .map(x => x?.target?.style?.['background-color'])
  2088. .find(x => x === 'rgb(244, 42, 50)') ? true : false;
  2089.  
  2090. return (
  2091. (mutationArr.length >= 4 && !modifiedHoverSquare)
  2092. || mutationArr.length >= 7
  2093. || modifiedHighlight
  2094. || modifiedElemPool
  2095. ) && !isPremove;
  2096. }
  2097. });
  2098.  
  2099. addSupportedChessSite('lichess.org', {
  2100. 'boardElem': obj => {
  2101. return document.querySelector('cg-board');
  2102. },
  2103.  
  2104. 'pieceElem': obj => {
  2105. return obj.boardQuerySelector('piece:not(.ghost)');
  2106. },
  2107.  
  2108. 'chessVariant': obj => {
  2109. const variantLinkElem = document.querySelector('.variant-link');
  2110.  
  2111. if(variantLinkElem) {
  2112. let variant = variantLinkElem?.innerText?.toLowerCase()?.replaceAll(' ', '-');
  2113.  
  2114. const replacementTable = {
  2115. 'correspondence': 'chess',
  2116. 'koth': 'kingofthehill',
  2117. 'three-check': '3check'
  2118. };
  2119.  
  2120. return replacementTable[variant] || variant;
  2121. }
  2122. },
  2123.  
  2124. 'boardOrientation': obj => {
  2125. const filesElem = document.querySelector('coords.files');
  2126.  
  2127. return filesElem?.classList?.contains('black') ? 'b' : 'w';
  2128. },
  2129.  
  2130. 'pieceElemFen': obj => {
  2131. const pieceElem = obj.pieceElem;
  2132.  
  2133. const pieceColor = pieceElem?.classList?.contains('white') ? 'w' : 'b';
  2134. const elemPieceName = [...pieceElem?.classList]?.find(className => Object.keys(pieceNameToFen).includes(className));
  2135.  
  2136. if(pieceColor && elemPieceName) {
  2137. const pieceName = pieceNameToFen[elemPieceName];
  2138.  
  2139. return pieceColor == 'w' ? pieceName.toUpperCase() : pieceName.toLowerCase();
  2140. }
  2141. },
  2142.  
  2143. 'pieceElemCoords': obj => {
  2144. const pieceElem = obj.pieceElem;
  2145.  
  2146. const key = pieceElem?.cgKey;
  2147.  
  2148. if(key) {
  2149. return chessCoordinatesToIndex(key);
  2150. }
  2151. },
  2152.  
  2153. 'boardDimensions': obj => {
  2154. return [8, 8];
  2155. },
  2156.  
  2157. 'isMutationNewMove': obj => {
  2158. const mutationArr = obj.mutationArr;
  2159.  
  2160. return mutationArr.length >= 4
  2161. || mutationArr.find(m => m.type === 'childList') ? true : false
  2162. || mutationArr.find(m => m?.target?.classList?.contains('last-move')) ? true : false;
  2163. }
  2164. });
  2165.  
  2166. addSupportedChessSite('playstrategy.org', {
  2167. 'boardElem': obj => {
  2168. return document.querySelector('cg-board');
  2169. },
  2170.  
  2171. 'pieceElem': obj => {
  2172. return obj.boardQuerySelector('piece[class*="-piece"]:not(.ghost)');
  2173. },
  2174.  
  2175. 'chessVariant': obj => {
  2176. const variantLinkElem = document.querySelector('.variant-link');
  2177.  
  2178. if(variantLinkElem) {
  2179. let variant = variantLinkElem?.innerText
  2180. ?.toLowerCase()
  2181. ?.replaceAll(' ', '-');
  2182.  
  2183. const replacementTable = {
  2184. 'correspondence': 'chess',
  2185. 'koth': 'kingofthehill',
  2186. 'three-check': '3check',
  2187. 'five-check': '5check',
  2188. 'no-castling': 'nocastle'
  2189. };
  2190.  
  2191. return replacementTable[variant] || variant;
  2192. }
  2193. },
  2194.  
  2195. 'boardOrientation': obj => {
  2196. const cgWrapElem = document.querySelector('.cg-wrap');
  2197.  
  2198. return cgWrapElem.classList?.contains('orientation-p1') ? 'w' : 'b';
  2199. },
  2200.  
  2201. 'pieceElemFen': obj => {
  2202. const pieceElem = obj.pieceElem;
  2203.  
  2204. const playerColor = getPlayerColorVariable();
  2205. const pieceColor = pieceElem?.classList?.contains('ally') ? playerColor : (playerColor == 'w' ? 'b' : 'w');
  2206.  
  2207. let pieceName = null;
  2208.  
  2209. [...pieceElem?.classList]?.forEach(className => {
  2210. if(className?.includes('-piece')) {
  2211. const elemPieceName = className?.split('-piece')?.[0];
  2212.  
  2213. if(elemPieceName && elemPieceName?.length === 1) {
  2214. pieceName = elemPieceName;
  2215. }
  2216. }
  2217. });
  2218.  
  2219. if(pieceColor && pieceName) {
  2220. return pieceColor == 'w' ? pieceName.toUpperCase() : pieceName.toLowerCase();
  2221. }
  2222. },
  2223.  
  2224. 'pieceElemCoords': obj => {
  2225. const pieceElem = obj.pieceElem;
  2226.  
  2227. const key = pieceElem?.cgKey;
  2228.  
  2229. if(key) {
  2230. return chessCoordinatesToIndex(key);
  2231. }
  2232. },
  2233.  
  2234. 'boardDimensions': obj => {
  2235. return getBoardDimensionsFromSize();
  2236. },
  2237.  
  2238. 'isMutationNewMove': obj => {
  2239. const mutationArr = obj.mutationArr;
  2240.  
  2241. return mutationArr.length >= 4
  2242. || mutationArr.find(m => m.type === 'childList') ? true : false
  2243. || mutationArr.find(m => m?.target?.classList?.contains('last-move')) ? true : false;
  2244. }
  2245. });
  2246.  
  2247. addSupportedChessSite('pychess.org', {
  2248. 'boardElem': obj => {
  2249. return document.querySelector('cg-board');
  2250. },
  2251.  
  2252. 'pieceElem': obj => {
  2253. return obj.boardQuerySelector('piece[class*="-piece"]:not(.ghost)');
  2254. },
  2255.  
  2256. 'chessVariant': obj => {
  2257. const variantLinkElem = document.querySelector('#main-wrap .tc .user-link');
  2258.  
  2259. if(variantLinkElem) {
  2260. let variant = variantLinkElem?.innerText
  2261. ?.toLowerCase()
  2262. ?.replaceAll(' ', '')
  2263. ?.replaceAll('-', '');
  2264.  
  2265. const replacementTable = {
  2266. 'correspondence': 'chess',
  2267. 'koth': 'kingofthehill',
  2268. 'nocastling': 'nocastle',
  2269. 'gorogoro+': 'gorogoro',
  2270. 'oukchaktrang': 'cambodian'
  2271. };
  2272.  
  2273. return replacementTable[variant] || variant;
  2274. }
  2275. },
  2276.  
  2277. 'boardOrientation': obj => {
  2278. const cgWrapElem = document.querySelector('.cg-wrap');
  2279.  
  2280. return cgWrapElem.classList?.contains('orientation-black') ? 'b' : 'w';
  2281. },
  2282.  
  2283. 'pieceElemFen': obj => {
  2284. const pieceElem = obj.pieceElem;
  2285.  
  2286. const playerColor = getPlayerColorVariable();
  2287. const pieceColor = pieceElem?.classList?.contains('ally') ? playerColor : (playerColor == 'w' ? 'b' : 'w');
  2288.  
  2289. let pieceName = null;
  2290.  
  2291. [...pieceElem?.classList]?.forEach(className => {
  2292. if(className?.includes('-piece')) {
  2293. const elemPieceName = className?.split('-piece')?.[0];
  2294.  
  2295. if(elemPieceName && elemPieceName?.length === 1) {
  2296. pieceName = elemPieceName;
  2297. }
  2298. }
  2299. });
  2300.  
  2301. if(pieceColor && pieceName) {
  2302. return pieceColor == 'w' ? pieceName.toUpperCase() : pieceName.toLowerCase();
  2303. }
  2304. },
  2305.  
  2306. 'pieceElemCoords': obj => {
  2307. const pieceElem = obj.pieceElem;
  2308.  
  2309. const key = pieceElem?.cgKey;
  2310.  
  2311. if(key) {
  2312. return chessCoordinatesToIndex(key);
  2313. }
  2314. },
  2315.  
  2316. 'boardDimensions': obj => {
  2317. return getBoardDimensionsFromSize();
  2318. },
  2319.  
  2320. 'isMutationNewMove': obj => {
  2321. const mutationArr = obj.mutationArr;
  2322.  
  2323. return mutationArr.length >= 4
  2324. || mutationArr.find(m => m.type === 'childList') ? true : false
  2325. || mutationArr.find(m => m?.target?.classList?.contains('last-move')) ? true : false;
  2326. }
  2327. });
  2328.  
  2329. addSupportedChessSite('chess.org', {
  2330. 'boardElem': obj => {
  2331. return document.querySelector('.cg-board');
  2332. },
  2333.  
  2334. 'pieceElem': obj => {
  2335. return obj.boardQuerySelector('piece:not(.ghost)');
  2336. },
  2337.  
  2338. 'chessVariant': obj => {
  2339. return 'chess';
  2340. },
  2341.  
  2342. 'boardOrientation': obj => {
  2343. const filesElem = document.querySelector('coords.files');
  2344.  
  2345. return filesElem?.classList?.contains('black') ? 'b' : 'w';
  2346. },
  2347.  
  2348. 'pieceElemFen': obj => {
  2349. const pieceElem = obj.pieceElem;
  2350.  
  2351. const pieceColor = pieceElem?.classList?.contains('white') ? 'w' : 'b';
  2352. const elemPieceName = [...pieceElem?.classList]?.find(className => Object.keys(pieceNameToFen).includes(className));
  2353.  
  2354. if(pieceColor && elemPieceName) {
  2355. const pieceName = pieceNameToFen[elemPieceName];
  2356.  
  2357. return pieceColor == 'w' ? pieceName.toUpperCase() : pieceName.toLowerCase();
  2358. }
  2359. },
  2360.  
  2361. 'pieceElemCoords': obj => {
  2362. const pieceElem = obj.pieceElem;
  2363.  
  2364. return getElemCoordinatesFromTransform(pieceElem);
  2365. },
  2366.  
  2367. 'boardDimensions': obj => {
  2368. return [8, 8];
  2369. },
  2370.  
  2371. 'isMutationNewMove': obj => {
  2372. const mutationArr = obj.mutationArr;
  2373.  
  2374. if(isUserMouseDown) {
  2375. return false;
  2376. }
  2377.  
  2378. return mutationArr.length >= 4
  2379. || mutationArr.find(m => m.type === 'childList') ? true : false
  2380. || mutationArr.find(m => m?.target?.classList?.contains('last-move')) ? true : false;
  2381. }
  2382. });
  2383.  
  2384. addSupportedChessSite('chess.coolmathgames.com', {
  2385. 'boardElem': obj => {
  2386. return document.querySelector('cg-board');
  2387. },
  2388.  
  2389. 'pieceElem': obj => {
  2390. return obj.boardQuerySelector('piece:not(.ghost)');
  2391. },
  2392.  
  2393. 'chessVariant': obj => {
  2394. return 'chess';
  2395. },
  2396.  
  2397. 'boardOrientation': obj => {
  2398. const boardElem = getBoardElem();
  2399.  
  2400. return document.querySelector('.ranks.black') ? 'b' : 'w';
  2401. },
  2402.  
  2403. 'pieceElemFen': obj => {
  2404. const pieceElem = obj.pieceElem;
  2405.  
  2406. const pieceColor = pieceElem?.classList?.contains('white') ? 'w' : 'b';
  2407. const elemPieceName = [...pieceElem?.classList]?.find(className => Object.keys(pieceNameToFen).includes(className));
  2408.  
  2409. if(pieceColor && elemPieceName) {
  2410. const pieceName = pieceNameToFen[elemPieceName];
  2411.  
  2412. return pieceColor == 'w' ? pieceName.toUpperCase() : pieceName.toLowerCase();
  2413. }
  2414. },
  2415.  
  2416. 'pieceElemCoords': obj => {
  2417. const pieceElem = obj.pieceElem;
  2418.  
  2419. const key = pieceElem?.cgKey;
  2420.  
  2421. if(key) {
  2422. return chessCoordinatesToIndex(key);
  2423. }
  2424. },
  2425.  
  2426. 'boardDimensions': obj => {
  2427. return [8, 8];
  2428. },
  2429.  
  2430. 'isMutationNewMove': obj => {
  2431. const mutationArr = obj.mutationArr;
  2432.  
  2433. if(isUserMouseDown) {
  2434. return false;
  2435. }
  2436.  
  2437. return mutationArr.length >= 4
  2438. || mutationArr.find(m => m.type === 'childList') ? true : false
  2439. || mutationArr.find(m => m?.target?.classList?.contains('last-move')) ? true : false;
  2440. }
  2441. });
  2442.  
  2443. addSupportedChessSite('papergames.io', {
  2444. 'boardElem': obj => {
  2445. return document.querySelector('.cm-chessboard');
  2446. },
  2447.  
  2448. 'pieceElem': obj => {
  2449. return obj.boardQuerySelector('*[data-piece][data-square]');
  2450. },
  2451.  
  2452. 'chessVariant': obj => {
  2453. return 'chess';
  2454. },
  2455.  
  2456. 'boardOrientation': obj => {
  2457. const boardElem = getBoardElem();
  2458.  
  2459. if(boardElem) {
  2460. const firstRankText = [...boardElem.querySelector('.coordinates').childNodes]?.[0].textContent;
  2461.  
  2462. return firstRankText == 'h' ? 'b' : 'w';
  2463. }
  2464. },
  2465.  
  2466. 'pieceElemFen': obj => {
  2467. const pieceElem = obj.pieceElem;
  2468.  
  2469. return convertPieceStrToFen(pieceElem?.dataset?.piece);
  2470. },
  2471.  
  2472. 'pieceElemCoords': obj => {
  2473. const pieceElem = obj.pieceElem;
  2474.  
  2475. const key = pieceElem?.dataset?.square;
  2476.  
  2477. if(key) {
  2478. return chessCoordinatesToIndex(key);
  2479. }
  2480. },
  2481.  
  2482. 'boardDimensions': obj => {
  2483. return [8, 8];
  2484. },
  2485.  
  2486. 'isMutationNewMove': obj => {
  2487. const mutationArr = obj.mutationArr;
  2488.  
  2489. return mutationArr.length >= 12;
  2490. }
  2491. });
  2492.  
  2493. addSupportedChessSite('vole.wtf', {
  2494. 'boardElem': obj => {
  2495. return document.querySelector('#board');
  2496. },
  2497.  
  2498. 'pieceElem': obj => {
  2499. return obj.boardQuerySelector('*[data-t][data-l][data-p]:not([data-p="0"]');
  2500. },
  2501.  
  2502. 'chessVariant': obj => {
  2503. return 'chess';
  2504. },
  2505.  
  2506. 'boardOrientation': obj => {
  2507. return 'w';
  2508. },
  2509.  
  2510. 'pieceElemFen': obj => {
  2511. const pieceElem = obj.pieceElem;
  2512.  
  2513. const pieceNum = Number(pieceElem?.dataset?.p);
  2514. const pieceFenStr = 'pknbrq';
  2515.  
  2516. if(pieceNum > 8) {
  2517. return pieceFenStr[pieceNum - 9].toUpperCase();
  2518. } else {
  2519. return pieceFenStr[pieceNum - 1];
  2520. }
  2521. },
  2522.  
  2523. 'pieceElemCoords': obj => {
  2524. const pieceElem = obj.pieceElem;
  2525.  
  2526. return [Number(pieceElem?.dataset?.l), 7 - Number(pieceElem?.dataset?.t)];
  2527. },
  2528.  
  2529. 'boardDimensions': obj => {
  2530. return [8, 8];
  2531. },
  2532.  
  2533. 'isMutationNewMove': obj => {
  2534. const mutationArr = obj.mutationArr;
  2535.  
  2536. return mutationArr.length >= 12;
  2537. }
  2538. });
  2539.  
  2540. addSupportedChessSite('immortal.game', {
  2541. 'boardElem': obj => {
  2542. return document.querySelector('div.pawn.relative, div.knight.relative, div.bishop.relative, div.rook.relative, div.queen.relative, div.king.relative')?.parentElement?.parentElement;
  2543. },
  2544.  
  2545. 'pieceElem': obj => {
  2546. return obj.boardQuerySelector('div.pawn.relative, div.knight.relative, div.bishop.relative, div.rook.relative, div.queen.relative, div.king.relative');
  2547. },
  2548.  
  2549. 'chessVariant': obj => {
  2550. return 'chess';
  2551. },
  2552.  
  2553. 'boardOrientation': obj => {
  2554. const coordA = [...document.querySelectorAll('svg text[x]')]
  2555. .find(elem => elem?.textContent == 'a');
  2556.  
  2557. const coordAX = Number(coordA?.getAttribute('x')) || 10;
  2558.  
  2559. return coordAX < 15 ? 'w' : 'b';
  2560. },
  2561.  
  2562. 'pieceElemFen': obj => {
  2563. const pieceElem = obj.pieceElem;
  2564.  
  2565. const pieceColor = pieceElem?.classList?.contains('white') ? 'w' : 'b';
  2566. const elemPieceName = [...pieceElem?.classList]?.find(className => Object.keys(pieceNameToFen).includes(className));
  2567.  
  2568. if(pieceColor && elemPieceName) {
  2569. const pieceName = pieceNameToFen[elemPieceName];
  2570.  
  2571. return pieceColor === 'w' ? pieceName.toUpperCase() : pieceName.toLowerCase();
  2572. }
  2573. },
  2574.  
  2575. 'pieceElemCoords': obj => {
  2576. const pieceElem = obj.pieceElem;
  2577.  
  2578. return getElemCoordinatesFromTransform(pieceElem?.parentElement);
  2579. },
  2580.  
  2581. 'boardDimensions': obj => {
  2582. return [8, 8];
  2583. },
  2584.  
  2585. 'isMutationNewMove': obj => {
  2586. const mutationArr = obj.mutationArr;
  2587.  
  2588. if(isUserMouseDown) {
  2589. return false;
  2590. }
  2591.  
  2592. return mutationArr.length >= 5;
  2593. }
  2594. });
  2595.  
  2596. addSupportedChessSite('chessarena.com', {
  2597. 'boardElem': obj => {
  2598. return document.querySelector('*[data-component="GameLayoutBoard"] cg-board');
  2599. },
  2600.  
  2601. 'pieceElem': obj => {
  2602. return obj.boardQuerySelector('cg-piece:not(*[style*="visibility: hidden;"])');
  2603. },
  2604.  
  2605. 'chessVariant': obj => {
  2606. return 'chess';
  2607. },
  2608.  
  2609. 'boardOrientation': obj => {
  2610. const titlesElem = document.querySelector('cg-titles');
  2611.  
  2612. return titlesElem?.classList?.contains('rotated') ? 'b' : 'w';
  2613. },
  2614.  
  2615. 'pieceElemFen': obj => {
  2616. const pieceElem = obj.pieceElem;
  2617.  
  2618. const pieceColor = pieceElem?.className?.[0];
  2619. const elemPieceName = pieceElem?.className?.[1];
  2620.  
  2621. if(pieceColor && elemPieceName) {
  2622. const pieceName = elemPieceName; // pieceNameToFen[elemPieceName]
  2623.  
  2624. return pieceColor === 'w' ? pieceName.toUpperCase() : pieceName.toLowerCase();
  2625. }
  2626. },
  2627.  
  2628. 'pieceElemCoords': obj => {
  2629. const pieceElem = obj.pieceElem;
  2630.  
  2631. return getElemCoordinatesFromTransform(pieceElem, { 'onlyFlipY': true });
  2632. },
  2633.  
  2634. 'boardDimensions': obj => {
  2635. return [8, 8];
  2636. },
  2637.  
  2638. 'isMutationNewMove': obj => {
  2639. const mutationArr = obj.mutationArr;
  2640.  
  2641. if(isUserMouseDown) {
  2642. return false;
  2643. }
  2644.  
  2645. return mutationArr.find(m => m?.attributeName === 'style') ? true : false;
  2646. }
  2647. });
  2648.  
  2649.  
  2650. addSupportedChessSite('chess.net', {
  2651. 'boardElem': obj => {
  2652. return document.querySelector('cg-board');
  2653. },
  2654.  
  2655. 'pieceElem': obj => {
  2656. return obj.boardQuerySelector('piece:not(.ghost)');
  2657. },
  2658.  
  2659. 'chessVariant': obj => {
  2660. const variantLinkElem = document.querySelector('.variant-link');
  2661.  
  2662. if(variantLinkElem) {
  2663. let variant = variantLinkElem?.innerText?.toLowerCase()?.replaceAll(' ', '-');
  2664.  
  2665. const replacementTable = {
  2666. 'correspondence': 'chess',
  2667. 'koth': 'kingofthehill',
  2668. 'three-check': '3check'
  2669. };
  2670.  
  2671. return replacementTable[variant] || variant;
  2672. }
  2673. },
  2674.  
  2675. 'boardOrientation': obj => {
  2676. const filesElem = document.querySelector('coords.files');
  2677.  
  2678. return filesElem?.classList?.contains('black') ? 'b' : 'w';
  2679. },
  2680.  
  2681. 'pieceElemFen': obj => {
  2682. const pieceElem = obj.pieceElem;
  2683.  
  2684. const pieceColor = pieceElem?.classList?.contains('white') ? 'w' : 'b';
  2685. const elemPieceName = [...pieceElem?.classList]?.find(className => Object.keys(pieceNameToFen).includes(className));
  2686.  
  2687. if(pieceColor && elemPieceName) {
  2688. const pieceName = pieceNameToFen[elemPieceName];
  2689.  
  2690. return pieceColor == 'w' ? pieceName.toUpperCase() : pieceName.toLowerCase();
  2691. }
  2692. },
  2693.  
  2694. 'pieceElemCoords': obj => {
  2695. const pieceElem = obj.pieceElem;
  2696.  
  2697. const key = pieceElem?.cgKey;
  2698.  
  2699. if(key) {
  2700. return chessCoordinatesToIndex(key);
  2701. }
  2702. },
  2703.  
  2704. 'boardDimensions': obj => {
  2705. return [8, 8];
  2706. },
  2707.  
  2708. 'isMutationNewMove': obj => {
  2709. const mutationArr = obj.mutationArr;
  2710.  
  2711. return mutationArr.length >= 4
  2712. || mutationArr.find(m => m.type === 'childList') ? true : false
  2713. || mutationArr.find(m => m?.target?.classList?.contains('last-move')) ? true : false;
  2714. }
  2715. });
  2716.  
  2717. addSupportedChessSite('freechess.club', {
  2718. 'boardElem': obj => {
  2719. return document.querySelector('cg-board');
  2720. },
  2721.  
  2722. 'pieceElem': obj => {
  2723. return obj.boardQuerySelector('piece:not(.ghost)');
  2724. },
  2725.  
  2726. 'chessVariant': obj => {
  2727. return 'chess';
  2728. },
  2729.  
  2730. 'boardOrientation': obj => {
  2731. const filesElem = document.querySelector('coords.files');
  2732.  
  2733. return filesElem?.classList?.contains('black') ? 'b' : 'w';
  2734. },
  2735.  
  2736. 'pieceElemFen': obj => {
  2737. const pieceElem = obj.pieceElem;
  2738.  
  2739. const pieceColor = pieceElem?.classList?.contains('white') ? 'w' : 'b';
  2740. const elemPieceName = [...pieceElem?.classList]?.find(className => Object.keys(pieceNameToFen).includes(className));
  2741.  
  2742. if(pieceColor && elemPieceName) {
  2743. const pieceName = pieceNameToFen[elemPieceName];
  2744.  
  2745. return pieceColor == 'w' ? pieceName.toUpperCase() : pieceName.toLowerCase();
  2746. }
  2747. },
  2748.  
  2749. 'pieceElemCoords': obj => {
  2750. const pieceElem = obj.pieceElem;
  2751.  
  2752. const key = pieceElem?.cgKey;
  2753.  
  2754. if(key) {
  2755. return chessCoordinatesToIndex(key);
  2756. }
  2757. },
  2758.  
  2759. 'boardDimensions': obj => {
  2760. return [8, 8];
  2761. },
  2762.  
  2763. 'isMutationNewMove': obj => {
  2764. const mutationArr = obj.mutationArr;
  2765.  
  2766. return mutationArr.length >= 4
  2767. || mutationArr.find(m => m.type === 'childList') ? true : false
  2768. || mutationArr.find(m => m?.target?.classList?.contains('last-move')) ? true : false;
  2769. }
  2770. });
  2771.  
  2772. addSupportedChessSite('play.chessclub.com', {
  2773. 'boardElem': obj => {
  2774. return document.querySelector('cg-board');
  2775. },
  2776.  
  2777. 'pieceElem': obj => {
  2778. return obj.boardQuerySelector('piece:not(.ghost)');
  2779. },
  2780.  
  2781. 'chessVariant': obj => {
  2782. return 'chess';
  2783. },
  2784.  
  2785. 'boardOrientation': obj => {
  2786. const filesElem = document.querySelector('coords.files');
  2787.  
  2788. return filesElem?.classList?.contains('black') ? 'b' : 'w';
  2789. },
  2790.  
  2791. 'pieceElemFen': obj => {
  2792. const pieceElem = obj.pieceElem;
  2793.  
  2794. const pieceColor = pieceElem?.classList?.contains('white') ? 'w' : 'b';
  2795. const elemPieceName = [...pieceElem?.classList]?.find(className => Object.keys(pieceNameToFen).includes(className));
  2796.  
  2797. if(pieceColor && elemPieceName) {
  2798. const pieceName = pieceNameToFen[elemPieceName];
  2799.  
  2800. return pieceColor == 'w' ? pieceName.toUpperCase() : pieceName.toLowerCase();
  2801. }
  2802. },
  2803.  
  2804. 'pieceElemCoords': obj => {
  2805. const pieceElem = obj.pieceElem;
  2806.  
  2807. const key = pieceElem?.cgKey;
  2808.  
  2809. if(key) {
  2810. return chessCoordinatesToIndex(key);
  2811. }
  2812. },
  2813.  
  2814. 'boardDimensions': obj => {
  2815. return [8, 8];
  2816. },
  2817.  
  2818. 'isMutationNewMove': obj => {
  2819. const mutationArr = obj.mutationArr;
  2820.  
  2821. return mutationArr.length >= 4
  2822. || mutationArr.find(m => m.type === 'childList') ? true : false
  2823. || mutationArr.find(m => m?.target?.classList?.contains('last-move')) ? true : false;
  2824. }
  2825. });
  2826.  
  2827. addSupportedChessSite('gameknot.com', {
  2828. 'boardElem': obj => {
  2829. return document.querySelector('#chess-board-acboard');
  2830. },
  2831.  
  2832. 'pieceElem': obj => {
  2833. return obj.boardQuerySelector('*[class*="chess-board-piece"] > img[src*="chess56."][style*="visible"]');
  2834. },
  2835.  
  2836. 'chessVariant': obj => {
  2837. return 'chess';
  2838. },
  2839.  
  2840. 'boardOrientation': obj => {
  2841. return document.querySelector('#chess-board-my-side-color .player_white') ? 'w' : 'b';
  2842. },
  2843.  
  2844. 'pieceElemFen': obj => {
  2845. const pieceElem = obj.pieceElem;
  2846.  
  2847. const left = Number(pieceElem.style.left.replace('px', ''));
  2848. const top = Number(pieceElem.style.top.replace('px', ''));
  2849.  
  2850. const pieceColor = left >= 0 ? 'w' : 'b';
  2851. const pieceName = 'kqrnbp'[(top * -1) / 60];
  2852.  
  2853. return pieceColor === 'w' ? pieceName.toUpperCase() : pieceName.toLowerCase();
  2854. },
  2855.  
  2856. 'pieceElemCoords': obj => {
  2857. const pieceElem = obj.pieceElem;
  2858.  
  2859. return getElemCoordinatesFromLeftTopPixels(pieceElem.parentElement);
  2860. },
  2861.  
  2862. 'boardDimensions': obj => {
  2863. return [8, 8];
  2864. },
  2865.  
  2866. 'isMutationNewMove': obj => {
  2867. const mutationArr = obj.mutationArr;
  2868.  
  2869. return mutationArr.find(m => m.type === 'childList') ? true : false
  2870. || mutationArr.find(m => m?.target?.classList?.contains('last-move')) ? true : false;
  2871. }
  2872. });
  2873.  
  2874. addSupportedChessSite('chesstempo.com', {
  2875. 'boardElem': obj => {
  2876. return document.querySelector('.ct-board-squares');
  2877. },
  2878.  
  2879. 'pieceElem': obj => {
  2880. return obj.boardQuerySelector('*[class*="ct-pieceClass"][class*="ct-piece-"]');
  2881. },
  2882.  
  2883. 'chessVariant': obj => {
  2884. return 'chess';
  2885. },
  2886.  
  2887. 'boardOrientation': obj => {
  2888. return document.querySelector('.ct-coord-column').innerText === 'a' ? 'w' : 'b';
  2889. },
  2890.  
  2891. 'pieceElemFen': obj => {
  2892. const pieceElem = obj.pieceElem;
  2893.  
  2894. const pieceNameClass = [...pieceElem.classList].find(x => x?.includes('ct-piece-'));
  2895. const colorNameCombo = pieceNameClass?.split('ct-piece-')?.pop();
  2896.  
  2897. const elemPieceColor = colorNameCombo.startsWith('white') ? 'w' : 'b';
  2898. const elemPieceName = colorNameCombo.substring(5);
  2899.  
  2900. const pieceName = pieceNameToFen[elemPieceName];
  2901.  
  2902. return elemPieceColor === 'w' ? pieceName.toUpperCase() : pieceName.toLowerCase();
  2903. },
  2904.  
  2905. 'pieceElemCoords': obj => {
  2906. const pieceElem = obj.pieceElem;
  2907.  
  2908. return [pieceElem?.ct?.piece?.piece?.column, pieceElem?.ct?.piece?.piece?.row];
  2909. },
  2910.  
  2911. 'boardDimensions': obj => {
  2912. return [8, 8];
  2913. },
  2914.  
  2915. 'isMutationNewMove': obj => {
  2916. const mutationArr = obj.mutationArr;
  2917.  
  2918. return mutationArr.length >= 4;
  2919. }
  2920. });
  2921.  
  2922. addSupportedChessSite('redhotpawn.com', {
  2923. 'boardElem': obj => {
  2924. return document.querySelector('#board-0_1');
  2925. },
  2926.  
  2927. 'pieceElem': obj => {
  2928. return obj.boardQuerySelector('li.piece[id*="-pc-"]');
  2929. },
  2930.  
  2931. 'chessVariant': obj => {
  2932. return 'chess';
  2933. },
  2934.  
  2935. 'boardOrientation': obj => {
  2936. const aCoordLeftStyleNum = Number([...document.querySelectorAll('.boardCoordinate')]
  2937. .find(elem => elem?.innerText === 'a')
  2938. ?.style?.left?.replace('px', ''));
  2939.  
  2940. return aCoordLeftStyleNum < 200 ? 'w' : 'b';
  2941. },
  2942.  
  2943. 'pieceElemFen': obj => {
  2944. const pieceElem = obj.pieceElem;
  2945.  
  2946. return (pieceElem?.id?.match(/-pc-(.*?)-/) || [])[1];
  2947. },
  2948.  
  2949. 'pieceElemCoords': obj => {
  2950. const pieceElem = obj.pieceElem;
  2951.  
  2952. return getElemCoordinatesFromLeftTopPixels(pieceElem);
  2953. },
  2954.  
  2955. 'boardDimensions': obj => {
  2956. return [8, 8];
  2957. },
  2958.  
  2959. 'isMutationNewMove': obj => {
  2960. const mutationArr = obj.mutationArr;
  2961.  
  2962. if(isUserMouseDown) {
  2963. return false;
  2964. }
  2965.  
  2966. return mutationArr.length >= 4;
  2967. }
  2968. });
  2969.  
  2970. addSupportedChessSite('simplechess.com', {
  2971. 'boardElem': obj => {
  2972. return document.querySelector('#chessboard');
  2973. },
  2974.  
  2975. 'pieceElem': obj => {
  2976. const getAll = obj.getAll;
  2977.  
  2978. const pieceElems = [...document.querySelectorAll('canvas.canvas_piece')].filter(elem => canvasHasPixelAt(elem, [0.5, 0.5]));
  2979.  
  2980. return getAll ? pieceElems : pieceElems[0];
  2981. },
  2982.  
  2983. 'chessVariant': obj => {
  2984. return 'chess';
  2985. },
  2986.  
  2987. 'boardOrientation': obj => {
  2988. return document.querySelector('#chessboard_coordy0')?.innerText === '8' ? 'w' : 'b';
  2989. },
  2990.  
  2991. 'pieceElemFen': obj => {
  2992. const pieceElem = obj.pieceElem;
  2993.  
  2994. const pieceTypeCoordPercentages = [
  2995. { 'name' : 'k', 'coords': [52/60, 26/60] },
  2996. { 'name' : 'q', 'coords': [8/60, 16/60] },
  2997. { 'name' : 'n', 'coords': [51/60, 42/60] },
  2998. { 'name' : 'b', 'coords': [9/60, 50/60] },
  2999. { 'name' : 'r', 'coords': [45/60, 15/60] },
  3000. { 'name' : 'p', 'coords': [0.5, 0.5] }
  3001. ];
  3002.  
  3003. const pieceColorCoordPercentages = {
  3004. 'k': [42/60, 27/60],
  3005. 'q': [30/60, 50/60],
  3006. 'n': [38/60, 41/60],
  3007. 'b': [30/60, 20/60]
  3008. };
  3009.  
  3010. let pieceName = null;
  3011.  
  3012. for(obj of pieceTypeCoordPercentages) {
  3013. const isThisPiece = canvasHasPixelAt(pieceElem, obj.coords);
  3014.  
  3015. if(isThisPiece) {
  3016. pieceName = obj.name;
  3017.  
  3018. break;
  3019. }
  3020. }
  3021.  
  3022. if(pieceName) {
  3023. const colorCoords = pieceColorCoordPercentages[pieceName] || [0.5, 0.5];
  3024.  
  3025. const pieceColor = getCanvasPixelColor(pieceElem, colorCoords);
  3026.  
  3027. return pieceColor === 'w' ? pieceName.toUpperCase() : pieceName.toLowerCase();
  3028. }
  3029. },
  3030.  
  3031. 'pieceElemCoords': obj => {
  3032. const pieceElem = obj.pieceElem;
  3033.  
  3034. return getElemCoordinatesFromLeftTopPixels(pieceElem);
  3035. },
  3036.  
  3037. 'boardDimensions': obj => {
  3038. return [8, 8];
  3039. },
  3040.  
  3041. 'isMutationNewMove': obj => {
  3042. const mutationArr = obj.mutationArr;
  3043.  
  3044. if(isUserMouseDown) {
  3045. return false;
  3046. }
  3047.  
  3048. return mutationArr.length >= 7;
  3049. }
  3050. });
  3051.  
  3052. addSupportedChessSite('chessworld.net', {
  3053. 'boardElem': obj => {
  3054. return document.querySelector('#ChessWorldChessBoard');
  3055. },
  3056.  
  3057. 'pieceElem': obj => {
  3058. return obj.boardQuerySelector('img[src*="merida"');
  3059. },
  3060.  
  3061. 'chessVariant': obj => {
  3062. return 'chess';
  3063. },
  3064.  
  3065. 'boardOrientation': obj => {
  3066. return document.querySelector('div[style*="boardb.jpg"]') ? 'w' : 'b';
  3067. },
  3068.  
  3069. 'pieceElemFen': obj => {
  3070. const pieceElem = obj.pieceElem;
  3071.  
  3072. const [elemPieceColor, elemPieceName] = pieceElem
  3073. ?.src
  3074. ?.split('/')
  3075. ?.pop()
  3076. ?.replace('.png', '')
  3077. ?.split('_');
  3078.  
  3079. const pieceColor = elemPieceColor === 'white' ? 'w' : 'b';
  3080. const pieceName = pieceNameToFen[elemPieceName];
  3081.  
  3082. return pieceColor === 'w' ? pieceName.toUpperCase() : pieceName.toLowerCase();
  3083. },
  3084.  
  3085. 'pieceElemCoords': obj => {
  3086. const pieceElem = obj.pieceElem;
  3087.  
  3088. return chessCoordinatesToIndex(pieceElem?.id);
  3089. },
  3090.  
  3091. 'boardDimensions': obj => {
  3092. return [8, 8];
  3093. },
  3094.  
  3095. 'isMutationNewMove': obj => {
  3096. const mutationArr = obj.mutationArr;
  3097.  
  3098. return mutationArr.length >= 2;
  3099. }
  3100. });
  3101.  
  3102. addSupportedChessSite('app.edchess.io', {
  3103. 'boardElem': obj => {
  3104. return document.querySelector('*[data-boardid="chessboard"]');
  3105. },
  3106.  
  3107. 'pieceElem': obj => {
  3108. return obj.boardQuerySelector('*[data-piece]');
  3109. },
  3110.  
  3111. 'chessVariant': obj => {
  3112. return 'chess';
  3113. },
  3114.  
  3115. 'boardOrientation': obj => {
  3116. return document.querySelector('*[data-square]')?.dataset?.square == 'h1' ? 'b' : 'w';
  3117. },
  3118.  
  3119. 'pieceElemFen': obj => {
  3120. const pieceElem = obj.pieceElem;
  3121. const [pieceColor, pieceName] = pieceElem?.dataset?.piece?.split('');
  3122.  
  3123. return pieceColor === 'w' ? pieceName.toUpperCase() : pieceName.toLowerCase();
  3124. },
  3125.  
  3126. 'pieceElemCoords': obj => {
  3127. const pieceElem = obj.pieceElem;
  3128.  
  3129. return chessCoordinatesToIndex(pieceElem?.parentElement?.parentElement?.dataset?.square);
  3130. },
  3131.  
  3132. 'boardDimensions': obj => {
  3133. return [8, 8];
  3134. },
  3135.  
  3136. 'isMutationNewMove': obj => {
  3137. const mutationArr = obj.mutationArr;
  3138.  
  3139. return mutationArr.length >= 2;
  3140. }
  3141. });
  3142.  
  3143. /*
  3144. _____ __ _ _____ _______ _____ _______ _____ ______ _______ _______ _____ _____ __ _
  3145. | | \ | | | | |_____| | | ____/ |_____| | | | | | \ |
  3146. __|__ | \_| __|__ | __|__ | | |_____ __|__ /_____ | | | __|__ |_____| | \_|
  3147.  
  3148. Code below this point is related to initialization. (e.g. wait for chess board and create the instance)
  3149. */
  3150.  
  3151. async function isAcasBackendReady() {
  3152. const res = await CommLink.commands.ping();
  3153.  
  3154. return res ? true : false;
  3155. }
  3156.  
  3157. async function start() {
  3158. await CommLink.commands.createInstance(commLinkInstanceID);
  3159.  
  3160. const pathname = window.location.pathname;
  3161. const adjustSizeByDimensions = domain === 'chess.com' && pathname?.includes('/variants');
  3162. const ignoreBodyRectLeft = domain === 'app.edchess.io';
  3163.  
  3164. const boardOrientation = getBoardOrientation();
  3165.  
  3166. instanceVars.playerColor.set(commLinkInstanceID, boardOrientation);
  3167. instanceVars.fen.set(commLinkInstanceID, getFen());
  3168.  
  3169. if(isBoardDrawerNeeded()) {
  3170. BoardDrawer = new UniversalBoardDrawer(chessBoardElem, {
  3171. 'window': window,
  3172. 'boardDimensions': getBoardDimensions(),
  3173. 'playerColor': getPlayerColorVariable(),
  3174. 'zIndex': domain === 'chessarena.com' ? 9999 : 500,
  3175. 'prepend': true,
  3176. 'debugMode': debugModeActivated,
  3177. 'adjustSizeByDimensions': adjustSizeByDimensions ? true : false,
  3178. 'adjustSizeConfig': {
  3179. 'noLeftAdjustment': true
  3180. },
  3181. 'ignoreBodyRectLeft': ignoreBodyRectLeft
  3182. });
  3183. }
  3184.  
  3185. await updatePlayerColor();
  3186.  
  3187. observeNewMoves();
  3188.  
  3189. CommLink.setIntervalAsync(async () => {
  3190. await CommLink.commands.createInstance(commLinkInstanceID);
  3191. }, 1000);
  3192. }
  3193.  
  3194. function startWhenBackendReady() {
  3195. let timesUrlForceOpened = 0;
  3196.  
  3197. const interval = CommLink.setIntervalAsync(async () => {
  3198. if(await isAcasBackendReady()) {
  3199. start();
  3200.  
  3201. interval.stop();
  3202. } else if(timesUrlForceOpened < 1) {
  3203. timesUrlForceOpened++;
  3204.  
  3205. GM_openInTab(getCurrentBackendURL(), true);
  3206. }
  3207. }, 1000);
  3208. }
  3209.  
  3210. function initializeIfSiteReady() {
  3211. const boardElem = getBoardElem();
  3212. const firstPieceElem = getPieceElem();
  3213.  
  3214. const bothElemsExist = boardElem && firstPieceElem;
  3215. const isChessComImageBoard = domain === 'chess.com' && boardElem?.className.includes('webgl-2d');
  3216. const boardElemChanged = chessBoardElem != boardElem;
  3217.  
  3218. if((bothElemsExist || isChessComImageBoard) && boardElemChanged) {
  3219. chessBoardElem = boardElem;
  3220.  
  3221. chessBoardElem.addEventListener('mousedown', () => { isUserMouseDown = true; });
  3222. chessBoardElem.addEventListener('mouseup', () => { isUserMouseDown = false; });
  3223. chessBoardElem.addEventListener('touchstart', () => { isUserMouseDown = true; });
  3224. chessBoardElem.addEventListener('touchend', () => { isUserMouseDown = false; });
  3225.  
  3226. if(!blacklistedURLs.includes(window.location.href)) {
  3227. startWhenBackendReady();
  3228. }
  3229. }
  3230. }
  3231.  
  3232. if(typeof GM_registerMenuCommand === 'function') {
  3233. GM_registerMenuCommand('[u] Open GreasyFork Page', e => {
  3234. GM_openInTab(greasyforkURL, true);
  3235. }, 'u');
  3236.  
  3237. GM_registerMenuCommand('[o] Open GUI Manually', e => {
  3238. GM_openInTab(getCurrentBackendURL(), true);
  3239. }, 'o');
  3240.  
  3241. GM_registerMenuCommand('[s] Start Manually', e => {
  3242. if(chessBoardElem) {
  3243. start();
  3244. } else {
  3245. displayImportantNotification('Failed to start manually', 'No chessboard element found!');
  3246. }
  3247. }, 's');
  3248.  
  3249. GM_registerMenuCommand('[g] Get Moves Manually', e => {
  3250. if(chessBoardElem) {
  3251. onNewMove(null, true);
  3252. } else {
  3253. displayImportantNotification('Failed to get moves', 'No chessboard element found!');
  3254. }
  3255. }, 'g');
  3256.  
  3257. GM_registerMenuCommand('[r] Render BoardDrawer Manually', e => {
  3258. if(typeof BoardDrawer?.updateDimensions === 'function') {
  3259. BoardDrawer.updateDimensions();
  3260. } else {
  3261. displayImportantNotification('Failed to render BoardDrawer', 'BoardDrawer not initialized or something else went wrong!');
  3262. }
  3263. }, 'r');
  3264.  
  3265. if(typeof GM_setClipboard === 'function') {
  3266. GM_registerMenuCommand('[c] Copy FEN to Clipboard', e => {
  3267. if(chessBoardElem) {
  3268. GM_setClipboard(getFen());
  3269. } else {
  3270. displayImportantNotification('Failed to get FEN', 'No chessboard element found!');
  3271. }
  3272. }, 'c');
  3273. }
  3274. }
  3275.  
  3276. setInterval(initializeIfSiteReady, 1000);
  3277.  
  3278. })(); // wraps around the whole userscript to enable async