Auto-detects games and shows 3 best moves. Click to play. Works on iOS with Userscripts app. No auto-play.
// ==UserScript==
// @name Chess.com Smart Advisor (Fixed - No Fail)
// @namespace https://sleazyfork.org/en/users/123456
// @version 3.0
// @description Auto-detects games and shows 3 best moves. Click to play. Works on iOS with Userscripts app. No auto-play.
// @author Potato
// @license MIT
// @match https://www.chess.com/game/*
// @match https://chess.com/game/*
// @run-at document-start
// @require https://cdnjs.cloudflare.com/ajax/libs/chess.js/0.10.3/chess.min.js
// @grant none
// ==/UserScript==
(function () {
'use strict';
// ─── CONFIG ────────────────────────────────────────────────────────────────
const SEARCH_DEPTH = 3;
const PIECE_VALUE = { p:100, n:320, b:330, r:500, q:900, k:20000 };
const PST = {
p:[0,0,0,0,0,0,0,0,50,50,50,50,50,50,50,50,10,10,20,30,30,20,10,10,
5,5,10,25,25,10,5,5,0,0,0,20,20,0,0,0,5,-5,-10,0,0,-10,-5,5,
5,10,10,-20,-20,10,10,5,0,0,0,0,0,0,0,0],
n:[-50,-40,-30,-30,-30,-30,-40,-50,-40,-20,0,0,0,0,-20,-40,
-30,0,10,15,15,10,0,-30,-30,5,15,20,20,15,5,-30,
-30,0,15,20,20,15,0,-30,-30,5,10,15,15,10,5,-30,
-40,-20,0,5,5,0,-20,-40,-50,-40,-30,-30,-30,-30,-40,-50],
b:[-20,-10,-10,-10,-10,-10,-10,-20,-10,0,0,0,0,0,0,-10,
-10,0,5,10,10,5,0,-10,-10,5,5,10,10,5,5,-10,
-10,0,10,10,10,10,0,-10,-10,10,10,10,10,10,10,-10,
-10,5,0,0,0,0,5,-10,-20,-10,-10,-10,-10,-10,-10,-20],
r:[0,0,0,0,0,0,0,0,5,10,10,10,10,10,10,5,-5,0,0,0,0,0,0,-5,
-5,0,0,0,0,0,0,-5,-5,0,0,0,0,0,0,-5,-5,0,0,0,0,0,0,-5,
-5,0,0,0,0,0,0,-5,0,0,0,5,5,0,0,0],
q:[-20,-10,-10,-5,-5,-10,-10,-20,-10,0,0,0,0,0,0,-10,
-10,0,5,5,5,5,0,-10,-5,0,5,5,5,5,0,-5,
0,0,5,5,5,5,0,-5,-10,5,5,5,5,5,0,-10,
-10,0,5,0,0,0,0,-10,-20,-10,-10,-5,-5,-10,-10,-20],
k:[-30,-40,-40,-50,-50,-40,-40,-30,-30,-40,-40,-50,-50,-40,-40,-30,
-30,-40,-40,-50,-50,-40,-40,-30,-30,-40,-40,-50,-50,-40,-40,-30,
-20,-30,-30,-40,-40,-30,-30,-20,-10,-20,-20,-20,-20,-20,-20,-10,
20,20,0,0,0,0,20,20,20,30,10,0,0,10,30,20],
};
let lastFen = '';
// ─── UI ──────────────────────────────────────────────────────────────────
function createUI() {
if (document.getElementById('chess-smart-advisor')) return;
const style = document.createElement('style');
style.textContent = `
#chess-smart-advisor {
position: fixed; bottom: 20px; right: 20px;
z-index: 2147483647; background: #1a1a2e;
color: #eee; font-family: 'Segoe UI', sans-serif;
font-size: 14px; border-radius: 12px;
padding: 16px 20px; box-shadow: 0 8px 24px rgba(0,0,0,0.8);
width: 330px; max-height: 420px; overflow-y: auto;
border: 1px solid #4a4a6a;
}
#chess-smart-advisor .header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; }
#chess-smart-advisor .header strong { color: #9b9bff; }
#chess-smart-advisor .status { font-size: 12px; color: #aaa; }
#chess-smart-advisor .move-item {
background: #2a2a44; padding: 8px 12px; margin: 4px 0;
border-radius: 6px; cursor: pointer;
display: flex; justify-content: space-between;
transition: background 0.2s;
}
#chess-smart-advisor .move-item:hover { background: #3a3a5e; }
#chess-smart-advisor .footer { margin-top: 8px; font-size: 11px; color: #666; }
`;
document.head.appendChild(style);
const panel = document.createElement('div');
panel.id = 'chess-smart-advisor';
panel.innerHTML = `
<div class="header">
<strong>🧠 Smart Advisor</strong>
<span class="status" id="advisor-status">⏳ Waiting for game...</span>
</div>
<div id="advisor-moves"><div style="color:#888;">No game detected yet.</div></div>
<div class="footer">⚡ Click any move to play it</div>
`;
document.body.appendChild(panel);
}
function setStatus(msg) {
const el = document.getElementById('advisor-status');
if (el) el.textContent = msg;
}
function displayMoves(moves) {
const container = document.getElementById('advisor-moves');
if (!container) return;
if (!moves || moves.length === 0) {
container.innerHTML = `<div style="color:#888;">No moves found.</div>`;
return;
}
let html = `<div style="margin-bottom:4px;color:#9b9bff;">🏆 Top ${moves.length} moves:</div>`;
moves.forEach((mv, idx) => {
const color = idx === 0 ? '#7dff7d' : (idx === 1 ? '#ffd966' : '#ff8a8a');
const san = mv.san || mv.from + mv.to;
html += `
<div class="move-item" data-from="${mv.from}" data-to="${mv.to}" data-promotion="${mv.promotion || ''}" style="border-left: 3px solid ${color};">
<span><strong>${idx+1}.</strong> ${san}</span>
<span style="color:${color};">▶</span>
</div>
`;
});
container.innerHTML = html;
document.querySelectorAll('#advisor-moves .move-item').forEach(el => {
el.addEventListener('click', function() {
const mv = {
from: this.dataset.from,
to: this.dataset.to,
promotion: this.dataset.promotion || undefined
};
playMove(mv);
});
});
}
// ─── EVALUATION ENGINE ──────────────────────────────────────────────────
function evaluate(game) {
if (game.in_checkmate()) return game.turn() === 'w' ? -30000 : 30000;
if (game.in_draw()) return 0;
let score = 0;
for (let r = 0; r < 8; r++) {
for (let f = 0; f < 8; f++) {
const p = game.board()[r][f];
if (!p) continue;
const idx = r * 8 + f;
const pval = PIECE_VALUE[p.type] || 0;
const pst = (PST[p.type] || [])[p.color === 'w' ? idx : 63 - idx] || 0;
score += p.color === 'w' ? pval + pst : -(pval + pst);
}
}
return score;
}
function minimax(game, depth, alpha, beta, max) {
if (depth === 0 || game.game_over()) return evaluate(game);
const moves = game.moves();
let best = max ? -Infinity : Infinity;
for (const m of moves) {
game.move(m);
const val = minimax(game, depth - 1, alpha, beta, !max);
game.undo();
if (max) { if (val > best) best = val; alpha = Math.max(alpha, val); }
else { if (val < best) best = val; beta = Math.min(beta, val); }
if (beta <= alpha) break;
}
return best;
}
function getTopMoves(game, count = 3) {
const moves = game.moves({ verbose: true });
if (!moves.length) return [];
const max = game.turn() === 'w';
const scored = moves.map(mv => {
game.move(mv);
const val = minimax(game, SEARCH_DEPTH - 1, -Infinity, Infinity, !max);
game.undo();
return { move: mv, score: val };
});
scored.sort((a, b) => max ? b.score - a.score : a.score - b.score);
return scored.slice(0, count).map(item => item.move);
}
// ─── MOVE EXECUTION ──────────────────────────────────────────────────────
function fireAt(x, y) {
const el = document.elementFromPoint(x, y);
if (!el) return;
const opts = { bubbles:true, cancelable:true, clientX:x, clientY:y, button:0 };
el.dispatchEvent(new MouseEvent('mousedown', opts));
el.dispatchEvent(new MouseEvent('mouseup', opts));
el.dispatchEvent(new MouseEvent('click', opts));
}
function squareCoords(sq) {
const board = document.querySelector('chess-board');
if (!board) return null;
const rect = board.getBoundingClientRect();
const sqSize = rect.width / 8;
const file = sq.charCodeAt(0) - 97;
const rank = parseInt(sq[1]) - 1;
const flipped = board.classList.contains('flipped');
return {
x: rect.left + (flipped ? (7 - file + 0.5) : (file + 0.5)) * sqSize,
y: rect.top + (flipped ? (rank + 0.5) : (7 - rank + 0.5)) * sqSize,
};
}
function playMove(mv) {
const from = mv.from, to = mv.to;
const fromEl = document.querySelector(`[data-square="${from}"]`);
if (fromEl) {
const r = fromEl.getBoundingClientRect();
fireAt(r.left + r.width/2, r.top + r.height/2);
setTimeout(() => {
const dest = squareCoords(to);
if (dest) fireAt(dest.x, dest.y);
if (mv.promotion) {
setTimeout(() => {
const q = document.querySelector('.promotion-piece.wq, .promotion-piece.bq');
if (q) q.click();
}, 300);
}
}, 200);
}
}
// ─── CORE LOGIC: HOOK API WITH RETRY ──────────────────────────────────
function hookChessAPI() {
// Try to find the board
const board = document.querySelector('chess-board');
if (!board) {
setStatus('⏳ Waiting for board...');
return false;
}
// Try to get initial FEN
const initialFen = board.getAttribute('fen');
if (initialFen) {
lastFen = initialFen;
processFEN(initialFen);
}
// Monitor board attribute for changes
const observer = new MutationObserver(() => {
const fen = board.getAttribute('fen');
if (fen && fen !== lastFen) {
lastFen = fen;
processFEN(fen);
}
});
observer.observe(board, { attributes: true, attributeFilter: ['fen'] });
setStatus('✅ Connected to board');
return true;
}
function processFEN(fen) {
try {
const game = new Chess(fen);
if (game.game_over()) {
setStatus('Game over');
displayMoves([]);
return;
}
const turn = fen.split(' ')[1];
const myColor = getMyColor();
if (turn !== myColor) {
setStatus("Opponent's turn...");
displayMoves([]);
return;
}
setStatus(game.in_check() ? '⚠️ In check!' : '🤔 Analyzing...');
const topMoves = getTopMoves(game, 3);
if (topMoves.length) {
setStatus('✅ Ready');
displayMoves(topMoves);
} else {
setStatus('No legal moves');
displayMoves([]);
}
} catch (e) {
setStatus('❌ Error parsing FEN');
}
}
function getMyColor() {
const board = document.querySelector('chess-board');
if (board && board.classList.contains('flipped')) return 'b';
return 'w';
}
// ─── INIT WITH SMART RETRY ──────────────────────────────────────────────
function init() {
createUI();
// Keep trying until the board is found (up to 30 seconds)
let attempts = 0;
const maxAttempts = 30;
const retryInterval = setInterval(() => {
attempts++;
if (hookChessAPI()) {
clearInterval(retryInterval);
setStatus('✅ Ready');
return;
}
if (attempts >= maxAttempts) {
clearInterval(retryInterval);
setStatus('❌ Board not found. Refresh?');
}
}, 1000);
}
// Boot when page is ready
if (document.readyState === 'complete') {
init();
} else {
window.addEventListener('load', init);
}
})();