Chess.com Cheat Engine

Chess.com cheat engine

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

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

})();