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