您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Show poker hand odds on TC
当前为
// ==UserScript== // @name Poker Odds Calculator // @namespace https://openuserjs.org/users/torn/pokerodds // @version 1.3.38 // @description Show poker hand odds on TC // @author Torn Community // @match https://www.torn.com/page.php?sid=holdem // @run-at document-body // @license MIT // @grant none // ==/UserScript== (function() { 'use strict'; // Utility function to add styles const addStyle = (css) => { const style = document.createElement('style'); style.type = 'text/css'; style.textContent = css; document.head.appendChild(style); }; // Add required styles addStyle(` #pokerCalc-div * { all: revert; } #pokerCalc-div { background-color: #eee; color: #444; padding: 5px; margin-top: 10px; font-family: Arial, sans-serif; } #pokerCalc-div table { border-collapse: collapse; margin-top: 10px; width: 100%; background: white; } #pokerCalc-div th, #pokerCalc-div td { border: 1px solid #444; padding: 5px; width: 25%; } #pokerCalc-div tr td:nth-child(1), #pokerCalc-div tr td:nth-child(3), #pokerCalc-div tr td:nth-child(4) { text-align: center; } #pokerCalc-div caption { margin-bottom: 2px; font-weight: 600; color: #333; } #pokerCalc-div tr:hover { background-color: #f5f5f5; } #pokerCalc-div .best-hand { background-color: #dfd; } `); class PokerCalculator { constructor() { this.upgradesToShow = 10; this.lastLength = 0; this.setupObserver(); } getFullDeck() { const suits = ['hearts', 'diamonds', 'spades', 'clubs']; const values = Array.from({length: 13}, (_, i) => i + 2); return suits.flatMap(suit => values.map(value => `${suit}-${value}`) ); } filterDeck(deck, cards) { return deck.filter(card => !cards.includes(card)); } prettifyCard(card) { if (card === 'null-0') return ''; const [suit, value] = card.split('-'); const suitSymbols = { 'diamonds': '♦', 'spades': '♠', 'hearts': '♥', 'clubs': '♣' }; const valueMap = { '14': 'A', '13': 'K', '12': 'Q', '11': 'J' }; const displayValue = valueMap[value] || value; const color = suit === 'hearts' || suit === 'diamonds' ? 'red' : 'black'; return `<span style="color: ${color}">${displayValue}${suitSymbols[suit]}</span>`; } makeHandObject(hand) { const resultMap = { cards: hand, suits: {}, values: {} }; hand.sort((a, b) => { const valueA = parseInt(a.split('-')[1]); const valueB = parseInt(b.split('-')[1]); return valueB - valueA; }) .filter(card => !card.includes('null')) .forEach(card => { const [suit, value] = card.split('-'); if (!resultMap.suits[suit]) resultMap.suits[suit] = []; if (!resultMap.values[value]) resultMap.values[value] = []; resultMap.suits[suit].push(card); resultMap.values[value].push(card); }); return resultMap; } // Hand evaluation methods hasRoyalFlush(hand, handObject) { for (const suit in handObject.suits) { const suitCards = handObject.suits[suit]; if (suitCards.length >= 5) { const values = new Set(suitCards.map(card => parseInt(card.split('-')[1]) )); if ([10,11,12,13,14].every(value => values.has(value))) { return suitCards .filter(card => parseInt(card.split('-')[1]) >= 10) .sort((a, b) => parseInt(b.split('-')[1]) - parseInt(a.split('-')[1]) ) .slice(0, 5); } } } return null; } hasStraightFlush(hand, handObject) { for (const suit in handObject.suits) { const suitCards = handObject.suits[suit]; if (suitCards.length >= 5) { const straight = this.hasStraight(suitCards, this.makeHandObject(suitCards) ); if (straight) return straight; } } return null; } hasFourOfAKind(hand, handObject) { const quads = Object.values(handObject.values) .find(cards => cards.length === 4); if (quads) { const kickers = hand.filter(card => !quads.includes(card) ).sort((a, b) => parseInt(b.split('-')[1]) - parseInt(a.split('-')[1]) ); return [...quads, kickers[0]]; } return null; } hasFullHouse(hand, handObject) { const trips = Object.values(handObject.values) .filter(cards => cards.length === 3) .sort((a, b) => parseInt(b[0].split('-')[1]) - parseInt(a[0].split('-')[1]) ); if (trips.length === 0) return null; for (const three of trips) { const threeValue = parseInt(three[0].split('-')[1]); const pair = Object.values(handObject.values) .find(cards => cards.length >= 2 && parseInt(cards[0].split('-')[1]) !== threeValue ); if (pair) { return [...three.slice(0, 3), ...pair.slice(0, 2)]; } } return null; } hasFlush(hand, handObject) { for (const suit in handObject.suits) { if (handObject.suits[suit].length >= 5) { return handObject.suits[suit] .sort((a, b) => parseInt(b.split('-')[1]) - parseInt(a.split('-')[1]) ) .slice(0, 5); } } return null; } hasStraight(hand, handObject) { const values = new Map(); hand.forEach(card => { const value = parseInt(card.split('-')[1]); if (!values.has(value) || parseInt(values.get(value).split('-')[1]) < value) { values.set(value, card); } }); const uniqueValues = Array.from(values.keys()).sort((a, b) => b - a); // Check regular straights for (let i = 0; i <= uniqueValues.length - 5; i++) { const straight = uniqueValues.slice(i, i + 5); if (straight[0] - straight[4] === 4) { return straight.map(value => values.get(value)); } } // Check Ace-low straight (A,2,3,4,5) if (uniqueValues.includes(14) && uniqueValues.includes(2) && uniqueValues.includes(3) && uniqueValues.includes(4) && uniqueValues.includes(5)) { return [ values.get(5), values.get(4), values.get(3), values.get(2), values.get(14) ]; } return null; } hasThreeOfAKind(hand, handObject) { const trips = Object.values(handObject.values) .find(cards => cards.length === 3); if (trips) { const kickers = hand.filter(card => !trips.includes(card) ).sort((a, b) => parseInt(b.split('-')[1]) - parseInt(a.split('-')[1]) ); return [...trips, ...kickers.slice(0, 2)]; } return null; } hasTwoPairs(hand, handObject) { const pairs = Object.values(handObject.values) .filter(cards => cards.length === 2) .sort((a, b) => parseInt(b[0].split('-')[1]) - parseInt(a[0].split('-')[1]) ); if (pairs.length >= 2) { const kickers = hand.filter(card => !pairs[0].includes(card) && !pairs[1].includes(card) ).sort((a, b) => parseInt(b.split('-')[1]) - parseInt(a.split('-')[1]) ); return [...pairs[0], ...pairs[1], kickers[0]]; } return null; } hasPair(hand, handObject) { const pair = Object.values(handObject.values) .find(cards => cards.length === 2); if (pair) { const kickers = hand.filter(card => !pair.includes(card) ).sort((a, b) => parseInt(b.split('-')[1]) - parseInt(a.split('-')[1]) ); return [...pair, ...kickers.slice(0, 3)]; } return null; } getHandScore(hand) { const filteredHand = hand.filter(card => !card.includes('null')); if (filteredHand.length < 5) return { description: '', score: 0 }; const handObject = this.makeHandObject(filteredHand); let handResult; let resultString = ''; let resultText = ''; const evaluators = [ { fn: this.hasRoyalFlush, score: '9', text: 'Royal flush' }, { fn: this.hasStraightFlush, score: '8', text: 'Straight flush' }, { fn: this.hasFourOfAKind, score: '7', text: 'Four of a kind' }, { fn: this.hasFullHouse, score: '6', text: 'Full house' }, { fn: this.hasFlush, score: '5', text: 'Flush' }, { fn: this.hasStraight, score: '4', text: 'Straight' }, { fn: this.hasThreeOfAKind, score: '3', text: 'Three of a kind' }, { fn: this.hasTwoPairs, score: '2', text: 'Two pairs' }, { fn: this.hasPair, score: '1', text: 'Pair' } ]; for (const { fn, score, text } of evaluators) { handResult = fn.call(this, filteredHand, handObject); if (handResult) { resultString = score; resultText = text; break; } } if (!handResult) { resultString = '0'; resultText = 'High card'; handResult = filteredHand.slice(0, 5); } handResult.forEach(card => { resultString += parseInt(card.split('-')[1]).toString(16); }); return { description: `${resultText}: ${handResult.map(card => this.prettifyCard(card) ).join(' ')}`, result: handResult, score: parseInt(resultString, 16) }; } calculateHandRank(myHand, communityCards, allCards) { if (!myHand?.score || !Array.isArray(communityCards) || !Array.isArray(allCards)) { return { rank: 'N/A', top: 'N/A', topNumber: 0, betterHands: 0, equalHands: 0, worseHands: 0, totalHands: 0 }; } const availableCards = allCards.filter(card => card && !communityCards.includes(card) && !myHand.result.includes(card) ); let betterHands = 0; let equalHands = 0; let worseHands = 0; let totalHands = 0; for (let i = 0; i < availableCards.length - 1; i++) { for (let j = i + 1; j < availableCards.length; j++) { const oppHand = this.getHandScore( communityCards.concat([availableCards[i], availableCards[j]]) ); if (oppHand.score > myHand.score) betterHands++; else if (oppHand.score === myHand.score) equalHands++; else worseHands++; totalHands++; } } if (totalHands === 0) { return { rank: 'N/A', top: 'N/A', topNumber: 0, betterHands: 0, equalHands: 0, worseHands: 0, totalHands: 0 }; } const trueRank = betterHands + Math.ceil(equalHands / 2); const percentile = ((betterHands + equalHands/2) / totalHands) * 100; return { rank: `${trueRank + 1} / ${totalHands}`, top: `${percentile.toFixed(1)}%`, topNumber: percentile / 100, betterHands, equalHands, worseHands, totalHands }; } setupObserver() { const observer = new MutationObserver(() => { if (!document.getElementById('pokerCalc-div')) { this.addStatisticsTable(); } this.update(); }); observer.observe(document.body, { childList: true, subtree: true }); } addStatisticsTable() { const root = document.querySelector('#react-root'); if (!root) return; const div = document.createElement('div'); div.id = 'pokerCalc-div'; div.innerHTML = ` <table id="pokerCalc-myHand"> <caption>Your Hand</caption> <thead> <tr> <th>Name</th> <th>Hand</th> <th>Rank</th> <th>Top</th> </tr> </thead> <tbody></tbody> </table> <table id="pokerCalc-upgrades"> <caption>Your Potential Hands</caption> <thead> <tr> <th>Chance</th> <th>Hand</th> <th>Rank</th> <th>Top</th> </tr> </thead> <tbody></tbody> </table> <table id="pokerCalc-oppPossHands"> <caption>Opponent Potential Hands</caption> <thead> <tr> <th>Chance</th> <th>Hand</th> <th>Rank</th> <th>Top</th> </tr> </thead> <tbody></tbody> </table> `; root.after(div); } update() { const allCards = this.getFullDeck(); const knownCards = Array.from( document.querySelectorAll("[class*='flipper___'] > div[class*='front___'] > div") ).map(e => { const card = (e.classList[1] || "null-0").split("_")[0] .replace("-A", "-14") .replace("-K", "-13") .replace("-Q", "-12") .replace("-J", "-11"); return card === "cardSize" ? "null-0" : card; }); const communityCards = knownCards.slice(0, 5); const filteredDeck = this.filterDeck(allCards, knownCards.filter(e => !e.includes("null")) ); if (JSON.stringify(knownCards).length === this.lastLength) return; this.lastLength = JSON.stringify(knownCards).length; const tables = { myHand: document.querySelector("#pokerCalc-myHand tbody"), upgrades: document.querySelector("#pokerCalc-upgrades tbody"), oppHands: document.querySelector("#pokerCalc-oppPossHands tbody") }; if (!tables.myHand || !tables.upgrades || !tables.oppHands) return; tables.myHand.innerHTML = ''; tables.upgrades.innerHTML = ''; tables.oppHands.innerHTML = ''; const playerNodes = document.querySelectorAll("[class*='playerMeGateway___']"); playerNodes.forEach(player => { const myCards = Array.from( player.querySelectorAll("div[class*='front___'] > div") ).map(e => { const card = (e.classList[1] || "null-0").split("_")[0] .replace("-A", "-14") .replace("-K", "-13") .replace("-Q", "-12") .replace("-J", "-11"); return card === "cardSize" ? "null-0" : card; }); const myHand = this.getHandScore(communityCards.concat(myCards)); if (myHand.score === 0) return; const myRank = this.calculateHandRank(myHand, communityCards, filteredDeck); // Update tables this.updateMyHandTable(tables.myHand, myHand, myRank); this.updateUpgradesTable(tables.upgrades, myHand, communityCards, myCards, filteredDeck); this.updateOpponentHandsTable(tables.oppHands, communityCards, filteredDeck); }); // Highlight best hands in each table this.highlightBestHands(); } updateMyHandTable(table, myHand, myRank) { table.innerHTML += ` <tr> <td>Me</td> <td>${myHand.description}</td> <td>${myRank.rank}</td> <td>${myRank.top}</td> </tr> `; } updateUpgradesTable(table, myHand, communityCards, myCards, allCards) { const upgrades = {}; const additionalCards = []; const communityLength = communityCards.filter(e => !e.includes("null")).length; if (communityLength === 3) { for (let a of allCards) { for (let b of allCards) { if (a > b) additionalCards.push([a, b]); } } } else if (communityLength === 4) { for (let a of allCards) { additionalCards.push([a]); } } for (let cards of additionalCards) { const newHand = this.getHandScore( communityCards.concat(cards).concat(myCards) ); if (newHand.score > myHand.score) { const type = this.getHandType(newHand); if (!upgrades[type]) { upgrades[type] = { hand: newHand, type, cards, score: newHand.score, duplicates: 0, chance: 0 }; } upgrades[type].duplicates++; } } const topUpgrades = Object.values(upgrades); topUpgrades.forEach(upgrade => { upgrade.chance = (upgrade.duplicates / additionalCards.length) * 100; const rank = this.calculateHandRank( upgrade.hand, communityCards.concat(upgrade.cards), this.filterDeck(allCards, upgrade.cards) ); upgrade.rank = rank.rank; upgrade.top = rank.top; }); const sortedUpgrades = topUpgrades .sort((a, b) => b.chance - a.chance) .slice(0, this.upgradesToShow); table.innerHTML = sortedUpgrades.map(upgrade => ` <tr> <td>${upgrade.chance.toFixed(2)}%</td> <td>${upgrade.type}</td> <td>${upgrade.rank}</td> <td>${upgrade.top}</td> </tr> `).join(''); } updateOpponentHandsTable(table, communityCards, allCards) { if (communityCards.filter(e => !e.includes("null")).length !== 5) return; const oppHands = {}; const additionalCards = []; for (let a of allCards) { for (let b of allCards) { if (a > b) additionalCards.push([a, b]); } } for (let cards of additionalCards) { const hand = this.getHandScore(communityCards.concat(cards)); const type = this.getHandType(hand); if (!oppHands[type]) { oppHands[type] = { hand, type, cards, score: hand.score, duplicates: 0, chance: 0 }; } oppHands[type].duplicates++; } const topHands = Object.values(oppHands); topHands.forEach(hand => { hand.chance = (hand.duplicates / additionalCards.length) * 100; const rank = this.calculateHandRank( hand.hand, communityCards.concat(hand.cards), this.filterDeck(allCards, hand.cards) ); hand.rank = rank.rank; hand.top = rank.top; }); const sortedHands = topHands .sort((a, b) => b.score - a.score) .slice(0, this.upgradesToShow); table.innerHTML = sortedHands.map(hand => ` <tr> <td>${hand.chance.toFixed(2)}%</td> <td>${hand.type}</td> <td>${hand.rank}</td> <td>${hand.top}</td> </tr> `).join(''); } getHandType(hand) { const base = hand.description.split(':')[0]; const details = hand.description.split('</span>'); if (base.includes('Four of a kind') || base.includes('Three of a kind') || base.includes('Pair')) { return `${base}: ${details[1].split('<span')[0].trim()}s`; } if (base.includes('Full house')) { return `${base}: ${details[1].split('<span')[0].trim()}s full of ${details.reverse()[0].split('</td>')[0]}s`; } if (base.includes('Straight')) { return `${base}: ${details[1].split('<span')[0].trim()}-high`; } if (base.includes('Two pairs')) { return `${base}: ${details[1].split('<span')[0].trim()}s and ${details[3].split('<span')[0].trim()}s`; } return base; } highlightBestHands() { ['#pokerCalc-myHand', '#pokerCalc-upgrades', '#pokerCalc-oppPossHands'].forEach(tableId => { const rows = Array.from(document.querySelectorAll(`${tableId} tbody tr`)); if (rows.length === 0) return; rows.forEach(row => row.classList.remove('best-hand')); const bestRow = rows.reduce((a, b) => { const valueA = parseFloat(a.children[3].innerText.replace(/[^0-9\.]/g, "")); const valueB = parseFloat(b.children[3].innerText.replace(/[^0-9\.]/g, "")); return valueA <= valueB ? a : b; }); bestRow.classList.add('best-hand'); }); } } // Initialize the calculator window.pokerCalculator = new PokerCalculator(); })();