// ==UserScript== // @name Better UI Smart Chess Bot // @name:fr Better UI Smart Chess bot // @namespace TropicalFrog3, sayfpack13 // @author TropicalFrog3, sayfpack13 // @version 2.0 // @supportURL https://mmgc.life/ // @match https://www.chess.com/* // @grant GM_getValue // @grant GM_setValue // @grant GM_xmlhttpRequest // @grant GM_getResourceText // @grant GM_registerMenuCommand // @description Our chess analysis system, modified with a better UI, is designed to give players the edge they need to win. By using advanced algorithms and cutting-edge technology, our system can analyze any chess position and suggest the best possible move, helping players to make smarter and more informed decisions on the board. // @description:fr Notre système d'analyse d'échecs, modifié avec une meilleure interface utilisateur (UI), est conçu pour donner aux joueurs l'avantage dont ils ont besoin pour gagner. En utilisant des algorithmes avancés et une technologie de pointe, notre système peut analyser n'importe quelle position d'échecs et suggérer le meilleur coup possible, aidant ainsi les joueurs à prendre des décisions plus intelligentes et plus éclairées sur l'échiquier. // @require https://greasyfork.org/scripts/460400-usergui-js/code/userguijs.js?version=1157130 // @resource jquery.js https://cdn.jsdelivr.net/npm/jquery@3.6.3/dist/jquery.min.js // @resource chessboard.js https://raw.githubusercontent.com/sayfpack13/chess-analysis-bot/main/tampermonkey%20script/content/chessboard.js // @resource chessboard.css https://raw.githubusercontent.com/sayfpack13/chess-analysis-bot/main/tampermonkey%20script/content/chessboard.css // @resource lozza.js https://raw.githubusercontent.com/sayfpack13/chess-analysis-bot/main/tampermonkey%20script/content/lozza.js // @resource stockfish-5.js https://raw.githubusercontent.com/sayfpack13/chess-analysis-bot/main/tampermonkey%20script/content/stockfish-5.js // @resource stockfish-2018.js https://raw.githubusercontent.com/sayfpack13/chess-analysis-bot/main/tampermonkey%20script/content/stockfish-2018.js // @run-at document-start // @license MIT // @downloadURL https://update.greasyfork.icu/scripts/493934/Better%20UI%20Smart%20Chess%20Bot.user.js // @updateURL https://update.greasyfork.icu/scripts/493934/Better%20UI%20Smart%20Chess%20Bot.meta.js // ==/UserScript== // VARS const repositoryRawURL = 'https://raw.githubusercontent.com/sayfpack13/chess-analysis-bot/main/tampermonkey%20script'; const LICHESS_API = "https://lichess.org/api/cloud-eval"; const CHESS_COM = 0; const LICHESS_ORG = 1; const TURN_UPDATE_FIX = false; const MAX_DEPTH = 20; const MIN_DEPTH = 1; const MAX_MOVETIME = 2000; const MIN_MOVETIME = 50; const MAX_ELO = 3500; const DEPTH_MODE = 0; const MOVETIME_MODE = 1; const rank = ["Beginner", "Intermediate", "Advanced", "Expert", "Master", "Grand Master"]; var nightMode = true; var engineMode = 0;// engine mode (0:depth / 1:movetime) var engineIndex = 0;// engine index (lozza => 0, stockfish => 1...) var reload_every = 10;// reload engine after x moves var reload_engine = false;// reload engine var enableUserLog = false;// enable interface log var enableEngineLog = false;// enable engine log var displayMovesOnSite = false;// display moves on chess board var show_opposite_moves = false;// show opponent best moves if available var use_book_moves = false;// use lichess api to get book moves var node_engine_url = "http://localhost:5000";// node server api url var node_engine_name = "stockfish-15.exe";// default engine name (node server engine only) var current_depth = Math.round(MAX_DEPTH / 2);// current engine depth var current_movetime = Math.round(MAX_MOVETIME / 3);// current engine move time var max_best_moves = 1; var lastBestMoveID = 0; const dbValues = { nightMode: 'nightMode', engineMode: 'engineMode', engineIndex: 'engineIndex', reload_every: 'reload_every', reload_engine: 'reload_engine', enableUserLog: 'enableUserLog', enableEngineLog: 'enableEngineLog', displayMovesOnSite: 'displayMovesOnSite', show_opposite_moves: "show_opposite_moves", use_book_moves: "use_book_moves", node_engine_url: "node_engine_url", node_engine_name: "node_engine_name", current_depth: "current_depth", current_movetime: "current_movetime", max_best_moves: "max_best_moves" }; var Gui; var closedGui = false; var reload_count = 1; var node_engine_id = 3; var Interface = null; var LozzaUtils = null; var CURRENT_SITE = null; var boardElem = null; var firstPieceElem = null; const MAX_LOGS = 50; var initialized = false; var firstMoveMade = false; var forcedBestMove = false; var engine = null; var engineObjectURL = null; var lastEngine = engineIndex; var chessBoardElem = null; var turn = '-'; var last_turn = null; var playerColor = null; var lastPlayerColor = null; var isPlayerTurn = null; var lastFen = null; var uiChessBoard = null; var activeGuiMoveHighlights = []; var activeSiteMoveHighlights = []; var engineLogNum = 1; var userscriptLogNum = 1; var enemyScore = 0; var myScore = 0; var possible_moves = []; // style const best_move_color = [0, 0, 250, 0.5]; const opposite_best_move_color = [250, 0, 0, 0.5]; const possible_moves_colors = [ //[r,g,b,a] [200, 180, 0, 0.9], [150, 180, 0, 0.9], [100, 180, 0, 0.9], [50, 180, 0, 0.9] ] const opposite_possible_moves_colors = [ //[r,g,b,a] [250, 200, 200, 0.9], [250, 150, 150, 0.9], [250, 100, 100, 0.9], [250, 50, 50, 0.9] ] const defaultFromSquareStyle = 'border: 4px solid rgb(0 0 0 / 50%);'; const defaultToSquareStyle = 'border: 4px dashed rgb(0 0 0 / 50%);'; let guiVisible = true; let lastGuiWidth = ""; let lastGuiHeight = ""; function toggleGUI() { const iframes = document.querySelectorAll('iframe'); if (iframes.length > 0) { const guiFrame = iframes[0]; if (guiVisible) { guiFrame.style.visibility = 'hidden'; lastGuiWidth = guiFrame.style.width; guiFrame.style.width = '0px'; lastGuiHeight = guiFrame.style.height ; guiFrame.style.height = '0px'; guiVisible = false; } else { guiFrame.style.visibility = 'visible'; guiFrame.style.width = lastGuiWidth; guiFrame.style.height = lastGuiHeight; guiVisible = true; } } else { console.error('No iframe found in the document.'); } } function moveResult(from, to, power, clear = true) { if (from.length < 2 || to.length < 2) { return; } if (clear) { clearBoard(); } if (!forcedBestMove) { if (isPlayerTurn) // my turn { myScore = myScore + Number(power); } else { enemyScore = enemyScore + Number(power); } Interface.boardUtils.updateBoardPower(myScore, enemyScore); } else { forcedBestMove = false; Gui.document.querySelector('#bestmove-btn').disabled = false; } // remove duplicated best moves possible_moves = removeDuplicates(possible_moves).slice(0, max_best_moves - 1); for (let a = 0; a < possible_moves.length && engineIndex != 0; a++) { Interface.boardUtils.markMove(possible_moves[a].slice(0, 2), possible_moves[a].slice(2, 4), (isPlayerTurn ? possible_moves_colors[a] : opposite_possible_moves_colors[a])); } Interface.boardUtils.markMove(from, to, (isPlayerTurn ? best_move_color : opposite_best_move_color)); Interface.stopBestMoveProcessingAnimation(); } function getBookMoves(request) { GM_xmlhttpRequest({ method: "GET", url: LICHESS_API + "?fen=" + request.fen + "&multiPv=1&variant=fromPosition", headers: { "Content-Type": "application/json" }, onload: function (response) { if (response.response.includes("error") || !response.ok) { if (lastBestMoveID != request.id) { return; } getBestMoves(request); } else { if (lastBestMoveID != request.id) { return; } let data = JSON.parse(response.response); let nextMove = data.pvs[0].moves.split(' ')[0]; moveResult(nextMove.slice(0, 2), nextMove.slice(2, 4), current_depth, true); } }, onerror: function (error) { if (lastBestMoveID != request.id) { return; } getBestMoves(request); } }); } function getNodeBestMoves(request) { GM_xmlhttpRequest({ method: "GET", url: node_engine_url + "/getBestMove?fen=" + request.fen + "&engine_mode=" + engineMode + "&depth=" + current_depth + "&movetime=" + current_movetime + "&turn=" + (last_turn || turn) + "&engine_name=" + node_engine_name, headers: { "Content-Type": "application/json" }, onload: function (response) { if (response.response == "false") { forcedBestMove = false; Gui.document.querySelector('#bestmove-btn').disabled = false; return Interface.log("check node server logs !!"); } if (lastBestMoveID != request.id) { return; } let data = JSON.parse(response.response); let server_fen = data.fen; let depth = data.depth; let movetime = data.movetime; let power = data.score; let move = data.move; if (engineMode == DEPTH_MODE) { Interface.updateBestMoveProgress(`Depth: ${depth}`); } else { Interface.updateBestMoveProgress(`Move time: ${movetime} ms`); } moveResult(move.slice(0, 2), move.slice(2, 4), power, true); }, onerror: function (error) { forcedBestMove = false; Gui.document.querySelector('#bestmove-btn').disabled = false; Interface.log("make sure node server is running !!"); } }); } function getElo() { let elo; if (engineMode == DEPTH_MODE) { elo = MAX_ELO / MAX_DEPTH; elo *= current_depth; } else { elo = MAX_ELO / MAX_MOVETIME; elo *= current_movetime; } elo = Math.round(elo); return elo; } function getRank() { let part; if (engineMode == DEPTH_MODE) { part = current_depth / (MAX_DEPTH / rank.length); } else { part = current_movetime / (MAX_MOVETIME / rank.length); } part = Math.round(part); if (part >= rank.length) { part = rank.length - 1; } return rank[part]; } function setEloDescription(eloElem) { eloElem.querySelector("#value").innerText = `Elo: ${getElo()}`; eloElem.querySelector("#rank").innerText = `Rank: ${getRank()}`; eloElem.querySelector("#power").innerText = engineMode == DEPTH_MODE ? `Depth: ${current_depth}` : `Move Time: ${current_movetime}`; } function isNotCompatibleBrowser() { return navigator.userAgent.toLowerCase().includes("firefox") } onload = function () { if (isNotCompatibleBrowser()) { Gui = new UserGui; } } if (!isNotCompatibleBrowser()) { Gui = new UserGui; } else { onload(); } Gui.settings.window.title = 'Better UI Smart Chess Bot - Press [G] to open / close'; Gui.settings.window.external = false; Gui.settings.window.size.width = 500; Gui.settings.gui.external.popup = false; Gui.settings.gui.external.style += GM_getResourceText('chessboard.css'); Gui.settings.gui.external.style += ` div[class^='board'] { background-color: black; } body { display: block; margin-left: auto; margin-right: auto; width: 360px; } #fen { margin-left: 10px; } #engine-log-container { max-height: 35vh; overflow: auto!important; } #userscript-log-container { max-height: 35vh; overflow: auto!important; } .sideways-card { display: flex; align-items: center; justify-content: space-between; } .rendered-form .card { margin-bottom: 10px; } .hidden { display: none; } .main-title-bar { display: flex; justify-content: space-between; } @keyframes wiggle { 0% { transform: scale(1); } 80% { transform: scale(1); } 85% { transform: scale(1.1); } 95% { transform: scale(1); } 100% { transform: scale(1); } } .wiggle { display: inline-block; animation: wiggle 1s infinite; } `; function alphabetPosition(text) { return text.charCodeAt(0) - 97; } function FenUtils() { this.board = [ [1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1, 1, 1], ]; this.pieceCodeToFen = pieceStr => { let [pieceColor, pieceName] = pieceStr.split(''); return pieceColor == 'w' ? pieceName.toUpperCase() : pieceName.toLowerCase(); } this.getFenCodeFromPieceElem = pieceElem => { if (CURRENT_SITE == CHESS_COM) { return this.pieceCodeToFen([...pieceElem.classList].find(x => x.match(/^(b|w)[prnbqk]{1}$/))); } else if (CURRENT_SITE == LICHESS_ORG) { let [pieceColor, pieceName] = pieceElem.cgPiece.split(' '); // fix pieceName if (pieceName == "knight") { pieceName = "n" } let pieceText = pieceColor[0] + pieceName[0]; return this.pieceCodeToFen(pieceText) } } this.getPieceColor = pieceFenStr => { return pieceFenStr == pieceFenStr.toUpperCase() ? 'w' : 'b'; } this.getPieceOppositeColor = pieceFenStr => { return this.getPieceColor(pieceFenStr) == 'w' ? 'b' : 'w'; } this.squeezeEmptySquares = fenStr => { return fenStr.replace(/11111111/g, '8') .replace(/1111111/g, '7') .replace(/111111/g, '6') .replace(/11111/g, '5') .replace(/1111/g, '4') .replace(/111/g, '3') .replace(/11/g, '2'); } this.posToIndex = pos => { let [x, y] = pos.split(''); return { 'y': 8 - y, 'x': 'abcdefgh'.indexOf(x) }; } this.getBoardPiece = pos => { let indexObj = this.posToIndex(pos); return this.board[indexObj.y][indexObj.x]; } this.getRights = () => { let rights = ''; // check for white let e1 = this.getBoardPiece('e1'), h1 = this.getBoardPiece('h1'), a1 = this.getBoardPiece('a1'); if (e1 == 'K' && h1 == 'R') rights += 'K'; if (e1 == 'K' && a1 == 'R') rights += 'Q'; //check for black let e8 = this.getBoardPiece('e8'), h8 = this.getBoardPiece('h8'), a8 = this.getBoardPiece('a8'); if (e8 == 'k' && h8 == 'r') rights += 'k'; if (e8 == 'k' && a8 == 'r') rights += 'q'; return rights ? rights : '-'; } this.getBasicFen = () => { let pieceElems = null; if (CURRENT_SITE == CHESS_COM) { pieceElems = [...chessBoardElem.querySelectorAll('.piece')]; } else if (CURRENT_SITE == LICHESS_ORG) { pieceElems = [...chessBoardElem.querySelectorAll('piece')]; } pieceElems.filter(pieceElem => !pieceElem.classList.contains("ghost")).forEach(pieceElem => { let pieceFenCode = this.getFenCodeFromPieceElem(pieceElem); if (CURRENT_SITE == CHESS_COM) { let [xPos, yPos] = pieceElem.classList.toString().match(/square-(\d)(\d)/).slice(1); this.board[8 - yPos][xPos - 1] = pieceFenCode; } else if (CURRENT_SITE == LICHESS_ORG) { let [xPos, yPos] = pieceElem.cgKey.split(''); this.board[8 - yPos][alphabetPosition(xPos)] = pieceFenCode; } }); let basicFen = this.squeezeEmptySquares(this.board.map(x => x.join('')).join('/')); return basicFen; } this.getFen = () => { let basicFen = this.getBasicFen(); let rights = this.getRights(); return `${basicFen} ${last_turn || turn} ${rights} - 0 1`; } } function InterfaceUtils() { this.boardUtils = { findSquareElem: (squareCode) => { if (!Gui?.document) return; return Gui.document.querySelector(`.square-${squareCode}`); }, markMove: (fromSquare, toSquare, rgba_color) => { if (!Gui?.document) return; if (CURRENT_SITE == CHESS_COM && fromSquare != "" && toSquare != "") { if (!isNotCompatibleBrowser()) { let [fromElem, toElem] = [this.boardUtils.findSquareElem(fromSquare), this.boardUtils.findSquareElem(toSquare)]; if(fromElem != null && toElem != null) { fromElem.style.scale = 1; toElem.style.scale = 1; fromElem.style.backgroundColor = `rgb(${rgba_color[0]},${rgba_color[1]},${rgba_color[2]})`; toElem.style.backgroundColor = `rgb(${rgba_color[0]},${rgba_color[1]},${rgba_color[2]})`; activeGuiMoveHighlights.push(fromElem); activeGuiMoveHighlights.push(toElem); } } } if (displayMovesOnSite || (!isPlayerTurn && show_opposite_moves)) { markMoveToSite(fromSquare, toSquare, rgba_color); } }, removeBestMarkings: () => { if (!Gui?.document) return; activeGuiMoveHighlights.forEach(elem => { elem.style.scale = 1.0; elem.style.backgroundColor = ""; }); activeGuiMoveHighlights = []; }, updateBoardFen: fen => { if (!Gui?.document) return; Gui.document.querySelector('#fen').textContent = fen.slice(0, fen.lastIndexOf('-') - 1); }, updateBoardPower: (myScore, enemyScore) => { if (!Gui?.document) return; Gui.document.querySelector('#enemy-score').textContent = enemyScore; Gui.document.querySelector('#my-score').textContent = myScore; }, updateBoardOrientation: orientation => { if (!Gui?.document) return; const orientationElem = Gui?.document?.querySelector('#orientation'); if (orientationElem) { orientationElem.textContent = orientation; } } } this.engineLog = str => { if (!Gui?.document || enableEngineLog == false) return; const logElem = document.createElement('div'); logElem.classList.add('list-group-item'); if (str.includes('info')) logElem.classList.add('list-group-item-info'); if (str.includes('bestmove')) logElem.classList.add('list-group-item-success'); logElem.innerText = `#${engineLogNum++} ${str}`; if (engineLogNum > MAX_LOGS) { Gui.document.querySelector('#engine-log-container').lastChild.remove(); } Gui.document.querySelector('#engine-log-container').prepend(logElem); } this.log = str => { if (!Gui?.document || enableUserLog == false) return; const logElem = document.createElement('div'); logElem.classList.add('list-group-item'); if (str.includes('info')) logElem.classList.add('list-group-item-info'); if (str.includes('bestmove')) logElem.classList.add('list-group-item-success'); const container = Gui?.document?.querySelector('#userscript-log-container'); if (container) { logElem.innerText = `#${userscriptLogNum++} ${str}`; if (userscriptLogNum > MAX_LOGS) { container.lastChild.remove(); } container.prepend(logElem); } } this.getBoardOrientation = () => { if (CURRENT_SITE == CHESS_COM) { return document.querySelector('.board.flipped') ? 'b' : 'w'; } else if (CURRENT_SITE == LICHESS_ORG) { return document.querySelector(".orientation-white") !== null ? 'w' : 'b' } } this.updateBestMoveProgress = text => { if (!Gui?.document || isNotCompatibleBrowser() || CURRENT_SITE === LICHESS_ORG) return; const progressBarElem = Gui.document.querySelector('#best-move-progress'); progressBarElem.innerText = text; progressBarElem.classList.remove('hidden'); progressBarElem.classList.add('wiggle'); } this.stopBestMoveProcessingAnimation = () => { if (!Gui?.document || isNotCompatibleBrowser() || CURRENT_SITE === LICHESS_ORG) return; const progressBarElem = Gui.document.querySelector('#best-move-progress'); progressBarElem.classList.remove('wiggle'); } this.hideBestMoveProgress = () => { if (!Gui?.document || isNotCompatibleBrowser() || CURRENT_SITE === LICHESS_ORG) return; const progressBarElem = Gui.document.querySelector('#best-move-progress'); if (!progressBarElem.classList.contains('hidden')) { progressBarElem.classList.add('hidden'); this.stopBestMoveProcessingAnimation(); } } } function LozzaUtility() { this.separateMoveCodes = moveCode => { moveCode = moveCode.trim(); let move = moveCode.split(' ')[1]; return [move.slice(0, 2), move.slice(2, 4)]; } this.extractInfo = str => { const keys = ['time', 'nps', 'depth', 'pv']; return keys.reduce((acc, key) => { let match = str.match(`${key} (\\d+)`); if (match) { acc[key] = (match[1]); } return acc; }, {}); } } function markMoveToSite(fromSquare, toSquare) { const highlight = (fromSquare, toSquare) => { const arrowId = `arrow-${fromSquare}${toSquare}`; const arrowData = `${fromSquare}${toSquare}`; const points = calculateArrowPoints(fromSquare, toSquare); const pointsString = points.map(point => `${point.x} ${point.y},`).join(' '); const rotArrow = CalculateArrowRotation(fromSquare, toSquare); const arrowElem = document.createElementNS("http://www.w3.org/2000/svg", "polygon"); arrowElem.setAttribute("id", arrowId); arrowElem.setAttribute("data-arrow", arrowData); arrowElem.setAttribute("class", "arrow"); arrowElem.setAttribute("points", pointsString); arrowElem.setAttribute("transform",`rotate(${rotArrow.deg} ${rotArrow.param1} ${rotArrow.param2})`); arrowElem.setAttribute("style", `fill: rgba(255, 170, 0, 0.8); opacity: 0.8;`); activeSiteMoveHighlights.push(arrowElem); const existingArrow = document.getElementById(arrowId); if (existingArrow) { existingArrow.remove(); } let arrowsSVG = chessBoardElem.querySelector('svg.arrows'); if (!arrowsSVG) { arrowsSVG = document.createElementNS("http://www.w3.org/2000/svg", "svg"); arrowsSVG.setAttribute("class", "arrows"); chessBoardElem.appendChild(arrowsSVG); } arrowsSVG.appendChild(arrowElem); } highlight(fromSquare, toSquare); } function CalculateArrowRotation(fromSquare, toSquare) { const startAngle = -90; const fromCoords = squareToCoords(fromSquare, true); const toCoords = squareToCoords(toSquare, true); const deltaX = toCoords.x - fromCoords.x; const deltaY = toCoords.y - fromCoords.y; const angleRad = Math.atan2(deltaY, deltaX); const angleDeg = angleRad * (180 / Math.PI); const rotationAngle = angleDeg + startAngle; const centerX = fromCoords.x; const centerY = fromCoords.y; return { deg: rotationAngle, param1: centerX, param2: centerY }; } function calculateArrowPoints(fromSquare, toSquare) { const fromCoords = squareToCoords(fromSquare, true); const toCoords = squareToCoords(toSquare, false); const lengthX = Math.abs(fromCoords.x - toCoords.x); const lengthY = Math.abs(fromCoords.y - toCoords.y); const lengthArrow = Math.sqrt(Math.pow(lengthX, 2) + Math.pow(lengthY, 2)); const arrowPoints = []; arrowPoints.push({ x: fromCoords.x - 1.375, y: fromCoords.y + 4.5}); // top left arrowPoints.push({ x: fromCoords.x - 1.375, y: fromCoords.y + lengthArrow - 4.25 }); // bottom left arrowPoints.push({ x: fromCoords.x - 3.25, y: fromCoords.y + lengthArrow - 4.25 }); // bottom left left arrowPoints.push({ x: fromCoords.x, y: fromCoords.y + lengthArrow }); // bottom arrowPoints.push({ x: fromCoords.x + 3.25, y: fromCoords.y + lengthArrow - 4.25}); // bottom right right arrowPoints.push({ x: fromCoords.x - 1.375 + 2.75, y: fromCoords.y + lengthArrow - 4.25}); // bottom right arrowPoints.push({ x: fromCoords.x - 1.375 + 2.75, y: fromCoords.y + 4.5 }); // top right return arrowPoints; } function squareToCoords(square, start) { var xLettre = square.charAt(0); var yLettre = square.charAt(1); var y = 0; var x = 0; switch (xLettre) { case 'a': x = 6.25; break; case 'b': x = 18.75; break; case 'c': x = 31.25; break; case 'd': x = 43.75; break; case 'e': x = 56.25; break; case 'f': x = 68.75; break; case 'g': x = 81.25; break; case 'h': x = 93.75; break; default: x = 0; } switch (yLettre) { case '8': y = 6.25; break; case '7': y = 18.75; break; case '6': y = 31.25; break; case '5': y = 43.75; break; case '4': y = 56.25; break; case '3': y = 68.75; break; case '2': y = 81.25; break; case '1': y = 93.75; break; default: y = 0; } if(!start) { x-=1.375; } return { x, y }; } function removeSiteMoveMarkings() { activeSiteMoveHighlights.forEach(elem => { elem?.remove(); }); activeSiteMoveHighlights = []; } function getTurn() { const getSquareNumber = (elem) => { for (var a = 0; a < elem.classList.length; a++) { if (elem.classList[a].includes("square")) { return elem.classList[a]; } } return ""; } const getSimiliarChessPiece = (squareNumber) => { let similiarChessPieces = chessBoardElem.querySelectorAll("." + squareNumber); for (var a = 0; a < similiarChessPieces.length; a++) { if (similiarChessPieces[a].classList.contains("piece") == true) { return similiarChessPieces[a]; } } return null; } const getChessPieceColor = (elem) => { for (var a = 0; a < elem.classList.length; a++) { if (elem.classList[a].length <= 2) { if (elem.classList[a][0] == "b") { return "b"; } else { return "w"; } } } return ""; } Interface.boardUtils.removeBestMarkings(); removeSiteMoveMarkings(); let chessPieces = chessBoardElem.querySelectorAll(".highlight"); for (var a = 0; a < chessPieces.length; a++) { // exclude custom highlighted squares if (chessPieces[a].classList.contains("custom") == true) { continue; } let squareNumber = getSquareNumber(chessPieces[a]); if (squareNumber == "") { return ""; } let similiarChessPiece = getSimiliarChessPiece(squareNumber); if (similiarChessPiece == null) { continue; } let chessPieceColor = getChessPieceColor(similiarChessPiece); if (chessPieceColor == "") { return ""; } if (chessPieceColor == "b") { return "w"; } else if (chessPieceColor == "w") { return "b"; } } return ""; } function updateBestMove(mutationArr) { const FenUtil = new FenUtils(); let currentFen = FenUtil.getFen(); if (currentFen != lastFen) { lastFen = currentFen; if (mutationArr) { let attributeMutationArr if (CURRENT_SITE == CHESS_COM) { attributeMutationArr = mutationArr.filter(m => m.target.classList.contains('piece') && m.attributeName == 'class'); } else if (CURRENT_SITE == LICHESS_ORG) { attributeMutationArr = mutationArr.filter(m => m.target.tagName == 'PIECE' && !m.target.classList.contains('fading') && m.attributeName == 'class'); } if (attributeMutationArr?.length) { turn = FenUtil.getPieceOppositeColor(FenUtil.getFenCodeFromPieceElem(attributeMutationArr[0].target)); last_turn = turn; // fix turn method if (TURN_UPDATE_FIX && getTurn() != "") { turn = getTurn(); } Interface.log(`Turn updated to ${turn}!`); } } updateBoard(); sendBestMove(); } } function sendBestMove() { if (!isPlayerTurn && !show_opposite_moves) { return; } sendBestMoveRequest(); } function sendBestMoveRequest() { const FenUtil = new FenUtils(); let currentFen = FenUtil.getFen(); possible_moves = []; reloadChessEngine(false, () => { Interface.log('Sending best move request to the engine!'); lastBestMoveID++; if (use_book_moves) { getBookMoves({ id: lastBestMoveID, fen: currentFen }); } else { getBestMoves({ id: lastBestMoveID, fen: currentFen }); } }); } function clearBoard() { Interface.stopBestMoveProcessingAnimation(); Interface.boardUtils.removeBestMarkings(); removeSiteMoveMarkings(); } function updateBoard(clear = true) { if (clear) clearBoard(); const FenUtil = new FenUtils(); let currentFen = FenUtil.getFen(); if (currentFen == ("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1") || currentFen == ("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR b KQkq - 0 1")) { enemyScore = 0; myScore = 0; Interface.boardUtils.updateBoardPower(myScore, enemyScore); } isPlayerTurn = playerColor == null || last_turn == null || last_turn == playerColor; Interface.boardUtils.updateBoardFen(currentFen); } function removeDuplicates(arr) { return arr.filter((item, index) => arr.indexOf(item) === index); } function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } function getBestMoves(request) { if (engineIndex != node_engine_id && CURRENT_SITE !== LICHESS_ORG) { // local engines while (!engine) { sleep(100); } engine.postMessage(`position fen ${request.fen}`); if (engineMode == DEPTH_MODE) { engine.postMessage('go depth ' + current_depth); } else { engine.postMessage('go movetime ' + current_movetime); } engine.onmessage = e => { if (lastBestMoveID != request.id) { return; } if (e.data.includes('bestmove')) { let move = e.data.split(' ')[1]; moveResult(move.slice(0, 2), move.slice(2, 4), current_depth, true); } else if (e.data.includes('info')) { const infoObj = LozzaUtils.extractInfo(e.data); let depth = infoObj.depth || current_depth; let move_time = infoObj.time || current_movetime; let possible_move = e.data.slice(e.data.lastIndexOf("pv"), e.data.length).split(" ")[1]; possible_moves.push(possible_move); if (engineMode == DEPTH_MODE) { Interface.updateBestMoveProgress(`Depth: ${depth}`); } else { Interface.updateBestMoveProgress(`Move time: ${move_time} ms`); } } Interface.engineLog(e.data); }; } else { getNodeBestMoves(request); } } var updatingBestMove = false; function observeNewMoves() { updateBestMove(); const boardObserver = new MutationObserver(mutationArr => { lastPlayerColor = playerColor; updatePlayerColor(() => { if (playerColor != lastPlayerColor) { Interface.log(`Player color changed from ${lastPlayerColor} to ${playerColor}!`); updateBestMove(); } else { updateBestMove(mutationArr); } }); }); boardObserver.observe(chessBoardElem, { childList: true, subtree: true, attributes: true }); } function addGuiPages() { if (Gui?.document) return; Gui.addPage("Main", `