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