Chess.com Smart Advisor (Fixed - No Fail)

Auto-detects games and shows 3 best moves. Click to play. Works on iOS with Userscripts app. No auto-play.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

Advertisement:

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

Advertisement:

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

})();