diff --git a/acas.user.js b/acas.user.js index 939af8e..2b2ced9 100644 --- a/acas.user.js +++ b/acas.user.js @@ -61,7 +61,9 @@ // @match https://chesstempo.com/* // @match https://www.redhotpawn.com/* // @match https://www.chessanytime.com/* +// @match https://www.simplechess.com/* // @match https://chessworld.net/* +// @match https://app.edchess.io/* // @grant GM_getValue // @grant GM_setValue // @grant GM_deleteValue @@ -76,13 +78,12 @@ // @require https://greasyfork.org/scripts/470418-commlink-js/code/CommLinkjs.js // @require https://greasyfork.org/scripts/470417-universalboarddrawer-js/code/UniversalBoardDrawerjs.js // @icon https://raw.githubusercontent.com/Psyyke/A.C.A.S/main/assets/images/grey-logo.png -// @version 2.2.0 +// @version 2.2.1 // @namespace HKR // @author HKR // @license GPL-3.0 // ==/UserScript== - /* e e88~-_ e ,d88~~\ d8b d888 \ d8b 8888 @@ -111,8 +112,6 @@ DANGER ZONE - DO NOT PROCEED IF YOU DON'T KNOW WHAT YOU'RE DOING*/ - - /* ______ _____ ______ _______ | ____ | | | |_____] |_____| | @@ -215,7 +214,6 @@ const dbValues = { const instanceVars = { playerColor: createInstanceVariable('playerColor'), - turn: createInstanceVariable('turn'), fen: createInstanceVariable('fen') }; @@ -278,11 +276,13 @@ const configKeys = { 'displayMovesOnExternalSite': 'displayMovesOnExternalSite', 'showMoveGhost': 'showMoveGhost', 'showOpponentMoveGuess': 'showOpponentMoveGuess', + 'showOpponentMoveGuessConstantly': 'showOpponentMoveGuessConstantly', 'onlyShowTopMoves': 'onlyShowTopMoves', 'maxMovetime': 'maxMovetime', 'chessVariant': 'chessVariant', 'chessEngine': 'chessEngine', 'lc0Weight': 'lc0Weight', + 'engineNodes': 'engineNodes', 'chessFont': 'chessFont', 'useChess960': 'useChess960', 'onlyCalculateOwnTurn': 'onlyCalculateOwnTurn', @@ -303,7 +303,7 @@ Object.values(configKeys).forEach(key => { let BoardDrawer = null; let chessBoardElem = null; let chesscomVariantPlayerColorsTable = null; -let activeSiteMoveHighlights = []; +let activeGuiMoveMarkings = []; let inactiveGuiMoveMarkings = []; let lastBoardRanks = null; @@ -384,10 +384,10 @@ CommLink.registerListener(`backend_${commLinkInstanceID}`, packet => { case 'getFen': return getFen(); case 'removeSiteMoveMarkings': - boardUtils.removeBestMarkings(); + boardUtils.removeMarkings(); return true; case 'markMoveToSite': - boardUtils.markMove(packet.data); + boardUtils.markMoves(packet.data); return true; } } catch(e) { @@ -396,198 +396,23 @@ CommLink.registerListener(`backend_${commLinkInstanceID}`, packet => { }); const boardUtils = { - markMove: moveObj => { - if(!getConfigValue(configKeys.displayMovesOnExternalSite)) return; - - const [from, to] = moveObj.player; - const [opponentFrom, opponentTo] = moveObj.opponent; - const ranking = moveObj.ranking; - - const existingExactSameMoveObj = activeSiteMoveHighlights.find(obj => { - const [activeFrom, activeTo] = obj.player; - const [activeOpponentFrom, activeOpponentTo] = obj.opponent; - - return from == activeFrom - && to == activeTo - && opponentFrom == activeOpponentFrom - && opponentTo == activeOpponentTo; - }); - - activeSiteMoveHighlights.map(obj => { - const [activeFrom, activeTo] = obj.player; - - const existingSameMoveObj = from == activeFrom && to == activeTo; - - if(existingSameMoveObj) { - obj.promotedRanking = 1; - } - - return obj; - }); - - const exactSameMoveDoesNotExist = typeof existingExactSameMoveObj !== 'object'; - - if(exactSameMoveDoesNotExist) { - - const showOpponentMoveGuess = getConfigValue(configKeys.showOpponentMoveGuess); - - const opponentMoveGuessExists = typeof opponentFrom == 'string'; - - const arrowStyle = ranking == 1 ? arrowStyles.best : arrowStyles.secondary; - - let opponentArrowElem = null; - - // create player move arrow element - const arrowElem = BoardDrawer.createShape('arrow', [from, to], - //{ style: arrowStyle } - ); - - // create opponent move arrow element - if(opponentMoveGuessExists && showOpponentMoveGuess) { - opponentArrowElem = BoardDrawer.createShape('arrow', [opponentFrom, opponentTo], - //{ style: arrowStyles.opponent } - ); - - const squareListener = BoardDrawer.addSquareListener(from, type => { - if(!opponentArrowElem) { - squareListener.remove(); - } - - switch(type) { - case 'enter': - opponentArrowElem.style.display = 'inherit'; - break; - case 'leave': - opponentArrowElem.style.display = 'none'; - break; - } - }); - } - - activeSiteMoveHighlights.push({ - ...moveObj, - 'opponentArrowElem': opponentArrowElem, - 'playerArrowElem': arrowElem - }); - } - - boardUtils.removeOldMarkings(); - boardUtils.paintMarkings(); - }, - removeOldMarkings: () => { - const markingLimit = getConfigValue(configKeys.moveSuggestionAmount); - const showGhost = getConfigValue(configKeys.showMoveGhost); - - const exceededMarkingLimit = activeSiteMoveHighlights.length > markingLimit; - - if(exceededMarkingLimit) { - const amountToRemove = activeSiteMoveHighlights.length - markingLimit; - - for(let i = 0; i < amountToRemove; i++) { - const oldestMarkingObj = activeSiteMoveHighlights[0]; - - activeSiteMoveHighlights = activeSiteMoveHighlights.slice(1); - - if(oldestMarkingObj?.playerArrowElem?.style) { - oldestMarkingObj.playerArrowElem.style.fill = 'grey'; - oldestMarkingObj.playerArrowElem.style.opacity = '0'; - oldestMarkingObj.playerArrowElem.style.transition = 'opacity 2.5s ease-in-out'; - } - - if(oldestMarkingObj?.opponentArrowElem?.style) { - oldestMarkingObj.opponentArrowElem.style.fill = 'grey'; - oldestMarkingObj.opponentArrowElem.style.opacity = '0'; - oldestMarkingObj.opponentArrowElem.style.transition = 'opacity 2.5s ease-in-out'; - } - - if(showGhost) { - inactiveGuiMoveMarkings.push(oldestMarkingObj); - } else { - oldestMarkingObj.playerArrowElem?.remove(); - oldestMarkingObj.opponentArrowElem?.remove(); - } - } - } - - if(showGhost) { - inactiveGuiMoveMarkings.forEach(markingObj => { - const activeDuplicateArrow = activeSiteMoveHighlights.find(x => { - const samePlayerArrow = x.player?.toString() == markingObj.player?.toString(); - const sameOpponentArrow = x.opponent?.toString() == markingObj.opponent?.toString(); - - return samePlayerArrow && sameOpponentArrow; - }); - - const duplicateExists = activeDuplicateArrow ? true : false; - - const removeArrows = () => { - inactiveGuiMoveMarkings = inactiveGuiMoveMarkings.filter(x => x.playerArrowElem != markingObj.playerArrowElem); - - markingObj.playerArrowElem?.remove(); - markingObj.opponentArrowElem?.remove(); - } - - if(duplicateExists) { - removeArrows(); - } else { - setTimeout(removeArrows, 2500); - } - }); - } - }, - paintMarkings: () => { - /* Account for none, or multiple 1 rank (multipv 1) markings. This is the priority order, - 1. Mark the last added rank 1 marking as the best (unless promoted marking is newer) - 2. (no rank 1 markings) Mark the lats added promoted rank 1 marking as the best - 3. (no promoted rank 1 markings) Mark the last added marking as the best - */ + markMoves: moveObjArr => { + boardUtils.removeMarkings(); - function rankMarkings(activeGuiMoveMarkings) { - const newestBestMarkingIndex = activeGuiMoveMarkings.findLastIndex(obj => obj.ranking === 1); - const newestPromotedBestMarkingIndex = activeGuiMoveMarkings.findLastIndex(obj => obj?.promotedRanking === 1); - const lastMarkingIndex = activeGuiMoveMarkings.length - 1; + const maxScale = 1; + const minScale = 0.5; + const totalRanks = moveObjArr.length; + const showOpponentMoveGuess = getConfigValue(configKeys.showOpponentMoveGuess); + const showOpponentMoveGuessConstantly = getConfigValue(configKeys.showOpponentMoveGuessConstantly); - const isLastMarkingBest = newestBestMarkingIndex === -1 && newestPromotedBestMarkingIndex === -1; - const bestIndex = isLastMarkingBest ? lastMarkingIndex : Math.max(...[newestBestMarkingIndex, newestPromotedBestMarkingIndex]); - - const bestMarking = []; - const secondaryMarkings = []; - - activeGuiMoveMarkings.forEach((obj, index) => { - if (index === bestIndex) { - bestMarking.push({ ...obj, status: 'best' }); - } else { - secondaryMarkings.push(obj); - } - }); - - secondaryMarkings.sort((a, b) => { - if (a.ranking !== b.ranking) return a.ranking - b.ranking; - if (a.promotedRanking !== b.promotedRanking) return (a.promotedRanking || Infinity) - (b.promotedRanking || Infinity); - return activeGuiMoveMarkings.indexOf(a) - activeGuiMoveMarkings.indexOf(b); - }); - - const sortedMarkings = secondaryMarkings.map((obj, index) => ({ - ...obj, - status: 'secondary' - })); - - return bestMarking.concat(sortedMarkings); - } - - const rankedMarkings = rankMarkings(activeSiteMoveHighlights); - - rankedMarkings.forEach((markingObj, idx) => { + moveObjArr.forEach((markingObj, idx) => { const [from, to] = markingObj.player; const [oppFrom, oppTo] = markingObj.opponent; - const playerArrowElem = markingObj.playerArrowElem; - const oppArrowElem = markingObj.opponentArrowElem; - + const oppMovesExist = oppFrom && oppTo; const rank = idx + 1; - const maxScale = 1; - const minScale = 0.4; - const totalRanks = rankedMarkings.length; + let playerArrowElem = null; + let oppArrowElem = null; let arrowStyle = arrowStyles.best; let lineWidth = 30; let arrowheadWidth = 80; @@ -607,45 +432,64 @@ const boardUtils = { startOffset = startOffset; } - // Update player move arrow element - BoardDrawer.createShape('arrow', [from, to], + playerArrowElem = BoardDrawer.createShape('arrow', [from, to], { style: arrowStyle, - lineWidth, arrowheadWidth, arrowheadHeight, startOffset, - existingElem: playerArrowElem + lineWidth, arrowheadWidth, arrowheadHeight, startOffset } ); - if(oppArrowElem) { - // Update opponent move arrow element - BoardDrawer.createShape('arrow', [oppFrom, oppTo], + if(oppMovesExist && showOpponentMoveGuess) { + oppArrowElem = BoardDrawer.createShape('arrow', [oppFrom, oppTo], { style: arrowStyles.opponent, - lineWidth, arrowheadWidth, arrowheadHeight, startOffset, - existingElem: oppArrowElem + lineWidth, arrowheadWidth, arrowheadHeight, startOffset } ); + + if(showOpponentMoveGuessConstantly) { + oppArrowElem.style.display = 'block'; + } else { + const squareListener = BoardDrawer.addSquareListener(from, type => { + if(!oppArrowElem) { + squareListener.remove(); + } + + switch(type) { + case 'enter': + oppArrowElem.style.display = 'inherit'; + break; + case 'leave': + oppArrowElem.style.display = 'none'; + break; + } + }); + } } - if(idx === 0) { - // move best arrow element on top (multiple same moves can hide the best move) - const parentElem = markingObj.playerArrowElem.parentElement; + if(idx === 0 && playerArrowElem) { + const parentElem = playerArrowElem.parentElement; + // move best arrow element on top (multiple same moves can hide the best move) parentElem.appendChild(playerArrowElem); if(oppArrowElem) { parentElem.appendChild(oppArrowElem); } } + + activeGuiMoveMarkings.push({ ...markingObj, playerArrowElem, oppArrowElem }); }); + + pastMoveObjects = []; }, - removeBestMarkings: () => { - activeSiteMoveHighlights.forEach(markingObj => { - markingObj.opponentArrowElem?.remove(); + removeMarkings: () => { + activeGuiMoveMarkings.forEach(markingObj => { + markingObj.oppArrowElem?.remove(); markingObj.playerArrowElem?.remove(); }); - activeSiteMoveHighlights = []; + activeGuiMoveMarkings = []; }, setBoardOrientation: orientation => { if(BoardDrawer) { @@ -839,27 +683,6 @@ function getBoardDimensionsFromSize() { } } -function defaultTurnFromMutation(mutationArr) { - const allChessPieceElems = getPieceElem(true); - - const attributeMutationArr = mutationArr.filter(m => allChessPieceElems.includes(m.target)); - const movedChessPieceElem = attributeMutationArr?.[0]?.target; - - if(movedChessPieceElem) { - const pieceFen = getPieceElemFen(movedChessPieceElem); - - if(pieceFen) { - const newTurn = getFenPieceOppositeColor(pieceFen); - - if(newTurn?.length === 1) { - instanceVars.turn.set(commLinkInstanceID, newTurn); - - return newTurn; - } - } - } -} - function chessCoordinatesToIndex(coord) { const x = coord.charCodeAt(0) - 97; let y = null; @@ -1094,12 +917,6 @@ function isMutationNewMove(mutationArr) { return isNewMove || false; } -function getMutationTurn(mutationArr) { - const turn = getSiteData('turnFromMutation', { mutationArr }); - - return turn || getPlayerColorVariable(); -} - function getFen(onlyBasic) { const [boardRanks, boardFiles] = getBoardDimensions(); @@ -1178,19 +995,32 @@ function resetCachedValues() { chesscomVariantPlayerColorsTable = null; } +function fenToArray(fen) { + const rows = fen.split('/'); + const board = []; + + for (let row of rows) { + const boardRow = []; + for (let char of row) { + if (isNaN(char)) { + boardRow.push(char); + } else { + boardRow.push(...Array(parseInt(char)).fill('')); + } + } + board.push(boardRow); + } + + return board; +} + function onNewMove(mutationArr, bypassFenChangeDetection) { const currentFullFen = getFen(); const lastFullFen = instanceVars.fen.get(commLinkInstanceID); const fenChanged = currentFullFen !== lastFullFen; - setTimeout(() => { - if(getFen() !== instanceVars.fen.get(commLinkInstanceID)) { - onNewMove(null, true); - } - }, 500); - - if(fenChanged || bypassFenChangeDetection) { + if((fenChanged || bypassFenChangeDetection)) { if(debugModeActivated) console.warn('NEW MOVE DETECTED!'); resetCachedValues(); @@ -1209,21 +1039,14 @@ function onNewMove(mutationArr, bypassFenChangeDetection) { resetCachedValues(); - instanceVars.turn.set(commLinkInstanceID, playerColor); - CommLink.commands.log(`Turn updated to ${playerColor}!`); } - boardUtils.removeBestMarkings(); + boardUtils.removeMarkings(); CommLink.commands.updateBoardFen(currentFullFen); - const turn = mutationArr ? getMutationTurn(mutationArr) : playerColor; - const onlyCalculateOwnTurn = getConfigValue(configKeys.onlyCalculateOwnTurn); - - if(debugModeActivated) console.warn('TURN:', turn, '| PLAYERCOLOR:', playerColor, '| ORIENTATION_CHANGED:', orientationChanged, '| ONLY_CALC_OWN_TURN:', onlyCalculateOwnTurn); - - if(orientationChanged || !onlyCalculateOwnTurn || turn === playerColor) { + if(orientationChanged) { CommLink.commands.calculateBestMoves(currentFullFen); } } @@ -1263,7 +1086,6 @@ async function updatePlayerColor() { lastBoardOrientation = boardOrientation; instanceVars.playerColor.set(commLinkInstanceID, boardOrientation); - instanceVars.turn.set(commLinkInstanceID, boardOrientation); boardUtils.setBoardOrientation(boardOrientation); @@ -1366,7 +1188,7 @@ addSupportedChessSite('chess.com', { const pieceFenStr = pieceElem?.dataset?.piece; - pieceColor = chesscomVariantPlayerColorsTable[pieceElem?.dataset?.color]; + pieceColor = chesscomVariantPlayerColorsTable?.[pieceElem?.dataset?.color]; pieceName = pieceElem?.dataset?.piece; if(pieceName?.length > 1) { @@ -1439,17 +1261,16 @@ addSupportedChessSite('chess.com', { const modifiedHighlight = mutationArr.find(m => m?.target?.classList?.contains('highlight')) ? true : false; const modifiedElemPool = mutationArr.find(m => m?.target?.classList?.contains('element-pool')) ? true : false; - return (mutationArr.length >= 4 && !modifiedHoverSquare) + const isPremove = mutationArr.filter(m => m?.target?.classList?.contains('highlight')) + .map(x => x?.target?.style?.['background-color']) + .find(x => x === 'rgb(244, 42, 50)') ? true : false; + + return ( + (mutationArr.length >= 4 && !modifiedHoverSquare) || mutationArr.length >= 7 || modifiedHighlight - || modifiedElemPool; - }, - - 'turnFromMutation': obj => { - const pathname = obj.pathname; - const mutationArr = obj.mutationArr; - - return defaultTurnFromMutation(mutationArr); + || modifiedElemPool + ) && !isPremove; } }); @@ -1517,13 +1338,6 @@ addSupportedChessSite('lichess.org', { return mutationArr.length >= 4 || mutationArr.find(m => m.type === 'childList') ? true : false || mutationArr.find(m => m?.target?.classList?.contains('last-move')) ? true : false; - }, - - 'turnFromMutation': obj => { - const pathname = obj.pathname; - const mutationArr = obj.mutationArr; - - return defaultTurnFromMutation(mutationArr); } }); @@ -1605,13 +1419,6 @@ addSupportedChessSite('playstrategy.org', { return mutationArr.length >= 4 || mutationArr.find(m => m.type === 'childList') ? true : false || mutationArr.find(m => m?.target?.classList?.contains('last-move')) ? true : false; - }, - - 'turnFromMutation': obj => { - const pathname = obj.pathname; - const mutationArr = obj.mutationArr; - - return defaultTurnFromMutation(mutationArr); } }); @@ -1694,13 +1501,6 @@ addSupportedChessSite('pychess.org', { return mutationArr.length >= 4 || mutationArr.find(m => m.type === 'childList') ? true : false || mutationArr.find(m => m?.target?.classList?.contains('last-move')) ? true : false; - }, - - 'turnFromMutation': obj => { - const pathname = obj.pathname; - const mutationArr = obj.mutationArr; - - return defaultTurnFromMutation(mutationArr); } }); @@ -1765,13 +1565,6 @@ addSupportedChessSite('chess.org', { return mutationArr.length >= 4 || mutationArr.find(m => m.type === 'childList') ? true : false || mutationArr.find(m => m?.target?.classList?.contains('last-move')) ? true : false; - }, - - 'turnFromMutation': obj => { - const pathname = obj.pathname; - const mutationArr = obj.mutationArr; - - return defaultTurnFromMutation(mutationArr); } }); @@ -1827,19 +1620,12 @@ addSupportedChessSite('chess.coolmath-games.com', { return mutationArr.length >= 4 || mutationArr.find(m => m.type === 'childList') ? true : false || mutationArr.find(m => m?.target?.classList?.contains('last-move')) ? true : false; - }, - - 'turnFromMutation': obj => { - const pathname = obj.pathname; - const mutationArr = obj.mutationArr; - - return defaultTurnFromMutation(mutationArr); } }); addSupportedChessSite('papergames.io', { 'boardElem': obj => { - return document.querySelector('#chessboard'); + return document.querySelector('#chess_board'); }, 'pieceElem': obj => { @@ -1884,13 +1670,6 @@ addSupportedChessSite('papergames.io', { const mutationArr = obj.mutationArr; return mutationArr.length >= 12; - }, - - 'turnFromMutation': obj => { - const pathname = obj.pathname; - const mutationArr = obj.mutationArr; - - return defaultTurnFromMutation(mutationArr); } }); @@ -1938,13 +1717,6 @@ addSupportedChessSite('vole.wtf', { const mutationArr = obj.mutationArr; return mutationArr.length >= 12; - }, - - 'turnFromMutation': obj => { - const pathname = obj.pathname; - const mutationArr = obj.mutationArr; - - return defaultTurnFromMutation(mutationArr); } }); @@ -2001,13 +1773,6 @@ addSupportedChessSite('immortal.game', { } return mutationArr.length >= 5; - }, - - 'turnFromMutation': obj => { - const pathname = obj.pathname; - const mutationArr = obj.mutationArr; - - return defaultTurnFromMutation(mutationArr); } }); @@ -2061,13 +1826,6 @@ addSupportedChessSite('chessarena.com', { } return mutationArr.find(m => m?.attributeName === 'style') ? true : false; - }, - - 'turnFromMutation': obj => { - const pathname = obj.pathname; - const mutationArr = obj.mutationArr; - - return defaultTurnFromMutation(mutationArr); } }); @@ -2136,13 +1894,6 @@ addSupportedChessSite('chess.net', { return mutationArr.length >= 4 || mutationArr.find(m => m.type === 'childList') ? true : false || mutationArr.find(m => m?.target?.classList?.contains('last-move')) ? true : false; - }, - - 'turnFromMutation': obj => { - const pathname = obj.pathname; - const mutationArr = obj.mutationArr; - - return defaultTurnFromMutation(mutationArr); } }); @@ -2198,13 +1949,6 @@ addSupportedChessSite('freechess.club', { return mutationArr.length >= 4 || mutationArr.find(m => m.type === 'childList') ? true : false || mutationArr.find(m => m?.target?.classList?.contains('last-move')) ? true : false; - }, - - 'turnFromMutation': obj => { - const pathname = obj.pathname; - const mutationArr = obj.mutationArr; - - return defaultTurnFromMutation(mutationArr); } }); @@ -2260,13 +2004,6 @@ addSupportedChessSite('play.chessclub.com', { return mutationArr.length >= 4 || mutationArr.find(m => m.type === 'childList') ? true : false || mutationArr.find(m => m?.target?.classList?.contains('last-move')) ? true : false; - }, - - 'turnFromMutation': obj => { - const pathname = obj.pathname; - const mutationArr = obj.mutationArr; - - return defaultTurnFromMutation(mutationArr); } }); @@ -2276,7 +2013,7 @@ addSupportedChessSite('gameknot.com', { }, 'pieceElem': obj => { - return obj.boardQuerySelector('*[class*="square_"] > img[src*="chess36."][style*="visible"]'); + return obj.boardQuerySelector('*[class*="chess-board-piece"] > img[src*="chess56."][style*="visible"]'); }, 'chessVariant': obj => { @@ -2312,20 +2049,8 @@ addSupportedChessSite('gameknot.com', { 'isMutationNewMove': obj => { const mutationArr = obj.mutationArr; - if(isUserMouseDown) { - return false; - } - - return mutationArr.length >= 4 - || mutationArr.find(m => m.type === 'childList') ? true : false + return mutationArr.find(m => m.type === 'childList') ? true : false || mutationArr.find(m => m?.target?.classList?.contains('last-move')) ? true : false; - }, - - 'turnFromMutation': obj => { - const pathname = obj.pathname; - const mutationArr = obj.mutationArr; - - return defaultTurnFromMutation(mutationArr); } }); @@ -2374,13 +2099,6 @@ addSupportedChessSite('chesstempo.com', { const mutationArr = obj.mutationArr; return mutationArr.length >= 4; - }, - - 'turnFromMutation': obj => { - const pathname = obj.pathname; - const mutationArr = obj.mutationArr; - - return defaultTurnFromMutation(mutationArr); } }); @@ -2429,19 +2147,12 @@ addSupportedChessSite('redhotpawn.com', { } return mutationArr.length >= 4; - }, - - 'turnFromMutation': obj => { - const pathname = obj.pathname; - const mutationArr = obj.mutationArr; - - return defaultTurnFromMutation(mutationArr); } }); -addSupportedChessSite('chessanytime.com', { +addSupportedChessSite('simplechess.com', { 'boardElem': obj => { - return document.querySelector('#play'); + return document.querySelector('#chessboard'); }, 'pieceElem': obj => { @@ -2457,7 +2168,7 @@ addSupportedChessSite('chessanytime.com', { }, 'boardOrientation': obj => { - return document.querySelector('#play_coordy0')?.innerText === '8' ? 'w' : 'b'; + return document.querySelector('#chessboard_coordy0')?.innerText === '8' ? 'w' : 'b'; }, 'pieceElemFen': obj => { @@ -2518,13 +2229,6 @@ addSupportedChessSite('chessanytime.com', { } return mutationArr.length >= 7; - }, - - 'turnFromMutation': obj => { - const pathname = obj.pathname; - const mutationArr = obj.mutationArr; - - return defaultTurnFromMutation(mutationArr); } }); @@ -2575,24 +2279,16 @@ addSupportedChessSite('chessworld.net', { const mutationArr = obj.mutationArr; return mutationArr.length >= 2; - }, - - 'turnFromMutation': obj => { - const pathname = obj.pathname; - const mutationArr = obj.mutationArr; - - return defaultTurnFromMutation(mutationArr); } }); -/* The site is very shitty I don't feel like finishing this -addSupportedChessSite('chessfriends.com', { +addSupportedChessSite('app.edchess.io', { 'boardElem': obj => { - return document.querySelector('div[id*="id_board_"'); + return document.querySelector('*[data-boardid="chessboard"]'); }, 'pieceElem': obj => { - return obj.boardQuerySelector('img[name][id*="id_piece_"][src*="/pieces/"]'); + return obj.boardQuerySelector('*[data-piece]'); }, 'chessVariant': obj => { @@ -2600,28 +2296,20 @@ addSupportedChessSite('chessfriends.com', { }, 'boardOrientation': obj => { - const firstSquareTop = document.querySelector('*[id*="id_square_00_"]')?.style?.top; - - return firstSquareTop === '0px' ? 'w' : 'b'; + return document.querySelector('*[data-square]')?.dataset?.square == 'h1' ? 'b' : 'w'; }, 'pieceElemFen': obj => { const pieceElem = obj.pieceElem; + const [pieceColor, pieceName] = pieceElem?.dataset?.piece?.split(''); - const dataStr = pieceElem.getAttribute('name'); - - if(dataStr?.length === 2) { - const pieceColor = dataStr[0]; - const elemPieceName = dataStr[1]; - - return pieceColor == 'w' ? elemPieceName.toUpperCase() : elemPieceName.toLowerCase(); - } + return pieceColor === 'w' ? pieceName.toUpperCase() : pieceName.toLowerCase(); }, 'pieceElemCoords': obj => { const pieceElem = obj.pieceElem; - return getElemCoordinatesFromLeftTopPixels(pieceElem); + return chessCoordinatesToIndex(pieceElem?.parentElement?.parentElement?.dataset?.square); }, 'boardDimensions': obj => { @@ -2631,25 +2319,9 @@ addSupportedChessSite('chessfriends.com', { 'isMutationNewMove': obj => { const mutationArr = obj.mutationArr; - if(isUserMouseDown) { - return false; - } - - return mutationArr.length >= 4 - || mutationArr.find(m => m.type === 'childList') ? true : false - || mutationArr.find(m => m?.target?.classList?.contains('last-move')) ? true : false; - }, - - 'turnFromMutation': obj => { - const pathname = obj.pathname; - const mutationArr = obj.mutationArr; - - return defaultTurnFromMutation(mutationArr); + return mutationArr.length >= 2; } -});*/ - - - +}); /* _____ __ _ _____ _______ _____ _______ _____ ______ _______ _______ _____ _____ __ _ @@ -2670,11 +2342,11 @@ async function start() { const pathname = window.location.pathname; const adjustSizeByDimensions = domain === 'chess.com' && pathname?.includes('/variants'); + const ignoreBodyRectLeft = domain === 'app.edchess.io'; const boardOrientation = getBoardOrientation(); instanceVars.playerColor.set(commLinkInstanceID, boardOrientation); - instanceVars.turn.set(commLinkInstanceID, boardOrientation); instanceVars.fen.set(commLinkInstanceID, getFen()); if(getConfigValue(configKeys.displayMovesOnExternalSite)) { @@ -2688,7 +2360,8 @@ async function start() { 'adjustSizeByDimensions': adjustSizeByDimensions ? true : false, 'adjustSizeConfig': { 'noLeftAdjustment': true - } + }, + 'ignoreBodyRectLeft': ignoreBodyRectLeft }); } @@ -2729,6 +2402,9 @@ function initializeIfSiteReady() { chessBoardElem.addEventListener('mousedown', () => { isUserMouseDown = true; }); chessBoardElem.addEventListener('mouseup', () => { isUserMouseDown = false; }); + chessBoardElem.addEventListener('touchstart', () => { isUserMouseDown = true; }); + chessBoardElem.addEventListener('touchend', () => { isUserMouseDown = false; }); + if(!blacklistedURLs.includes(window.location.href)) { startWhenBackendReady(); diff --git a/assets/js/acas-backend-instance.js b/assets/js/acas-backend-instance.js index b25458d..31c9bf4 100644 --- a/assets/js/acas-backend-instance.js +++ b/assets/js/acas-backend-instance.js @@ -7,6 +7,7 @@ class BackendInstance { 'displayMovesOnExternalSite': 'displayMovesOnExternalSite', 'showMoveGhost': 'showMoveGhost', 'showOpponentMoveGuess': 'showOpponentMoveGuess', + 'showOpponentMoveGuessConstantly': 'showOpponentMoveGuessConstantly', 'onlyShowTopMoves': 'onlyShowTopMoves', 'maxMovetime': 'maxMovetime', 'chessVariant': 'chessVariant', @@ -62,10 +63,8 @@ class BackendInstance { this.searchDepth = null; this.engineNodes = 1; - - this.engineFinishedCalculation = null; + this.currentMovetimeTimeout = null; - this.newCalculationRequestBeforeLastEnded = false; this.pastMoveObjects = []; this.bestMoveMarkingElem = null; @@ -73,6 +72,13 @@ class BackendInstance { this.inactiveGuiMoveMarkings = []; this.unprocessedPackets = []; + this.lastCalculatedFen = null; + this.pendingCalculations = []; + + this.lastFen = null; + this.lastOrientation = null; + this.currentFen = null; + this.currentSpeeches = []; this.defaultStartpos = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'; @@ -110,7 +116,7 @@ class BackendInstance { this.CommLink.registerSendCommand('ping'); this.CommLink.registerSendCommand('getFen'); - this.CommLink.registerSendCommand('removeSiteMoveMarking'); + this.CommLink.registerSendCommand('removeSiteMoveMarkings'); this.CommLink.registerSendCommand('markMoveToSite'); this.CommLinkReceiver = this.CommLink.registerListener(`frontend_${this.instanceID}`, packet => { @@ -168,282 +174,140 @@ class BackendInstance { Interface = { boardUtils: { - markMove: moveObj => { - const [from, to] = moveObj.player; - const [opponentFrom, opponentTo] = moveObj.opponent; - const ranking = moveObj.ranking; - - const existingExactSameMoveObj = this.activeGuiMoveMarkings.find(obj => { - const [activeFrom, activeTo] = obj.player; - const [activeOpponentFrom, activeOpponentTo] = obj.opponent; - - return from == activeFrom - && to == activeTo - && opponentFrom == activeOpponentFrom - && opponentTo == activeOpponentTo; - }); + markMoves: moveObjArr => { + this.Interface.boardUtils.removeMarkings(); - this.activeGuiMoveMarkings.map(obj => { - const [activeFrom, activeTo] = obj.player; - - const existingSameMoveObj = from == activeFrom && to == activeTo; - - if(existingSameMoveObj) { - obj.promotedRanking = 1; - } - - return obj; - }); - - const exactSameMoveDoesNotExist = typeof existingExactSameMoveObj !== 'object'; - - if(exactSameMoveDoesNotExist) { - const showOpponentMoveGuess = this.getConfigValue(this.configKeys.showOpponentMoveGuess); - const opponentMoveGuessExists = typeof opponentFrom == 'string'; - - const arrowStyle = ranking == 1 ? this.arrowStyles.best : this.arrowStyles.secondary; - - let opponentArrowElem = null; - - // Create player move arrow element - const arrowElem = this.BoardDrawer.createShape('arrow', [from, to], - //{ style: arrowStyle } - ); - - // Create opponent move arrow element - if(opponentMoveGuessExists && showOpponentMoveGuess) { - opponentArrowElem = this.BoardDrawer.createShape('arrow', [opponentFrom, opponentTo], - //{ style: this.arrowStyles.opponent } - ); - - const squareListener = this.BoardDrawer.addSquareListener(from, type => { - if(!opponentArrowElem) { - squareListener.remove(); - } - - switch(type) { - case 'enter': - opponentArrowElem.style.display = 'inherit'; - break; - case 'leave': - opponentArrowElem.style.display = 'none'; - break; - } - }); - } - - this.activeGuiMoveMarkings.push({ - ...moveObj, - 'opponentArrowElem': opponentArrowElem, - 'playerArrowElem': arrowElem - }); - } + const maxScale = 1; + const minScale = 0.5; + const totalRanks = moveObjArr.length; + const showOpponentMoveGuess = this.getConfigValue(this.configKeys.showOpponentMoveGuess); + const showOpponentMoveGuessConstantly = this.getConfigValue(this.configKeys.showOpponentMoveGuessConstantly); - this.Interface.boardUtils.removeOldMarkings(); - this.Interface.boardUtils.paintMarkings(); - }, - removeOldMarkings: () => { - const markingLimit = this.getConfigValue(this.configKeys.moveSuggestionAmount); - const showGhost = this.getConfigValue(this.configKeys.showMoveGhost); - - const exceededMarkingLimit = this.activeGuiMoveMarkings.length > markingLimit; - - if(exceededMarkingLimit) { - const amountToRemove = this.activeGuiMoveMarkings.length - markingLimit; - - for(let i = 0; i < amountToRemove; i++) { - const oldestMarkingObj = this.activeGuiMoveMarkings[0]; - - this.activeGuiMoveMarkings = this.activeGuiMoveMarkings.slice(1); - - if(oldestMarkingObj?.playerArrowElem?.style) { - oldestMarkingObj.playerArrowElem.style.fill = 'grey'; - oldestMarkingObj.playerArrowElem.style.opacity = '0'; - oldestMarkingObj.playerArrowElem.style.transition = 'opacity 2.5s ease-in-out'; - } - - if(oldestMarkingObj?.opponentArrowElem?.style) { - oldestMarkingObj.opponentArrowElem.style.fill = 'grey'; - oldestMarkingObj.opponentArrowElem.style.opacity = '0'; - oldestMarkingObj.opponentArrowElem.style.transition = 'opacity 2.5s ease-in-out'; - } - - if(showGhost) { - this.inactiveGuiMoveMarkings.push(oldestMarkingObj); - } else { - oldestMarkingObj.playerArrowElem?.remove(); - oldestMarkingObj.opponentArrowElem?.remove(); - } - } - } - - if(showGhost) { - this.inactiveGuiMoveMarkings.forEach(markingObj => { - const activeDuplicateArrow = this.activeGuiMoveMarkings.find(x => { - const samePlayerArrow = x.player?.toString() == markingObj.player?.toString(); - const sameOpponentArrow = x.opponent?.toString() == markingObj.opponent?.toString(); - - return samePlayerArrow && sameOpponentArrow; - }); - - const duplicateExists = activeDuplicateArrow ? true : false; - - const removeArrows = () => { - this.inactiveGuiMoveMarkings = this.inactiveGuiMoveMarkings.filter(x => x.playerArrowElem != markingObj.playerArrowElem); - - markingObj.playerArrowElem?.remove(); - markingObj.opponentArrowElem?.remove(); - } - - if(duplicateExists) { - removeArrows(); - } else { - setTimeout(removeArrows, 2500); - } - }); - } - }, - paintMarkings: () => { - console.log('PAINT MARKINGS!'); - - /* Account for none, or multiple 1 rank (multipv 1) markings. This is the priority order, - 1. Mark the last added rank 1 marking as the best (unless promoted marking is newer) - 2. (no rank 1 markings) Mark the lats added promoted rank 1 marking as the best - 3. (no promoted rank 1 markings) Mark the last added marking as the best - */ - - function rankMarkings(activeGuiMoveMarkings) { - const newestBestMarkingIndex = activeGuiMoveMarkings.findLastIndex(obj => obj.ranking === 1); - const newestPromotedBestMarkingIndex = activeGuiMoveMarkings.findLastIndex(obj => obj?.promotedRanking === 1); - const lastMarkingIndex = activeGuiMoveMarkings.length - 1; - - const isLastMarkingBest = newestBestMarkingIndex === -1 && newestPromotedBestMarkingIndex === -1; - const bestIndex = isLastMarkingBest ? lastMarkingIndex : Math.max(...[newestBestMarkingIndex, newestPromotedBestMarkingIndex]); - - const bestMarking = []; - const secondaryMarkings = []; - - activeGuiMoveMarkings.forEach((obj, index) => { - if (index === bestIndex) { - bestMarking.push({ ...obj, status: 'best' }); - } else { - secondaryMarkings.push(obj); - } - }); - - secondaryMarkings.sort((a, b) => { - if (a.ranking !== b.ranking) return a.ranking - b.ranking; - if (a.promotedRanking !== b.promotedRanking) return (a.promotedRanking || Infinity) - (b.promotedRanking || Infinity); - return activeGuiMoveMarkings.indexOf(a) - activeGuiMoveMarkings.indexOf(b); - }); - - const sortedMarkings = secondaryMarkings.map((obj, index) => ({ - ...obj, - status: 'secondary' - })); - - return bestMarking.concat(sortedMarkings); - } - - const rankedMarkings = rankMarkings(this.activeGuiMoveMarkings); - - rankedMarkings.forEach((markingObj, idx) => { + moveObjArr.forEach((markingObj, idx) => { const [from, to] = markingObj.player; const [oppFrom, oppTo] = markingObj.opponent; - const playerArrowElem = markingObj.playerArrowElem; - const oppArrowElem = markingObj.opponentArrowElem; - + const oppMovesExist = oppFrom && oppTo; const rank = idx + 1; - const maxScale = 1; - const minScale = 0.4; - const totalRanks = rankedMarkings.length; - + + let playerArrowElem = null; + let oppArrowElem = null; let arrowStyle = this.arrowStyles.best; let lineWidth = 30; let arrowheadWidth = 80; let arrowheadHeight = 60; let startOffset = 30; - + if(idx !== 0) { - console.log('Secondary move', markingObj); - arrowStyle = this.arrowStyles.secondary; - + const arrowScale = totalRanks === 2 ? 0.75 : maxScale - (maxScale - minScale) * ((rank - 1) / (totalRanks - 1)); - + lineWidth = lineWidth * arrowScale; arrowheadWidth = arrowheadWidth * arrowScale; arrowheadHeight = arrowheadHeight * arrowScale; startOffset = startOffset; } - - // Update player move arrow element - this.BoardDrawer.createShape('arrow', [from, to], - { + + playerArrowElem = this.BoardDrawer.createShape('arrow', [from, to], + { style: arrowStyle, - lineWidth, arrowheadWidth, arrowheadHeight, startOffset, - existingElem: playerArrowElem + lineWidth, arrowheadWidth, arrowheadHeight, startOffset } ); - - if(oppArrowElem) { - // Update opponent move arrow element - this.BoardDrawer.createShape('arrow', [oppFrom, oppTo], - { + + if(oppMovesExist && showOpponentMoveGuess) { + oppArrowElem = this.BoardDrawer.createShape('arrow', [oppFrom, oppTo], + { style: this.arrowStyles.opponent, - lineWidth, arrowheadWidth, arrowheadHeight, startOffset, - existingElem: oppArrowElem + lineWidth, arrowheadWidth, arrowheadHeight, startOffset } ); - } - - if(idx === 0) { - console.log('Best move', markingObj); + if(showOpponentMoveGuessConstantly) { + oppArrowElem.style.display = 'block'; + } else { + const squareListener = this.BoardDrawer.addSquareListener(from, type => { + if(!oppArrowElem) { + squareListener.remove(); + } + + switch(type) { + case 'enter': + oppArrowElem.style.display = 'inherit'; + break; + case 'leave': + oppArrowElem.style.display = 'none'; + break; + } + }); + } + } + + if(idx === 0 && playerArrowElem) { + const parentElem = playerArrowElem.parentElement; + // move best arrow element on top (multiple same moves can hide the best move) - const parentElem = markingObj.playerArrowElem.parentElement; - parentElem.appendChild(playerArrowElem); - + if(oppArrowElem) { parentElem.appendChild(oppArrowElem); } } + + this.activeGuiMoveMarkings.push({ ...markingObj, playerArrowElem, oppArrowElem }); }); + + this.pastMoveObjects = []; }, - removeBestMarkings: () => { + removeMarkings: () => { this.activeGuiMoveMarkings.forEach(markingObj => { - markingObj.opponentArrowElem?.remove(); + markingObj.oppArrowElem?.remove(); markingObj.playerArrowElem?.remove(); }); this.activeGuiMoveMarkings = []; }, updateBoardFen: fen => { - USERSCRIPT.instanceVars.fen.set(this.instanceID, fen); + if(this.currentFen !== fen) { + this.currentFen = fen; + + USERSCRIPT.instanceVars.fen.set(this.instanceID, fen); - this.chessground.set({ fen }); - this.instanceElem.querySelector('.instance-fen').innerText = fen; + this.chessground.set({ fen }); + + this.instanceElem.querySelector('.instance-fen').innerText = fen; + + this.engineStopCalculating(); + this.Interface.boardUtils.removeMarkings(); + + this.currentSpeeches.forEach(synthesis => synthesis.cancel()); + this.currentSpeeches = []; - this.onNewMove(fen); + this.calculateBestMoves(fen); + } }, updateBoardOrientation: orientation => { - const orientationWord = orientation == 'b' ? 'black' : 'white'; - - const evalBarElem = this.instanceElem.querySelector('.eval-bar'); + if(orientation != this.lastOrientation) { + this.lastOrientation = orientation; + this.lastCalculatedFen = null; - if(orientation == 'b') - evalBarElem.classList.add('reversed'); - else - evalBarElem.classList.remove('reversed'); + const orientationWord = orientation == 'b' ? 'black' : 'white'; - this.chessground.toggleOrientation(); - this.chessground.redrawAll(); - this.chessground.set({ 'orientation': orientationWord }); - - this.BoardDrawer.setOrientation(orientation); + const evalBarElem = this.instanceElem.querySelector('.eval-bar'); + + if(orientation == 'b') + evalBarElem.classList.add('reversed'); + else + evalBarElem.classList.remove('reversed'); + + this.chessground.toggleOrientation(); + this.chessground.redrawAll(); + this.chessground.set({ 'orientation': orientationWord }); + + this.BoardDrawer.setOrientation(orientation); + } } }, updateMoveProgress: (text, status) => { @@ -614,7 +478,9 @@ class BackendInstance { engineStartNewGame(variant) { const chessVariant = formatVariant(variant); - this.engineStopCalculating(); + if(!this.isEngineNotCalculating()) { + this.engineStopCalculating(); + } this.sendMsgToEngine('ucinewgame'); // very important to be before setting variant and so forth this.sendMsgToEngine('uci'); // to display variants @@ -638,32 +504,22 @@ class BackendInstance { } engineStopCalculating() { - this.sendMsgToEngine('stop'); - } + if(!this.isEngineNotCalculating()) { + clearTimeout(this.currentMovetimeTimeout); + } - isPlayerTurn() { - const playerColor = USERSCRIPT.instanceVars.playerColor.get(this.instanceID); - const turn = USERSCRIPT.instanceVars.turn.get(this.instanceID); + console.error('STOP!'); - return !playerColor || turn == playerColor; + this.sendMsgToEngine('stop'); } - onNewMove(fen) { - this.Interface.boardUtils.removeBestMarkings(); - - this.engineStopCalculating(); - - this.currentSpeeches.forEach(synthesis => synthesis.cancel()); - this.currentSpeeches = []; + isPlayerTurn(lastFen, currentFen) { + const playerColor = USERSCRIPT.instanceVars.playerColor.get(this.instanceID); + const turn = this.getTurnFromFenChange(lastFen, currentFen); - // Not sure if 'ucinewgame' resets variants or other settings, so disabling this for now. - // Missing the 'ucinewgame' after each match shouldn't have any negative effects. - /* - const isStartPos = getBasicFenLowerCased(this.variantStartPosFen) == getBasicFenLowerCased(fen); + console.error('Turn: ', turn); - if(isStartPos) { - this.sendMsgToEngine('ucinewgame'); - }*/ + return !playerColor || turn == playerColor; } speak(spokenText, speechConfig) { @@ -687,7 +543,7 @@ class BackendInstance { this.currentSpeeches.push(speakText(spokenText, speechConfig)); } - } + } updateSettings(updateObj) { function findSettingKeyFromData(key) { @@ -737,16 +593,138 @@ class BackendInstance { } } + isAbnormalPieceChange(lastFen, newFen) { + if(!lastFen) return false; + + const lastPieceCount = countTotalPieces(lastFen); + const newPieceCount = countTotalPieces(newFen); + + // (need to implement fix for variants which may add pieces legally) + const countChange = newPieceCount - lastPieceCount; + + /* Possible "countChange" value explanations, + (countChange < -1) -> multiple pieces have disappeared (atomic chess variant or a faulty newFen?) + (countChange = -1) -> piece has been eaten + (countChange = 0) -> piece moved + (countChange = 1) -> piece has spawned + (countChange > 1) -> multiple pieces have spawned (possibly a new game?) + */ + + // Large abnormal piece changes are allowed, as they usually mean something significant has happened + // Smaller abnormal piece changes are most likely caused by a faulty newFen provided by the A.C.A.S on the site + return (-3 < countChange && countChange < -1) || (0 < countChange && countChange < 2); + } + + // Kind of similar to isAbnormalPieceChange function, however it focuses on titles rather than pieces + // It checks for how many titles had changes happen in them + isCorrectAmountOfBoardChanges(lastFen, newFen) { + if(!lastFen) return true; + + let board1 = lastFen.split(" ")[0].replace(/\d/g, d => ' '.repeat(d)).split('/').join(''); + let board2 = newFen.split(" ")[0].replace(/\d/g, d => ' '.repeat(d)).split('/').join(''); + + let diff = 0; + + for (let i = 0; i < board1.length; i++) { + if (board1[i] !== board2[i]) { + diff += 1; + } + } + + /* Possible "diff" value explanations, + (diff = 0) -> no changes, same board layout + (diff = 1) -> only one title abruptly changed, shouldn't be possible + (diff = 2) -> a piece moved, maybe it ate another piece + (diff = 3) -> three titles had changes, shouldn't be possible + (diff > 3) -> a lot of titles had changes, maybe a new game started, the change is significant so allowing it + */ + + return diff === 2 || diff > 3; + } + + getTurnFromFenChange(lastFen, newFen) { + const currentPlayerColor = USERSCRIPT.instanceVars.playerColor.get(this.instanceID); + const onlyCalculateOwnTurn = this.getConfigValue(this.configKeys.onlyCalculateOwnTurn); + + if(!lastFen || !onlyCalculateOwnTurn) return currentPlayerColor; + + const lastBoard = lastFen.split(' ')[0]; + const newBoard = newFen.split(' ')[0]; + + const lastBoardArray = fenToArray(lastBoard); + const newBoardArray = fenToArray(newBoard); + + const isNewFenDefaultPos = newFen.split(' ')[0] == this.defaultStartpos.split(' ')[0]; + + const dimensions = getBoardDimensionsFromFenStr(newFen); + const boardDimensions = { 'width': dimensions[0], 'height': dimensions[1] }; + + let movedPiece = ''; + let amountOfMovedPieces = 0; + + for (let i = 0; i < boardDimensions.width; i++) { + for (let j = 0; j < boardDimensions.height; j++) { + if (lastBoardArray[i][j] !== newBoardArray[i][j]) { + if(newBoardArray[i][j] === '') { + movedPiece = lastBoardArray[i][j]; + + amountOfMovedPieces++; + } + } + } + } + + // When a lot of pieces are moved, it's most likely a new game + if(amountOfMovedPieces > 3 || isNewFenDefaultPos) { + // Clear the pendingCalculations arr of old calculations + this.pendingCalculations = this.pendingCalculations.filter(x => !x?.finished); + + return currentPlayerColor; + } + + const turn = movedPiece.toUpperCase() === movedPiece ? 'b' : 'w'; + + return turn; + } + async calculateBestMoves(currentFen) { - if(this.engineFinishedCalculation === false) { + const onlyCalculateOwnTurn = this.getConfigValue(this.configKeys.onlyCalculateOwnTurn); + + const correctAmountOfChanges = this.isCorrectAmountOfBoardChanges(this.lastFen, currentFen); + const isAbnormalPieceChange = this.isAbnormalPieceChange(this.lastFen, currentFen); + + let isPlayerTurn = false; + + if(correctAmountOfChanges && !isAbnormalPieceChange) { + isPlayerTurn = this.isPlayerTurn(this.lastFen, currentFen); + + this.lastFen = currentFen; + } + + const isFenChanged = this.lastCalculatedFen !== currentFen + const isFenChangeAllowed = !onlyCalculateOwnTurn || (correctAmountOfChanges && !isAbnormalPieceChange && isPlayerTurn) + + if(isFenChanged && isFenChangeAllowed) { + this.lastCalculatedFen = currentFen; + } else { + this.CommLink.commands.removeSiteMoveMarkings(); + + return; + } + + if(!this.isEngineNotCalculating()) { + this.engineStopCalculating(); + console.warn(`Engine didn't finish before the next best move request came, won't show the cancelled calculation results!`); - - this.newCalculationRequestBeforeLastEnded = true; - clearTimeout(this.currentMovetimeTimeout); + return; } - this.engineFinishedCalculation = false; + this.pendingCalculations.push({ 'fen': currentFen, 'finished': false }); + + this.Interface.boardUtils.removeMarkings(); + + console.error('CALCULATING!', this.pendingCalculations, currentFen); log.info(`Fen: "${currentFen}"`); log.info(`Updating board Fen`); @@ -776,10 +754,10 @@ class BackendInstance { const movetime = this.getConfigValue(this.configKeys.maxMovetime); if(typeof movetime == 'number') { - this.currentMovetimeTimeout = setTimeout(() => { - if(movetime != 0 && !this.engineFinishedCalculation) { - console.log('Stopped'); + const startFen = this.currentFen; + this.currentMovetimeTimeout = setTimeout(() => { + if(startFen == this.currentFen && movetime != 0 && !this.isEngineNotCalculating()) { this.engineStopCalculating(); } }, movetime + 5); @@ -798,10 +776,16 @@ class BackendInstance { this.getEngineAcasObj(i).sendMsg(msg); } + isEngineNotCalculating() { + return this.pendingCalculations.find(x => !x.finished) ? false : true; + } + engineMessageHandler(msg) { const data = parseUCIResponse(msg); + const oldestUnfinishedCalcRequestObj = this.pendingCalculations.find(x => !x.finished); + const isMessageForCurrentFen = oldestUnfinishedCalcRequestObj?.fen === this.currentFen; - if(!data?.currmovenumber) console.warn(msg); + if(!data?.currmovenumber) console.warn(msg, `(FOR FEN: ${oldestUnfinishedCalcRequestObj?.fen})`); if(msg.includes('option name UCI_Variant type combo')) { const chessVariants = extractVariantNames(msg); @@ -812,7 +796,7 @@ class BackendInstance { } if(msg.includes('info')) { - if(data?.multipv == 1) { + if(data?.multipv === '1') { if(data?.depth) { if(data?.mate) { const isWinning = data.mate > 0; @@ -832,7 +816,7 @@ class BackendInstance { } } - if(data?.pv) { + if(data?.pv && isMessageForCurrentFen) { const moveRegex = /[a-zA-Z]\d+/g; const ranking = convertToCorrectType(data?.multipv) || 1; let moves = data.pv.split(' ').map(move => move.match(moveRegex)); @@ -853,44 +837,56 @@ class BackendInstance { let isSearchInfinite = this.searchDepth ? false : true; if(this.chessEngineType === 'lc0') { - isSearchInfinite = this.engineNodes > 999999999 ? true : false; + isSearchInfinite = this.engineNodes > 9e6 ? true : false; } - if(!onlyShowTopMoves || (isSearchInfinite && !isMovetimeLimited)) { - this.Interface.boardUtils.markMove(moveObj); + const markingLimit = this.getConfigValue(this.configKeys.moveSuggestionAmount); + const topMoveObjects = this.pastMoveObjects?.slice(markingLimit * -1); + + if(topMoveObjects.length === markingLimit) { + this.Interface.boardUtils.markMoves(topMoveObjects); + + topMoveObjects.forEach(moveObj => { + const spokenText = moveObj.player?.map(x => + x.split('').map(x =>`"${x}"`).join(' ') // e.g a1 -> "a" "1" + ).join(', '); + + this.speak(spokenText); + }); if(displayMovesExternally) { - this.CommLink.commands.markMoveToSite(moveObj); + this.CommLink.commands.markMoveToSite(topMoveObjects); } } } if(data?.bestmove) { - if(!this.newCalculationRequestBeforeLastEnded) { - this.engineFinishedCalculation = true; - - const displayMovesExternally = this.getConfigValue(this.configKeys.displayMovesOnExternalSite); + oldestUnfinishedCalcRequestObj.finished = true; + + if(isMessageForCurrentFen && this.activeGuiMoveMarkings.length === 0) { const markingLimit = this.getConfigValue(this.configKeys.moveSuggestionAmount); + const displayMovesExternally = this.getConfigValue(this.configKeys.displayMovesOnExternalSite); - const topMoveObjects = this.pastMoveObjects?.slice(markingLimit * -1); + let topMoveObjects = this.pastMoveObjects?.slice(markingLimit * -1); - topMoveObjects.forEach(moveObj => { - this.Interface.boardUtils.markMove(moveObj); + if(topMoveObjects?.length === 0) { + topMoveObjects = []; + topMoveObjects.push({ 'player': [data.bestmove.slice(0,2), data.bestmove.slice(2, data.bestmove.length)], 'opponent': [null, null], 'ranking': 1 }); + } + this.Interface.boardUtils.markMoves(topMoveObjects); + + if(displayMovesExternally) { + this.CommLink.commands.markMoveToSite(topMoveObjects); + } + + topMoveObjects.forEach(moveObj => { const spokenText = moveObj.player?.map(x => x.split('').map(x =>`"${x}"`).join(' ') // e.g a1 -> "a" "1" ).join(', '); this.speak(spokenText); - - if(displayMovesExternally) { - this.CommLink.commands.markMoveToSite(moveObj); - } }); - - this.pastMoveObjects = []; - } else { - this.newCalculationRequestBeforeLastEnded = false; } } @@ -980,6 +976,12 @@ class BackendInstance { variantText += ` (${this.chessVariant} not supported)`; } + const onlyCalculateOwnTurn = this.getConfigValue(this.configKeys.onlyCalculateOwnTurn); + + if(variant != 'chess' && onlyCalculateOwnTurn) { + toast.warning(`"Only Own Turn" setting might not work for variants! (todo)`, 5000); + } + const chessFont = formatChessFont(this.getConfigValue(this.configKeys.chessFont)); this.variantStartPosFen = startpos; @@ -992,6 +994,10 @@ class BackendInstance { const boardDimensions = { 'width': dimensions[0], 'height': dimensions[1] }; const instanceIdQuery = `[data-instance-id="${this.instanceID}"]`; + this.currentFen = fen; + + this.pendingCalculations = []; + log.info(`Variant: "${variantText}"\n\nFen: "${fen}"\n\nDimension: "${boardDimensions.width}x${boardDimensions.height}"`); const boardPieceDimensions = getPieceStyleDimensions(boardDimensions); diff --git a/assets/js/acas-globals.js b/assets/js/acas-globals.js index 092f3d1..55d13e0 100644 --- a/assets/js/acas-globals.js +++ b/assets/js/acas-globals.js @@ -148,6 +148,51 @@ function convertToCorrectType(data) { return data; } +function countPieces(fen) { + const pieceCount = {}; + const position = fen.split(' ')[0]; + + for (let char of position) { + if (/[rnbqkpRNBQKP]/.test(char)) { + pieceCount[char] = (pieceCount[char] || 0) + 1; + } + } + + return pieceCount; +} + +function countTotalPieces(fen) { + let pieceCount = 0; + const position = fen.split(' ')[0]; + + for (let char of position) { + if (/[rnbqkpRNBQKP]/.test(char)) { + pieceCount += (pieceCount[char] || 0) + 1; + } + } + + return pieceCount; +} + +function fenToArray(fen) { + const rows = fen.split('/'); + const board = []; + + for (let row of rows) { + const boardRow = []; + for (let char of row) { + if (isNaN(char)) { + boardRow.push(char); + } else { + boardRow.push(...Array(parseInt(char)).fill('')); + } + } + board.push(boardRow); + } + + return board; +} + function capitalize(s) { return s && s[0].toUpperCase() + s.slice(1); } diff --git a/index.css b/index.css index 0550f47..8756a35 100644 --- a/index.css +++ b/index.css @@ -996,4 +996,14 @@ cg-container { display: flex; align-items: center; font-size: 10px; +} +.input-combination-box { + display: flex; + flex-direction: column; + grid-gap: 10px; +} +.sub-input { + padding-left: 10px; + margin-left: 10px; + border-left: 2px solid rgba(255, 255, 255, 0.05); } \ No newline at end of file diff --git a/index.html b/index.html index 3b8dbaa..b9307e9 100644 --- a/index.html +++ b/index.html @@ -119,6 +119,7 @@