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.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, Greasemonkey alebo Violentmonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey alebo Userscripts.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie správcu používateľských skriptov.

(Už mám správcu používateľských skriptov, nechajte ma ho nainštalovať!)

Advertisement:

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

(Už mám správcu používateľských štýlov, nechajte ma ho nainštalovať!)

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

})();