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