您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Auto-play best Stockfish moves with UI controls
// ==UserScript== // @name Chess.com Stockfish Auto Move // @namespace http://tampermonkey.net/ // @version 2.2 // @description Auto-play best Stockfish moves with UI controls // @author Omkar04 // @match https://www.chess.com/* // @grant GM_getResourceText // @grant GM_addStyle // @resource STOCKFISH https://cdn.jsdelivr.net/gh/niklasf/stockfish.js/stockfish.js // @require https://cdnjs.cloudflare.com/ajax/libs/chess.js/0.10.3/chess.min.js // @icon https://www.google.com/s2/favicons?sz=64&domain=chess.com // @run-at document-start // @license GPL-3.0 // ==/UserScript== (function () { "use strict"; // ===================== // Config // ===================== const DEFAULT_DELAY = 100; const MIN_DELAY = 10; const MAX_DELAY = 10000; const DEFAULT_DEPTH = 12; // ===================== // State // ===================== let enabled = false; let autoMove = false; let autoMoveDelay = DEFAULT_DELAY; let depth = DEFAULT_DEPTH; let markMove = true; let menuShown = true; let thinking = false; let lastFEN; // ===================== // Utils // ===================== const qs = (sel) => document.querySelector(sel); function setText(id, text) { const el = qs(id); if (el) el.textContent = text; } function getBoard() { return qs(".board")?.game || null; } // ===================== // Stockfish setup // ===================== const sfCode = GM_getResourceText("STOCKFISH"); const sf = new Worker(URL.createObjectURL(new Blob([sfCode], { type: "application/javascript" }))); console.log("✅ Stockfish loaded"); sf.onmessage = (e) => { const line = e.data; if (!line.startsWith("bestmove")) return; thinking = false; const [ , bestMove ] = line.split(" "); if (!bestMove || bestMove.length < 4) return; const from = bestMove.slice(0, 2); const to = bestMove.slice(2, 4); const promo = bestMove[4] || "q"; console.log("🔥 Best move:", from, "→", to, "promo:", promo); setText("#sf-bestmove", getSan(bestMove)); if (autoMove) { movePiece(from, to, promo); } else if (markMove) { drawArrow({ f: from, t: to }); } }; // ===================== // UI Panel // ===================== const panel = document.createElement("div"); panel.id = "sf-panel"; panel.innerHTML = ` <h3>♟️ Stockfish</h3> Best Move: <i id="sf-bestmove"></i><br> <button id="sf-toggle" class="off">▶ Enable Cheat</button><br> <button id="sf-clearmarkings">Clear Arrow Marking</button><br> Depth: <select id="sf-depth"> ${[4, 6, 8, 10, 12, 15, 18, 20, 22, 24].map(d => `<option value="${d}" ${d === DEFAULT_DEPTH ? "selected" : ""}>${d}</option>` ).join("")} </select><br> <label for="sf-amdelay">Auto Move Delay (ms):</label><br> <div style="display:flex; align-items:center; gap:6px;"> <input type="number" id="sf-amdelay" value="${DEFAULT_DELAY}"> <button id="sf-dconf"> <svg viewBox="0 0 24 24"> <polyline points="20 6 9 17 4 12"></polyline> </svg> </button> </div> <label><input type="checkbox" id="sf-automove"> Auto Move</label><br> <label><input type="checkbox" id="sf-markmove" checked> Mark Move</label> <p>Press F1 to show/hide menu</p> `; document.documentElement.appendChild(panel); // ===================== // UI Styles // ===================== GM_addStyle(` #sf-panel { position: fixed; top: 100px; right: 20px; width: 200px; background: rgba(0,0,0,0.85); color: white; padding: 10px; border-radius: 8px; z-index: 999999; font-size: 14px; font-family: Arial, sans-serif; } #sf-panel button, #sf-panel select, #sf-panel input[type="checkbox"] { margin: 4px 0; padding: 4px; } #sf-toggle.off { background-color: #43d15d; } #sf-toggle.on { background-color: #d14343; } #sf-toggle { border-radius: 12px; border: 2px solid #fff; } button:hover { filter: brightness(0.9); box-shadow: 0 2px 6px rgba(0,0,0,0.2); } #sf-amdelay { width: 150px; height: 30px; border: 2px solid #fff; border-radius: 10px; padding-left: 6px; } #sf-dconf { height: 30px; width: 34px; border-radius: 50%; background-color: #43d15d; border: none; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.2s ease-in-out; } #sf-dconf:hover { transform: scale(1.1); background-color: #36b94f; } #sf-dconf svg { width: 16px; height: 16px; stroke: white; stroke-width: 3; fill: none; } p { font-size: 10px; color: grey; } `); // ===================== // UI Events // ===================== qs("#sf-toggle").onclick = () => { enabled = !enabled; qs("#sf-toggle").className = enabled ? "on" : "off"; qs("#sf-toggle").textContent = enabled ? "⏸ Disable Cheat" : "▶ Enable Cheat"; }; qs("#sf-clearmarkings").onclick = () => drawArrow({ remove: true }); qs("#sf-depth").onchange = (e) => depth = parseInt(e.target.value); qs("#sf-dconf").onclick = () => { let val = parseInt(qs("#sf-amdelay").value); if (isNaN(val)) val = DEFAULT_DELAY; if (val < MIN_DELAY) val = MIN_DELAY; if (val > MAX_DELAY) val = MAX_DELAY; autoMoveDelay = val; qs("#sf-amdelay").value = val; }; qs("#sf-automove").onchange = (e) => autoMove = e.target.checked; qs("#sf-markmove").onchange = (e) => markMove = e.target.checked; document.addEventListener("keydown", (e) => { if (e.key === "F1") { e.preventDefault(); menuShown = !menuShown; qs("#sf-panel").style.display = menuShown ? "block" : "none"; } }); // ===================== // Helpers // ===================== function getSan(uciMove) { const game = getBoard(); if (!game) return ""; const chess = new Chess(game.getFEN()); const move = { from: uciMove.slice(0, 2), to: uciMove.slice(2, 4) }; if (uciMove.length === 5) move.promotion = uciMove[4]; return chess.move(move)?.san || uciMove; } function drawArrow({ f, t, color = "blue", o = 1, remove = false } = {}) { const game = getBoard(); if (!game) return; if (remove) return game.markings.removeAll(); game.markings.addOne({ data: { keyPressed: "none", from: f, to: t, opacity: o }, type: "arrow" }); setTimeout(() => { const arrow = qs(`#arrow-${f}${t}`); if (arrow) arrow.style.fill = color; }, 10); } function movePiece(from, to, promotion = "q") { const game = getBoard(); if (!game) return false; const legal = game.getLegalMoves(); const move = legal.find(m => m.from === from && m.to === to); if (!move) return false; setTimeout(() => { game.move({ ...move, promotion, animate: true, userGenerated: true }); }, autoMoveDelay); console.log("✅ Played:", from, "→", to); return true; } // ================================= // Attempt to remove all the ads // ================================= function removeAds() { document.querySelectorAll("[id^='google_ads_iframe'], [id*='__container__'], .ad-slot, .ad-container, .ad-banner, .ad-block") .forEach(el => el.remove()); } function startAdObserver() { removeAds(); const observer = new MutationObserver(mutations => { for (const mutation of mutations) { for (const node of mutation.addedNodes) { if (!(node instanceof HTMLElement)) continue; if ( node.id?.startsWith("google_ads_iframe") || node.id?.includes("__container__") || node.classList?.contains("ad-slot") || node.classList?.contains("ad-container") || node.classList?.contains("ad-banner") || node.classList?.contains("ad-block") ) { node.remove(); } removeAds(); } } }); observer.observe(document.body, { childList: true, subtree: true }); } if (document.body) { startAdObserver(); } else { window.addEventListener("DOMContentLoaded", startAdObserver); } // ===================== // Main loop // ===================== setInterval(() => { if (!enabled) return; const game = getBoard(); if (!game || game.isGameOver()) return; const fen = game.getFEN(); if (fen !== lastFEN) { lastFEN = fen; drawArrow({ remove: true }); } if (game.getTurn() === game.getPlayingAs()) { if (thinking) return; console.log("⏳ Thinking... depth", depth, "FEN:", fen); setText("#sf-bestmove", "⏳ Thinking..."); sf.postMessage("position fen " + fen); sf.postMessage("go depth " + depth); thinking = true; } else { drawArrow({ remove: true }); } }, 1000); })();