Greasy Fork is available in English.
Chess.com cheat engine
// ==UserScript==
// @name Chess.com Cheat Engine
// @license MIT
// @namespace http://tampermonkey.net/
// @version 9.0
// @description Chess.com cheat engine
// @author rexxx
// @match https://www.chess.com/*
// @grant GM_xmlhttpRequest
// @grant GM_addStyle
// @connect unpkg.com
// @connect cdn.jsdelivr.net
// @connect cdnjs.cloudflare.com
// @connect lichess.org
// @connect stockfish.online
// @connect tablebase.lichess.ovh
// @connect tablebase.lichess.ovh
// @run-at document-idle
// @grant unsafeWindow
// ==/UserScript==
(function () {
'use strict';
const CONFIG = {
engineDepth: 16,
pollInterval: 1000,
minThinkTime: 300,
maxThinkTime: 1200,
blunderChance: 0.05,
occasionalSlowMove: 0.05,
slowMoveDelay: 2000,
behaviorVariability: true,
auto: {
enabled: false,
minDelay: 500,
maxDelay: 2000,
fastPlayChance: 0.25,
fatigueEnabled: true,
breakChance: 0.02,
autoQueue: true
},
arrowOpacity: 0.8,
showPanel: true,
showThreats: true,
stealthMode: false,
useBook: true,
personality: 'master',
};
const SELECTORS = {
board: ['wc-chess-board', 'chess-board'],
chat: '.chat-scroll-area-component',
moves: 'vertical-move-list, wc-move-list, .move-list-component',
clocks: '.clock-component',
gameOver: [
'.game-over-modal-container',
'.modal-game-over-component',
'[data-cy="game-over-modal"]',
'.game-result-header'
],
drawOffer: '.draw-offer-component',
promotion: {
dialog: '.promotion-window, .promotion-piece',
items: '.promotion-piece'
}
};
const State = {
engineFound: false,
isThinking: false,
lastFen: null,
playerColor: null,
gameId: null,
moveCount: 0,
boredomLevel: 0,
personality: null,
ui: {
overlay: null,
panel: null,
statusDot: null,
autoIndicator: null
},
workers: {
stockfish: null
},
cache: {
fen: new Map(),
board: null
}
};
const Utils = {
sleep: (ms) => new Promise(r => setTimeout(r, ms)),
log: (msg, type = 'info') => {
const colors = {
info: '#3eff3e',
warn: '#ffcc00',
error: '#ff4444',
debug: '#aaaaff'
};
console.log(`%c[BA] ${msg}`, `color: ${colors[type]}; font-weight: bold;`);
},
randomRange: (min, max) => Math.random() * (max - min) + min,
isTabActive: () => !document.hidden,
query: (selector, root = document) => {
if (Array.isArray(selector)) {
for (const s of selector) {
const el = root.querySelector(s);
if (el) return el;
}
return null;
}
return root.querySelector(selector);
}
};
const UI = {
injectStyles: () => {
GM_addStyle(`
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;700&display=swap');
.ba-overlay { pointer-events: none; z-index: 10000; position: absolute; top: 0; left: 0; transition: opacity 0.2s; }
.ba-stealth { opacity: 0 !important; pointer-events: none !important; }
.ba-panel {
position: fixed; top: 50px; left: 50px; z-index: 10001;
width: 300px;
background: rgba(10, 10, 12, 0.95);
color: #e0e0e0;
border: 1px solid #333;
border-left: 2px solid #4caf50;
border-radius: 8px;
font-family: 'Inter', sans-serif;
box-shadow: 0 10px 40px rgba(0,0,0,0.6);
overflow: hidden;
display: flex; flex-direction: column;
}
.ba-header {
padding: 12px 16px;
background: linear-gradient(90deg, rgba(76,175,80,0.1), transparent);
border-bottom: 1px solid rgba(255,255,255,0.05);
display: flex; justify-content: space-between; align-items: center;
cursor: grab; user-select: none;
}
.ba-logo { font-weight: 800; font-size: 14px; letter-spacing: 1px; color: #4caf50; }
.ba-logo span { color: #fff; opacity: 0.7; font-weight: 400; }
.ba-minimize { cursor: pointer; opacity: 0.5; transition: 0.2s; }
.ba-minimize:hover { opacity: 1; color: #fff; }
.ba-tabs { display: flex; background: rgba(0,0,0,0.2); }
.ba-tab {
flex: 1; text-align: center; padding: 10px 0;
font-size: 11px; font-weight: 600; color: #666;
cursor: pointer; transition: 0.2s;
border-bottom: 2px solid transparent;
}
.ba-tab:hover { color: #aaa; background: rgba(255,255,255,0.02); }
.ba-tab.active { color: #e0e0e0; border-bottom: 2px solid #4caf50; background: rgba(76,175,80,0.05); }
.ba-content { padding: 16px; min-height: 150px; max-height: 400px; overflow-y: auto; }
.ba-page { display: none; }
.ba-page.active { display: block; animation: fadeIn 0.2s; }
.ba-row { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; }
.ba-label { font-size: 12px; color: #aaa; }
.ba-value { font-family: 'JetBrains Mono', monospace; font-size: 12px; color: #4caf50; }
.ba-slider-container { margin-bottom: 14px; }
.ba-slider-header { display: flex; justify-content: space-between; margin-bottom: 6px; }
.ba-slider {
-webkit-appearance: none; width: 100%; height: 4px;
background: #333; border-radius: 2px; outline: none;
}
.ba-slider::-webkit-slider-thumb {
-webkit-appearance: none; width: 12px; height: 12px;
background: #4caf50; border-radius: 50%; cursor: pointer;
box-shadow: 0 0 10px rgba(76,175,80,0.4);
}
.ba-checkbox {
width: 16px; height: 16px; border: 1px solid #444;
background: #111; border-radius: 3px; cursor: pointer;
display: flex; align-items: center; justify-content: center;
}
.ba-checkbox.checked { background: #4caf50; border-color: #4caf50; }
.ba-checkbox.checked::after { content: '✓'; font-size: 10px; color: #000; font-weight: bold; }
.ba-status-box {
background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.05);
border-radius: 6px; padding: 12px; margin-bottom: 16px; text-align: center;
}
.ba-eval-large { font-family: 'JetBrains Mono'; font-size: 24px; font-weight: 700; color: #fff; margin-bottom: 4px; display: block;}
.ba-best-move-large { font-family: 'JetBrains Mono'; font-size: 14px; color: #4caf50; background: rgba(76,175,80,0.1); padding: 4px 8px; border-radius: 4px; display: inline-block; }
.ba-footer {
padding: 8px 16px; font-size: 10px; color: #555;
border-top: 1px solid rgba(255,255,255,0.05);
display: flex; gap: 12px;
}
.ba-key { color: #888; background: #222; padding: 1px 4px; border-radius: 3px; font-family: monospace; border: 1px solid #333; }
@keyframes fadeIn { from { opacity: 0; transform: translateY(5px); } to { opacity: 1; transform: translateY(0); } }
.ba-arrow { stroke-linecap: round; opacity: ${CONFIG.arrowOpacity}; filter: drop-shadow(0 0 4px rgba(0,0,0,0.5)); }
`);
},
createInterface: () => {
if (State.ui.panel) return;
const panel = document.createElement('div');
panel.className = 'ba-panel';
panel.innerHTML = `
<div class="ba-header">
<div class="ba-logo">REXXX<span>.MENU</span></div>
<div class="ba-minimize">_</div>
</div>
<div class="ba-tabs">
<div class="ba-tab active" data-tab="main">MAIN</div>
<div class="ba-tab" data-tab="timings">TIMINGS</div>
<div class="ba-tab" data-tab="visuals">VISUALS</div>
</div>
<div class="ba-content">
<div id="tab-main" class="ba-page active">
<div class="ba-status-box">
<span class="ba-eval-large">0.00</span>
<span class="ba-best-move-large">Waiting...</span>
</div>
<div class="ba-row">
<span class="ba-label">Auto-Play</span>
<div class="ba-checkbox ${CONFIG.auto.enabled ? 'checked' : ''}" id="toggle-auto"></div>
</div>
<div class="ba-row">
<span class="ba-label">Opening Book</span>
<div class="ba-checkbox ${CONFIG.useBook ? 'checked' : ''}" id="toggle-book"></div>
</div>
</div>
<div id="tab-timings" class="ba-page">
<div class="ba-slider-container">
<div class="ba-slider-header">
<span class="ba-label">Min Delay (ms)</span>
<span class="ba-value" id="val-min">${CONFIG.auto.minDelay}</span>
</div>
<input type="range" class="ba-slider" min="0" max="2000" value="${CONFIG.auto.minDelay}" id="slide-min">
</div>
<div class="ba-slider-container">
<div class="ba-slider-header">
<span class="ba-label">Max Delay (ms)</span>
<span class="ba-value" id="val-max">${CONFIG.auto.maxDelay}</span>
</div>
<input type="range" class="ba-slider" min="500" max="5000" value="${CONFIG.auto.maxDelay}" id="slide-max">
</div>
<div class="ba-slider-container">
<div class="ba-slider-header">
<span class="ba-label">Blunder Chance %</span>
<span class="ba-value" id="val-blunder">${CONFIG.blunderChance * 100}</span>
</div>
<input type="range" class="ba-slider" min="0" max="20" value="${CONFIG.blunderChance * 100}" id="slide-blunder">
</div>
</div>
<div id="tab-visuals" class="ba-page">
<div class="ba-row">
<span class="ba-label">Arrow Opacity</span>
<div class="ba-slider" style="width: 100px;"></div>
</div>
<div class="ba-row">
<span class="ba-label">Show Threats</span>
<div class="ba-checkbox ${CONFIG.showThreats ? 'checked' : ''}" id="toggle-threats"></div>
</div>
</div>
</div>
<div class="ba-footer">
<span><span class="ba-key">A</span> Toggle Auto</span>
<span><span class="ba-key">X</span> Stealth</span>
</div>
`;
document.body.appendChild(panel);
State.ui.panel = panel;
UI.makeDraggable(panel);
UI.initListeners(panel);
},
initListeners: (panel) => {
panel.querySelectorAll('.ba-tab').forEach(t => {
t.addEventListener('click', (e) => {
panel.querySelectorAll('.ba-tab').forEach(x => x.classList.remove('active'));
panel.querySelectorAll('.ba-page').forEach(x => x.classList.remove('active'));
e.target.classList.add('active');
panel.querySelector(`#tab-${e.target.dataset.tab}`).classList.add('active');
});
});
const toggle = (id, configPath, callback) => {
panel.querySelector(`#${id}`).addEventListener('click', (e) => {
const keys = configPath.split('.');
if (keys.length === 2) CONFIG[keys[0]][keys[1]] = !CONFIG[keys[0]][keys[1]];
else CONFIG[configPath] = !CONFIG[configPath];
e.target.classList.toggle('checked');
if (callback) callback();
});
};
toggle('toggle-auto', 'auto.enabled');
toggle('toggle-book', 'useBook');
toggle('toggle-threats', 'showThreats');
const slider = (id, valId, configPath) => {
const el = panel.querySelector(`#${id}`);
const display = panel.querySelector(`#${valId}`);
el.addEventListener('input', (e) => {
const val = parseInt(e.target.value);
const keys = configPath.split('.');
if (keys.length === 2) CONFIG[keys[0]][keys[1]] = val;
else CONFIG[configPath] = val;
display.textContent = val;
});
};
slider('slide-min', 'val-min', 'auto.minDelay');
slider('slide-max', 'val-max', 'auto.maxDelay');
},
makeDraggable: (el) => {
const header = el.querySelector('.ba-header');
let isDragging = false;
let startX, startY, initialLeft, initialTop;
header.addEventListener('mousedown', (e) => {
isDragging = true;
startX = e.clientX;
startY = e.clientY;
initialLeft = el.offsetLeft;
initialTop = el.offsetTop;
header.style.cursor = 'grabbing';
});
document.addEventListener('mousemove', (e) => {
if (!isDragging) return;
const dx = e.clientX - startX;
const dy = e.clientY - startY;
el.style.left = `${initialLeft + dx}px`;
el.style.top = `${initialTop + dy}px`;
});
document.addEventListener('mouseup', () => {
isDragging = false;
header.style.cursor = 'grab';
});
},
toggleStealth: () => {
CONFIG.stealthMode = !CONFIG.stealthMode;
const p = State.ui.panel;
if (p) p.style.opacity = CONFIG.stealthMode ? '0' : '1';
document.querySelectorAll('.ba-overlay').forEach(el => {
el.classList.toggle('ba-stealth', CONFIG.stealthMode);
});
},
updatePanel: (evalData, bestMove) => {
if (!State.ui.panel) return;
const evalBox = State.ui.panel.querySelector('.ba-eval-large');
const moveBox = State.ui.panel.querySelector('.ba-best-move-large');
if (evalData) {
evalBox.textContent = evalData.type === 'mate' ? `M${evalData.value}` : evalData.value.toFixed(2);
evalBox.style.color = evalData.value > 0.5 ? '#4caf50' : (evalData.value < -0.5 ? '#ff5252' : '#e0e0e0');
}
if (bestMove) {
moveBox.textContent = bestMove.move || '...';
}
State.ui.panel.querySelector('#toggle-auto').classList.toggle('checked', CONFIG.auto.enabled);
},
updateStatus: (color) => {
if (!State.ui.panel) return;
State.ui.panel.style.borderLeftColor = color;
const logo = State.ui.panel.querySelector('.ba-logo');
if (logo) logo.style.color = color;
if (color === 'red') {
const moveBox = State.ui.panel.querySelector('.ba-best-move-large');
if (moveBox) moveBox.textContent = 'Engine Failed';
}
},
clearOverlay: () => {
document.querySelectorAll('.ba-overlay').forEach(e => e.remove());
},
drawMove: (move, color = '#4caf50', secondary = false) => {
if (CONFIG.stealthMode) return;
if (!secondary) UI.clearOverlay();
const board = Game.getBoard();
if (!board) return;
const rect = board.getBoundingClientRect();
let overlay = secondary ? document.querySelector('.ba-overlay') : null;
if (!overlay) {
overlay = document.createElement('div');
overlay.className = 'ba-overlay';
if (CONFIG.stealthMode) overlay.classList.add('ba-stealth');
overlay.style.width = rect.width + 'px';
overlay.style.height = rect.height + 'px';
overlay.style.left = rect.left + window.scrollX + 'px';
overlay.style.top = rect.top + window.scrollY + 'px';
document.body.appendChild(overlay);
}
const from = move.substring(0, 2);
const to = move.substring(2, 4);
const file = (c) => c.charCodeAt(0) - 97;
const rank = (c) => parseInt(c) - 1;
const isFlipped = State.playerColor === 'b';
const sqSize = rect.width / 8;
const getPos = (sq) => {
const f = file(sq[0]);
const r = rank(sq[1]);
return {
x: (isFlipped ? 7 - f : f) * sqSize + sqSize / 2,
y: (isFlipped ? r : 7 - r) * sqSize + sqSize / 2
};
};
const p1 = getPos(from);
const p2 = getPos(to);
let svg = overlay.querySelector('svg');
if (!svg) {
svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.style.width = '100%';
svg.style.height = '100%';
overlay.appendChild(svg);
}
const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
line.setAttribute('x1', p1.x);
line.setAttribute('y1', p1.y);
line.setAttribute('x2', p2.x);
line.setAttribute('y2', p2.y);
line.setAttribute('stroke', color);
line.setAttribute('stroke-width', sqSize * (secondary ? 0.1 : 0.18));
line.setAttribute('class', 'ba-arrow');
if (secondary) line.setAttribute('stroke-dasharray', '5,5');
const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
circle.setAttribute('cx', p2.x);
circle.setAttribute('cy', p2.y);
circle.setAttribute('r', sqSize * (secondary ? 0.1 : 0.18));
circle.setAttribute('fill', color);
circle.setAttribute('opacity', secondary ? 0.5 : 0.8);
svg.appendChild(line);
svg.appendChild(circle);
}
};
const Game = {
getBoard: () => {
if (State.cache.board && State.cache.board.isConnected) return State.cache.board;
State.cache.board = Utils.query(SELECTORS.board);
return State.cache.board;
},
getElementAtSquare: (sq) => {
const board = Game.getBoard();
if (!board) return null;
const piece = board.querySelector(`.piece.square-${Game.squareToCoords(sq)}`) ||
board.querySelector(`.piece.${sq}`);
if (piece) return piece;
if (board.shadowRoot) {
const shadowPiece = board.shadowRoot.querySelector(`.piece.square-${Game.squareToCoords(sq)}`);
if (shadowPiece) return shadowPiece;
}
return board;
},
squareToCoords: (sq) => {
const file = sq.charCodeAt(0) - 96;
const rank = sq[1];
return `${file}${rank}`;
},
detectColor: () => {
const board = Game.getBoard();
if (!board) return null;
const isFlipped = board.classList.contains('flipped') || board.getAttribute('flipped') === 'true';
return isFlipped ? 'b' : 'w';
},
isValidFen: (fen) => {
if (!fen || typeof fen !== 'string') return false;
return fen.split(' ').length >= 4;
},
getFen: () => {
const board = Game.getBoard();
if (!board) return null;
if (board.game && board.game.getFEN) return board.game.getFEN();
const keys = Object.keys(board);
const reactKey = keys.find(k => k.startsWith('__reactFiber') || k.startsWith('__reactInternal'));
if (reactKey) {
let curr = board[reactKey];
while (curr) {
if (curr.memoizedProps?.game?.fen) return curr.memoizedProps.game.fen;
if (typeof curr.memoizedProps?.fen === 'string') return curr.memoizedProps.fen;
curr = curr.return;
}
}
return null;
},
isMyTurn: (fen) => {
if (!fen || !State.playerColor) return false;
const turn = fen.split(' ')[1];
return turn === State.playerColor;
},
isCapture: (move) => {
const board = Game.getBoard();
if (!board) return false;
const to = move.substring(2, 4);
const coords = Game.squareToCoords(to);
return !!board.querySelector(`.piece.square-${coords}`);
}
};
const OpeningBook = {
fetchMove: (fen) => {
if (!CONFIG.useBook) return null;
return new Promise((resolve) => {
GM_xmlhttpRequest({
method: 'GET',
url: `https://explorer.lichess.ovh/masters?fen=${fen}`,
onload: (response) => {
try {
const data = JSON.parse(response.responseText);
if (data.moves && data.moves.length > 0) {
const topMoves = data.moves.slice(0, 3);
const totalGames = topMoves.reduce((sum, m) => sum + m.white + m.draw + m.black, 0);
let r = Math.random() * totalGames;
for (const move of topMoves) {
const games = move.white + move.draw + move.black;
if (r < games) {
resolve(move.uci);
return;
}
r -= games;
}
resolve(topMoves[0].uci);
} else {
resolve(null);
}
} catch (e) {
resolve(null);
}
},
onerror: () => resolve(null)
});
});
}
};
const Engine = {
init: async () => {
if (State.workers.stockfish) return;
Utils.log('Initializing Stockfish...');
UI.updateStatus('orange'); // Connecting
try {
// Use GM_xmlhttpRequest to bypass CSP
const scriptContent = await new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: 'GET',
url: 'https://unpkg.com/[email protected]/stockfish.js',
onload: (res) => resolve(res.responseText),
onerror: (err) => reject(err)
});
});
const blob = new Blob([scriptContent], { type: 'application/javascript' });
State.workers.stockfish = new Worker(URL.createObjectURL(blob));
State.workers.stockfish.onmessage = (e) => {
const msg = e.data;
if (msg === 'uciok') {
State.engineFound = true;
UI.updateStatus('#4caf50'); // Green
Utils.log('Stockfish Ready');
}
if (msg.startsWith('bestmove')) {
const move = msg.split(' ')[1];
Engine.handleBestMove(move);
}
if (msg.startsWith('info') && msg.includes('score')) {
Engine.parseScore(msg);
}
};
State.workers.stockfish.postMessage('uci');
State.workers.stockfish.postMessage('isready');
State.workers.stockfish.postMessage(`setoption name MultiPV value 1`);
} catch (e) {
Utils.log('Stockfish Init Failed: ' + e, 'error');
UI.updateStatus('red');
}
},
analyze: async (fen) => {
if (!State.workers.stockfish || !State.engineFound) return;
State.isThinking = true;
if (CONFIG.useBook) {
const bookMove = await OpeningBook.fetchMove(fen);
if (bookMove) {
Utils.log(`Book Move Found: ${bookMove}`);
Engine.handleBestMove(bookMove, true);
return;
}
}
State.workers.stockfish.postMessage('stop');
State.workers.stockfish.postMessage(`position fen ${fen}`);
const multipv = CONFIG.showThreats ? 2 : 1;
State.workers.stockfish.postMessage(`setoption name MultiPV value ${multipv}`);
State.workers.stockfish.postMessage(`go depth ${CONFIG.engineDepth}`);
},
parseScore: (msg) => {
const scoreMatch = msg.match(/score (cp|mate) (-?\d+)/);
const pvMatch = msg.match(/multipv (\d+)/);
const depthMatch = msg.match(/depth (\d+)/);
const moveMatch = msg.match(/ pv (\w+)/);
if (scoreMatch && pvMatch && moveMatch) {
const type = scoreMatch[1];
let value = parseInt(scoreMatch[2]);
const mpv = parseInt(pvMatch[1]);
if (type === 'cp') value = value / 100;
if (mpv === 1) {
State.currentEval = { type, value, depth: parseInt(depthMatch?.[1] || 0) };
State.currentBestMove = moveMatch[1];
}
if (mpv === 1 && msg.includes(' pv ')) {
const pvMoves = msg.split(' pv ')[1].split(' ');
if (pvMoves.length > 1) {
State.opponentResponse = pvMoves[1];
}
}
}
},
handleBestMove: async (bestMove, isBook = false) => {
State.isThinking = false;
Utils.log(`Best Move: ${bestMove} ${isBook ? '(Book)' : ''}`);
let finalMove = bestMove;
UI.drawMove(finalMove, '#4caf50');
if (CONFIG.showThreats && State.opponentResponse) {
UI.drawMove(State.opponentResponse, '#ff5252', true);
}
UI.updatePanel(State.currentEval, { move: finalMove });
if (CONFIG.auto.enabled && Game.isMyTurn(State.lastFen)) {
let min = CONFIG.auto.minDelay;
let max = CONFIG.auto.maxDelay;
if (isBook || State.moveCount < 10) {
min = 200; max = 600;
} else if (Game.isCapture(finalMove)) {
min = 150; max = 450;
Utils.log('Humanizer: Reflex Capture');
} else {
if (State.currentEval && Math.abs(State.currentEval.value) > 3) {
max = 800;
}
else if (State.currentEval && Math.abs(State.currentEval.value) < 1.0) {
min += 400; max += 1000;
}
if (Math.random() < 0.05) {
min = 2500; max = 5000;
Utils.log('Humanizer: Deep Thinking...');
}
}
const delay = Utils.randomRange(min, max);
Utils.log(`Waiting ${Math.round(delay)}ms...`);
await Utils.sleep(delay);
await Humanizer.executeMove(finalMove);
}
}
};
const Humanizer = {
createEvent: (type, x, y, options = {}) => {
const defaults = {
bubbles: true,
cancelable: true,
view: window,
detail: 1,
screenX: x,
screenY: y,
clientX: x,
clientY: y,
pointerId: 1,
pointerType: 'mouse',
isPrimary: true,
button: 0,
buttons: 1,
which: 1,
composed: true
};
return new PointerEvent(type, { ...defaults, ...options });
},
getElementsAt: (x, y) => {
return document.elementsFromPoint(x, y).filter(el =>
el.tagName !== 'HTML' && el.tagName !== 'BODY' && !el.classList.contains('ba-overlay')
);
},
makeGodMove: (from, to, promo) => false,
showClick: (x, y, color = 'red') => {
const dot = document.createElement('div');
dot.style.cssText = `
position: absolute;
left: ${x}px; top: ${y}px;
width: 12px; height: 12px;
background: ${color}; border-radius: 50%;
z-index: 100000; pointer-events: none;
transform: translate(-50%, -50%);
box-shadow: 0 0 4px white;
`;
document.body.appendChild(dot);
setTimeout(() => dot.remove(), 800);
},
dragDrop: async (fromSq, toSq) => {
const board = Game.getBoard();
if (!board) return;
const startPos = Humanizer.getCoords(fromSq);
const endPos = Humanizer.getCoords(toSq);
if (!startPos || !endPos) return;
Humanizer.showClick(startPos.x, startPos.y, '#00ff00');
const fromCoords = Game.squareToCoords(fromSq);
const pieceEl = board.querySelector(`.piece.square-${fromCoords}`) ||
document.elementFromPoint(startPos.x, startPos.y);
const targetSource = pieceEl || board;
Utils.log(`Draggable: ${fromSq} -> ${toSq}`);
const opts = { bubbles: true, composed: true, buttons: 1, pointerId: 1, isPrimary: true };
targetSource.dispatchEvent(new PointerEvent('pointerover', { ...opts, clientX: startPos.x, clientY: startPos.y }));
targetSource.dispatchEvent(new PointerEvent('pointerdown', { ...opts, clientX: startPos.x, clientY: startPos.y }));
targetSource.dispatchEvent(new MouseEvent('mousedown', { ...opts, clientX: startPos.x, clientY: startPos.y }));
await Utils.sleep(Utils.randomRange(20, 40));
const initialMoveX = startPos.x + (endPos.x - startPos.x) * 0.1;
const initialMoveY = startPos.y + (endPos.y - startPos.y) * 0.1;
document.dispatchEvent(new PointerEvent('pointermove', { ...opts, clientX: initialMoveX, clientY: initialMoveY }));
await Utils.sleep(20);
const steps = 6;
for (let i = 1; i <= steps; i++) {
const t = i / steps;
const curX = startPos.x + (endPos.x - startPos.x) * t;
const curY = startPos.y + (endPos.y - startPos.y) * t;
document.dispatchEvent(new PointerEvent('pointermove', {
...opts,
clientX: curX,
clientY: curY,
pressure: 0.5
}));
document.dispatchEvent(new MouseEvent('mousemove', { ...opts, clientX: curX, clientY: curY }));
if (i % 2 === 0) await Utils.sleep(5);
}
const toCoords = Game.squareToCoords(toSq);
const targetEl = board.querySelector(`.square-${toCoords}`) ||
document.elementFromPoint(endPos.x, endPos.y);
const dropTarget = targetEl || board;
Humanizer.showClick(endPos.x, endPos.y, 'red');
dropTarget.dispatchEvent(new PointerEvent('pointerup', { ...opts, clientX: endPos.x, clientY: endPos.y }));
dropTarget.dispatchEvent(new MouseEvent('mouseup', { ...opts, clientX: endPos.x, clientY: endPos.y }));
dropTarget.dispatchEvent(new PointerEvent('click', { ...opts, clientX: endPos.x, clientY: endPos.y }));
},
clickSquare: async (sq) => { },
executeMove: async (move) => {
const currentFen = Game.getFen();
if (currentFen !== State.lastFen && State.moveCount > 0) return;
const from = move.substring(0, 2);
const to = move.substring(2, 4);
const promo = move.length > 4 ? move[4] : null;
if (Humanizer.makeGodMove(from, to, promo)) {
Utils.log('API Success?');
}
Utils.log(`Auto-playing (Drag): ${from} -> ${to}`);
await Humanizer.dragDrop(from, to);
if (promo) {
await Utils.sleep(300);
const screenPromo = document.querySelector('.promotion-window');
if (screenPromo) {
const promoImg = screenPromo.querySelector(`img[src*="${promo}"]`) ||
screenPromo.querySelector(`.${promo}`);
if (promoImg) {
const center = promoImg.getBoundingClientRect();
const x = center.left + center.width / 2;
const y = center.top + center.height / 2;
const opts = { bubbles: true, composed: true, buttons: 1 };
promoImg.dispatchEvent(new PointerEvent('pointerdown', { ...opts, clientX: x, clientY: y }));
promoImg.dispatchEvent(new PointerEvent('pointerup', { ...opts, clientX: x, clientY: y }));
promoImg.dispatchEvent(new PointerEvent('click', { ...opts, clientX: x, clientY: y }));
}
}
}
},
getCoords: (sq) => {
const board = Game.getBoard();
if (!board) return null;
const rect = board.getBoundingClientRect();
const sqSize = rect.width / 8;
const isFlipped = State.playerColor === 'b';
const f = sq.charCodeAt(0) - 97;
const r = parseInt(sq[1]) - 1;
const x = rect.left + (isFlipped ? 7 - f : f) * sqSize + sqSize / 2;
const y = rect.top + (isFlipped ? r : 7 - r) * sqSize + sqSize / 2;
return { x, y };
}
};
const Main = {
init: async () => {
UI.injectStyles();
UI.createInterface();
let board = null;
while (!board) {
board = Game.getBoard();
if (!board) await Utils.sleep(500);
}
Utils.log('Board detected. Starting Engine...');
await Engine.init();
Main.setupObservers();
Main.gameLoop();
document.addEventListener('keydown', (e) => {
if (e.key === 'a' && !e.ctrlKey && !e.shiftKey && !e.target.matches('input')) {
CONFIG.auto.enabled = !CONFIG.auto.enabled;
Utils.log(`Auto-Play: ${CONFIG.auto.enabled}`);
UI.updatePanel(State.currentEval, {});
}
if (e.key === 'x' && !e.target.matches('input')) {
UI.toggleStealth();
}
});
},
checkAutoQueue: () => {
if (!CONFIG.auto.autoQueue) return;
const buttons = [
'button[data-cy="new-game-index-main"]',
'.game-over-button-component.primary',
'.ui_v5-button-component.ui_v5-button-primary'
];
for (const sel of buttons) {
const btn = document.querySelector(sel);
if (btn && btn.offsetParent !== null) {
Utils.log('Auto-Queue: Clicking New Game...');
btn.click();
break;
}
}
},
setupObservers: () => {
const movesList = Utils.query(SELECTORS.moves) || document.body;
const observer = new MutationObserver((mutations) => {
requestAnimationFrame(Main.gameLoop);
});
observer.observe(movesList, { childList: true, subtree: true, characterData: true });
setInterval(Main.gameLoop, CONFIG.pollInterval);
},
gameLoop: () => {
const fen = Game.getFen();
if (!fen || fen === State.lastFen) return;
State.playerColor = Game.detectColor();
State.lastFen = fen;
State.currentEval = null;
UI.clearOverlay();
UI.updatePanel(null, null);
if (Utils.query(SELECTORS.gameOver)) {
if (CONFIG.auto.autoQueue) {
setTimeout(Main.checkAutoQueue, 2000);
}
return;
}
if (Game.isMyTurn(fen)) {
Engine.analyze(fen);
} else {
UI.updateStatus('#888');
}
}
};
Main.init();
})();