您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Improved userscript with dynamic time management based on game phase, configurable Stockfish threads/hash, and the existing evaluation bar and move analysis.
当前为
// ==UserScript== // @name Chess.com Bot — Dynamic Time Management // @namespace thehackerclient // @version 3.0 // Major update: Dynamic Time Management & Engine Options // @description Improved userscript with dynamic time management based on game phase, configurable Stockfish threads/hash, and the existing evaluation bar and move analysis. // @match https://www.chess.com/* // @auther thehackerclient // @grant GM_getResourceText // @license MIT // @require https://code.jquery.com/jquery-3.6.0.min.js // @resource stockfish.js https://cdnjs.cloudflare.com/ajax/libs/stockfish.js/9.0.0/stockfish.js // @run-at document-start // ==/UserScript== (function () { 'use strict'; console.log('--- Chess Bot v3.1 Initializing: Stability Refactor Complete ---'); // 🎯 Use a reliable CDN link for Stockfish Web Worker const STOCKFISH_URL = 'https://cdn.jsdelivr.net/gh/nmvsh/Stockfish.js@master/stockfish.js'; // --------- Config & state --------- const STORAGE_KEY = 'chess_bot_settings_v3_1'; const DEFAULTS = { autoRun: true, autoMovePiece: false, delayMin: 1.0, delayMax: 3.0, stockfishThreads: 4, stockfishHash: 256, lastDepth: 18, showPV: true, colors: { move1: 'rgba(235,97,80,0.7)', move2:'rgba(255,165,0,0.6)', move3:'rgba(255,255,0,0.5)', threat:'rgba(0,128,255,0.35)' }, highlightMs: 1400 }; let settings = Object.assign({}, DEFAULTS, loadSettings()); // State variables let board = null; let engine = { worker: null }; let candidateMoves = []; let isThinking = false; let canGo = true; let lastPlayedMove = null; let lastPositionBestScore = { score: 0, mate: null, turn: 'w', initial: true }; let lastMoveClassification = { type: 'N/A', cpl: 0, move: '' }; // Move Classification Thresholds (in centipawns) const T_BEST = 0; const T_EXCELLENT = 5; const T_GOOD = 15; const T_INACCURACY = 40; const T_MISTAKE = 120; const T_BLUNDER = 250; // Debounce and throttle helpers (using standard JS) function debounce(fn, wait){ let t; return function(...a){ clearTimeout(t); t = setTimeout(()=>fn.apply(this,a), wait); }; } function throttle(fn, wait){ let last=0; return function(...a){ const now=Date.now(); if(now-last>wait){ last=now; fn.apply(this,a);} }; } // Robust board detection function findBoard(){ const selectors = ['chess-board', 'wc-chess-board', '[data-cy="board"]', '.board']; for (const selector of selectors) { const el = document.querySelector(selector); // Ensure the element is visible and not part of an archive/puzzle board only if (el && el.offsetWidth > 100) return el; } return null; } // --------- Persistence & Engine Setup --------- function loadSettings(){ try{ const raw = localStorage.getItem(STORAGE_KEY); return raw? JSON.parse(raw): {}; }catch(e){ return {}; } } function saveSettings(){ try{ localStorage.setItem(STORAGE_KEY, JSON.stringify(settings)); }catch(e){} } // Refactored engine creation for maximum reliability function createStockfishWorker(){ try{ if(engine.worker) engine.worker.terminate(); // 🎯 The key change: Load the worker directly from the CDN URL engine.worker = new Worker(STOCKFISH_URL); engine.worker.onmessage = e => handleEngineMessage(e.data); engine.worker.onerror = e => console.error('Stockfish Worker Error:', e); // Initialize Stockfish options engine.worker.postMessage('ucinewgame'); engine.worker.postMessage(`setoption name Threads value ${settings.stockfishThreads}`); engine.worker.postMessage(`setoption name Hash value ${settings.stockfishHash}`); console.log(`Stockfish worker created from CDN. Threads: ${settings.stockfishThreads}, Hash: ${settings.stockfishHash}MB. UCINewGame sent.`); }catch(err){ console.error('FATAL: Failed to create Stockfish worker.', err); } } function safeRestartEngine(){ console.log('Restarting engine to apply new settings...'); isThinking = false; try{ if(engine.worker) engine.worker.terminate(); }catch(e){} createStockfishWorker(); } // --------- Evaluation and Classification Logic --------- function updateEvaluationBar(scoreCp, mateScore) { const barWhiteEl = document.getElementById('evalBarWhiteAdvantage'); const scoreTextEl = document.getElementById('evalPercent'); if (!barWhiteEl || !scoreTextEl) return; let displayScore; let barHeightPercent; if (mateScore !== null) { displayScore = mateScore > 0 ? `M+${mateScore}` : `M${mateScore}`; barHeightPercent = mateScore > 0 ? 98 : 2; } else { const clampedCp = Math.max(-800, Math.min(800, scoreCp)); barHeightPercent = 50 + (clampedCp / 16); barHeightPercent = Math.max(0, Math.min(100, barHeightPercent)); displayScore = (scoreCp / 100).toFixed(2); if (scoreCp >= 0) displayScore = `+${displayScore}`; } barWhiteEl.style.height = `${barHeightPercent}%`; scoreTextEl.innerText = displayScore; const whiteAdvantage = barHeightPercent; if (whiteAdvantage > 80) { scoreTextEl.style.color = '#fff'; scoreTextEl.style.top = '10%'; scoreTextEl.style.transform = 'translate(-50%, 0)'; } else if ((100 - whiteAdvantage) > 80) { scoreTextEl.style.color = '#000'; scoreTextEl.style.top = '90%'; scoreTextEl.style.transform = 'translate(-50%, -100%)'; } else { scoreTextEl.style.color = '#1a1a1a'; scoreTextEl.style.top = '50%'; scoreTextEl.style.transform = 'translate(-50%, -50%)'; } } function classifyMove(cplLoss) { if (cplLoss <= T_BEST) return 'Best Move'; if (cplLoss <= T_EXCELLENT) return 'Excellent'; if (cplLoss <= T_GOOD) return 'Good'; if (cplLoss <= T_INACCURACY) return 'Inaccuracy'; if (cplLoss <= T_MISTAKE) return 'Mistake'; if (cplLoss <= T_BLUNDER) return 'Blunder'; return 'Major Blunder'; } function updateMoveClassificationDisplay() { const el = document.getElementById('moveClassText'); if (!el) return; let classification = lastMoveClassification.type; let move = lastMoveClassification.move; el.innerHTML = `${move ? `(${move})` : ''} <strong>${classification}</strong>`; el.title = lastMoveClassification.cpl > 0 ? `CPL: ${lastMoveClassification.cpl.toFixed(0)}` : ''; let color = '#333'; switch(classification) { case 'Major Blunder': case 'Blunder': color = '#d9534f'; break; case 'Mistake': color = '#f0ad4e'; break; case 'Inaccuracy': color = '#f7e382'; break; case 'Good': color = '#5bc0de'; break; case 'Excellent': color = '#5cb85c'; break; case 'Best Move': color = '#008000'; break; case 'Thinking...': color = '#0d6efd'; break; default: color = '#666'; break; } el.style.color = color; } // --------- Engine message parsing and classification --------- function parseScore(match){ if(!match) return 0; const type = match[1]; const val = parseInt(match[2]); if(type === 'cp') return val; // Mate scores are normalized to extreme centipawns return val > 0 ? 100000 - val : -100000 - val; } function handleEngineMessage(msg){ if(typeof msg !== 'string') return; if(msg.startsWith('info') && msg.includes('pv')){ const pvTokens = msg.split(' pv ')[1].trim().split(/\s+/); if(pvTokens && pvTokens.length){ const scoreMatch = msg.match(/score (cp|mate) (-?\d+)/); const score = parseScore(scoreMatch); let rawCp = null; let rawMate = null; if (scoreMatch) { const type = scoreMatch[1]; const val = parseInt(scoreMatch[2]); if (type === 'cp') rawCp = val; if (type === 'mate') rawMate = val; } const depthMatch = msg.match(/depth (\d+)/); const depth = depthMatch? parseInt(depthMatch[1]) : settings.lastDepth; const move = pvTokens[0]; const exists = candidateMoves.find(c=>c.move===move); if(!exists) candidateMoves.push({move, score, depth, pv: pvTokens, rawCp, rawMate}); else if(depth>exists.depth) { Object.assign(exists, {score, depth, pv: pvTokens, rawCp, rawMate}); } } } if(msg.startsWith('bestmove')){ candidateMoves.sort((a,b) => b.score - a.score); candidateMoves = candidateMoves.slice(0,3); showTopMoves(); if(candidateMoves.length > 0) { const bestMove = candidateMoves[0]; const finalCp = bestMove.rawCp !== null ? bestMove.rawCp : bestMove.score; const finalMate = bestMove.rawMate; const currentTurn = board.game.getTurn(); // --- Move Quality Analysis --- if (!lastPositionBestScore.initial && lastPlayedMove) { const scoreBefore = lastPositionBestScore.score; const turnBefore = lastPositionBestScore.turn; const movePlayed = lastPlayedMove; const expectedAdvantage = turnBefore === 'w' ? scoreBefore : -scoreBefore; const actualAdvantage = turnBefore === 'w' ? finalCp : -finalCp; let cplLoss = expectedAdvantage - actualAdvantage; const absCplLoss = Math.max(0, cplLoss); if (lastPositionBestScore.mate !== null || finalMate !== null || Math.abs(cplLoss) > 5000) { // Skip CPL calculation if there was a mate or extreme score change lastMoveClassification = { type: 'Analysis Complete', cpl: 0, move: movePlayed }; } else { const classification = classifyMove(absCplLoss); lastMoveClassification = { type: classification, cpl: absCplLoss, move: movePlayed }; } console.log(`Move Analysis (${movePlayed}, ${turnBefore}): CPL ${absCplLoss.toFixed(0)}, Type: ${lastMoveClassification.type}`); lastPlayedMove = null; } // Update the last position score for the *next* analysis. lastPositionBestScore = { score: finalCp, mate: finalMate, turn: currentTurn, initial: false }; updateEvaluationBar(finalCp, finalMate); updateMoveClassificationDisplay(); } const move = msg.split(' ')[1]; if(settings.autoMovePiece && move) performMove(move); isThinking = false; } } // --------- Highlighting and Move Execution (Simplified for stability) --------- function mapSquareForBoard(sq){ if(!board || !sq || sq.length<2) return sq; // Check for common 'flipped' class on chess.com boards const isFlipped = board.classList && board.classList.contains('flipped'); if(!isFlipped) return sq; const file = sq[0]; const rank = sq[1]; const flippedFile = String.fromCharCode('h'.charCodeAt(0) - (file.charCodeAt(0)-'a'.charCodeAt(0))); const flippedRank = (9 - parseInt(rank)).toString(); return flippedFile + flippedRank; } function getBoardSquareEl(sq){ try{ return board.querySelector(`[data-square="${sq}"]`) || board.querySelector(`.square-${sq}`); } catch(e){ return null; } } function attachHighlight(el, cls, color){ if(!el) return null; let overlay = el.querySelector('.' + cls); if(!overlay){ overlay = document.createElement('div'); overlay.className = cls; overlay.style.cssText='position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:60;border-radius:3px;'; el.appendChild(overlay); } overlay.style.backgroundColor = color; return overlay; } function detachHighlights(selector){ try{ document.querySelectorAll(selector).forEach(n=>n.parentElement && n.parentElement.removeChild(n)); } catch(e){} } function addPVNote(cm, index){ try{ const id = `pvNote-${index}`; let note = document.getElementById(id); if(!note){ note = document.createElement('div'); note.id=id; note.style.cssText = ` position:absolute; right:6px; top:${6 + index*28}px; padding:6px 8px; border-radius:6px; background:rgba(0,0,0,0.75); color:#fff; z-index:120; font-size:12px; font-family:Inter,Arial,sans-serif; `; board.parentElement.appendChild(note); } let scoreDisplay = cm.rawMate !== null ? (cm.rawMate > 0 ? `M+${cm.rawMate}` : `M${cm.rawMate}`) : (cm.rawCp !== null ? (cm.rawCp/100).toFixed(2) : (cm.score/100).toFixed(2)); note.innerText = `#${index+1} ${cm.move} (${scoreDisplay}) PV: ${cm.pv.slice(0,6).join(' ')}`; // Auto-remove PV note after highlight duration setTimeout(()=>{ if(note && note.parentElement) note.parentElement.removeChild(note); }, settings.highlightMs + 5000); }catch(e){} } function showTopMoves(){ if(!board || !board.game) return; detachHighlights('.botMoveHighlight'); detachHighlights('.botThreatHighlight'); // 1. Highlight Top Moves candidateMoves.forEach((cm, i) => { const from = mapSquareForBoard(cm.move.slice(0,2)); const to = mapSquareForBoard(cm.move.slice(2,4)); const color = i===0? settings.colors.move1 : (i===1? settings.colors.move2 : settings.colors.move3); [from, to].forEach(sq => { const el = getBoardSquareEl(sq); if(el) { const ov = attachHighlight(el, 'botMoveHighlight', color); setTimeout(()=>{ if(ov && ov.parentElement) ov.parentElement.removeChild(ov); }, settings.highlightMs); } }); if(settings.showPV && cm.pv && cm.pv.length){ addPVNote(cm, i); } }); // 2. Show Threats (opponent's legal moves) showThreats(); } function showThreats(){ if(!board || !board.game) return; try{ // Note: Chess.com's internal game object (board.game) needs to provide legal moves const legalMoves = board.game.getLegalMoves ? board.game.getLegalMoves() : []; const turn = board.game.getTurn ? board.game.getTurn() : 'w'; const opponent = turn === 'w' ? 'b' : 'w'; legalMoves.forEach(m=>{ // Only highlight opponent's moves if they are capturable by the current player if(m.color===opponent){ const sq = mapSquareForBoard(m.to); const el = getBoardSquareEl(sq); if(el){ const ov = attachHighlight(el, 'botThreatHighlight', settings.colors.threat); setTimeout(()=>{ if(ov && ov.parentElement) ov.parentElement.removeChild(ov); }, settings.highlightMs); } } }); }catch(e){ console.warn('Failed to show threats', e); } } function performMove(moveUCI){ if(!board || !board.game) return; try{ const from = moveUCI.slice(0,2); const to = moveUCI.slice(2,4); const promotion = moveUCI.length>4? moveUCI[4] : null; const legal = board.game.getLegalMoves ? board.game.getLegalMoves() : []; for(const m of legal){ if(m.from===from && m.to===to){ if(m.promotion && promotion){ m.promotion = promotion; } // Use Chess.com's internal move function board.game.move(Object.assign({}, m, {animate:false, userGenerated:true})); console.log(`Auto-moving piece: ${moveUCI}`); break; }} }catch(e){ console.error('performMove failed', e); } } // ------------------------------------------------------------------- // 🎯 CORE LOGIC FOR ENGINE COMMANDS (runChessEngine) // ------------------------------------------------------------------- function runChessEngine(type, value){ if(!board || !engine.worker || !board.game) { console.warn('Engine run skipped: Missing board, worker, or game object.'); return; } try{ const fen = board.game.getFEN ? board.game.getFEN() : 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'; if(isThinking) engine.worker.postMessage('stop'); candidateMoves = []; engine.worker.postMessage('position fen ' + fen); isThinking = true; const command = type === 'depth' ? `go depth ${value}` : `go movetime ${Math.max(100, value)}`; // Stockfish uses time limit (ms) for deeper calculation engine.worker.postMessage(command); console.log(`Engine command sent: ${command}`); }catch(e){ console.error('runChessEngine error', e); } } // ------------------------------------------------------------------- // 🎯 DYNAMIC TIME MANAGEMENT (autoLoop) // ------------------------------------------------------------------- const autoLoop = throttle(()=>{ if(!board || !board.game) return; if(settings.autoRun && canGo && !isThinking && board.game.getTurn() === board.game.getPlayingAs()){ canGo = false; const history = board.game.getMoveHistory ? board.game.getMoveHistory() : (board.game.history || []); const halfMoveCount = history.length; const moveNumber = Math.floor((halfMoveCount + 1) / 2); // 1-indexed full move let targetDelaySeconds; // --- Dynamic Time Management Logic --- const minTime = settings.delayMin; const maxTime = settings.delayMax; if (moveNumber <= 10) { // Opening (Moves 1-10): Faster, more standard time targetDelaySeconds = minTime * 1.0; } else if (moveNumber <= 30) { // Middlegame (Moves 11-30): Max complexity, use max time targetDelaySeconds = maxTime; } else { // Endgame (Moves 31+): High accuracy required, moderate time targetDelaySeconds = minTime + (maxTime - minTime) * 0.7; // ~70% of max } // Apply slight randomization const variance = 0.95 + Math.random() * 0.1; // +/- 5% targetDelaySeconds = Math.max(minTime * 0.5, targetDelaySeconds * variance); const movetimeMs = Math.round(targetDelaySeconds * 1000); console.log(`[Move ${moveNumber}] Dynamic Time: ${targetDelaySeconds.toFixed(2)}s (${movetimeMs}ms)`); setTimeout(()=>{ runChessEngine('movetime', movetimeMs); canGo = true; }, 100); } }, 150); // --------- GUI Setup --------- function initGUI(){ board = findBoard(); if(!board) return false; if(document.getElementById('botGUI_v3_wrapper')) return true; const wrapper = document.createElement('div'); wrapper.id = 'botGUI_v3_wrapper'; wrapper.style.cssText = 'display:flex; align-items:flex-start;'; const container = document.createElement('div'); container.id = 'botGUI_v3'; container.style.cssText = ` background:rgba(255,255,255,0.95); padding:10px; margin:8px 0 8px 8px; max-width:280px; font-family:Inter,Arial,sans-serif; border-radius:8px; box-shadow:0 6px 20px rgba(0,0,0,0.08); box-sizing: border-box; `; container.innerHTML = ` <div style="font-weight:700;margin-bottom:6px;font-size:16px;color:#333;border-bottom:2px solid #eee;padding-bottom:6px;"> 🤖 Chess Bot Live Analysis v3.1 </div> <div id="moveClassification" style="margin-top:4px;margin-bottom:8px;font-weight:600;font-size:13px;"> Last Move: <span id="moveClassText" style="color:#666;">N/A</span> </div> <div id="depthText" style="margin-top:10px;font-size:13px;">Manual Depth: <strong>${settings.lastDepth}</strong></div> <input type="range" id="depthSlider" min="1" max="30" value="${settings.lastDepth}" step="1" style="width:100%; height: 20px; margin: 4px 0;"> <div style="margin-top:10px; font-weight: 600; font-size:13px;">Dynamic Engine Time (seconds)</div> <div style="margin-top:4px;display:flex;justify-content:space-between;align-items:center;font-size:12px;"> <label style="color:#444;">Min Base:</label> <input id="delayMinInput" type="number" min="0.1" step="0.1" value="${settings.delayMin}" style="width:60px;padding:3px;border:1px solid #ccc;border-radius:3px;font-size:12px;"> <label style="color:#444;">Max Cap:</label> <input id="delayMaxInput" type="number" min="0.1" step="0.1" value="${settings.delayMax}" style="width:60px;padding:3px;border:1px solid #ccc;border-radius:3px;font-size:12px;"> </div> <div style="margin-top:12px; font-weight: 600; font-size:13px;">Stockfish Performance (Requires Reload)</div> <div style="margin-top:4px;display:flex;justify-content:space-between;align-items:center;font-size:12px;"> <label style="color:#444;">Threads:</label> <input id="threadsInput" type="number" min="1" max="16" step="1" value="${settings.stockfishThreads}" style="width:60px;padding:3px;border:1px solid #ccc;border-radius:3px;font-size:12px;"> <label style="color:#444;">Hash (MB):</label> <input id="hashInput" type="number" min="16" max="2048" step="32" value="${settings.stockfishHash}" style="width:60px;padding:3px;border:1px solid #ccc;border-radius:3px;font-size:12px;"> </div> <div style="margin-top:12px;display:flex;flex-direction:column;gap:4px;font-size:13px;"> <div style="cursor:pointer;"><input type="checkbox" id="autoRunCB" style="margin-right:5px;"> <label for="autoRunCB" style="cursor:pointer;">Auto Run Analysis</label></div> <div style="cursor:pointer;"><input type="checkbox" id="autoMoveCB" style="margin-right:5px;"> <label for="autoMoveCB" style="cursor:pointer;">Auto Move (⚠️ Caution)</label></div> </div> <div style="margin-top:15px;display:flex;gap:8px;"> <button id="reloadBtn" style="flex:1;padding:8px 6px;border-radius:6px;border:1px solid #ccc;cursor:pointer;background-color:#f8f9fa;color:#333;font-weight:600;">Reload Engine</button> <button id="analyseBtn" style="flex:1;padding:8px 6px;border-radius:6px;background-color:#0d6efd;color:#fff;border:none;cursor:pointer;font-weight:600;">Analyse Now (Manual Depth)</button> </div> <div style="margin-top:8px;font-size:11px;color:#666;text-align:center;">Top 3 moves and threats are highlighted briefly.</div> `; // Evaluation Bar HTML const evalBarHtml = ` <div id="evalBarWrapper" style="margin: 8px 8px 8px 8px; display: flex; flex-direction: column; align-items: center; max-height: 400px; flex-shrink: 0;"> <div style="font-size:12px; color:#666; font-weight:600; text-align:center;">Evaluation</div> <div id="evalBar" style=" height: 300px; width: 24px; border-radius: 4px; overflow: hidden; box-shadow: 0 2px 5px rgba(0,0,0,0.2); position: relative; margin-top: 4px; background-color: #000; "> <div id="evalBarWhiteAdvantage" style=" background-color: #fff; position: absolute; bottom: 0; width: 100%; height: 50%; transition: height 0.3s ease-out; "></div> <div id="evalPercent" style=" position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-weight: 700; color: #1a1a1a; font-size: 11px; text-shadow: 0 0 1px #fff; width: 100%; text-align: center; z-index: 10; transition: all 0.3s ease-out; ">+0.00</div> </div> <div style="font-size:10px; color:#666; margin-top:2px;">W% / B%</div> </div> `; try{ wrapper.appendChild(container); wrapper.innerHTML += evalBarHtml; // Attempt to insert the wrapper near the board, usually its parent or a sibling board.parentElement.appendChild(wrapper); }catch(e){ document.body.appendChild(wrapper); } // Attach Event Listeners document.getElementById('autoRunCB').checked = !!settings.autoRun; document.getElementById('autoMoveCB').checked = !!settings.autoMovePiece; document.getElementById('depthSlider').oninput = e => { settings.lastDepth = parseInt(e.target.value); document.getElementById('depthText').innerHTML = `Manual Depth: <strong>${settings.lastDepth}</strong>`; saveSettings(); }; document.getElementById('autoRunCB').onchange = e => { settings.autoRun = e.target.checked; saveSettings(); }; document.getElementById('autoMoveCB').onchange = e => { settings.autoMovePiece = e.target.checked; saveSettings(); }; // Time inputs document.getElementById('delayMinInput').onchange = e => { settings.delayMin = parseFloat(e.target.value) || 0.1; if(settings.delayMin > settings.delayMax) settings.delayMax = settings.delayMin; document.getElementById('delayMaxInput').value = settings.delayMax; saveSettings(); }; document.getElementById('delayMaxInput').onchange = e => { settings.delayMax = parseFloat(e.target.value) || 0.1; if(settings.delayMax < settings.delayMin) settings.delayMin = settings.delayMax; document.getElementById('delayMinInput').value = settings.delayMin; saveSettings(); }; // Performance inputs (trigger engine restart) document.getElementById('threadsInput').onchange = e => { settings.stockfishThreads = parseInt(e.target.value) || 1; if(settings.stockfishThreads < 1) settings.stockfishThreads = 1; e.target.value = settings.stockfishThreads; saveSettings(); safeRestartEngine(); }; document.getElementById('hashInput').onchange = e => { settings.stockfishHash = parseInt(e.target.value) || 128; if(settings.stockfishHash < 16) settings.stockfishHash = 16; e.target.value = settings.stockfishHash; saveSettings(); safeRestartEngine(); }; document.getElementById('reloadBtn').onclick = () => { safeRestartEngine(); }; document.getElementById('analyseBtn').onclick = () => { runChessEngine('depth', settings.lastDepth); }; updateMoveClassificationDisplay(); console.log('GUI initialized.'); return true; } // --------- Initialization & observers --------- async function waitUntil(conditionFn, interval=100){ return new Promise(resolve=>{ const t = setInterval(()=>{ try{ if(conditionFn()){ clearInterval(t); resolve(); } }catch(e){} }, interval); }); } (async function init(){ // Wait for the board element to exist await waitUntil(()=> findBoard()); // Wait for the board's internal game object to be initialized await waitUntil(()=> (board = findBoard()) && board.game); createStockfishWorker(); initGUI(); // Observe the body for board changes (e.g., game ending or new game starting) const mo = new MutationObserver(debounce(()=>{ board = findBoard(); if(board && !document.getElementById('botGUI_v3_wrapper')) initGUI(); }, 300)); mo.observe(document.body, {childList:true, subtree:true}); // Main analysis loop: Checks periodically if a move is needed setInterval(autoLoop, 150); let lastMoveCount = null; // Polling loop to detect moves made by the opponent or user setInterval(()=>{ try{ if(board && board.game){ // Use getMoveHistory or fall back to history property const moveHistory = board.game.getMoveHistory ? board.game.getMoveHistory() : (board.game.history || []); const moves = moveHistory.length; if(lastMoveCount === null) lastMoveCount = moves; if(moves !== lastMoveCount){ const lastMove = moveHistory[moves - 1]; // Attempt to get UCI format of the move lastPlayedMove = lastMove.uci || (lastMove.from + lastMove.to + (lastMove.promotion || '')); lastMoveCount = moves; // Reset display and indicate thinking updateEvaluationBar(0, null); lastMoveClassification = { type: 'Thinking...', cpl: 0, move: lastPlayedMove }; updateMoveClassificationDisplay(); if(settings.autoRun) { autoLoop(); // Start dynamic time analysis } else { // Run a very quick analysis if autoRun is disabled, just to update the score/bar runChessEngine('movetime', 500); } } } }catch(e){ console.error("Move detection interval error:", e); } }, 600); console.log('Improved Chess Bot v3.1 is ready and stable.'); })(); })();