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

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

当前为 2023-02-22 提交的版本,查看 最新版本

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