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