C.A.S (Chess.com Assistance System)

Chess analysis bot made for educational purposes only (Chrome + Firefox + Edge ...)

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

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