C.A.S (Chess Assistance System)

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

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

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