C.A.S (Chess Assistance System)

Enhance your chess performance with a cutting-edge real-time move analysis and strategy assistance system

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

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