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.

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

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

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

Advertisement:

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

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

})();