Torn High-Low Helper (Advanced)

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

  1. // ==UserScript==
  2. // @name Torn High-Low Helper (Advanced)
  3. // @namespace http://tampermonkey.net/
  4. // @version 2.0
  5. // @description Hides incorrect button for high-low based on remaining cards.
  6. // @author Lollipop :)
  7. // @match https://www.torn.com/page.php?sid=highlow
  8. // @grant none
  9. // ==/UserScript==
  10.  
  11. (function() {
  12. 'use strict';
  13.  
  14. const CHECK_INTERVAL = 300; // Check slightly more often
  15. const MAX_WAIT_TIME = 15000; // Wait a maximum of 15 seconds
  16.  
  17. let checkTimer = null;
  18. let waitTime = 0;
  19.  
  20. // --- Game State ---
  21. let remainingDeck = {}; // Stores count of each card value (2-14)
  22. let lastDealerCardValue = null;
  23. let lastPlayerCardValue = null; // To avoid double-counting on rapid mutations
  24. let isGameActive = false; // Track if we are in an active game round
  25.  
  26. // --- Card Value Mapping (Ace High) ---
  27. const CARD_VALUES = {
  28. '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9, '10': 10,
  29. 'J': 11, 'Q': 12, 'K': 13, 'A': 14
  30. };
  31. const ALL_RANKS = Object.keys(CARD_VALUES); // ['2', '3', ..., 'A']
  32.  
  33. // --- Helper Function to Parse Card Value ---
  34. function getCardValue(ratingText) {
  35. if (!ratingText) return null;
  36. const text = ratingText.trim().toUpperCase();
  37. return CARD_VALUES[text] || null; // Return numeric value or null
  38. }
  39.  
  40. // --- Reset Deck for New Game ---
  41. function resetDeck() {
  42. console.log("Resetting deck for new game.");
  43. remainingDeck = {};
  44. for (const rank in CARD_VALUES) {
  45. remainingDeck[CARD_VALUES[rank]] = 4; // 4 suits per rank
  46. }
  47. lastDealerCardValue = null;
  48. lastPlayerCardValue = null;
  49. isGameActive = true; // Mark game as active after reset (usually follows start)
  50. console.log("Initial Deck:", JSON.stringify(remainingDeck));
  51. }
  52.  
  53. // --- Update Deck Count When Card is Revealed ---
  54. function removeCardFromDeck(cardValue) {
  55. if (cardValue !== null && remainingDeck[cardValue] > 0) {
  56. remainingDeck[cardValue]--;
  57. console.log(`Removed card ${Object.keys(CARD_VALUES).find(key => CARD_VALUES[key] === cardValue)} (${cardValue}). Remaining: ${remainingDeck[cardValue]}`);
  58. // console.log("Current Deck State:", JSON.stringify(remainingDeck));
  59. return true; // Card was successfully removed
  60. } else if (cardValue !== null) {
  61. 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.`);
  62. }
  63. return false; // Card not removed (either invalid or count was 0)
  64. }
  65.  
  66. // --- Calculate Higher/Lower Counts ---
  67. function calculateProbabilities(dealerValue) {
  68. if (dealerValue === null || !isGameActive) {
  69. return { lower: 0, higher: 0, totalRemaining: 0 }; // Not enough info or game not active
  70. }
  71.  
  72. let lowerCount = 0;
  73. let higherCount = 0;
  74. let totalRemaining = 0;
  75.  
  76. for (const value in remainingDeck) {
  77. const numericValue = parseInt(value, 10);
  78. const count = remainingDeck[value];
  79. totalRemaining += count;
  80. if (numericValue < dealerValue) {
  81. lowerCount += count;
  82. } else if (numericValue > dealerValue) {
  83. higherCount += count;
  84. }
  85. // Cards with equal value don't count for higher/lower
  86. }
  87. console.log(`Dealer: ${dealerValue}. Remaining: Lower=${lowerCount}, Higher=${higherCount}, Total=${totalRemaining}`);
  88. return { lower: lowerCount, higher: higherCount, totalRemaining: totalRemaining };
  89. }
  90.  
  91.  
  92. // --- Function to perform the check and update logic ---
  93. function checkAndUpdateGame() {
  94. // Select elements every time to ensure they are current
  95. const dealerCardElement = document.querySelector('.dealer-card');
  96. const playerCardElement = document.querySelector('.you-card');
  97. const lowerButton = document.querySelector('.actions-wrap .action-btn-wrap.low');
  98. const higherButton = document.querySelector('.actions-wrap .action-btn-wrap.high');
  99. const startButton = document.querySelector('.action-btn-wrap.startGame'); // For reset detection
  100. const resultWrap = document.querySelector('.game-result-wrap'); // For reset detection
  101. const highlowWrap = document.querySelector('.highlow-main-wrap'); // To check if game interface is visible
  102.  
  103. if (!dealerCardElement || !playerCardElement || !lowerButton || !higherButton || !highlowWrap) {
  104. console.warn("Core game elements not found during update check.");
  105. return; // Should not happen if waitForElements worked, but safety first
  106. }
  107.  
  108. // --- Check for Game End/Reset Conditions ---
  109. // If start button is visible OR result wrap is visible, the active game has ended.
  110. // We also check if the main highlow wrap is hidden (often happens briefly between rounds)
  111. const gameEnded = (startButton && startButton.offsetParent !== null) ||
  112. (resultWrap && resultWrap.offsetParent !== null) ||
  113. (highlowWrap.offsetParent === null);
  114.  
  115. if (gameEnded && isGameActive) {
  116. console.log("Game appears to have ended or reset. Setting isGameActive to false.");
  117. isGameActive = false;
  118. // Reset button visibility to default (hidden until next dealer card)
  119. lowerButton.style.display = 'none';
  120. higherButton.style.display = 'none';
  121. lastDealerCardValue = null; // Clear last known cards
  122. lastPlayerCardValue = null;
  123. }
  124.  
  125. // --- If game is not active, do nothing further ---
  126. if (!isGameActive) {
  127. // Make sure buttons remain hidden if game isn't active
  128. if (lowerButton.style.display !== 'none') lowerButton.style.display = 'none';
  129. if (higherButton.style.display !== 'none') higherButton.style.display = 'none';
  130. // console.log("Game not active. Waiting for Start Game.");
  131. return;
  132. }
  133.  
  134. // --- Process Current Cards ---
  135. const currentDealerRatingElement = dealerCardElement?.querySelector('span.rating');
  136. const currentPlayerRatingElement = playerCardElement?.querySelector('span.rating');
  137.  
  138. const dealerRatingText = currentDealerRatingElement?.textContent;
  139. const playerRatingText = currentPlayerRatingElement?.textContent?.trim() ?? '';
  140.  
  141. const currentDealerValue = getCardValue(dealerRatingText);
  142. const currentPlayerValue = getCardValue(playerRatingText);
  143.  
  144. // --- Track Revealed Cards ---
  145. // Only remove if the value changed and is valid
  146. if (currentDealerValue !== null && currentDealerValue !== lastDealerCardValue) {
  147. console.log(`New Dealer Card: ${dealerRatingText} (${currentDealerValue})`);
  148. removeCardFromDeck(currentDealerValue);
  149. lastDealerCardValue = currentDealerValue;
  150. lastPlayerCardValue = null; // Reset player card tracking when dealer changes
  151. }
  152.  
  153. // Player card is revealed (end of a round, before next dealer card)
  154. if (currentPlayerValue !== null && currentPlayerValue !== lastPlayerCardValue) {
  155. console.log(`Player Card Revealed: ${playerRatingText} (${currentPlayerValue})`);
  156. removeCardFromDeck(currentPlayerValue);
  157. lastPlayerCardValue = currentPlayerValue;
  158. // Hide buttons as choice is made
  159. lowerButton.style.display = 'none';
  160. higherButton.style.display = 'none';
  161. return; // Don't make prediction when player card is shown
  162. }
  163.  
  164. // --- Player Card is hidden, Dealer Card is shown: Make Prediction ---
  165. if (playerRatingText === '' && currentDealerValue !== null) {
  166. const { lower, higher, totalRemaining } = calculateProbabilities(currentDealerValue);
  167.  
  168. if (totalRemaining === 0 && isGameActive) {
  169. console.warn("No cards left in tracked deck, but game is active? Deck might be out of sync.");
  170. // Fallback to simple strategy if deck is empty? Or hide both? Hide based on simple for now.
  171. if (currentDealerValue <= 7) { // Simple fallback
  172. lowerButton.style.display = 'none';
  173. higherButton.style.display = 'inline-block';
  174. } else {
  175. higherButton.style.display = 'none';
  176. lowerButton.style.display = 'inline-block';
  177. }
  178. return;
  179. }
  180.  
  181. // Decision Logic: Hide the less likely button
  182. if (higher > lower) {
  183. // More higher cards remaining -> Suggest 'Higher'
  184. lowerButton.style.display = 'none';
  185. higherButton.style.display = 'inline-block';
  186. console.log(`Prediction: HIGHER (H: ${higher} > L: ${lower})`);
  187. } else if (lower > higher) {
  188. // More lower cards remaining -> Suggest 'Lower'
  189. higherButton.style.display = 'none';
  190. lowerButton.style.display = 'inline-block';
  191. console.log(`Prediction: LOWER (L: ${lower} > H: ${higher})`);
  192. } else {
  193. // Equal probability - Use simple strategy as tie-breaker (7 is middle)
  194. // Or maybe show both? Hiding one is the request.
  195. console.log(`Prediction: EQUAL (L: ${lower}, H: ${higher}). Using simple tie-breaker.`);
  196. if (currentDealerValue <= 7) { // Favor higher for <= 7
  197. lowerButton.style.display = 'none';
  198. higherButton.style.display = 'inline-block';
  199. } else { // Favor lower for > 7
  200. higherButton.style.display = 'none';
  201. lowerButton.style.display = 'inline-block';
  202. }
  203. }
  204. } else if (playerRatingText === '' && currentDealerValue === null) {
  205. // No dealer card yet (very start of game after clicking start)
  206. lowerButton.style.display = 'none';
  207. higherButton.style.display = 'none';
  208. }
  209. // (Case where player card is revealed is handled earlier)
  210. }
  211.  
  212. // --- Function to apply styles and setup observer ---
  213. function initializeGameLogic() {
  214. console.log("Torn High-Low Helper (Advanced): Core game elements found. Initializing...");
  215.  
  216. const startButton = document.querySelector('.action-btn-wrap.startGame');
  217.  
  218. // --- Apply one-time styles (Start button position) ---
  219. if (startButton) {
  220. try {
  221. startButton.style.position = 'relative';
  222. startButton.style.top = '257px'; // Adjust as needed
  223. startButton.style.left = '-50px'; // Adjust as needed
  224. console.log("Start button repositioned.");
  225.  
  226. // --- Add listener to Start button for deck reset ---
  227. startButton.addEventListener('click', () => {
  228. console.log("Start Game button clicked - Resetting deck.");
  229. resetDeck();
  230. // No need to call checkAndUpdateGame here, mutation observer will catch card changes
  231. });
  232. console.log("Added click listener to Start button.");
  233.  
  234. } catch (e) {
  235. console.error("Error styling or adding listener to Start button:", e);
  236. }
  237. } else {
  238. console.log('Start Game button not found on initial load (likely game in progress or already finished).');
  239. // Attempt to determine initial state if possible (might be unreliable)
  240. const dealerCardElement = document.querySelector('.dealer-card span.rating');
  241. if(dealerCardElement && dealerCardElement.textContent.trim() !== '') {
  242. console.log("Game seems to be in progress. Initial deck state might be inaccurate until next game.");
  243. isGameActive = true; // Assume active, but deck is unknown
  244. // We won't have past cards, so prediction will be off until reset.
  245. } else {
  246. isGameActive = false;
  247. }
  248. }
  249.  
  250. // Initial deck reset if we are definitely on the start screen
  251. if (startButton && startButton.offsetParent !== null) {
  252. resetDeck(); // Reset deck state
  253. isGameActive = false; // Not active until *after* start is clicked
  254. }
  255.  
  256.  
  257. // --- Setup MutationObserver ---
  258. // Observe a parent container that includes cards and buttons for state changes
  259. const gameContainer = document.querySelector('.highlow-main-wrap'); // Or a more specific wrapper if available
  260. if (!gameContainer) {
  261. console.error("Cannot find main game container for MutationObserver!");
  262. return;
  263. }
  264.  
  265. const observer = new MutationObserver(mutationsList => {
  266. // Use a debounce/throttle mechanism if performance becomes an issue
  267. // For now, just call the update function on any observed change
  268. try {
  269. // Check if the start button appeared (indicates game ended)
  270. const currentStartButton = document.querySelector('.action-btn-wrap.startGame');
  271. if (currentStartButton && currentStartButton.offsetParent !== null && isGameActive) {
  272. console.log("Mutation detected Start Button visibility - flagging game end.");
  273. isGameActive = false; // Game ended
  274. // No need to reset deck here, start button click handler does that.
  275. }
  276. // Check if result screen appeared
  277. const currentResultWrap = document.querySelector('.game-result-wrap');
  278. if (currentResultWrap && currentResultWrap.offsetParent !== null && isGameActive) {
  279. console.log("Mutation detected Result Wrap visibility - flagging game end.");
  280. isGameActive = false; // Game ended
  281. }
  282.  
  283.  
  284. // Re-run the check on any relevant mutation inside the container
  285. checkAndUpdateGame();
  286. } catch(e) {
  287. console.error("Error during MutationObserver callback:", e);
  288. }
  289. });
  290.  
  291. // Configuration: watch for changes in children (cards appearing/changing)
  292. // and subtree (text changes within spans)
  293. const config = {
  294. childList: true,
  295. subtree: true,
  296. characterData: true // Needed for card rank text changes
  297. };
  298.  
  299. try {
  300. observer.observe(gameContainer, config);
  301. console.log("MutationObserver started, watching game container.");
  302. } catch(e) {
  303. console.error("Error starting MutationObserver:", e);
  304. }
  305.  
  306. // --- Initial Check ---
  307. // Run once after setup to set initial state based on current DOM
  308. // Reset deck if start button is visible initially
  309. if (startButton && startButton.offsetParent !== null) {
  310. resetDeck();
  311. isGameActive = false;
  312. } else {
  313. // If not on start screen, try to determine if game is active
  314. const dealerCardElement = document.querySelector('.dealer-card span.rating');
  315. const playerCardElement = document.querySelector('.you-card span.rating');
  316. if(dealerCardElement && dealerCardElement.textContent.trim() !== '') {
  317. console.log("Initial check: Game seems in progress.");
  318. // We *don't* know the deck state accurately here if loaded mid-game.
  319. // Initialize an empty deck or full deck? Let's assume full deck but mark as potentially inaccurate.
  320. resetDeck(); // Reset to full deck
  321. console.warn("Deck reset, but history is unknown as script loaded mid-game.");
  322. // Manually remove current dealer/player cards if visible?
  323. const initialDealerVal = getCardValue(dealerCardElement.textContent);
  324. const initialPlayerVal = getCardValue(playerCardElement?.textContent);
  325. if (initialDealerVal) removeCardFromDeck(initialDealerVal);
  326. if (initialPlayerVal) removeCardFromDeck(initialPlayerVal);
  327.  
  328. isGameActive = true;
  329. checkAndUpdateGame(); // Run prediction logic
  330. } else {
  331. console.log("Initial check: Game not started or finished.");
  332. isGameActive = false;
  333. resetDeck(); // Ensure deck is ready for when start is clicked
  334. checkAndUpdateGame(); // Hide buttons etc.
  335. }
  336. }
  337.  
  338.  
  339. }
  340.  
  341.  
  342. // --- Wait for core elements to exist before initializing ---
  343. function waitForElements() {
  344. // Check for essential elements needed for the script's core logic
  345. if (document.querySelector('.highlow-main-wrap') && // Main container is crucial
  346. document.querySelector('.dealer-card') &&
  347. document.querySelector('.you-card') &&
  348. document.querySelector('.actions-wrap .action-btn-wrap.low') &&
  349. document.querySelector('.actions-wrap .action-btn-wrap.high')
  350. )
  351. {
  352. clearInterval(checkTimer); // Stop checking
  353. initializeGameLogic(); // Run the main setup
  354. } else {
  355. waitTime += CHECK_INTERVAL;
  356. if (waitTime >= MAX_WAIT_TIME) {
  357. clearInterval(checkTimer);
  358. console.error("Torn High-Low Helper (Advanced): Timed out waiting for game elements to load.");
  359. } else {
  360. console.log("Waiting for game elements...");
  361. }
  362. }
  363. }
  364.  
  365. // Start the check
  366. checkTimer = setInterval(waitForElements, CHECK_INTERVAL);
  367.  
  368. })(); // End of IIFE wrapper