- // ==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