C.A.S (Chess.com Assistance System)

Chess analysis bot made for educational purposes only (Chrome + Firefox + Edge ...)

目前為 2023-02-20 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name C.A.S (Chess.com Assistance System)
  3. // @namespace sayfpack
  4. // @author sayfpack
  5. // @version 3.2
  6. // @homepageURL https://github.com/sayfpack13/chess-analysis-bot
  7. // @supportURL https://mmgc.life/
  8. // @match https://www.chess.com/*
  9. // @grant GM_getValue
  10. // @grant GM_setValue
  11. // @grant GM_xmlhttpRequest
  12. // @grant GM_getResourceText
  13. // @grant GM_registerMenuCommand
  14. // @description Chess analysis bot made for educational purposes only (Chrome + Firefox + Edge ...)
  15. // @require https://greasyfork.org/scripts/460400-usergui-js/code/userguijs.js?version=1152084
  16. // @resource jquery.js https://cdn.jsdelivr.net/npm/jquery@3.6.3/dist/jquery.min.js
  17. // @resource chessboard.js https://raw.githubusercontent.com/Hakorr/Userscripts/main/Other/A.C.A.S/content/chessboard.js
  18. // @resource chessboard.css https://raw.githubusercontent.com/Hakorr/Userscripts/main/Other/A.C.A.S/content/chessboard.css
  19. // @resource lozza.js https://raw.githubusercontent.com/Hakorr/Userscripts/main/Other/A.C.A.S/content/lozza.js
  20. // @resource stockfish.js https://github.com/exoticorn/stockfish-js/releases/download/sf_5_js/stockfish.js
  21. // @resource stockfish2.js https://github.com/lichess-org/stockfish.js/releases/download/ddugovic-250718/stockfish.js
  22. // @run-at document-start
  23. // @inject-into content
  24. // ==/UserScript==
  25.  
  26. /*
  27. e88~-_ e ,d88~~\
  28. d888 \ d8b 8888
  29. 8888 /Y88b `Y88b
  30. 8888 / Y88b `Y88b,
  31. Y888 / d88b /____Y88b d88b 8888
  32. "88_-~ Y88P / Y88b Y88P \__88P'
  33. */
  34.  
  35.  
  36. let displayMovesOnSite = true;
  37. let node_engine_id = [3];
  38. let engine_name = "stockfish-15" // engine name (node server)
  39. let show_opposite_moves = false;
  40. let use_book_moves = false;
  41. let engineIndex = 0; // engine index
  42. let reload_every = 5; // reload engine after x moves
  43. let enableUserLog = true; // enable engine log
  44. let nodeEngineIndex = 0;
  45.  
  46. let Gui;
  47. let closedGui = false;
  48. let reload_count = 1;
  49. const repositoryRawURL = 'https://raw.githubusercontent.com/Hakorr/Userscripts/main/Other/A.C.A.S';
  50. const repositoryURL = 'https://github.com/sayfpack13/chess-analysis-bot';
  51. const LICHESS_API = "https://lichess.org/api/cloud-eval";
  52.  
  53.  
  54. const dbValues = {
  55. engineDepthQuery: 'engineDepthQuery',
  56. openGuiAutomatically: 'openGuiAutomatically',
  57. subtleMode: 'subtleMode',
  58. subtletiness: 'subtletiness'
  59. };
  60.  
  61.  
  62.  
  63.  
  64. let Interface = null;
  65. let LozzaUtils = null;
  66.  
  67. let initialized = false;
  68. let firstMoveMade = false;
  69.  
  70.  
  71. let engine = null;
  72. let engine2 = null;
  73. let engineObjectURL = null;
  74. let lastEngine = engineIndex;
  75. let engineObjectURL2 = null;
  76.  
  77. let chessBoardElem = null;
  78. let turn = '-';
  79. let playerColor = null;
  80. let lastFen = null;
  81.  
  82. let uiChessBoard = null;
  83.  
  84. let activeGuiMoveHighlights = [];
  85. let activeSiteMoveHighlights = [];
  86.  
  87. let engineLogNum = 1;
  88. let userscriptLogNum = 1;
  89. let enemyScore = 0;
  90. let myScore = 0;
  91.  
  92.  
  93. function moveResult(givenFen = "", from, to, power, clear = true) {
  94. // make sure both fens are equal
  95. // because engine requests/results are async
  96. const FenUtil = new FenUtils();
  97. let fen = FenUtil.getFen();
  98. if (givenFen != "" && givenFen != fen) {
  99. return;
  100. }
  101.  
  102. if (from.length < 2 || to.length < 2) {
  103. return;
  104. }
  105.  
  106. if (clear) {
  107. removeSiteMoveMarkings();
  108. Interface.boardUtils.removeBestMarkings();
  109. }
  110.  
  111. const isPlayerTurn = playerColor == turn;
  112.  
  113.  
  114. if (isPlayerTurn) // my turn
  115. myScore = myScore + Number(power);
  116. else
  117. enemyScore = enemyScore + Number(power);
  118.  
  119. Interface.boardUtils.updateBoardPower(myScore, enemyScore);
  120.  
  121. if (displayMovesOnSite) {
  122. markMoveToSite(from, to, isPlayerTurn, clear);
  123. }
  124.  
  125.  
  126.  
  127. Interface.boardUtils.markMove(from, to, isPlayerTurn, clear);
  128. Interface.stopBestMoveProcessingAnimation();
  129.  
  130.  
  131. }
  132.  
  133.  
  134. function getBookMoves(fen, lichess, turn, depth = "", movetime = "") {
  135. GM_xmlhttpRequest({
  136. method: "GET",
  137. url: LICHESS_API + "?fen=" + fen + "&multiPv=3&variant=fromPosition",
  138. headers: {
  139. "Content-Type": "application/json"
  140. },
  141. onload: function (response) {
  142. if (response.response.includes("error")) {
  143.  
  144. getBestMoves(fen, lichess, turn, depth, movetime);
  145.  
  146. } else {
  147. let data = JSON.parse(response.response);
  148. let nextMove = data.pvs[0].moves.split(' ')[0];
  149. let score = depth;
  150.  
  151.  
  152. moveResult(fen, nextMove.slice(0, 2), nextMove.slice(2, 4), score, true);
  153. }
  154.  
  155.  
  156. }, onerror: function (error) {
  157. getBestMoves(fen, lichess, turn, depth, movetime);
  158.  
  159. }
  160. });
  161.  
  162. }
  163.  
  164. function getNodeBestMoves(fen, lichess, turn, depth = "", movetime = "") {
  165. var engine_type
  166. if (nodeEngineIndex == 0) {
  167. engine_type = "os"
  168. }
  169. if (nodeEngineIndex == 1) {
  170. engine_type = "js"
  171. }
  172.  
  173. GM_xmlhttpRequest({
  174. method: "GET",
  175. url: "http://localhost:5000/getBestMove?fen=" + fen + "&depth=" + depth + "&movetime=" + movetime + "&turn=" + turn + "&lichess=" + lichess + "&engine_type=" + engine_type + "&engine_name=" + engine_name,
  176. headers: {
  177. "Content-Type": "application/json"
  178. },
  179. onload: function (response) {
  180. if (response.response == "false") {
  181. return;
  182. }
  183. let data = JSON.parse(response.response);
  184. let fen = data.fen;
  185. let depth = data.depth;
  186. let power = data.score;
  187. let nextMove = data.move;
  188. let oppositeMove = data.opposite_move;
  189.  
  190.  
  191.  
  192.  
  193. moveResult(fen, nextMove.slice(0, 2), nextMove.slice(2, 4), power, true);
  194. if (oppositeMove != "false" && show_opposite_moves)
  195. moveResult(fen, oppositeMove.slice(0, 2), oppositeMove.slice(2, 4), power, false);
  196.  
  197.  
  198.  
  199. }, onerror: function (error) {
  200. console.log("check node server");
  201. console.log("using local engine !!");
  202. loadRandomChessengine(fen);
  203.  
  204. }
  205. });
  206.  
  207. }
  208.  
  209.  
  210. function eloToTitle(elo) {
  211. return elo >= 2200 ? "Master"
  212. : elo >= 2000 ? "Expert"
  213. : elo >= 1600 ? "Advanced"
  214. : elo >= 1000 ? "Intermediate"
  215. : "Beginner";
  216. }
  217.  
  218. const engineEloArr = [
  219. { elo: 200, data: 'go depth 1' },
  220. { elo: 400, data: 'go depth 2' },
  221. { elo: 500, data: 'go depth 3' },
  222. { elo: 700, data: 'go depth 4' },
  223. { elo: 800, data: 'go depth 5' },
  224. { elo: 1000, data: 'go depth 6' },
  225. { elo: 1100, data: 'go depth 7' },
  226. { elo: 1200, data: 'go depth 8' },
  227. { elo: 1300, data: 'go depth 9' },
  228. { elo: 1400, data: 'go depth 10' },
  229. { elo: 1500, data: 'go depth 11' },
  230. { elo: 1600, data: 'go depth 13' },
  231. { elo: 1700, data: 'go depth 14' },
  232. { elo: 1800, data: 'go depth 15' },
  233. { elo: 1900, data: 'go depth 16' },
  234. { elo: 2000, data: 'go depth 17' },
  235. { elo: 2100, data: 'go depth 18' },
  236. { elo: 2200, data: 'go depth 19' },
  237. { elo: 2300, data: 'go depth 20' }
  238. ];
  239.  
  240. function getCurrentEngineElo() {
  241. return engineEloArr.find(x => x.data == GM_getValue(dbValues.engineDepthQuery))?.elo;
  242. }
  243.  
  244. function getEloDescription(elo, depth) {
  245.  
  246.  
  247. return `Power: ${elo}, Desc: (${eloToTitle(elo)}), DEPTH: ${Number(depth) + 1}`;
  248. }
  249.  
  250. function isCompatibleBrowser() {
  251. return navigator.userAgent.toLowerCase().includes("firefox")
  252. }
  253.  
  254. onload = function () {
  255. if (isCompatibleBrowser()) {
  256. Gui = new UserGui;
  257. }
  258.  
  259. }
  260.  
  261. if (!isCompatibleBrowser()) {
  262. Gui = new UserGui;
  263. } else {
  264. onload();
  265. }
  266.  
  267.  
  268. Gui.settings.window.title = 'C.A.S';
  269. Gui.settings.window.external = true;
  270. Gui.settings.window.size.width = 500;
  271. Gui.settings.gui.external.popup = false;
  272. Gui.settings.gui.external.style += GM_getResourceText('chessboard.css');
  273. Gui.settings.gui.external.style += `
  274. div[class^='board'] {
  275. background-color: black;
  276. }
  277. .best-move-from {
  278. background-color: #31ff7f;
  279. transform: scale(0.85);
  280. }
  281. .best-move-to {
  282. background-color: #31ff7f;
  283. }
  284. .negative-best-move-from {
  285. background-color: #fd0000;
  286. transform: scale(0.85);
  287. }
  288. .negative-best-move-to {
  289. background-color: #fd0000;
  290. }
  291. body {
  292. display: block;
  293. margin-left: auto;
  294. margin-right: auto;
  295. width: 360px;
  296. }
  297. #fen {
  298. margin-left: 10px;
  299. }
  300. #engine-log-container {
  301. max-height: 35vh;
  302. overflow: auto!important;
  303. }
  304. #userscript-log-container {
  305. max-height: 35vh;
  306. overflow: auto!important;
  307. }
  308. .sideways-card {
  309. display: flex;
  310. align-items: center;
  311. justify-content: space-between;
  312. }
  313. .rendered-form .card {
  314. margin-bottom: 10px;
  315. }
  316. .hidden {
  317. display: none;
  318. }
  319. .main-title-bar {
  320. display: flex;
  321. justify-content: space-between;
  322. }
  323. @keyframes wiggle {
  324. 0% { transform: scale(1); }
  325. 80% { transform: scale(1); }
  326. 85% { transform: scale(1.1); }
  327. 95% { transform: scale(1); }
  328. 100% { transform: scale(1); }
  329. }
  330.  
  331. .wiggle {
  332. display: inline-block;
  333. animation: wiggle 1s infinite;
  334. }
  335. `;
  336.  
  337. function FenUtils() {
  338. this.board = [
  339. [1, 1, 1, 1, 1, 1, 1, 1],
  340. [1, 1, 1, 1, 1, 1, 1, 1],
  341. [1, 1, 1, 1, 1, 1, 1, 1],
  342. [1, 1, 1, 1, 1, 1, 1, 1],
  343. [1, 1, 1, 1, 1, 1, 1, 1],
  344. [1, 1, 1, 1, 1, 1, 1, 1],
  345. [1, 1, 1, 1, 1, 1, 1, 1],
  346. [1, 1, 1, 1, 1, 1, 1, 1],
  347. ];
  348.  
  349. this.pieceCodeToFen = pieceStr => {
  350. const [pieceColor, pieceName] = pieceStr.split('');
  351.  
  352. return pieceColor == 'w' ? pieceName.toUpperCase() : pieceName.toLowerCase();
  353. }
  354.  
  355. this.getFenCodeFromPieceElem = pieceElem => {
  356. return this.pieceCodeToFen([...pieceElem.classList].find(x => x.match(/^(b|w)[prnbqk]{1}$/)));
  357. }
  358.  
  359. this.getPieceColor = pieceFenStr => {
  360. return pieceFenStr == pieceFenStr.toUpperCase() ? 'w' : 'b';
  361. }
  362.  
  363. this.getPieceOppositeColor = pieceFenStr => {
  364. return this.getPieceColor(pieceFenStr) == 'w' ? 'b' : 'w';
  365. }
  366.  
  367. this.squeezeEmptySquares = fenStr => {
  368. return fenStr.replace(/11111111/g, '8')
  369. .replace(/1111111/g, '7')
  370. .replace(/111111/g, '6')
  371. .replace(/11111/g, '5')
  372. .replace(/1111/g, '4')
  373. .replace(/111/g, '3')
  374. .replace(/11/g, '2');
  375. }
  376.  
  377. this.posToIndex = pos => {
  378. const [x, y] = pos.split('');
  379.  
  380. return { 'y': 8 - y, 'x': 'abcdefgh'.indexOf(x) };
  381. }
  382.  
  383. this.getBoardPiece = pos => {
  384. const indexObj = this.posToIndex(pos);
  385.  
  386. return this.board[indexObj.y][indexObj.x];
  387. }
  388.  
  389. this.getRights = () => {
  390. let rights = '';
  391.  
  392. // check for white
  393. const e1 = this.getBoardPiece('e1'),
  394. h1 = this.getBoardPiece('h1'),
  395. a1 = this.getBoardPiece('a1');
  396.  
  397. if (e1 == 'K' && h1 == 'R') rights += 'K';
  398. if (e1 == 'K' && a1 == 'R') rights += 'Q';
  399.  
  400. //check for black
  401. const e8 = this.getBoardPiece('e8'),
  402. h8 = this.getBoardPiece('h8'),
  403. a8 = this.getBoardPiece('a8');
  404.  
  405. if (e8 == 'k' && h8 == 'r') rights += 'k';
  406. if (e8 == 'k' && a8 == 'r') rights += 'q';
  407.  
  408. return rights ? rights : '-';
  409. }
  410.  
  411.  
  412.  
  413. this.getBasicFen = () => {
  414. const pieceElems = [...chessBoardElem.querySelectorAll('.piece')];
  415.  
  416. pieceElems.forEach(pieceElem => {
  417. const pieceFenCode = this.getFenCodeFromPieceElem(pieceElem);
  418. const [xPos, yPos] = pieceElem.classList.toString().match(/square-(\d)(\d)/).slice(1);
  419.  
  420. this.board[8 - yPos][xPos - 1] = pieceFenCode;
  421. });
  422.  
  423. const basicFen = this.squeezeEmptySquares(this.board.map(x => x.join('')).join('/'));
  424.  
  425. return basicFen;
  426. }
  427.  
  428. this.getFen = () => {
  429. const basicFen = this.getBasicFen();
  430. const rights = this.getRights();
  431.  
  432.  
  433. return `${basicFen} ${turn} ${rights} - 0 1`;
  434. }
  435. }
  436.  
  437. function InterfaceUtils() {
  438. this.boardUtils = {
  439. findSquareElem: (squareCode) => {
  440. if (!Gui?.document) return;
  441.  
  442. return Gui.document.querySelector(`.square-${squareCode}`);
  443. },
  444. markMove: (fromSquare, toSquare, isPlayerTurn, clear = true) => {
  445. if (!Gui?.document) return;
  446.  
  447. const [fromElem, toElem] = [this.boardUtils.findSquareElem(fromSquare), this.boardUtils.findSquareElem(toSquare)];
  448.  
  449. if (isPlayerTurn && clear) {
  450. fromElem.classList.add('best-move-from');
  451. toElem.classList.add('best-move-to');
  452. } else {
  453. fromElem.classList.add('negative-best-move-from');
  454. toElem.classList.add('negative-best-move-to');
  455. }
  456.  
  457. activeGuiMoveHighlights.push(fromElem);
  458. activeGuiMoveHighlights.push(toElem);
  459. },
  460. removeBestMarkings: () => {
  461. if (!Gui?.document) return;
  462.  
  463. activeGuiMoveHighlights.forEach(elem => {
  464. elem.classList.remove('best-move-from', 'best-move-to', 'negative-best-move-from', 'negative-best-move-to');
  465. });
  466.  
  467. activeGuiMoveHighlights = [];
  468. },
  469. updateBoardFen: fen => {
  470. if (!Gui?.document) return;
  471.  
  472. Gui.document.querySelector('#fen').textContent = fen;
  473. },
  474. updateBoardPower: (myScore, enemyScore) => {
  475. if (!Gui?.document) return;
  476.  
  477. Gui.document.querySelector('#enemy-score').textContent = enemyScore;
  478. Gui.document.querySelector('#my-score').textContent = myScore;
  479. },
  480. updateBoardOrientation: orientation => {
  481. if (!Gui?.document) return;
  482.  
  483. const orientationElem = Gui?.document?.querySelector('#orientation');
  484.  
  485. if (orientationElem) {
  486. orientationElem.textContent = orientation;
  487. }
  488. }
  489. }
  490.  
  491. this.engineLog = str => {
  492. if (!Gui?.document || enableUserLog == 0) return;
  493.  
  494. const logElem = document.createElement('div');
  495. logElem.classList.add('list-group-item');
  496.  
  497. if (str.includes('info')) logElem.classList.add('list-group-item-info');
  498. if (str.includes('bestmove')) logElem.classList.add('list-group-item-success');
  499.  
  500. logElem.innerText = `#${engineLogNum++} ${str}`;
  501.  
  502. Gui.document.querySelector('#engine-log-container').prepend(logElem);
  503. }
  504.  
  505. this.log = str => {
  506. if (!Gui?.document || enableUserLog == 0) return;
  507.  
  508. const logElem = document.createElement('div');
  509. logElem.classList.add('list-group-item');
  510.  
  511. if (str.includes('info')) logElem.classList.add('list-group-item-info');
  512. if (str.includes('bestmove')) logElem.classList.add('list-group-item-success');
  513.  
  514. const container = Gui?.document?.querySelector('#userscript-log-container');
  515.  
  516. if (container) {
  517. logElem.innerText = `#${userscriptLogNum++} ${str}`;
  518.  
  519. container.prepend(logElem);
  520. }
  521. }
  522.  
  523. this.getBoardOrientation = () => {
  524. return document.querySelector('.board.flipped') ? 'b' : 'w';
  525. }
  526.  
  527. this.updateBestMoveProgress = text => {
  528. if (!Gui?.document) return;
  529.  
  530. const progressBarElem = Gui.document.querySelector('#best-move-progress');
  531.  
  532. progressBarElem.innerText = text;
  533.  
  534. progressBarElem.classList.remove('hidden');
  535. progressBarElem.classList.add('wiggle');
  536. }
  537.  
  538. this.stopBestMoveProcessingAnimation = () => {
  539. if (!Gui?.document) return;
  540.  
  541. const progressBarElem = Gui.document.querySelector('#best-move-progress');
  542.  
  543. progressBarElem.classList.remove('wiggle');
  544. }
  545.  
  546. this.hideBestMoveProgress = () => {
  547. if (!Gui?.document) return;
  548.  
  549. const progressBarElem = Gui.document.querySelector('#best-move-progress');
  550.  
  551. if (!progressBarElem.classList.contains('hidden')) {
  552. progressBarElem.classList.add('hidden');
  553. this.stopBestMoveProcessingAnimation();
  554. }
  555. }
  556. }
  557.  
  558. function LozzaUtility() {
  559. this.separateMoveCodes = moveCode => {
  560. moveCode = moveCode.trim();
  561.  
  562. let move = moveCode.split(' ')[1];
  563.  
  564. return [move.slice(0, 2), move.slice(2, 4)];
  565. }
  566.  
  567. this.extractInfo = str => {
  568. const keys = ['time', 'nps', 'depth'];
  569.  
  570. return keys.reduce((acc, key) => {
  571. const match = str.match(`${key} (\\d+)`);
  572.  
  573. if (match) {
  574. acc[key] = Number(match[1]);
  575. }
  576.  
  577. return acc;
  578. }, {});
  579. }
  580. }
  581.  
  582. function fenSquareToChessComSquare(fenSquareCode) {
  583. const [x, y] = fenSquareCode.split('');
  584.  
  585. return `square-${['abcdefgh'.indexOf(x) + 1]}${y}`;
  586. }
  587.  
  588. function markMoveToSite(fromSquare, toSquare, isPlayerTurn, clear) {
  589. const highlight = (fenSquareCode, style) => {
  590. const squareClass = fenSquareToChessComSquare(fenSquareCode);
  591.  
  592. const highlightElem = document.createElement('div');
  593. highlightElem.classList.add('highlight');
  594. highlightElem.classList.add(squareClass);
  595. highlightElem.dataset.testElement = 'highlight';
  596. highlightElem.style = style;
  597.  
  598. activeSiteMoveHighlights.push(highlightElem);
  599.  
  600. const existingHighLight = document.querySelector(`.highlight.${squareClass}`);
  601.  
  602. if (existingHighLight) {
  603. existingHighLight.remove();
  604. }
  605.  
  606. chessBoardElem.prepend(highlightElem);
  607. }
  608.  
  609. const defaultFromSquareStyle = 'background-color: rgb(249 121 255 / 90%); border: 4px solid rgb(0 0 0 / 50%);';
  610. const defaultToSquareStyle = 'background-color: rgb(129 129 129 / 90%); border: 4px dashed rgb(0 0 0 / 50%);';
  611. const negativeFromSquareStyle = 'background-color: rgb(255 0 0 / 20%); border: 4px solid rgb(0 0 0 / 50%);';
  612. const negativeToSquareStyle = 'background-color: rgb(255 0 0 / 20%); border: 4px dashed rgb(0 0 0 / 50%);';
  613.  
  614. const subtleMode = GM_getValue(dbValues.subtleMode);
  615. const subtletiness = GM_getValue(dbValues.subtletiness);
  616.  
  617. highlight(fromSquare, subtleMode ? `background-color: rgb(0 0 0 / ${subtletiness}%);` : (clear ? defaultFromSquareStyle : negativeFromSquareStyle));
  618. highlight(toSquare, subtleMode ? `background-color: rgb(0 0 0 / ${subtletiness}%);` : (clear ? defaultToSquareStyle : negativeToSquareStyle));
  619. }
  620.  
  621. function removeSiteMoveMarkings() {
  622. activeSiteMoveHighlights.forEach(elem => {
  623. elem?.remove();
  624. });
  625.  
  626. activeSiteMoveHighlights = [];
  627. }
  628.  
  629. function updateBestMove(mutationArr) {
  630.  
  631.  
  632. const FenUtil = new FenUtils();
  633.  
  634. let currentFen = FenUtil.getFen();
  635.  
  636.  
  637. if (currentFen != lastFen) {
  638. lastFen = currentFen;
  639.  
  640.  
  641.  
  642. if (mutationArr) {
  643. const attributeMutationArr = mutationArr.filter(m => m.target.classList.contains('piece') && m.attributeName == 'class');
  644.  
  645. if (attributeMutationArr?.length) {
  646. turn = FenUtil.getPieceOppositeColor(FenUtil.getFenCodeFromPieceElem(attributeMutationArr[0].target));
  647. Interface.log(`Turn updated to ${turn}!`);
  648. }
  649. }
  650.  
  651. Interface.stopBestMoveProcessingAnimation();
  652.  
  653. //currentFen=FenUtil.getFen();
  654.  
  655. Interface.boardUtils.removeBestMarkings();
  656.  
  657. removeSiteMoveMarkings();
  658.  
  659. Interface.boardUtils.updateBoardFen(currentFen);
  660.  
  661. reloadChessEngine(false, () => {
  662.  
  663.  
  664.  
  665. // send engine only when it's my turn
  666. if (playerColor == null || turn == playerColor) {
  667. Interface.log('Sending best move request to the engine!');
  668.  
  669. let depth = engineEloArr.findIndex(x => x.data == GM_getValue(dbValues.engineDepthQuery)) + 1;
  670.  
  671. if (use_book_moves) {
  672. getBookMoves(currentFen, false, turn, depth);
  673. } else {
  674. getBestMoves(currentFen, false, turn, depth);
  675. }
  676.  
  677. }
  678.  
  679. });
  680.  
  681.  
  682.  
  683.  
  684.  
  685.  
  686. }
  687. }
  688. function sleep(ms) {
  689. return new Promise(resolve => setTimeout(resolve, ms));
  690. }
  691.  
  692. function getBestMoves(fen, lichess, turn, depth = 0, movetime = "") {
  693. if (!node_engine_id.includes(engineIndex)) {
  694. // local engines
  695. while (!engine) {
  696. sleep(100);
  697. }
  698. engine.postMessage(`position fen ${fen}`);
  699. engine.postMessage(GM_getValue(dbValues.engineDepthQuery));
  700. } else {
  701. // node server
  702. console.log("using node server");
  703.  
  704.  
  705.  
  706. getNodeBestMoves(fen, lichess, turn, depth, movetime);
  707. }
  708. }
  709.  
  710. function observeNewMoves() {
  711. updateBestMove();
  712.  
  713. const boardObserver = new MutationObserver(mutationArr => {
  714. const lastPlayerColor = playerColor;
  715.  
  716. updatePlayerColor();
  717.  
  718.  
  719. if (playerColor != lastPlayerColor) {
  720. Interface.log(`Player color changed from ${lastPlayerColor} to ${playerColor}!`);
  721.  
  722. updateBestMove();
  723. } else {
  724. updateBestMove(mutationArr);
  725. }
  726. });
  727.  
  728. boardObserver.observe(chessBoardElem, { childList: true, subtree: true, attributes: true });
  729. }
  730.  
  731. function addGuiPages() {
  732. if (Gui?.document) return;
  733.  
  734. Gui.addPage("Main", `
  735. <div class="rendered-form" id="main-tab">
  736. <script>${GM_getResourceText('jquery.js')}</script>
  737. <script>${GM_getResourceText('chessboard.js')}</script>
  738. <div class="card">
  739. <div class="card-body" id="chessboard">
  740. <div class="main-title-bar">
  741. <h5 class="card-title">Live Chessboard</h5>
  742. <p id="best-move-progress"></p>
  743. </div>
  744.  
  745. <div id="board" style="width: 447px"></div>
  746. </div>
  747. <div id="orientation" class="hidden"></div>
  748. <div class="card-footer sideways-card">FEN :<small class="text-muted"><div id="fen"></div></small></div>
  749. <div class="card-footer sideways-card">ENEMY SCORE :<div id="enemy-score"></div></div>
  750. <div class="card-footer sideways-card">MY SCORE : <div id="my-score"></div></div>
  751. </div>
  752. <script>
  753. const orientationElem = document.querySelector('#orientation');
  754. const fenElem = document.querySelector('#fen');
  755.  
  756. let board = ChessBoard('board', {
  757. pieceTheme: '${repositoryRawURL}/content/chesspieces/{piece}.svg',
  758. position: 'start',
  759. orientation: '${playerColor == 'b' ? 'black' : 'white'}'
  760. });
  761.  
  762. const orientationObserver = new MutationObserver(() => {
  763. board = ChessBoard('board', {
  764. pieceTheme: '${repositoryRawURL}/content/chesspieces/{piece}.svg',
  765. position: fenElem.textContent,
  766. orientation: orientationElem.textContent == 'b' ? 'black' : 'white'
  767. });
  768. });
  769.  
  770. const fenObserver = new MutationObserver(() => {
  771. board.position(fenElem.textContent);
  772. });
  773.  
  774. orientationObserver.observe(orientationElem, { attributes: true, childList: true, characterData: true });
  775. fenObserver.observe(fenElem, { attributes: true, childList: true, characterData: true });
  776. </script>
  777. </div>
  778. `);
  779.  
  780. Gui.addPage('Log', `
  781. <div class="rendered-form" id="log-tab">
  782. <div class="card">
  783. <div class="card-body">
  784. <h5 class="card-title">Userscript Log</h5>
  785. <ul class="list-group" id="userscript-log-container"></ul>
  786. </div>
  787. </div>
  788. <div class="card">
  789. <div class="card-body">
  790. <h5 class="card-title">Engine Log</h5>
  791. <ul class="list-group" id="engine-log-container"></ul>
  792. </div>
  793. </div>
  794. </div>
  795. `);
  796.  
  797. let depth = engineEloArr.findIndex(x => x.data == GM_getValue(dbValues.engineDepthQuery));
  798. depth += 1;
  799. const subtletiness = GM_getValue(dbValues.subtletiness);
  800.  
  801.  
  802. const openGuiAutomatically = GM_getValue(dbValues.openGuiAutomatically) == true;
  803. const subtleMode = GM_getValue(dbValues.subtleMode) == true;
  804.  
  805.  
  806.  
  807.  
  808.  
  809. Gui.addPage('Settings', `
  810. <div class="rendered-form" id="settings-tab">
  811. <div class="card">
  812. <div class="card-body">
  813. <h5 class="card-title">Engine</h5>
  814. <div class="form-group field-select-engine">
  815. <select class="form-control" name="select-engine" id="select-engine">
  816. <option value="option-lozza" id="select-engine-0">Lozza</option>
  817. <option value="option-stockfish" id="select-engine-2">Stockfish 5</option>
  818. <option value="option-stockfish2" id="select-engine-3">Stockfish 2018</option>
  819. <option value="option-nodeserver" id="select-engine-4">Node Server Engines</option>
  820. </select>
  821. </div>
  822.  
  823. <div class="form-group field-select-engine">
  824. <select class="form-control" name="select-engine-node" id="select-engine-node" style="display:${node_engine_id.includes(engineIndex) ? 'block' : 'none'};">
  825. <option value="option-nodeserver-os-engine" id="select-engine-0">System Engine</option>
  826. <option value="option-nodeserver-js" id="select-engine-1">Stockfish.js</option>
  827. </select>
  828. </div>
  829. <div id="engine-name-div" style="display:${(node_engine_id.includes(engineIndex) && nodeEngineIndex == 0) ? 'block' : 'none'};">
  830. <label for="engine-name">Engine Name:</label>
  831. <input type="text" id="engine-name" value="${engine_name}">
  832. </div>
  833. </div>
  834. </div>
  835.  
  836.  
  837. <div class="card">
  838. <div class="card-body">
  839. <h5 class="card-title">Engine Strength</h5>
  840. <input type="range" class="form-range" min="0" max="${engineEloArr.length - 1}" value="${depth}" id="depth-range">
  841. <input type="number" class="form-range" min="1" max="${engineEloArr.length - 1}" value="${depth}" id="depth-range-number">
  842. </div>
  843. <div class="card-footer sideways-card" id="depth-elo">Elo <small id="elo">${getEloDescription(getCurrentEngineElo(), depth)}</small></div>
  844. </div>
  845.  
  846. <div class="card">
  847. <div class="card-body">
  848. <h5 class="card-title">Other</h5>
  849.  
  850. <div>
  851. <input type="checkbox" id="enable-user-log" ${enableUserLog ? 'checked' : ''}>
  852. <label for="enable-user-log">Enable User Scripts Log</label>
  853. </div>
  854.  
  855.  
  856. <div>
  857. <input type="checkbox" id="use-book-moves" ${use_book_moves ? 'checked' : ''}>
  858. <label for="use-book-moves">Use book moves</label>
  859. </div>
  860.  
  861.  
  862. <div>
  863. <input type="checkbox" id="show-opposite-moves" ${show_opposite_moves ? 'checked' : ''}>
  864. <label for="show-opposite-moves">Show Opponent best moves</label>
  865. </div>
  866.  
  867. <div id="reload-count-div" style="display:${node_engine_id.includes(engineIndex) ? 'none' : 'block'};">
  868. <label for="reload-count">Reload Engine every</label>
  869. <input type="number" id="reload-count" value="${reload_every}">
  870. <label for="reload-count"> moves</label>
  871. </div>
  872. </div>
  873.  
  874.  
  875. <div class="card">
  876. <div class="card-body">
  877. <h5 class="card-title">Visual</h5>
  878. <div id="display-moves-on-site-warning" class="alert alert-danger ${displayMovesOnSite ? '' : 'hidden'}">
  879. <strong>Highly risky!</strong> DOM manipulation (moves displayed on site) is easily detectable! Use with caution.
  880. </div>
  881. <input type="checkbox" id="display-moves-on-site" ${displayMovesOnSite ? 'checked' : ''}>
  882. <label for="display-moves-on-site">Display moves on site</label>
  883. <!-- Display moves on site additional settings -->
  884. <div class="card ${displayMovesOnSite ? '' : 'hidden'}" id="display-moves-on-site-additional">
  885. <div class="card-body">
  886. <!-- Open GUI automatically checkbox -->
  887. <div>
  888. <input type="checkbox" id="open-gui-automatically" ${openGuiAutomatically ? 'checked' : ''}>
  889. <label for="open-gui-automatically">Open GUI automatically</label>
  890. </div>
  891. <!-- Subtle mode settinngs -->
  892. <div>
  893. <!-- Subtle mode checkbox -->
  894. <div>
  895. <input type="checkbox" id="subtle-mode" ${subtleMode ? 'checked' : ''}>
  896. <label for="subtle-mode">Subtle mode</label>
  897. </div>
  898. <!-- Subtle mode additional settings -->
  899. <div>
  900. <div class="card ${subtleMode ? '' : 'hidden'}" id="subtletiness-range-container">
  901. <div class="card-body">
  902. <!-- Subtletiness range -->
  903. <h6 class="card-title">Visibility</h6>
  904. <input type="range" class="form-range" min="1" max="50" value="${subtletiness}" id="subtletiness-range">
  905. </div>
  906. <div class="card-footer sideways-card">Percentage <small id="subtletiness-info">${subtletiness}%</small></div>
  907. </div>
  908. </div>
  909. </div>
  910. </div>
  911. </div>
  912. </div>
  913. </div>
  914. </div>
  915. `);
  916.  
  917.  
  918. }
  919.  
  920.  
  921.  
  922. function openGUI() {
  923. Interface.log(`Opening GUI!`);
  924.  
  925. const hide = elem => elem.classList.add('hidden');
  926. const show = elem => elem.classList.remove('hidden');
  927.  
  928. Gui.open(() => {
  929. const depthEloElem = Gui.document.querySelector('#depth-elo');
  930. const depthRangeElem = Gui.document.querySelector('#depth-range');
  931. const depthRangeNumberElem = Gui.document.querySelector('#depth-range-number');
  932. const eloElem = Gui.document.querySelector('#elo');
  933. const engineElem = Gui.document.querySelector('#select-engine');
  934. const nodeEngineElem = Gui.document.querySelector('#select-engine-node');
  935. const engineNameDivElem = Gui.document.querySelector('#engine-name-div');
  936. const reloadEveryDivElem = Gui.document.querySelector('#reload-count-div');
  937. const engineNameElem = Gui.document.querySelector('#engine-name');
  938. engineElem.selectedIndex = engineIndex;
  939. nodeEngineElem.selectedIndex = nodeEngineIndex;
  940.  
  941. if (isCompatibleBrowser()) {
  942. depthRangeElem.style.display = "none";
  943. depthRangeNumberElem.style.display = "block";
  944. } else {
  945. depthRangeElem.style.display = "block";
  946. depthRangeNumberElem.style.display = "none";
  947. }
  948.  
  949.  
  950. const useLocalEngineElem = Gui.document.querySelector('#use-book-moves');
  951. const showOppositeMovesElem = Gui.document.querySelector('#show-opposite-moves');
  952.  
  953.  
  954.  
  955.  
  956. const displayMovesOnSiteElem = Gui.document.querySelector('#display-moves-on-site');
  957. const displayMovesOnSiteWarningElem = Gui.document.querySelector('#display-moves-on-site-warning');
  958.  
  959. const openGuiAutomaticallyElem = Gui.document.querySelector('#open-gui-automatically');
  960. const openGuiAutomaticallyAdditionalElem = Gui.document.querySelector('#display-moves-on-site-additional');
  961.  
  962. const subtleModeElem = Gui.document.querySelector('#subtle-mode');
  963. const subtletinessRangeContainerElem = Gui.document.querySelector('#subtletiness-range-container');
  964. const subtletinessRange = Gui.document.querySelector('#subtletiness-range');
  965.  
  966. const subtletinessInfo = Gui.document.querySelector('#subtletiness-info');
  967.  
  968.  
  969. const reloadEveryElem = Gui.document.querySelector('#reload-count');
  970.  
  971. const enableUserLogElem = Gui.document.querySelector('#enable-user-log');
  972.  
  973.  
  974.  
  975. if (isCompatibleBrowser()) {
  976. Gui.document.querySelector('#content').style.maxHeight = "500px";
  977. Gui.document.querySelector('#content').style.overflow = "scroll";
  978. Gui.document.querySelector('#chessboard').style.display = "none";
  979. Gui.document.querySelector('#orientation').style.display = "none";
  980. Gui.document.querySelector('#engine-log-container').style.maxHeight = "100px";
  981. Gui.document.querySelector('#engine-log-container').style.overflow = "scroll";
  982. Gui.document.querySelector('#userscript-log-container').style.maxHeight = "100px";
  983. Gui.document.querySelector('#userscript-log-container').style.overflow = "scroll";
  984.  
  985. Gui.document.querySelector('#button-close-gui').addEventListener('click', e => {
  986. e.preventDefault();
  987. if (closedGui == true) {
  988. closedGui = false;
  989. Gui.document.querySelector("#content").style.display = "block";
  990. }
  991. else {
  992. closedGui = true;
  993. Gui.document.querySelector("#content").style.display = "none";
  994.  
  995. }
  996.  
  997.  
  998. });
  999. }
  1000.  
  1001.  
  1002. engineNameElem.onchange = () => {
  1003. engine_name = engineNameElem.value;
  1004. console.log(engine_name);
  1005. }
  1006.  
  1007. enableUserLogElem.onchange = () => {
  1008. const isChecked = enableUserLogElem.checked;
  1009.  
  1010. if (isChecked)
  1011. enableUserLog = true;
  1012. else
  1013. enableUserLog = false;
  1014. }
  1015.  
  1016. reloadEveryElem.onchange = () => {
  1017. reload_every = reloadEveryElem.value;
  1018. }
  1019.  
  1020. engineElem.onchange = () => {
  1021. lastEngine = engineIndex;
  1022. engineIndex = engineElem.selectedIndex;
  1023.  
  1024.  
  1025.  
  1026. if (node_engine_id.includes(engineIndex)) {
  1027. reloadEveryDivElem.style.display = "none";
  1028. nodeEngineElem.style.display = "block";
  1029.  
  1030. if(nodeEngineIndex==0){
  1031. engineNameDivElem.style.display="block";
  1032. }else{
  1033. engineNameDivElem.style.display="none";
  1034. }
  1035. }
  1036. else {
  1037. reloadEveryDivElem.style.display = "block";
  1038. nodeEngineElem.style.display = "none";
  1039. engineNameDivElem.style.display="none";
  1040. }
  1041.  
  1042.  
  1043.  
  1044.  
  1045.  
  1046. if (engineObjectURL) {
  1047. URL.revokeObjectURL(engineObjectURL);
  1048. engineObjectURL = null;
  1049. URL.revokeObjectURL(engineObjectURL2);
  1050. engineObjectURL2 = null;
  1051. }
  1052.  
  1053.  
  1054.  
  1055.  
  1056. reloadChessEngine(true, () => {
  1057. Interface.boardUtils.removeBestMarkings();
  1058.  
  1059. removeSiteMoveMarkings();
  1060.  
  1061. Interface.boardUtils.updateBoardPower(0, 0);
  1062. });
  1063.  
  1064. }
  1065.  
  1066.  
  1067. nodeEngineElem.onchange = () => {
  1068. nodeEngineIndex = nodeEngineElem.selectedIndex;
  1069.  
  1070. if (nodeEngineIndex == 0) {
  1071. engineNameDivElem.style.display = "block";
  1072. } else {
  1073. engineNameDivElem.style.display = "none";
  1074. }
  1075. }
  1076. depthRangeElem.onchange = () => {
  1077. changeDepth(depthRangeElem.value, eloElem);
  1078. };
  1079.  
  1080. depthRangeNumberElem.onchange = () => {
  1081. changeDepth(depthRangeNumberElem.value - 1, eloElem);
  1082. };
  1083.  
  1084. showOppositeMovesElem.onchange = () => {
  1085. const isChecked = showOppositeMovesElem.checked;
  1086.  
  1087. if (isChecked) {
  1088. show_opposite_moves = true;
  1089. } else {
  1090. show_opposite_moves = false;
  1091. }
  1092. }
  1093.  
  1094. useLocalEngineElem.onchange = () => {
  1095. const isChecked = useLocalEngineElem.checked;
  1096.  
  1097. if (isChecked) {
  1098. use_book_moves = true;
  1099. } else {
  1100. use_book_moves = false;
  1101. }
  1102. }
  1103.  
  1104. displayMovesOnSiteElem.onchange = () => {
  1105. const isChecked = displayMovesOnSiteElem.checked;
  1106.  
  1107. if (isChecked) {
  1108. displayMovesOnSite = true;
  1109.  
  1110. show(displayMovesOnSiteWarningElem);
  1111. show(openGuiAutomaticallyAdditionalElem);
  1112.  
  1113. openGuiAutomaticallyElem.checked = GM_getValue(dbValues.openGuiAutomatically);
  1114. } else {
  1115. displayMovesOnSite = false;
  1116. GM_setValue(dbValues.openGuiAutomatically, true);
  1117.  
  1118. hide(displayMovesOnSiteWarningElem);
  1119. hide(openGuiAutomaticallyAdditionalElem);
  1120. }
  1121. };
  1122.  
  1123. openGuiAutomaticallyElem.onchange = () => {
  1124. GM_setValue(dbValues.openGuiAutomatically, openGuiAutomaticallyElem.checked);
  1125. };
  1126.  
  1127. subtleModeElem.onchange = () => {
  1128. const isChecked = subtleModeElem.checked;
  1129.  
  1130. if (isChecked) {
  1131. GM_setValue(dbValues.subtleMode, true);
  1132. show(subtletinessRangeContainerElem);
  1133. } else {
  1134. GM_setValue(dbValues.subtleMode, false);
  1135. hide(subtletinessRangeContainerElem);
  1136. }
  1137. };
  1138.  
  1139. subtletinessRange.onchange = () => {
  1140. GM_setValue(dbValues.subtletiness, subtletinessRange.value);
  1141. subtletinessInfo.innerText = `${subtletinessRange.value}%`;
  1142. };
  1143.  
  1144. window.onunload = () => {
  1145. if (Gui.window && !Gui.window.closed) {
  1146. Gui.window.close();
  1147. }
  1148. };
  1149.  
  1150. const isWindowClosed = setInterval(() => {
  1151. if (Gui.window.closed) {
  1152. clearInterval(isWindowClosed);
  1153. if (engine != null)
  1154. engine.terminate();
  1155. }
  1156. }, 1000);
  1157.  
  1158. observeNewMoves();
  1159.  
  1160. Interface.log('Initialized!');
  1161. });
  1162. }
  1163.  
  1164. function changeDepth(val, eloElem) {
  1165. const depth = val;
  1166. const engineEloObj = engineEloArr[depth];
  1167.  
  1168. const description = getEloDescription(engineEloObj.elo, Number(depth));
  1169. const engineQuery = engineEloObj.data;
  1170.  
  1171. GM_setValue(dbValues.engineDepthQuery, engineQuery);
  1172.  
  1173. eloElem.innerText = description;
  1174. }
  1175.  
  1176. function reloadChessEngine(forced, callback) {
  1177. // reload only if using local engines
  1178. if (node_engine_id.includes(engineIndex))
  1179. callback();
  1180. else if (reload_count >= reload_every || forced == true) {
  1181. reload_count = 1;
  1182. Interface.log(`Reloading the chess engine!`);
  1183.  
  1184. if (engine)
  1185. engine.terminate();
  1186. if (engine2)
  1187. engine2.terminate();
  1188. loadChessEngine(callback);
  1189. }
  1190. else {
  1191. reload_count = reload_count + 1;
  1192. callback();
  1193. }
  1194. }
  1195.  
  1196. function loadRandomChessengine(fen) {
  1197. if (!engineObjectURL2)
  1198. engineObjectURL2 = URL.createObjectURL(new Blob([GM_getResourceText('stockfish2.js')], { type: 'application/javascript' }));
  1199.  
  1200.  
  1201.  
  1202.  
  1203. if (engineObjectURL2) {
  1204. engine2 = new Worker(engineObjectURL2);
  1205. engine2.onmessage = e => {
  1206. if (e.data.includes('bestmove')) {
  1207.  
  1208. let move = e.data.split(' ')[1];
  1209. let move2 = e.data.split(' ')[3];
  1210.  
  1211. moveResult(fen, move.slice(0, 2), move.slice(2, 4), 0, true);
  1212. if ((move2 != undefined || move2 != "") && show_opposite_moves) {
  1213. moveResult(fen, move2.slice(0, 2), move2.slice(2, 4), 0, false);
  1214. }
  1215. }
  1216.  
  1217.  
  1218. };
  1219.  
  1220. while (!engine2) {
  1221. sleep(1000);
  1222. }
  1223. engine2.postMessage('ucinewgame');
  1224.  
  1225. engine2.postMessage(`position fen ${fen}`);
  1226.  
  1227.  
  1228. engine2.postMessage(GM_getValue(dbValues.engineDepthQuery));
  1229.  
  1230. }
  1231. }
  1232.  
  1233. function loadChessEngine(callback) {
  1234. if (!engineObjectURL) {
  1235. if (engineIndex == 0)
  1236. engineObjectURL = URL.createObjectURL(new Blob([GM_getResourceText('lozza.js')], { type: 'application/javascript' }));
  1237. else if (engineIndex == 1)
  1238. engineObjectURL = URL.createObjectURL(new Blob([GM_getResourceText('stockfish.js')], { type: 'application/javascript' }));
  1239. else if (engineIndex == 2)
  1240. engineObjectURL = URL.createObjectURL(new Blob([GM_getResourceText('stockfish2.js')], { type: 'application/javascript' }));
  1241. }
  1242.  
  1243. if (engineObjectURL) {
  1244. engine = new Worker(engineObjectURL);
  1245.  
  1246. engine.onmessage = e => {
  1247. if (e.data.includes('bestmove')) {
  1248.  
  1249.  
  1250. let move = e.data.split(' ')[1];
  1251. let move2 = e.data.split(' ')[3];
  1252.  
  1253. moveResult("", move.slice(0, 2), move.slice(2, 4), 0, true);
  1254. if ((move2 != undefined || move2 != "") && show_opposite_moves) {
  1255. moveResult("", move2.slice(0, 2), move2.slice(2, 4), 0, false);
  1256. }
  1257. }
  1258.  
  1259. else if (e.data.includes('info')) {
  1260.  
  1261. const infoObj = LozzaUtils.extractInfo(e.data);
  1262.  
  1263. if (infoObj?.depth) {
  1264. Interface.updateBestMoveProgress(`Depth ${infoObj.depth}`);
  1265. }
  1266. }
  1267. Interface.engineLog(e.data);
  1268. };
  1269.  
  1270. engine.postMessage('ucinewgame');
  1271.  
  1272. Interface.log(`Loaded the chess engine!`);
  1273. }
  1274.  
  1275. callback();
  1276. }
  1277.  
  1278. function initializeDatabase() {
  1279. const initValue = (name, value) => {
  1280. if (GM_getValue(name) == undefined) {
  1281. GM_setValue(name, value);
  1282. }
  1283. };
  1284.  
  1285. initValue(dbValues.engineDepthQuery, 'go depth 5');
  1286. initValue(dbValues.subtleMode, false);
  1287. initValue(dbValues.openGuiAutomatically, true);
  1288. initValue(dbValues.subtletiness, 25);
  1289.  
  1290. Interface.log(`Initialized the database!`);
  1291. }
  1292.  
  1293. async function updatePlayerColor() {
  1294. const boardOrientation = Interface.getBoardOrientation();
  1295.  
  1296. playerColor = boardOrientation;
  1297. turn = boardOrientation;
  1298.  
  1299. Interface.boardUtils.updateBoardOrientation(playerColor);
  1300. }
  1301.  
  1302. async function initialize(openInterface) {
  1303. Interface = new InterfaceUtils();
  1304. LozzaUtils = new LozzaUtility();
  1305.  
  1306. const boardOrientation = Interface.getBoardOrientation();
  1307. turn = boardOrientation;
  1308.  
  1309. initializeDatabase();
  1310.  
  1311. loadChessEngine(() => {
  1312.  
  1313. });
  1314.  
  1315. updatePlayerColor();
  1316.  
  1317. if (openInterface) {
  1318. addGuiPages();
  1319. openGUI();
  1320. } else {
  1321. observeNewMoves();
  1322. }
  1323. }
  1324.  
  1325. if (typeof GM_registerMenuCommand == 'function') {
  1326. GM_registerMenuCommand("Open C.A.S", e => {
  1327. if (chessBoardElem) {
  1328. initialize(true);
  1329. }
  1330. }, 's');
  1331. }
  1332.  
  1333. const waitForChessBoard = setInterval(() => {
  1334. const boardElem = document.querySelector('chess-board');
  1335. const firstPieceElem = document.querySelector('.piece');
  1336.  
  1337. if (boardElem && firstPieceElem && chessBoardElem != boardElem) {
  1338. chessBoardElem = boardElem;
  1339.  
  1340. if (window.location.href != 'https://www.chess.com/play') {
  1341. const openGuiAutomatically = GM_getValue(dbValues.openGuiAutomatically);
  1342.  
  1343. if (openGuiAutomatically == undefined) {
  1344. initialize(true);
  1345. } else {
  1346. initialize(openGuiAutomatically);
  1347. }
  1348. }
  1349. }
  1350. }, 1000);