Torn High-Low Helper (Advanced)

Hides incorrect button for high-low based on remaining cards.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Torn High-Low Helper (Advanced)
// @namespace    http://tampermonkey.net/
// @version      2.0
// @description  Hides incorrect button for high-low based on remaining cards.
// @author       Lollipop :)
// @match        https://www.torn.com/page.php?sid=highlow
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    const CHECK_INTERVAL = 300; // Check slightly more often
    const MAX_WAIT_TIME = 15000; // Wait a maximum of 15 seconds

    let checkTimer = null;
    let waitTime = 0;

    // --- Game State ---
    let remainingDeck = {}; // Stores count of each card value (2-14)
    let lastDealerCardValue = null;
    let lastPlayerCardValue = null; // To avoid double-counting on rapid mutations
    let isGameActive = false; // Track if we are in an active game round

    // --- Card Value Mapping (Ace High) ---
    const CARD_VALUES = {
        '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9, '10': 10,
        'J': 11, 'Q': 12, 'K': 13, 'A': 14
    };
    const ALL_RANKS = Object.keys(CARD_VALUES); // ['2', '3', ..., 'A']

    // --- Helper Function to Parse Card Value ---
    function getCardValue(ratingText) {
        if (!ratingText) return null;
        const text = ratingText.trim().toUpperCase();
        return CARD_VALUES[text] || null; // Return numeric value or null
    }

    // --- Reset Deck for New Game ---
    function resetDeck() {
        console.log("Resetting deck for new game.");
        remainingDeck = {};
        for (const rank in CARD_VALUES) {
            remainingDeck[CARD_VALUES[rank]] = 4; // 4 suits per rank
        }
        lastDealerCardValue = null;
        lastPlayerCardValue = null;
        isGameActive = true; // Mark game as active after reset (usually follows start)
        console.log("Initial Deck:", JSON.stringify(remainingDeck));
    }

    // --- Update Deck Count When Card is Revealed ---
    function removeCardFromDeck(cardValue) {
        if (cardValue !== null && remainingDeck[cardValue] > 0) {
            remainingDeck[cardValue]--;
             console.log(`Removed card ${Object.keys(CARD_VALUES).find(key => CARD_VALUES[key] === cardValue)} (${cardValue}). Remaining: ${remainingDeck[cardValue]}`);
             // console.log("Current Deck State:", JSON.stringify(remainingDeck));
             return true; // Card was successfully removed
        } else if (cardValue !== null) {
            console.warn(`Attempted to remove card ${Object.keys(CARD_VALUES).find(key => CARD_VALUES[key] === cardValue)} (${cardValue}), but count is already ${remainingDeck[cardValue] ?? 'undefined'}. Deck might be out of sync.`);
        }
        return false; // Card not removed (either invalid or count was 0)
    }

     // --- Calculate Higher/Lower Counts ---
    function calculateProbabilities(dealerValue) {
        if (dealerValue === null || !isGameActive) {
            return { lower: 0, higher: 0, totalRemaining: 0 }; // Not enough info or game not active
        }

        let lowerCount = 0;
        let higherCount = 0;
        let totalRemaining = 0;

        for (const value in remainingDeck) {
            const numericValue = parseInt(value, 10);
            const count = remainingDeck[value];
            totalRemaining += count;
            if (numericValue < dealerValue) {
                lowerCount += count;
            } else if (numericValue > dealerValue) {
                higherCount += count;
            }
            // Cards with equal value don't count for higher/lower
        }
         console.log(`Dealer: ${dealerValue}. Remaining: Lower=${lowerCount}, Higher=${higherCount}, Total=${totalRemaining}`);
        return { lower: lowerCount, higher: higherCount, totalRemaining: totalRemaining };
    }


    // --- Function to perform the check and update logic ---
    function checkAndUpdateGame() {
        // Select elements every time to ensure they are current
        const dealerCardElement = document.querySelector('.dealer-card');
        const playerCardElement = document.querySelector('.you-card');
        const lowerButton = document.querySelector('.actions-wrap .action-btn-wrap.low');
        const higherButton = document.querySelector('.actions-wrap .action-btn-wrap.high');
        const startButton = document.querySelector('.action-btn-wrap.startGame'); // For reset detection
        const resultWrap = document.querySelector('.game-result-wrap'); // For reset detection
        const highlowWrap = document.querySelector('.highlow-main-wrap'); // To check if game interface is visible

        if (!dealerCardElement || !playerCardElement || !lowerButton || !higherButton || !highlowWrap) {
            console.warn("Core game elements not found during update check.");
            return; // Should not happen if waitForElements worked, but safety first
        }

        // --- Check for Game End/Reset Conditions ---
        // If start button is visible OR result wrap is visible, the active game has ended.
        // We also check if the main highlow wrap is hidden (often happens briefly between rounds)
        const gameEnded = (startButton && startButton.offsetParent !== null) ||
                          (resultWrap && resultWrap.offsetParent !== null) ||
                          (highlowWrap.offsetParent === null);

        if (gameEnded && isGameActive) {
            console.log("Game appears to have ended or reset. Setting isGameActive to false.");
            isGameActive = false;
            // Reset button visibility to default (hidden until next dealer card)
            lowerButton.style.display = 'none';
            higherButton.style.display = 'none';
            lastDealerCardValue = null; // Clear last known cards
            lastPlayerCardValue = null;
        }

        // --- If game is not active, do nothing further ---
        if (!isGameActive) {
            // Make sure buttons remain hidden if game isn't active
             if (lowerButton.style.display !== 'none') lowerButton.style.display = 'none';
             if (higherButton.style.display !== 'none') higherButton.style.display = 'none';
            // console.log("Game not active. Waiting for Start Game.");
            return;
        }

        // --- Process Current Cards ---
        const currentDealerRatingElement = dealerCardElement?.querySelector('span.rating');
        const currentPlayerRatingElement = playerCardElement?.querySelector('span.rating');

        const dealerRatingText = currentDealerRatingElement?.textContent;
        const playerRatingText = currentPlayerRatingElement?.textContent?.trim() ?? '';

        const currentDealerValue = getCardValue(dealerRatingText);
        const currentPlayerValue = getCardValue(playerRatingText);

        // --- Track Revealed Cards ---
        // Only remove if the value changed and is valid
        if (currentDealerValue !== null && currentDealerValue !== lastDealerCardValue) {
            console.log(`New Dealer Card: ${dealerRatingText} (${currentDealerValue})`);
            removeCardFromDeck(currentDealerValue);
            lastDealerCardValue = currentDealerValue;
            lastPlayerCardValue = null; // Reset player card tracking when dealer changes
        }

        // Player card is revealed (end of a round, before next dealer card)
        if (currentPlayerValue !== null && currentPlayerValue !== lastPlayerCardValue) {
             console.log(`Player Card Revealed: ${playerRatingText} (${currentPlayerValue})`);
             removeCardFromDeck(currentPlayerValue);
             lastPlayerCardValue = currentPlayerValue;
             // Hide buttons as choice is made
             lowerButton.style.display = 'none';
             higherButton.style.display = 'none';
             return; // Don't make prediction when player card is shown
        }

         // --- Player Card is hidden, Dealer Card is shown: Make Prediction ---
        if (playerRatingText === '' && currentDealerValue !== null) {
             const { lower, higher, totalRemaining } = calculateProbabilities(currentDealerValue);

             if (totalRemaining === 0 && isGameActive) {
                 console.warn("No cards left in tracked deck, but game is active? Deck might be out of sync.");
                  // Fallback to simple strategy if deck is empty? Or hide both? Hide based on simple for now.
                  if (currentDealerValue <= 7) { // Simple fallback
                    lowerButton.style.display = 'none';
                    higherButton.style.display = 'inline-block';
                 } else {
                     higherButton.style.display = 'none';
                     lowerButton.style.display = 'inline-block';
                 }
                 return;
             }

             // Decision Logic: Hide the less likely button
             if (higher > lower) {
                 // More higher cards remaining -> Suggest 'Higher'
                 lowerButton.style.display = 'none';
                 higherButton.style.display = 'inline-block';
                 console.log(`Prediction: HIGHER (H: ${higher} > L: ${lower})`);
             } else if (lower > higher) {
                 // More lower cards remaining -> Suggest 'Lower'
                 higherButton.style.display = 'none';
                 lowerButton.style.display = 'inline-block';
                 console.log(`Prediction: LOWER (L: ${lower} > H: ${higher})`);
             } else {
                 // Equal probability - Use simple strategy as tie-breaker (7 is middle)
                 // Or maybe show both? Hiding one is the request.
                 console.log(`Prediction: EQUAL (L: ${lower}, H: ${higher}). Using simple tie-breaker.`);
                 if (currentDealerValue <= 7) { // Favor higher for <= 7
                    lowerButton.style.display = 'none';
                    higherButton.style.display = 'inline-block';
                 } else { // Favor lower for > 7
                     higherButton.style.display = 'none';
                     lowerButton.style.display = 'inline-block';
                 }
             }
        } else if (playerRatingText === '' && currentDealerValue === null) {
            // No dealer card yet (very start of game after clicking start)
            lowerButton.style.display = 'none';
            higherButton.style.display = 'none';
        }
        // (Case where player card is revealed is handled earlier)
    }

    // --- Function to apply styles and setup observer ---
    function initializeGameLogic() {
        console.log("Torn High-Low Helper (Advanced): Core game elements found. Initializing...");

        const startButton = document.querySelector('.action-btn-wrap.startGame');

        // --- Apply one-time styles (Start button position) ---
        if (startButton) {
            try {
                startButton.style.position = 'relative';
                startButton.style.top = '257px'; // Adjust as needed
                startButton.style.left = '-50px'; // Adjust as needed
                console.log("Start button repositioned.");

                // --- Add listener to Start button for deck reset ---
                startButton.addEventListener('click', () => {
                    console.log("Start Game button clicked - Resetting deck.");
                    resetDeck();
                    // No need to call checkAndUpdateGame here, mutation observer will catch card changes
                });
                 console.log("Added click listener to Start button.");

            } catch (e) {
                console.error("Error styling or adding listener to Start button:", e);
            }
        } else {
            console.log('Start Game button not found on initial load (likely game in progress or already finished).');
             // Attempt to determine initial state if possible (might be unreliable)
             const dealerCardElement = document.querySelector('.dealer-card span.rating');
             if(dealerCardElement && dealerCardElement.textContent.trim() !== '') {
                 console.log("Game seems to be in progress. Initial deck state might be inaccurate until next game.");
                 isGameActive = true; // Assume active, but deck is unknown
                 // We won't have past cards, so prediction will be off until reset.
             } else {
                 isGameActive = false;
             }
        }

        // Initial deck reset if we are definitely on the start screen
        if (startButton && startButton.offsetParent !== null) {
             resetDeck(); // Reset deck state
             isGameActive = false; // Not active until *after* start is clicked
        }


        // --- Setup MutationObserver ---
        // Observe a parent container that includes cards and buttons for state changes
        const gameContainer = document.querySelector('.highlow-main-wrap'); // Or a more specific wrapper if available
        if (!gameContainer) {
             console.error("Cannot find main game container for MutationObserver!");
             return;
        }

        const observer = new MutationObserver(mutationsList => {
            // Use a debounce/throttle mechanism if performance becomes an issue
            // For now, just call the update function on any observed change
            try {
                // Check if the start button appeared (indicates game ended)
                const currentStartButton = document.querySelector('.action-btn-wrap.startGame');
                if (currentStartButton && currentStartButton.offsetParent !== null && isGameActive) {
                    console.log("Mutation detected Start Button visibility - flagging game end.");
                    isGameActive = false; // Game ended
                    // No need to reset deck here, start button click handler does that.
                }
                 // Check if result screen appeared
                 const currentResultWrap = document.querySelector('.game-result-wrap');
                 if (currentResultWrap && currentResultWrap.offsetParent !== null && isGameActive) {
                     console.log("Mutation detected Result Wrap visibility - flagging game end.");
                     isGameActive = false; // Game ended
                 }


                // Re-run the check on any relevant mutation inside the container
                checkAndUpdateGame();
            } catch(e) {
                 console.error("Error during MutationObserver callback:", e);
            }
        });

        // Configuration: watch for changes in children (cards appearing/changing)
        // and subtree (text changes within spans)
        const config = {
            childList: true,
            subtree: true,
            characterData: true // Needed for card rank text changes
        };

        try {
            observer.observe(gameContainer, config);
            console.log("MutationObserver started, watching game container.");
        } catch(e) {
            console.error("Error starting MutationObserver:", e);
        }

        // --- Initial Check ---
        // Run once after setup to set initial state based on current DOM
        // Reset deck if start button is visible initially
         if (startButton && startButton.offsetParent !== null) {
             resetDeck();
             isGameActive = false;
         } else {
             // If not on start screen, try to determine if game is active
             const dealerCardElement = document.querySelector('.dealer-card span.rating');
             const playerCardElement = document.querySelector('.you-card span.rating');
             if(dealerCardElement && dealerCardElement.textContent.trim() !== '') {
                 console.log("Initial check: Game seems in progress.");
                 // We *don't* know the deck state accurately here if loaded mid-game.
                 // Initialize an empty deck or full deck? Let's assume full deck but mark as potentially inaccurate.
                 resetDeck(); // Reset to full deck
                 console.warn("Deck reset, but history is unknown as script loaded mid-game.");
                 // Manually remove current dealer/player cards if visible?
                 const initialDealerVal = getCardValue(dealerCardElement.textContent);
                 const initialPlayerVal = getCardValue(playerCardElement?.textContent);
                 if (initialDealerVal) removeCardFromDeck(initialDealerVal);
                 if (initialPlayerVal) removeCardFromDeck(initialPlayerVal);

                 isGameActive = true;
                 checkAndUpdateGame(); // Run prediction logic
             } else {
                 console.log("Initial check: Game not started or finished.");
                 isGameActive = false;
                 resetDeck(); // Ensure deck is ready for when start is clicked
                 checkAndUpdateGame(); // Hide buttons etc.
             }
         }


    }


    // --- Wait for core elements to exist before initializing ---
    function waitForElements() {
        // Check for essential elements needed for the script's core logic
         if (document.querySelector('.highlow-main-wrap') && // Main container is crucial
            document.querySelector('.dealer-card') &&
            document.querySelector('.you-card') &&
            document.querySelector('.actions-wrap .action-btn-wrap.low') &&
            document.querySelector('.actions-wrap .action-btn-wrap.high')
           )
        {
            clearInterval(checkTimer); // Stop checking
            initializeGameLogic(); // Run the main setup
        } else {
            waitTime += CHECK_INTERVAL;
            if (waitTime >= MAX_WAIT_TIME) {
                clearInterval(checkTimer);
                console.error("Torn High-Low Helper (Advanced): Timed out waiting for game elements to load.");
            } else {
                 console.log("Waiting for game elements...");
            }
        }
    }

    // Start the check
    checkTimer = setInterval(waitForElements, CHECK_INTERVAL);

})(); // End of IIFE wrapper