Greasy Fork

Greasy Fork is available in English.

Chess.com Stockfish Bot

Chess.com Stockfish Bot with Auto-Match using Stockfish Online API

当前为 2025-02-28 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Chess.com Stockfish Bot
// @namespace    BottleOrg Scripts
// @version      1.6.2.12
// @description  Chess.com Stockfish Bot with Auto-Match using Stockfish Online API
// @author       Quoc Bao Nguyen & Deepseek V3 AI & Gemini 2.0 AI & Grok 3 (xAI)
// @license      Chess.com Bot/Cheat by BottleOrg(me Quoc Bao Nguyen)
// @match       https://www.chess.com/play/*
// @match       https://www.chess.com/game/*
// @match       https://www.chess.com/puzzles/*
// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant       GM_getValue
// @grant       GM_setValue
// @grant       GM_xmlhttpRequest
// @grant       GM_getResourceText
// @grant       GM_registerMenuCommand
// @require     http://greasyfork.icu/scripts/445697/code/index.js
// @require     https://code.jquery.com/jquery-3.6.0.min.js
// @run-at      document-start
// CHANGELOGS:
// 1.6.2.4: Initial fix for depth jumping from 11 to 100, integrated Stockfish API response parsing (bestmove b7b6), fixed AI not moving with L key by refining movePiece and rescan.
// 1.6.2.5: Improved board detection, fixed rescan FEN generation (still had issues), enhanced move execution logging, addressed "Chessboard not found" errors.
// 1.6.2.6: Fixed UI not loading by ensuring UI creation outside board check, corrected rescan piece parsing to avoid TypeError, improved board detection reliability.
// 1.6.2.7: Fixed UI glitch with better CSS (z-index: 10000, padding, borders), ensured moves execute with Auto Move enabled, refined event binding timing.
// 1.6.2.8: Fixed "always loading" by resetting isThinking in all API outcomes, updated rescan to use UCI-style square notation, capped depth at 15 in code but not fully in keys.
// 1.6.2.9: Corrected rescan to properly map Chess.com squares (11-88) to FEN (0-63), fixed inverted board issue, ensured moves align with getLegalMoves.
// 1.6.2.10: Capped MAX_DEPTH at 15, removed key bindings above 15 (H-M, ,), updated UI to reflect "Q-G (1-15)", fixed API depth limit errors.
// 1.6.2.11: Added "Auto-Match" checkbox, triggered new game on game end by clicking "New Game" tab and "Play" button, used game-over detection.
// 1.6.2.12: Modified "Auto-Match" to trigger only once on enable (not on game end), added hasAutoMatched flag, moved logic to checkbox change event.
// ==/UserScript==

const currentVersion = '1.6.2.12';

function main() {
    var myVars = document.myVars = { autoMove: false, autoRun: false, autoMatch: false, delay: 0.1, hasAutoMatched: false };
    var myFunctions = document.myFunctions = {};
    var currentStockfishVersion = "Stockfish API";
    var uiElementsLoaded = false;
    const stockfishAPI_URI = "https://stockfish.online/api/s/v2.php";

    var stop_b = 0, stop_w = 0, s_br = 0, s_br2 = 0, s_wr = 0, s_wr2 = 0;

    myFunctions.rescan = function() {
        console.log("Rescanning board...");
        var boardElement = document.querySelector('chess-board, wc-chess-board');
        if (!boardElement) {
            console.warn("No board element found. Using default FEN.");
            return "rnbqkbnr/pppppppp/5n2/8/8/5N2/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
        }
        var pieces = $(boardElement).find(".piece").map(function() { return this.className; }).get();
        if (!pieces.length) {
            console.warn("No pieces found. Using default FEN.");
            return "rnbqkbnr/pppppppp/5n2/8/8/5N2/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
        }
        var boardArray = Array(64).fill('');
        pieces.forEach(piece => {
            var classes = piece.split(' ');
            var squareClass = classes.find(c => c.startsWith('square-'));
            var pieceClass = classes.find(c => /^[wb][prnbqk]$/.test(c));
            if (squareClass && pieceClass) {
                var squareNum = squareClass.replace('square-', '');
                var file = parseInt(squareNum[0]) - 1;
                var rank = parseInt(squareNum[1]) - 1;
                var square = (7 - rank) * 8 + file;
                if (square >= 0 && square < 64) {
                    var pieceChar = {'wp': 'P', 'bp': 'p', 'wr': 'R', 'br': 'r', 'wn': 'N', 'bn': 'n',
                                    'wb': 'B', 'bb': 'b', 'wq': 'Q', 'bq': 'q', 'wk': 'K', 'bk': 'k'}[pieceClass];
                    boardArray[square] = pieceChar;
                }
            }
        });
        var fen = '';
        for (var i = 0; i < 64; i++) {
            if (i % 8 === 0 && i > 0) fen += '/';
            var piece = boardArray[i];
            if (!piece) {
                var emptyCount = 1;
                while (i + 1 < 64 && !boardArray[i + 1] && (i + 1) % 8 !== 0) {
                    emptyCount++;
                    i++;
                }
                fen += emptyCount;
            } else {
                fen += piece;
            }
        }
        var turn = $('.coordinates').children().first().text() === "1" ? 'b' : 'w';
        var castling = (stop_w ? '' : 'KQ') + (stop_b ? '' : 'kq') || '-';
        fen += ` ${turn} ${castling} - 0 1`;
        console.log("Generated FEN:", fen);
        return fen;
    };

    myFunctions.color = function(dat) {
        console.log("myFunctions.color CALLED with:", dat);
        const bestmoveUCI = dat.split(' ')[1];
        console.log("Extracted bestmove UCI:", bestmoveUCI);
        if (myVars.autoMove) myFunctions.movePiece(bestmoveUCI);
        else myFunctions.highlightMove(bestmoveUCI);
        isThinking = false;
        myFunctions.spinner();
    };

    myFunctions.highlightMove = function(bestmoveUCI) {
        var res1 = bestmoveUCI.substring(0, 2), res2 = bestmoveUCI.substring(2, 4);
        $(board).prepend(`<div class="highlight square-${res2}" style="background-color: rgb(235, 97, 80); opacity: 0.71;"></div>`)
            .children(':first').delay(1800).queue(function() { $(this).remove(); });
        $(board).prepend(`<div class="highlight square-${res1}" style="background-color: rgb(235, 97, 80); opacity: 0.71;"></div>`)
            .children(':first').delay(1800).queue(function() { $(this).remove(); });
        console.log("Highlighted:", bestmoveUCI);
    };

    myFunctions.movePiece = function(bestmoveUCI) {
        console.log("movePiece CALLED with:", bestmoveUCI);
        if (!board || !board.game) {
            console.error("Board or board.game not initialized!");
            return;
        }
        const fromSquare = bestmoveUCI.substring(0, 2);
        const toSquare = bestmoveUCI.substring(2, 4);
        const legalMoves = board.game.getLegalMoves();
        console.log("Legal moves:", legalMoves);
        let foundMove = legalMoves.find(move => move.from === fromSquare && move.to === toSquare);
        if (foundMove) {
            console.log("Executing move:", foundMove);
            board.game.move({ ...foundMove, promotion: 'q', animate: true, userGenerated: true });
            console.log("Move executed:", bestmoveUCI);
        } else {
            console.warn("No legal move found for:", bestmoveUCI);
        }
    };

    myFunctions.reloadChessEngine = function() { console.log("Reload not needed for API."); };
    myFunctions.loadChessEngine = function() {
        console.log("Using Stockfish API.");
        if (uiElementsLoaded) $('#engineVersionText')[0].innerHTML = "Engine: <strong>Stockfish API</strong>";
    };

    myFunctions.fetchBestMoveFromAPI = function(fen, depth) {
        const apiURL = `${stockfishAPI_URI}?fen=${encodeURIComponent(fen)}&depth=${depth}`;
        console.log(`Fetching from: ${apiURL}`);
        GM_xmlhttpRequest({
            method: "GET",
            url: apiURL,
            onload: function(response) {
                if (response.status === 200) {
                    try {
                        const jsonResponse = JSON.parse(response.responseText);
                        if (jsonResponse.success) {
                            console.log("API Response:", jsonResponse);
                            myFunctions.color(jsonResponse.bestmove);
                        } else {
                            console.error("API failed:", jsonResponse);
                            isThinking = false;
                            myFunctions.spinner();
                        }
                    } catch (e) {
                        console.error("API parse error:", e);
                        isThinking = false;
                        myFunctions.spinner();
                    }
                } else {
                    console.error("API error:", response.status);
                    isThinking = false;
                    myFunctions.spinner();
                }
            },
            onerror: function(error) {
                console.error("API request error:", error);
                isThinking = false;
                myFunctions.spinner();
            }
        });
    };

    myFunctions.startNewGame = function() {
        console.log("Starting new game...");
        const newGameTab = document.querySelector('[data-tab="newGame"]');
        if (newGameTab) {
            newGameTab.click();
            console.log("Clicked New Game tab.");
            setTimeout(() => {
                const playButton = document.querySelector('.cc-button-component.cc-button-primary.cc-button-xx-large.cc-button-full');
                if (playButton) {
                    playButton.click();
                    console.log("Clicked Play button.");
                    myVars.hasAutoMatched = true; // Mark as done
                } else {
                    console.error("Play button not found!");
                }
            }, 500);
        } else {
            console.error("New Game tab not found!");
        }
    };

    var lastValue = 11, MAX_DEPTH = 15, MIN_DEPTH = 1;

    myFunctions.runChessEngine = function(depth) {
        depth = Math.max(MIN_DEPTH, Math.min(MAX_DEPTH, depth));
        var fen = myFunctions.rescan();
        console.log(`Analyzing FEN: ${fen}, Depth: ${depth}`);
        isThinking = true;
        myFunctions.spinner();
        myFunctions.fetchBestMoveFromAPI(fen, depth);
        lastValue = depth;
        updateDepthDisplay();
    };

    function updateDepthDisplay() {
        if (uiElementsLoaded && $('#depthText')[0]) $('#depthText')[0].innerHTML = `Depth: <strong>${lastValue}</strong>`;
    }

    myFunctions.incrementDepth = function(delta) {
        lastValue = Math.max(MIN_DEPTH, Math.min(MAX_DEPTH, lastValue + delta));
        updateDepthDisplay();
    };

    myFunctions.autoRun = function() {
        if (board && board.game && board.game.getTurn() === board.game.getPlayingAs()) {
            myFunctions.runChessEngine(lastValue);
        }
    };

    document.onkeydown = function(e) {
        switch (e.keyCode) {
            case 81: myFunctions.runChessEngine(1); break;  // Q
            case 87: myFunctions.runChessEngine(2); break;  // W
            case 69: myFunctions.runChessEngine(3); break;  // E
            case 82: myFunctions.runChessEngine(4); break;  // R
            case 84: myFunctions.runChessEngine(5); break;  // T
            case 89: myFunctions.runChessEngine(6); break;  // Y
            case 85: myFunctions.runChessEngine(7); break;  // U
            case 73: myFunctions.runChessEngine(8); break;  // I
            case 79: myFunctions.runChessEngine(9); break;  // O
            case 80: myFunctions.runChessEngine(10); break; // P
            case 65: myFunctions.runChessEngine(11); break; // A
            case 83: myFunctions.runChessEngine(12); break; // S
            case 68: myFunctions.runChessEngine(13); break; // D
            case 70: myFunctions.runChessEngine(14); break; // F
            case 71: myFunctions.runChessEngine(15); break; // G
            case 187: myFunctions.incrementDepth(1); break; // +
            case 189: myFunctions.incrementDepth(-1); break;// -
        }
    };

    myFunctions.spinner = function() {
        if (uiElementsLoaded && $('#overlay')[0]) {
            $('#overlay')[0].style.display = isThinking ? 'block' : 'none';
        }
    };

    let dynamicStyles = null;
    function addAnimation(body) {
        if (!dynamicStyles) {
            dynamicStyles = document.createElement('style');
            document.head.appendChild(dynamicStyles);
        }
        dynamicStyles.sheet.insertRule(body, dynamicStyles.length);
    }

    var loaded = false;
    myFunctions.loadEx = function() {
        try {
            console.log("Attempting to load UI...");
            board = document.querySelector('chess-board, wc-chess-board');
            var div = document.createElement('div');
            div.innerHTML = `
                <div style="margin: 8px; padding: 10px; background: white; border: 1px solid #000; border-radius: 5px;">
                    <p id="depthText">Depth: <strong>${lastValue}</strong></p>
                    <button id="depthMinus">-</button>
                    <button id="depthPlus">+</button>
                    <p style="font-size: 12px;">Keys: Q-G (1-15), +/-</p>
                    <p id="engineVersionText">Engine: Stockfish API</p>
                    <label><input type="checkbox" id="autoRun"> Auto Run</label><br>
                    <label><input type="checkbox" id="autoMove"> Auto Move</label><br>
                    <label><input type="checkbox" id="autoMatch"> Auto-Match</label><br>
                    <label>Min Delay (s): <input type="number" id="timeDelayMin" min="0.1" value="0.1" step="0.1" style="width: 60px;"></label><br>
                    <label>Max Delay (s): <input type="number" id="timeDelayMax" min="0.1" value="1" step="0.1" style="width: 60px;"></label>
                </div>`;
            div.style.cssText = 'position: fixed; top: 10px; right: 10px; z-index: 10000;';
            document.body.appendChild(div);

            setTimeout(() => {
                $('#depthPlus').off('click').on('click', () => myFunctions.incrementDepth(1));
                $('#depthMinus').off('click').on('click', () => myFunctions.incrementDepth(-1));
                $('#autoMatch').on('change', () => {
                    myVars.autoMatch = $('#autoMatch')[0].checked;
                    if (myVars.autoMatch && !myVars.hasAutoMatched) {
                        myFunctions.startNewGame();
                    }
                });
                console.log("Event listeners bound.");
            }, 100);

            var spinCont = document.createElement('div');
            spinCont.id = 'overlay';
            spinCont.style.cssText = 'display: none; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);';
            div.appendChild(spinCont);
            var spinr = document.createElement('div');
            spinr.style.cssText = "height: 64px; width: 64px; animation: rotate 0.8s infinite linear; border: 5px solid firebrick; border-right-color: transparent; border-radius: 50%;";
            spinCont.appendChild(spinr);
            addAnimation(`@keyframes rotate { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }`);

            loaded = true;
            uiElementsLoaded = true;
            console.log("UI loaded successfully.");
            myFunctions.loadChessEngine();
        } catch (error) {
            console.error("loadEx error:", error);
        }
    };

    function other(delay) {
        var endTime = Date.now() + delay;
        var timer = setInterval(() => {
            if (Date.now() >= endTime) {
                myFunctions.autoRun();
                canGo = true;
                clearInterval(timer);
            }
        }, 10);
    }

    const waitForChessBoard = setInterval(() => {
        if (!loaded) {
            myFunctions.loadEx();
        } else {
            if (!board) board = document.querySelector('chess-board, wc-chess-board');
            myVars.autoRun = $('#autoRun')[0].checked;
            myVars.autoMove = $('#autoMove')[0].checked;
            myVars.autoMatch = $('#autoMatch')[0].checked;
            let minDel = parseFloat($('#timeDelayMin')[0].value) || 0.1;
            let maxDel = parseFloat($('#timeDelayMax')[0].value) || 1;
            myVars.delay = Math.random() * (maxDel - minDel) + minDel;
            myFunctions.spinner();
            myTurn = board && board.game && board.game.getTurn() === board.game.getPlayingAs();
            updateDepthDisplay();

            // Check for game end (for logging/debugging only, no auto-match here)
            const gameOver = document.querySelector('.game-over-message-component') || document.querySelector('.game-result');
            if (gameOver) {
                console.log("Game ended detected.");
            }

            if (myVars.autoRun && canGo && !isThinking && myTurn) {
                canGo = false;
                other(myVars.delay * 1000);
            }
        }
    }, 500);

    setTimeout(() => {
        if (!loaded) myFunctions.loadEx();
    }, 2000);
}

var isThinking = false, canGo = true, myTurn = false, board;

window.addEventListener("load", () => main());