C.A.S (Chess Assistance System)

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

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

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