High-Low Oracle (WIP) v3.3.3

v3.2.3 + Version Jump to v3.3.3 + Fix CSS Injection

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name High-Low Oracle (WIP) v3.3.3
// @namespace http://tampermonkey.net/
// @version 3.3.3
// @description v3.2.3 + Version Jump to v3.3.3 + Fix CSS Injection
// @author Gemini
// @match *://www.torn.com/page.php?sid=highlow*
// @run-at document-start
// ==/UserScript==

(function() {
    'use strict';

    const getFreshDeck = () => ({ 2:4, 3:4, 4:4, 5:4, 6:4, 7:4, 8:4, 9:4, 10:4, 11:4, 12:4, 13:4, 14:4 });
    const SUIT_SYMBOLS = { 'S': '♠', 'H': '♥', 'D': '♦', 'C': '♣' };
    const SUIT_COLORS = { 'S': '#fff', 'C': '#fff', 'H': '#ff4444', 'D': '#ff4444' };
    const VALUES = [2,3,4,5,6,7,8,9,10,11,12,13,14];
    const SUITS = ['S', 'H', 'D', 'C'];

    let state = JSON.parse(localStorage.getItem('hi_lo_stable_state')) || {
        deck: getFreshDeck(),
        history: [], suitHistory: [], streakHistory: [],
        currentStreak: 0,
        phase: "DEALER", pos: { x: 20, y: 70 }, 
        gridOpen: false, suitGridOpen: false, helpOpen: false,
        selectedVal: 14, selectedSuit: 'S',
        displayDealer: '--', displayDealerSuit: '',
        displayPlayer: '--', displayPlayerSuit: '',
        isShuffled: false
    };

    const save = () => localStorage.setItem('hi_lo_stable_state', JSON.stringify(state));

    const injectStyles = () => {
        // Clean up any old versions of the script styles to prevent conflicts
        const oldStyles = document.querySelectorAll('style[id^="oracle-"]');
        oldStyles.forEach(s => s.remove());

        const s = document.createElement('style');
        s.id = 'oracle-v333-fixed-css';
        s.innerHTML = `
            #oracle-root {
                position: fixed !important; z-index: 999999999 !important;
                background: rgba(10,10,10,0.98) !important; color: #fff !important;
                width: 310px !important; border: 2px solid #00ffcc; border-radius: 12px; 
                padding: 12px !important; font-family: sans-serif !important; 
                left: ${state.pos.x}px; top: ${state.pos.y}px;
                box-shadow: 0 0 20px rgba(0,0,0,0.8);
                touch-action: none;
                display: block !important;
                visibility: visible !important;
            }
            .header-bar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 5px; font-weight: bold; border-bottom: 1px solid #333; padding-bottom: 5px; cursor: grab; padding: 5px; }
            #help-trigger { background: #007bff; color: white; width: 22px; height: 22px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 14px; cursor: pointer; }
            #dna-btn { color: #f2b01f; border: 1px solid #f2b01f; padding: 2px 8px; border-radius: 4px; font-size: 10px; cursor: pointer; font-weight: bold; }
            .track-val { font-size: 20px; font-weight: bold; min-height: 24px; text-align: center; }
            .main-display { text-align: center; padding: 10px 0; }
            .btn-grid { display: flex; gap: 8px; margin-top: 8px; height: 50px; }
            .bottom-row { display: flex; gap: 8px; margin-top: 10px; height: 40px; }
            .c-btn { background: #1a1a1a; color: #fff; border: 1px solid #444; flex: 1; border-radius: 8px; font-size: 20px; display: flex; align-items: center; justify-content: center; cursor: pointer; }
            .a-btn { background: #00ffcc !important; color: #000 !important; flex: 1.5; border-radius: 8px; font-size: 18px; font-weight: bold; cursor: pointer; border: none; }
            #reset-btn { flex: 2; background: #311; color: #f44; border: 1px solid #522; border-radius: 8px; font-weight: bold; cursor: pointer; }
            #undo-btn { flex: 1; background: #222; color: #888; border: 1px solid #444; border-radius: 8px; font-weight: bold; cursor: pointer; }
            .grid-item { background: #222; padding: 12px; text-align: center; border-radius: 6px; font-weight: bold; border: 1px solid #444; cursor: pointer; }
            .grid-item.active { background: #00ffcc; color: #000; }
            #help-overlay { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: rgba(0,0,0,0.98); z-index: 1000000000; display: flex; flex-direction: column; padding: 25px; box-sizing: border-box; overflow-y: auto; }
            .help-header { color: #00ffcc; font-size: 24px; font-weight: bold; margin-bottom: 15px; border-bottom: 2px solid #00ffcc; padding-bottom: 8px; }
            .help-step { margin-bottom: 12px; font-size: 15px; line-height: 1.4; color: #eee; }
            .const-box { border: 1px solid #f2b01f; padding: 12px; border-radius: 8px; margin: 10px 0; background: rgba(242, 176, 31, 0.05); }
            .help-close { background: #00ffcc; color: #000; text-align: center; padding: 15px; border-radius: 10px; cursor: pointer; font-weight: bold; font-size: 18px; margin-top: 15px; }
        `;
        document.head.appendChild(s);
    };

    const initUI = () => {
        if (document.getElementById('oracle-root')) return;
        
        injectStyles();
        const container = document.createElement('div');
        container.id = 'oracle-root';
        document.body.appendChild(container);

        let active = false;
        let currentX, currentY, initialX, initialY, xOffset = state.pos.x, yOffset = state.pos.y;

        const dragStart = (e) => {
            if (e.target.closest('.header-bar')) {
                initialX = e.clientX - xOffset;
                initialY = e.clientY - yOffset;
                active = true;
            }
        };

        const dragEnd = () => {
            active = false;
            state.pos.x = xOffset;
            state.pos.y = yOffset;
            save();
        };

        const drag = (e) => {
            if (active) {
                e.preventDefault();
                let newX = e.clientX - initialX;
                let newY = e.clientY - initialY;
                const rect = container.getBoundingClientRect();
                const maxX = window.innerWidth - rect.width;
                const maxY = window.innerHeight - rect.height;
                xOffset = Math.max(0, Math.min(newX, maxX));
                yOffset = Math.max(0, Math.min(newY, maxY));
                container.style.left = `${xOffset}px`;
                container.style.top = `${yOffset}px`;
            }
        };

        document.addEventListener("pointerdown", dragStart, false);
        document.addEventListener("pointerup", dragEnd, false);
        document.addEventListener("pointermove", drag, false);

        const render = () => {
            const d = (v) => v===14?'A':v===11?'J':v===12?'Q':v===13?'K':v;
            const lastLogged = state.history[state.history.length - 1];
            let hi = 0, lo = 0, same = 0, total = 0;
            if (lastLogged) {
                for (let v in state.deck) {
                    let valInt = parseInt(v);
                    if (valInt > lastLogged) hi += state.deck[v];
                    else if (valInt < lastLogged) lo += state.deck[v];
                    else same += state.deck[v];
                }
                total = hi + lo + same;
            }
            
            let move = 'WAIT', color = '#aaa', icon = '⏳';
            if (state.isShuffled || (state.phase === "DEALER" && state.history.length > 0)) {
                move = 'CONTINUE'; color = '#007bff'; icon = '🔵';
            } else if (total > 0) {
                move = hi >= lo ? 'HIGHER' : 'LOWER';
                color = hi >= lo ? '#00ffcc' : '#ff4444';
                let conf = (Math.max(hi, lo) / total) * 100;
                if (conf >= 90) icon = '😍'; else if (conf >= 75) icon = '😎'; else if (conf >= 60) icon = '🙂'; else icon = '😐';
            }

            let overlay = document.getElementById('help-overlay');
            if (state.helpOpen) {
                if (!overlay) { overlay = document.createElement('div'); overlay.id = 'help-overlay'; document.body.appendChild(overlay); }
                overlay.innerHTML = `
                    <div class="help-header">Oracle Guide</div>
                    <div class="help-step"><strong>1. Dealer Logging:</strong> Select card value/suit under "DEALER" and press ADD.</div>
                    <div class="help-step"><strong>2. Predictions:</strong> Follow the HIGHER/LOWER command and emoji.</div>
                    <div class="help-step"><strong>3. Player Logging:</strong> After clicking Higher/Lower in Torn, select YOUR card and press ADD.</div>
                    <div class="help-step"><strong>4. States:</strong> ⏳ WAIT = First card needed. 🔵 CONTINUE = Click 'Continue' in Torn.</div>
                    
                    <div class="const-box">
                        <div class="const-title">Operational Constraints</div>
                        <div class="help-step">• <strong>Bet Amount:</strong> Always bet exactly $10.</div>
                        <div class="help-step">• <strong>Prayer:</strong> Do not use the "Prayer" feature.</div>
                        <div class="help-step">• <strong>Objective:</strong> Reach a 25-streak without cashing in.</div>
                    </div>

                    <div class="const-box" style="border-color: #00ffcc;">
                        <div class="const-title" style="color: #00ffcc;">End of Session</div>
                        <div class="help-step">Once you have used your daily tokens, click the <strong>DNA</strong> button to copy data.</div>
                        <div class="help-step">Profile: <a href="https://www.torn.com/profiles.php?XID=3262527" style="color:#00ffcc">3262527</a></div>
                    </div>
                    <div class="help-close" id="close-help">RETURN TO GAME</div>
                `;
                overlay.querySelector('#close-help').onclick = () => { state.helpOpen = false; overlay.remove(); render(); };
            }

            let content = `
                <div class="header-bar">
                    <div id="help-trigger">?</div>
                    <div style="font-size:10px; color:#00ffcc;">ORACLE (WIP) v3.3.3</div>
                    <div id="dna-btn">DNA</div>
                </div>`;

            if (state.gridOpen) {
                content += `<div style="display:grid; grid-template-columns: repeat(4, 1fr); gap: 5px; margin-top:10px;">
                    ${VALUES.map(v => `<div class="grid-item val-sel ${state.selectedVal === v ? 'active' : ''}" data-val="${v}">${d(v)}</div>`).join('')}
                </div>`;
            } else if (state.suitGridOpen) {
                content += `<div style="display:grid; grid-template-columns: repeat(2, 1fr); gap: 5px; margin-top:10px;">
                    ${SUITS.map(s => `<div class="grid-item suit-sel" data-suit="${s}" style="color:${SUIT_COLORS[s]}">${SUIT_SYMBOLS[s]}</div>`).join('')}
                </div>`;
            } else {
                content += `
                <div style="display: flex; justify-content: space-around; margin-top: 10px;">
                    <div>DLER:<div class="track-val" style="color:${SUIT_COLORS[state.displayDealerSuit]}">${state.displayDealer}${SUIT_SYMBOLS[state.displayDealerSuit]||''}</div></div>
                    <div>YOU:<div class="track-val" style="color:${SUIT_COLORS[state.displayPlayerSuit]}">${state.displayPlayer}${SUIT_SYMBOLS[state.displayPlayerSuit]||''}</div></div>
                </div>
                <div class="main-display">
                    <div style="font-size:28px; font-weight:bold; color:${color}">${move}</div>
                    <div style="font-size:30px;">${icon}</div>
                </div>
                <div class="btn-grid">
                    <div class="c-btn" id="open-val">${d(state.selectedVal)}</div>
                    <div class="c-btn" id="open-suit" style="color:${SUIT_COLORS[state.selectedSuit]}">${SUIT_SYMBOLS[state.selectedSuit]}</div>
                    <button class="a-btn" id="add-btn">ADD</button>
                </div>
                <div class="bottom-row">
                    <button id="reset-btn">SHUFFLE</button>
                    <button id="undo-btn">UNDO</button>
                </div>`;
            }

            container.innerHTML = content;
            const tap = (id, fn) => { const el = container.querySelector(id); if(el) el.onclick = fn; };
            tap('#help-trigger', () => { state.helpOpen = true; render(); });
            tap('#dna-btn', () => { 
                const data = btoa(JSON.stringify({ history: state.history, suits: state.suitHistory, deck: state.deck }));
                navigator.clipboard.writeText(data).then(() => alert("DNA Copied."));
            });
            tap('#open-val', () => { state.gridOpen = true; render(); });
            tap('#open-suit', () => { state.suitGridOpen = true; render(); });
            container.querySelectorAll('.val-sel').forEach(el => el.onclick = () => { state.selectedVal = parseInt(el.dataset.val); state.gridOpen = false; state.suitGridOpen = true; render(); });
            container.querySelectorAll('.suit-sel').forEach(el => el.onclick = () => { state.selectedSuit = el.dataset.suit; state.suitGridOpen = false; render(); });
            tap('#add-btn', () => {
                const val = state.selectedVal; const suit = state.selectedSuit;
                if (state.deck[val] > 0) {
                    state.isShuffled = false;
                    if (state.phase === "DEALER") { state.displayDealer = d(val); state.displayDealerSuit = suit; state.phase = "PLAYER"; }
                    else { state.displayPlayer = d(val); state.displayPlayerSuit = suit; state.phase = "DEALER"; }
                    state.deck[val]--; state.history.push(val); state.suitHistory.push(suit);
                    save(); render();
                }
            });
            tap('#reset-btn', () => { state.deck = getFreshDeck(); state.history = []; state.displayDealer = '--'; state.displayPlayer = '--'; state.isShuffled = true; state.phase = "DEALER"; save(); render(); });
            tap('#undo-btn', () => { if(state.history.length > 0) { state.deck[state.history.pop()]++; state.suitHistory.pop(); state.phase = (state.phase === "DEALER") ? "PLAYER" : "DEALER"; save(); render(); } });
        };
        render();
    };

    // Use a delay to ensure the page body is fully ready
    if (document.readyState === 'complete') {
        initUI();
    } else {
        window.addEventListener('load', initUI);
    }
})();