// ==UserScript==
// @name Chess.com Bot — With Evaluation Bar (Updated Stockfish) newer
// @namespace thehackerclient
// @version 4.6 // Version incremented
// @description Improved userscript: top 3 moves & threats, persistent settings, debounce/throttle, safer engine lifecycle, promotion handling, better board detection, min/max delay, and real-time evaluation bar. Now using updated Stockfish from a new resource.
// @match https://www.chess.com/*
// @auther thehackerclient
// @license MIT
// @grant GM_getResourceText
// @require https://code.jquery.com/jquery-3.6.0.min.js
// @resource stockfish.js https://cdn.jsdelivr.net/gh/niklasf/stockfish.js/stockfish.js
// @run-at document-start
// ==/UserScript==
(function () {
'use strict';
// --------- Config & state ---------
const STORAGE_KEY = 'chess_bot_settings_v2';
const DEFAULTS = {
autoRun: true,
autoMovePiece: false,
delayMin: 0.5,
delayMax: 2,
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());
// 🔹 Force autoMovePiece always false at startup
settings.autoMovePiece = false;
let board = null; // DOM element for chess-board / wc-chess-board
let engine = { worker: null }; // stockfish worker wrapper
let stockfishObjectURL = null;
// Added rawCp and rawMate to the candidate move structure for display purposes
let candidateMoves = []; // [{move:'e2e4', score:120, depth:... , pv:[], rawCp: 120, rawMate: null }]
let isThinking = false;
let canGo = true;
let lastFen = '';
// === NEW STATE FOR DYNAMIC CALCULATION STATUS ===
let currentCalcStatus = {
depth: 0,
seldepth: 0,
nodes: 0,
nps: 0,
time: 0,
isSearching: false
};
// ===============================================
// debounce/throttle helpers
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);} }; }
// safer query for board element — supports both modern and older chess.com tags
function findBoard(){ return $('chess-board')[0] || $('wc-chess-board')[0] || document.querySelector('[data-cy="board"]') || null; }
// --------- Persistence (UNCHANGED) ---------
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){} }
// --------- Stockfish lifecycle (UNCHANGED) ---------
function createStockfishWorker(){
try{
// ⭐️ Updated to use the new resource URL via GM_getResourceText
if(stockfishObjectURL === null){
const text = GM_getResourceText('stockfish.js');
stockfishObjectURL = URL.createObjectURL(new Blob([text], {type:'application/javascript'}));
}
if(engine.worker) engine.worker.terminate();
engine.worker = new Worker(stockfishObjectURL);
engine.worker.onmessage = e => handleEngineMessage(e.data);
engine.worker.postMessage('ucinewgame');
console.log('Stockfish worker created with new resource');
}catch(err){ console.error('Failed to create Stockfish worker', err); }
}
function safeRestartEngine(){ isThinking = false; try{ if(engine.worker) engine.worker.terminate(); }catch(e){} createStockfishWorker(); }
// --------- Evaluation Bar Logic (UNCHANGED) ---------
/**
* Updates the visual evaluation bar based on the best move's score.
* @param {number} scoreCp - The centipawn score (positive for White, negative for Black).
* @param {number | null} mateScore - The number of moves to mate (positive for White, negative for Black), or null if not mate.
*/
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) {
// Mate score: mateScore is the number of moves to mate. Positive for White, Negative for Black.
displayScore = mateScore > 0 ? `M+${mateScore}` : `M${mateScore}`;
// Map mate score to near 100% or near 0%
barHeightPercent = mateScore > 0 ? 98 : 2;
} else {
// Centipawn score: scoreCp is the advantage for White.
// For bar visualization, clamp CP value to a visible range (e.g., +/- 800 cp)
const clampedCp = Math.max(-800, Math.min(800, scoreCp));
// Simple linear scaling: 16 cp = 1% change (0% at -800, 50% at 0, 100% at +800)
barHeightPercent = 50 + (clampedCp / 16);
barHeightPercent = Math.max(0, Math.min(100, barHeightPercent));
displayScore = (scoreCp / 100).toFixed(2);
if (scoreCp >= 0) displayScore = `+${displayScore}`;
}
// Update the bar height (White's territory grows from the bottom up, pushing Black's down)
barWhiteEl.style.height = `${barHeightPercent}%`;
// Update the displayed score
scoreTextEl.innerText = displayScore;
// Adjust text color for contrast (White text on black background, Black text on white background)
const whiteAdvantage = barHeightPercent;
const blackAdvantage = 100 - barHeightPercent;
// If the advantage is overwhelming for one side, move the text label to the other side for contrast
if (whiteAdvantage > 80) { // Mostly white: text to black side
scoreTextEl.style.color = '#fff';
scoreTextEl.style.top = '10%';
} else if (blackAdvantage > 80) { // Mostly black: text to white side
scoreTextEl.style.color = '#000';
scoreTextEl.style.top = '90%';
} else { // Neutral: centered text
scoreTextEl.style.color = '#1a1a1a';
scoreTextEl.style.top = '50%';
}
}
// --------- Engine message parsing (UPDATED to track status) ---------
// This function is for calculating a score value primarily for sorting candidate moves.
function parseScore(match){
if(!match) return 0;
const type = match[1];
const val = parseInt(match[2]);
if(type === 'cp') return val;
// Mate score is converted to a large positive/negative value for sorting
return val > 0 ? 100000 - val : -100000 - val;
}
function getInfoToken(msg, key, isInt=false) {
const match = msg.match(new RegExp(`${key} (\\S+)`));
if (match) return isInt ? parseInt(match[1]) : match[1];
return null;
}
function handleEngineMessage(msg){
if(typeof msg !== 'string') return;
if(msg.startsWith('info')){
// === UPDATE CALCULATION STATUS IN REAL-TIME ===
currentCalcStatus.isSearching = true;
const depth = getInfoToken(msg, 'depth', true);
const seldepth = getInfoToken(msg, 'seldepth', true);
const nodes = getInfoToken(msg, 'nodes', true);
const nps = getInfoToken(msg, 'nps', true);
const time = getInfoToken(msg, 'time', true);
if (depth !== null) currentCalcStatus.depth = depth;
if (seldepth !== null) currentCalcStatus.seldepth = seldepth;
if (nodes !== null) currentCalcStatus.nodes = nodes;
if (nps !== null) currentCalcStatus.nps = nps;
if (time !== null) currentCalcStatus.time = time;
throttledUpdateStatus();
// ===============================================
if(msg.includes('pv')){
const pvTokens = msg.split(' pv ')[1].trim().split(/\s+/);
if(pvTokens && pvTokens.length){
const move = pvTokens[0];
const scoreMatch = msg.match(/score (cp|mate) (-?\d+)/);
const score = parseScore(scoreMatch); // Score for sorting
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 exists = candidateMoves.find(c=>c.move===move);
// Store both the sortable score and the raw scores for display
if(!exists) candidateMoves.push({move, score, depth, pv: pvTokens, rawCp, rawMate});
else if(depth>exists.depth) {
exists.score=score;
exists.depth=depth;
exists.pv=pvTokens;
exists.rawCp=rawCp;
exists.rawMate=rawMate;
}
}
}
}
if(msg.startsWith('bestmove')){
currentCalcStatus.isSearching = false; // Stop searching status
throttledUpdateStatus(); // Final status update
candidateMoves.sort((a,b) => b.score - a.score);
candidateMoves = candidateMoves.slice(0,3);
showTopMoves();
// --- Update Evaluation Bar with the best move's score ---
if(candidateMoves.length > 0) {
const bestMove = candidateMoves[0];
// Use the raw mate score if available, otherwise use the raw CP score.
const finalCp = bestMove.rawCp !== null ? bestMove.rawCp : bestMove.score;
const finalMate = bestMove.rawMate;
updateEvaluationBar(finalCp, finalMate);
}
const move = msg.split(' ')[1];
if(settings.autoMovePiece && move) performMove(move);
isThinking = false;
}
}
// === NEW FUNCTION: Update the calculation status GUI elements ===
function updateCalculationStatus() {
const statusEl = document.getElementById('calcStatus');
const searchStatusEl = document.getElementById('searchStatusText');
if (!statusEl || !searchStatusEl) return;
if (currentCalcStatus.isSearching) {
searchStatusEl.innerText = 'Calculating...';
searchStatusEl.style.color = '#d9534f'; // Red/Orange for calculating
const nodesK = (currentCalcStatus.nodes / 1000).toFixed(0);
const npsK = (currentCalcStatus.nps / 1000).toFixed(0);
const timeS = (currentCalcStatus.time / 1000).toFixed(1);
statusEl.innerHTML = `
<div style="display:flex; justify-content:space-between;"><span>Depth:</span> <strong>${currentCalcStatus.depth} / ${currentCalcStatus.seldepth}</strong></div>
<div style="display:flex; justify-content:space-between;"><span>Nodes:</span> <strong>${nodesK}k</strong></div>
<div style="display:flex; justify-content:space-between;"><span>NPS:</span> <strong>${npsK}k</strong></div>
<div style="display:flex; justify-content:space-between;"><span>Time:</span> <strong>${timeS}s</strong></div>
`;
} else {
searchStatusEl.innerText = 'Idle / Ready';
searchStatusEl.style.color = '#5cb85c'; // Green for ready
statusEl.innerHTML = `
<div style="display:flex; justify-content:space-between;"><span>Depth:</span> <strong>-</strong></div>
<div style="display:flex; justify-content:space-between;"><span>Nodes:</span> <strong>-</strong></div>
<div style="display:flex; justify-content:space-between;"><span>NPS:</span> <strong>-</strong></div>
<div style="display:flex; justify-content:space-between;"><span>Time:</span> <strong>-</strong></div>
`;
}
}
// Throttle the status update to avoid excessive DOM manipulation
const throttledUpdateStatus = throttle(updateCalculationStatus, 150);
// ===============================================================
// --------- Utilities (omitted for brevity, unchanged) ---------
function mapSquareForBoard(sq){
if(!board || !sq || sq.length<2) return sq;
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}"]`); }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.position='absolute'; overlay.style.top=0; overlay.style.left=0; overlay.style.width='100%'; overlay.style.height='100%'; overlay.style.pointerEvents='none'; overlay.style.zIndex=60; 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){} }
// --------- Highlighting & UI (omitted for brevity, largely unchanged) ---------
function showTopMoves(){
if(!board || !board.game) return;
detachHighlights('.botMoveHighlight');
detachHighlights('.botThreatHighlight');
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);
}
});
showThreats();
}
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.position='absolute'; note.style.right='6px'; note.style.top=(6 + index*28)+'px'; note.style.padding='6px 8px'; note.style.borderRadius='6px'; note.style.background='rgba(0,0,0,0.6)'; note.style.color='#fff'; note.style.zIndex=120; note.style.fontSize='12px'; 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(' ')}`;
setTimeout(()=>{ if(note && note.parentElement) note.parentElement.removeChild(note); }, settings.highlightMs + 5000);
}catch(e){}
}
function showThreats(){
if(!board || !board.game) return;
try{
const legalMoves = board.game.getLegalMoves();
const opponent = board.game.getTurn() === 'w' ? 'b' : 'w';
legalMoves.forEach(m=>{ 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); }
}
// --- FIX APPLIED HERE (UNCHANGED) ---
function performMove(moveUCI){
if(!board || !board.game) return;
try{
const from = moveUCI.slice(0,2);
const to = moveUCI.slice(2,4);
// Stockfish UCI for promotion includes the piece type, e.g., 'e7e8q'
const promotionChar = moveUCI.length > 4 ? moveUCI[4] : null;
// Map the promotion character to the full piece type (e.g., 'q' -> 'q')
let promotionPiece = null;
if (promotionChar) {
// Ensure it's one of the valid promotion pieces
if (['q', 'r', 'b', 'n'].includes(promotionChar.toLowerCase())) {
promotionPiece = promotionChar.toLowerCase();
}
}
const legal = board.game.getLegalMoves();
let moveFound = false;
for(const m of legal){
// Check for matching move, ignoring promotion initially
if(m.from === from && m.to === to){
// Check if it's a promotion move
if(m.promotion){
// The engine move UCI included a promotion, and the legal move is a promotion
if(promotionPiece){
m.promotion = promotionPiece; // Set the promotion piece
console.log(`[Bot] Performing move: ${moveUCI} with promotion to ${promotionPiece}`);
} else {
// Default to Queen if the engine's UCI format was incomplete but it's a promotion square
m.promotion = 'q';
console.warn(`[Bot] Performing move: ${moveUCI}. Legal move required promotion but none was provided. Defaulting to 'q'.`);
}
}
// Perform the move.
board.game.move(Object.assign({}, m, {animate:false, userGenerated:true}));
moveFound = true;
break;
}
}
if (!moveFound) {
console.error(`[Bot] performMove failed: No legal move found for UCI ${moveUCI}.`);
}
}catch(e){
console.error('[Bot] performMove error:', e);
}
}
// --------- Engine runner & controls (SLIGHTLY UPDATED to reset status) ---------
function runChessEngine(depth){
if(!board || !engine.worker || !board.game) return;
try{
const fen = board.game.getFEN();
if(isThinking && fen === lastFen && depth===settings.lastDepth) return;
lastFen = fen;
candidateMoves = [];
// Reset calculation status when a new search starts
currentCalcStatus = { depth: 0, seldepth: 0, nodes: 0, nps: 0, time: 0, isSearching: true };
throttledUpdateStatus();
engine.worker.postMessage('position fen ' + fen);
isThinking = true;
engine.worker.postMessage('go depth ' + depth);
}catch(e){ console.error('runChessEngine error', e); }
}
const debouncedRun = debounce((d)=> runChessEngine(d), 300);
const autoLoop = throttle(()=>{
if(!board || !board.game) return;
if(settings.autoRun && canGo && !isThinking && board.game.getTurn() === board.game.getPlayingAs()){
canGo = false;
const delaySeconds = Math.random() * (settings.delayMax - settings.delayMin) + settings.delayMin;
setTimeout(()=>{ debouncedRun(settings.lastDepth); canGo = true; }, Math.max(200, delaySeconds*1000));
}
}, 200);
// --------- GUI (updated for Eval Bar AND Calculation Status) ---------
function initGUI(){
board = findBoard();
if(!board) return false;
if(document.getElementById('botGUI_v2_wrapper')) return true; // Check for the new wrapper
// 1. Create the main wrapper for the GUI and Eval Bar
const wrapper = document.createElement('div');
wrapper.id = 'botGUI_v2_wrapper';
wrapper.style.cssText = 'display:flex; align-items:flex-start;';
// 2. Create the existing control panel container
const container = document.createElement('div');
container.id = 'botGUI_v2';
// Adjusted margin to allow space for the bar on the right
container.style = '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)';
container.innerHTML = `
<div style="font-weight:600;margin-bottom:6px;">Chess Bot — Improved</div>
<div id="statusSection" style="margin-bottom:10px;padding:6px;border:1px solid #ccc;border-radius:4px;font-size:13px;">
<div style="font-weight:600;margin-bottom:4px;">Calculation Status: <span id="searchStatusText" style="font-weight:700; color:#5cb85c;">Idle / Ready</span></div>
<div id="calcStatus">
<div style="display:flex; justify-content:space-between;"><span>Depth:</span> <strong>-</strong></div>
<div style="display:flex; justify-content:space-between;"><span>Nodes:</span> <strong>-</strong></div>
<div style="display:flex; justify-content:space-between;"><span>NPS:</span> <strong>-</strong></div>
<div style="display:flex; justify-content:space-between;"><span>Time:</span> <strong>-</strong></div>
</div>
</div>
<div id="depthText">Depth: <strong>${settings.lastDepth}</strong></div>
<input type="range" id="depthSlider" min="1" max="30" value="${settings.lastDepth}" step="1" style="width:100%">
<div style="margin-top:6px;"><input type="checkbox" id="autoRunCB"> <label for="autoRunCB">Auto Run</label></div>
<div><input type="checkbox" id="autoMoveCB"> <label for="autoMoveCB">Auto Move</label></div>
<div style="margin-top:6px;">Delay (s):
<input id="delayMinInput" type="number" min="0" step="0.1" value="${settings.delayMin}" style="width:60px"> -
<input id="delayMaxInput" type="number" min="0" step="0.1" value="${settings.delayMax}" style="width:60px">
</div>
<div style="margin-top:8px;display:flex;gap:6px;">
<button id="reloadBtn" style="flex:1;padding:6px;border-radius:6px">Reload Engine</button>
<button id="analyseBtn" style="flex:1;padding:6px;border-radius:6px">Analyse Now</button>
</div>
<div style="margin-top:8px;font-size:12px;color:#666">Top 3 moves are highlighted briefly; PVs show on the board edge.</div>
`;
// 3. Evaluation Bar HTML (UNCHANGED)
const evalBarHtml = `
<div id="evalBarWrapper" style="margin: 8px 8px 8px 8px; display: flex; flex-direction: column; align-items: center; max-height: 400px;">
<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; /* Black color base (below) */
">
<div id="evalBarWhiteAdvantage" style="
background-color: #fff;
position: absolute;
bottom: 0;
width: 100%;
height: 50%; /* Default 50% for equality */
transition: height 0.3s ease-out;
"></div>
<div id="evalPercent" style="
position: absolute;
top: 50%; /* Initial center position */
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; /* Transition position too */
">+0.00</div>
</div>
<div style="font-size:10px; color:#666; margin-top:2px;">W% / B%</div>
</div>
`;
// 4. Append to DOM
try{
wrapper.appendChild(container);
wrapper.innerHTML += evalBarHtml; // Append eval bar HTML next to the container
// Try to append to the board's main parent element
board.parentElement.parentElement.appendChild(wrapper);
}catch(e){
// Fallback: append both to body
document.body.appendChild(wrapper);
}
// 5. Setup controls (UNCHANGED)
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 = `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(); };
document.getElementById('delayMinInput').onchange = e => {
settings.delayMin = parseFloat(e.target.value) || 0;
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;
if(settings.delayMax < settings.delayMin) settings.delayMin = settings.delayMax;
document.getElementById('delayMinInput').value = settings.delayMin;
saveSettings();
};
document.getElementById('reloadBtn').onclick = () => { safeRestartEngine(); };
document.getElementById('analyseBtn').onclick = () => { debouncedRun(settings.lastDepth); };
return true;
}
// --------- Initialization & observers (UNCHANGED) ---------
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(){
await waitUntil(()=> findBoard());
board = findBoard();
await waitUntil(()=> (board = findBoard()) && board.game);
createStockfishWorker();
initGUI();
const mo = new MutationObserver(()=>{ board = findBoard(); });
mo.observe(document.body, {childList:true, subtree:true});
setInterval(autoLoop, 150);
let lastMoveCount = null;
setInterval(()=>{
try{
if(board && board.game){
const moves = board.game.getMoveHistory ? board.game.getMoveHistory().length : (board.game.history? board.game.history.length:0);
if(lastMoveCount === null) lastMoveCount = moves;
if(moves !== lastMoveCount){
lastMoveCount = moves;
// Reset the bar to neutral when a new move is made and analysis hasn't started yet
updateEvaluationBar(0, null);
if(settings.autoRun) debouncedRun(settings.lastDepth);
}
}
}catch(e){}
}, 600);
console.log('Improved Chess Bot ready');
})();
})();