Auto move bot using Stockfish on Chess.com with start/stop UI
Ekde
// ==UserScript==
// @name Chess.com Stockfish AutoMove Bot with UI
// @namespace http://tampermonkey.net/
// @version 0.2
// @description Auto move bot using Stockfish on Chess.com with start/stop UI
// @author Nimmmuary123456
// @match https://www.chess.com/*
// @grant none
// ==/UserScript==
(function() {
'use strict';
// Stockfish wasm build URL (you can self-host for reliability)
const STOCKFISH_URL = 'https://cdn.jsdelivr.net/gh/niklasf/stockfish.js/stockfish.wasm.js';
let stockfish;
let isRunning = false;
let analysisRunning = false;
// Create UI panel
const panel = document.createElement('div');
panel.style.position = 'fixed';
panel.style.top = '10px';
panel.style.right = '10px';
panel.style.backgroundColor = '#222';
panel.style.color = 'white';
panel.style.padding = '10px';
panel.style.borderRadius = '8px';
panel.style.zIndex = 99999;
panel.style.fontFamily = 'Arial, sans-serif';
panel.style.width = '200px';
panel.style.boxShadow = '0 0 10px black';
const title = document.createElement('div');
title.textContent = 'Stockfish AutoMove Bot';
title.style.fontWeight = 'bold';
title.style.marginBottom = '8px';
title.style.textAlign = 'center';
panel.appendChild(title);
const status = document.createElement('div');
status.textContent = 'Status: Stopped';
status.style.marginBottom = '8px';
panel.appendChild(status);
const btnStartStop = document.createElement('button');
btnStartStop.textContent = 'Start';
btnStartStop.style.width = '100%';
btnStartStop.style.padding = '8px';
btnStartStop.style.fontSize = '16px';
btnStartStop.style.cursor = 'pointer';
panel.appendChild(btnStartStop);
document.body.appendChild(panel);
// Load Stockfish engine as Web Worker
function loadStockfish() {
return new Promise((resolve, reject) => {
stockfish = new Worker(STOCKFISH_URL);
stockfish.onmessage = (event) => {
const line = event.data;
if (line === 'uciok') {
resolve();
}
};
stockfish.onerror = (e) => {
reject(e);
};
stockfish.postMessage('uci');
});
}
// Send command to stockfish
function sendCommand(cmd) {
//console.log('Sending to Stockfish:', cmd);
stockfish.postMessage(cmd);
}
// Get FEN from Chess.com board
function getFEN() {
// Chess.com stores the FEN string in window.__board?.fen() or window.Chessboard
try {
if (window.__board && typeof window.__board.fen === 'function') {
return window.__board.fen();
}
// Alternative: read from Chess.com's internal chess object
if (window.Chess && window.Chess.fen) {
return window.Chess.fen();
}
// Or from Chess.com analysis panel - DOM fallback (last move in PGN)
// Try to read FEN from page URL or from chessboard data attribute
const fenInput = document.querySelector('input#fen-input');
if (fenInput) return fenInput.value;
} catch (e) {}
return null;
}
// Find squares on board and simulate clicks to play move
function playMove(move) {
if (!move || move.length < 4) return;
const from = move.slice(0, 2);
const to = move.slice(2, 4);
// Chess.com board squares have classes like "square-63" but it's complicated;
// Let's try to use data-coordinates attributes (Chess.com uses algebraic notation)
// The chessboard div uses data-square="e2", etc.
const boardSquares = document.querySelectorAll('[data-square]');
let fromEl, toEl;
boardSquares.forEach(sq => {
if (sq.getAttribute('data-square') === from) fromEl = sq;
if (sq.getAttribute('data-square') === to) toEl = sq;
});
if (fromEl && toEl) {
// Simulate click sequence
fromEl.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }));
fromEl.dispatchEvent(new MouseEvent('mouseup', { bubbles: true }));
toEl.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }));
toEl.dispatchEvent(new MouseEvent('mouseup', { bubbles: true }));
console.log(`Moved from ${from} to ${to}`);
status.textContent = `Moved: ${move}`;
} else {
console.log('Could not find squares to move:', from, to);
status.textContent = 'Error: Could not find squares';
}
}
// Analyze position and get best move from Stockfish
function analyzePosition(fen) {
return new Promise((resolve) => {
let bestMove = null;
function onMessage(event) {
const line = event.data;
//console.log('Stockfish:', line);
if (line.startsWith('bestmove')) {
bestMove = line.split(' ')[1];
stockfish.removeEventListener('message', onMessage);
resolve(bestMove);
}
}
stockfish.addEventListener('message', onMessage);
sendCommand('ucinewgame');
sendCommand(`position fen ${fen}`);
sendCommand('go depth 15');
});
}
// Main bot loop
async function botLoop() {
if (!isRunning) return;
if (analysisRunning) return; // prevent overlapping calls
analysisRunning = true;
const fen = getFEN();
if (!fen) {
console.log('FEN not found, retrying...');
status.textContent = 'Waiting for board...';
analysisRunning = false;
setTimeout(botLoop, 3000);
return;
}
console.log('Current FEN:', fen);
status.textContent = 'Analyzing...';
const bestMove = await analyzePosition(fen);
if (bestMove && isRunning) {
playMove(bestMove);
}
analysisRunning = false;
if (isRunning) {
setTimeout(botLoop, 4000); // wait 4 seconds before next analysis
}
}
// Start the bot
async function startBot() {
if (isRunning) return;
status.textContent = 'Loading Stockfish...';
await loadStockfish();
isRunning = true;
status.textContent = 'Running...';
btnStartStop.textContent = 'Stop';
botLoop();
}
// Stop the bot
function stopBot() {
if (!isRunning) return;
isRunning = false;
status.textContent = 'Stopped';
btnStartStop.textContent = 'Start';
if (stockfish) {
stockfish.terminate();
stockfish = null;
}
}
btnStartStop.addEventListener('click', () => {
if (isRunning) {
stopBot();
} else {
startBot();
}
});
})();