Chess.com Stockfish AutoMove Bot with UI

Auto move bot using Stockfish on Chess.com with start/stop UI

当前为 2025-07-19 提交的版本,查看 最新版本

// ==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();
        }
    });

})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址