您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Displays real-time basic strategy advice for Blackjack on both desktop and Torn PDA.
// ==UserScript== // @name Torn Blackjack Assist // @namespace torn.blackjack.assist // @version 2.1 // @description Displays real-time basic strategy advice for Blackjack on both desktop and Torn PDA. // @author eaksquad // @match https://www.torn.com/page.php?sid=blackjack* // @match https://www.torn.com/pda.php*step=blackjack* // @grant none // @license MIT // ==/UserScript== (function() { 'use strict'; const A = { HIT: 'Hit', STAND: 'Stand', DOUBLE: 'Double', SPLIT: 'Split' }; const strategy = { hard: { 8: [A.HIT, A.HIT, A.HIT, A.HIT, A.HIT, A.HIT, A.HIT, A.HIT, A.HIT, A.HIT, A.HIT], 9: [A.HIT, A.HIT, A.DOUBLE, A.DOUBLE, A.DOUBLE, A.DOUBLE, A.HIT, A.HIT, A.HIT, A.HIT, A.HIT], 10: [A.HIT, A.DOUBLE, A.DOUBLE, A.DOUBLE, A.DOUBLE, A.DOUBLE, A.DOUBLE, A.DOUBLE, A.DOUBLE, A.HIT, A.HIT], 11: [A.HIT, A.DOUBLE, A.DOUBLE, A.DOUBLE, A.DOUBLE, A.DOUBLE, A.DOUBLE, A.DOUBLE, A.DOUBLE, A.DOUBLE, A.HIT], 12: [A.HIT, A.HIT, A.STAND, A.STAND, A.STAND, A.HIT, A.HIT, A.HIT, A.HIT, A.HIT, A.HIT], 13: [A.HIT, A.STAND, A.STAND, A.STAND, A.STAND, A.STAND, A.HIT, A.HIT, A.HIT, A.HIT, A.HIT], 14: [A.HIT, A.STAND, A.STAND, A.STAND, A.STAND, A.STAND, A.HIT, A.HIT, A.HIT, A.HIT, A.HIT], 15: [A.HIT, A.STAND, A.STAND, A.STAND, A.STAND, A.STAND, A.HIT, A.HIT, A.HIT, A.HIT, A.HIT], 16: [A.HIT, A.STAND, A.STAND, A.STAND, A.STAND, A.STAND, A.HIT, A.HIT, A.HIT, A.HIT, A.HIT], 17: Array(11).fill(A.STAND), 18: Array(11).fill(A.STAND), 19: Array(11).fill(A.STAND), 20: Array(11).fill(A.STAND), 21: Array(11).fill(A.STAND), }, soft: { 13: [A.HIT, A.HIT, A.HIT, A.HIT, A.DOUBLE, A.DOUBLE, A.HIT, A.HIT, A.HIT, A.HIT, A.HIT], 14: [A.HIT, A.HIT, A.HIT, A.HIT, A.DOUBLE, A.DOUBLE, A.HIT, A.HIT, A.HIT, A.HIT, A.HIT], 15: [A.HIT, A.HIT, A.HIT, A.DOUBLE, A.DOUBLE, A.DOUBLE, A.HIT, A.HIT, A.HIT, A.HIT, A.HIT], 16: [A.HIT, A.HIT, A.HIT, A.DOUBLE, A.DOUBLE, A.DOUBLE, A.HIT, A.HIT, A.HIT, A.HIT, A.HIT], 17: [A.HIT, A.HIT, A.DOUBLE, A.DOUBLE, A.DOUBLE, A.DOUBLE, A.DOUBLE, A.HIT, A.HIT, A.HIT, A.HIT], 18: [A.STAND, A.DOUBLE, A.DOUBLE, A.DOUBLE, A.DOUBLE, A.STAND, A.STAND, A.HIT, A.HIT, A.STAND, A.STAND], 19: Array(11).fill(A.STAND), 20: Array(11).fill(A.STAND), }, pair: { 2: [A.SPLIT, A.SPLIT, A.SPLIT, A.SPLIT, A.SPLIT, A.SPLIT, A.HIT, A.HIT, A.HIT, A.HIT, A.HIT], 3: [A.SPLIT, A.SPLIT, A.SPLIT, A.SPLIT, A.SPLIT, A.SPLIT, A.HIT, A.HIT, A.HIT, A.HIT, A.HIT], 4: [A.HIT, A.HIT, A.HIT, A.SPLIT, A.SPLIT, A.HIT, A.HIT, A.HIT, A.HIT, A.HIT, A.HIT], 5: [A.HIT, A.DOUBLE, A.DOUBLE, A.DOUBLE, A.DOUBLE, A.DOUBLE, A.DOUBLE, A.DOUBLE, A.DOUBLE, A.HIT, A.HIT], 6: [A.HIT, A.SPLIT, A.SPLIT, A.SPLIT, A.SPLIT, A.SPLIT, A.HIT, A.HIT, A.HIT, A.HIT, A.HIT], 7: [A.SPLIT, A.SPLIT, A.SPLIT, A.SPLIT, A.SPLIT, A.SPLIT, A.SPLIT, A.HIT, A.HIT, A.HIT, A.HIT], 8: Array(11).fill(A.SPLIT), 9: [A.SPLIT, A.SPLIT, A.SPLIT, A.SPLIT, A.SPLIT, A.STAND, A.SPLIT, A.SPLIT, A.STAND, A.STAND, A.STAND], 10: Array(11).fill(A.STAND), 11: Array(11).fill(A.SPLIT), } }; function getCardValue(cardElement) { if (!cardElement) return 0; const match = cardElement.className.match(/card-\w+-(\w+)/); if (!match || !match[1]) return 0; const rank = match[1]; if (['J', 'Q', 'K'].includes(rank)) return 10; if (rank === 'A') return 11; return parseInt(rank, 10); } function getHandInfo(cardContainerSelector) { const cardElements = document.querySelectorAll(`${cardContainerSelector} div[class*="card-"]`); const cards = Array.from(cardElements).map(getCardValue); let sum = cards.reduce((a, b) => a + b, 0); let aces = cards.filter(v => v === 11).length; while (sum > 21 && aces-- > 0) sum -= 10; const isPair = cards.length === 2 && cards[0] === cards[1]; return { cards, total: sum, isSoft: aces > 0 && sum <= 21, isPair }; } function getDecision(dealerCardValue, playerHand) { // *** THE FINAL FIX *** A dealer Ace (11) maps to index 1 in our tables. const dealerIndex = dealerCardValue === 11 ? 1 : dealerCardValue; if (playerHand.isPair) { const pairValue = playerHand.cards[0] === 11 ? 11 : playerHand.cards[0]; return strategy.pair[pairValue]?.[dealerIndex] || A.HIT; } if (playerHand.isSoft) { return strategy.soft[playerHand.total]?.[dealerIndex] || A.STAND; } return strategy.hard[playerHand.total]?.[dealerIndex] || A.STAND; } function mainObserverCallback() { const dealerCardEl = document.querySelector('.dealer-cards div[class*="card-"]'); const dealerCardValue = getCardValue(dealerCardEl); const playerHand = getHandInfo('.player-cards'); const canTakeAction = document.querySelector('#hit, #stand'); if (!dealerCardValue || playerHand.cards.length < 2 || !canTakeAction || playerHand.total >= 21) { updateOverlay('---'); return; } const decision = getDecision(dealerCardValue, playerHand); updateOverlay(decision, playerHand.total); } // --- UI, Styling, Draggable Logic --- function addStyles() { const style = document.createElement('style'); style.innerHTML = `#bj-helper-overlay { position: fixed; background-color: rgba(0,0,0,0.85); color: white; padding: 12px 18px; border-radius: 10px; border: 2px solid; font-family: Arial,"Helvetica Neue",Helvetica,sans-serif; text-align: center; z-index: 9999; box-shadow: 0 4px 8px rgba(0,0,0,0.5); min-width: 150px; transition: all 0.3s ease; cursor: move; user-select: none; } #bj-helper-advice-text { font-size: 24px; font-weight: bold; text-shadow: 0 0 5px #000; } #bj-helper-hand-total { font-size: 14px; opacity: 0.8; margin-top: 4px; } .bj-hit { border-color: #ff9900; background-color: rgba(102,60,0,0.7); } .bj-stand, .bj-double { border-color: #4CAF50; background-color: rgba(30,77,32,0.7); } .bj-split { border-color: #2196F3; background-color: rgba(13,60,99,0.7); } .bj-idle { border-color: #666; }`; document.head.appendChild(style); } function makeDraggable(element) { const STORAGE_POS_KEY = 'bjHelperPosition'; let isDragging = false, offsetX, offsetY; const savedPos = localStorage.getItem(STORAGE_POS_KEY); if (savedPos) { const { top, left } = JSON.parse(savedPos); element.style.top = top; element.style.left = left; } else { element.style.right = '20px'; element.style.bottom = '20px'; } const onDragStart = (e) => { isDragging = true; e.preventDefault(); const event = e.touches ? e.touches[0] : e; offsetX = event.clientX - element.offsetLeft; offsetY = event.clientY - element.offsetTop; element.style.right = ''; element.style.bottom = ''; window.addEventListener('mousemove', onDragMove, { passive: false }); window.addEventListener('touchmove', onDragMove, { passive: false }); window.addEventListener('mouseup', onDragEnd); window.addEventListener('touchend', onDragEnd); }; const onDragMove = (e) => { if (!isDragging) return; e.preventDefault(); const event = e.touches ? e.touches[0] : e; element.style.left = `${event.clientX - offsetX}px`; element.style.top = `${event.clientY - offsetY}px`; }; const onDragEnd = () => { isDragging = false; localStorage.setItem(STORAGE_POS_KEY, JSON.stringify({ top: element.style.top, left: element.style.left })); window.removeEventListener('mousemove', onDragMove); window.removeEventListener('touchmove', onDragMove); window.removeEventListener('mouseup', onDragEnd); window.removeEventListener('touchend', onDragEnd); }; element.addEventListener('mousedown', onDragStart); element.addEventListener('touchstart', onDragStart); } function createOverlay() { if (document.getElementById('bj-helper-overlay')) return; const overlay = document.createElement('div'); overlay.id = 'bj-helper-overlay'; overlay.innerHTML = `<div><div id="bj-helper-advice-text">---</div><div id="bj-helper-hand-total">Waiting for hand</div></div>`; document.body.appendChild(overlay); makeDraggable(overlay); } function updateOverlay(decision = '---', total = 0) { const overlay = document.getElementById('bj-helper-overlay'); const adviceEl = document.getElementById('bj-helper-advice-text'); const totalEl = document.getElementById('bj-helper-hand-total'); if (!overlay || !adviceEl || !totalEl) return; adviceEl.textContent = decision; totalEl.textContent = total > 0 ? `on your ${total}` : 'Waiting for hand'; overlay.className = ''; if (decision !== '---') { overlay.classList.add(`bj-${decision.toLowerCase()}`); } else { overlay.classList.add('bj-idle'); } } function initialize() { const gameContainer = document.querySelector('.blackjack, .blackjack-wrap'); if (!gameContainer) return; if (!document.getElementById('bj-helper-overlay')) { addStyles(); createOverlay(); } const observer = new MutationObserver(mainObserverCallback); observer.observe(gameContainer, { childList: true, subtree: true }); mainObserverCallback(); } if (document.readyState === 'loading') { window.addEventListener('DOMContentLoaded', initialize); } else { initialize(); } })();