您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Poker odds assistant
当前为
- // ==UserScript==
- // @name FlopMaster
- // @namespace https://greasyfork.org/en/users/1469540-davrone
- // @version 1.0
- // @description Poker odds assistant
- // @author Davrone
- // @match https://www.torn.com/page.php?sid=holdem
- // @run-at document-body
- // @license MIT
- // @grant none
- // ==/UserScript==
- function debugBindErrors(methods) {
- for (let method of methods) {
- console.log(`Testing method: ${method}`);
- try {
- if (this[method] === undefined) {
- console.error(`Method ${method} is undefined!`);
- }
- } catch (e) {
- console.error(`Error checking method ${method}:`, e);
- }
- }
- }
- let GM_addStyle = function(s) {
- let style = document.createElement("style");
- style.type = "text/css";
- style.textContent = s;
- document.head.appendChild(style);
- }
- class FloatingSuits {
- constructor(container, options = {}) {
- this.container = container;
- this.suits = [];
- this.options = {
- suitCount: options.suitCount || 30,
- minSize: options.minSize || 16,
- maxSize: options.maxSize || 35,
- minSpeed: options.minSpeed || 20,
- maxSpeed: options.maxSpeed || 60,
- minSwayAmount: options.minSwayAmount || 10,
- maxSwayAmount: options.maxSwayAmount || 40,
- minSwayTime: options.minSwayTime || 2,
- maxSwayTime: options.maxSwayTime || 6
- };
- this.suitSymbols = ['♠', '♥', '♦', '♣'];
- this.suitClasses = ['spades', 'hearts', 'diamonds', 'clubs'];
- this.running = false;
- this.suitElements = [];
- this.init();
- }
- init() {
- this.container.innerHTML = '';
- this.suitElements = [];
- const rect = this.container.getBoundingClientRect();
- for (let i = 0; i < this.options.suitCount; i++) {
- this.createSuit(rect, true);
- }
- }
- createSuit(rect, initial = false) {
- const suitIndex = Math.floor(Math.random() * this.suitSymbols.length);
- const size = Math.floor(Math.random() * (this.options.maxSize - this.options.minSize)) + this.options.minSize;
- const suitElement = document.createElement('div');
- suitElement.className = `floating-suit ${this.suitClasses[suitIndex]}`;
- suitElement.textContent = this.suitSymbols[suitIndex];
- suitElement.style.fontSize = `${size}px`;
- if (Math.random() < 0.2) {
- suitElement.classList.add('gold');
- }
- const x = Math.random() * (rect.width - size);
- const y = initial ? Math.random() * rect.height : -size;
- suitElement.style.left = `${x}px`;
- suitElement.style.top = `${y}px`;
- this.container.appendChild(suitElement);
- const speed = this.options.minSpeed + Math.random() * (this.options.maxSpeed - this.options.minSpeed);
- const sway = this.options.minSwayAmount + Math.random() * (this.options.maxSwayAmount - this.options.minSwayAmount);
- const swayTime = this.options.minSwayTime + Math.random() * (this.options.maxSwayTime - this.options.minSwayTime);
- const rotation = Math.random() * 360;
- const rotationSpeed = (Math.random() - 0.5) * 2;
- suitElement.style.transform = `rotate(${rotation}deg)`;
- this.suitElements.push({
- element: suitElement,
- x: x,
- y: y,
- speed: speed,
- sway: sway,
- swayTime: swayTime,
- swayOffset: Math.random() * Math.PI * 2, // Random starting point
- rotation: rotation,
- rotationSpeed: rotationSpeed,
- size: size
- });
- return this.suitElements[this.suitElements.length - 1];
- }
- start() {
- if (!this.running) {
- this.running = true;
- this.lastTime = performance.now();
- requestAnimationFrame(this.update.bind(this));
- }
- }
- stop() {
- this.running = false;
- }
- update(timestamp) {
- if (!this.running) return;
- const deltaTime = (timestamp - this.lastTime) / 1000; // Convert to seconds
- this.lastTime = timestamp;
- const rect = this.container.getBoundingClientRect();
- for (let i = this.suitElements.length - 1; i >= 0; i--) {
- const suit = this.suitElements[i];
- suit.y += suit.speed * deltaTime;
- const swayX = Math.sin((timestamp / 1000 / suit.swayTime) + suit.swayOffset) * suit.sway;
- suit.x = Math.max(0, Math.min(rect.width - suit.size, suit.x + (swayX * deltaTime)));
- suit.rotation += suit.rotationSpeed * deltaTime * 20;
- suit.element.style.transform = `rotate(${suit.rotation}deg)`;
- suit.element.style.left = `${suit.x}px`;
- suit.element.style.top = `${suit.y}px`;
- if (suit.y > rect.height) {
- suit.element.remove();
- this.suitElements.splice(i, 1);
- this.createSuit(rect);
- }
- }
- requestAnimationFrame(this.update.bind(this));
- }
- setDensity(count) {
- const rect = this.container.getBoundingClientRect();
- const currentCount = this.suitElements.length;
- if (count > currentCount) {
- for (let i = 0; i < count - currentCount; i++) {
- this.createSuit(rect);
- }
- } else if (count < currentCount) {
- const toRemove = currentCount - count;
- for (let i = 0; i < toRemove; i++) {
- if (this.suitElements.length > 0) {
- const index = Math.floor(Math.random() * this.suitElements.length);
- this.suitElements[index].element.remove();
- this.suitElements.splice(index, 1);
- }
- }
- }
- this.options.suitCount = count;
- }
- }
- class PokerCalculatorModule {
- constructor() {
- this.debugMode = false;
- this.lastProcessTime = 0;
- this.processingThrottle = 1500;
- this.lastUpdateTime = 0;
- this.renderDelayTime = 500;
- this.upgradesToShow = 10;
- this.lastLength = 0;
- this.isHandActive = false;
- this.opponentStats = new Map();
- this.preflopStats = {
- 'AA': { wins: 85.2, ties: 0.5, total: 85.2 },
- 'KK': { wins: 82.4, ties: 0.5, total: 82.4 },
- 'QQ': { wins: 80.0, ties: 0.5, total: 80.0 },
- 'JJ': { wins: 77.5, ties: 0.5, total: 77.5 },
- 'TT': { wins: 75.1, ties: 0.5, total: 75.1 },
- '99': { wins: 72.1, ties: 0.5, total: 72.1 },
- '88': { wins: 69.1, ties: 0.5, total: 69.1 },
- '77': { wins: 66.2, ties: 0.5, total: 66.2 },
- '66': { wins: 63.3, ties: 0.5, total: 63.3 },
- '55': { wins: 60.3, ties: 0.5, total: 60.3 },
- '44': { wins: 57.0, ties: 0.5, total: 57.0 },
- '33': { wins: 53.7, ties: 0.5, total: 53.7 },
- '22': { wins: 50.3, ties: 0.5, total: 50.3 },
- 'AKs': { wins: 66.1, ties: 0.9, total: 67.0 },
- 'AQs': { wins: 65.3, ties: 0.8, total: 66.1 },
- 'AJs': { wins: 64.6, ties: 0.8, total: 65.4 },
- 'ATs': { wins: 63.9, ties: 0.8, total: 64.7 },
- 'A9s': { wins: 62.3, ties: 0.7, total: 63.0 },
- 'A8s': { wins: 61.4, ties: 0.7, total: 62.1 },
- 'A7s': { wins: 60.4, ties: 0.7, total: 61.1 },
- 'A6s': { wins: 59.3, ties: 0.7, total: 60.0 },
- 'A5s': { wins: 59.2, ties: 0.7, total: 59.9 },
- 'A4s': { wins: 58.5, ties: 0.7, total: 59.2 },
- 'A3s': { wins: 57.8, ties: 0.7, total: 58.5 },
- 'A2s': { wins: 57.1, ties: 0.7, total: 57.8 },
- 'AK': { wins: 64.5, ties: 0.9, total: 65.4 },
- 'AQ': { wins: 63.6, ties: 0.9, total: 64.5 },
- 'AJ': { wins: 62.7, ties: 0.9, total: 63.6 },
- 'AT': { wins: 62.0, ties: 0.9, total: 62.9 },
- 'A9': { wins: 60.2, ties: 0.9, total: 61.1 },
- 'A8': { wins: 59.2, ties: 0.9, total: 60.1 },
- 'A7': { wins: 58.2, ties: 0.9, total: 59.1 },
- 'A6': { wins: 57.1, ties: 0.9, total: 58.0 },
- 'A5': { wins: 56.8, ties: 0.9, total: 57.7 },
- 'A4': { wins: 56.2, ties: 0.9, total: 57.1 },
- 'A3': { wins: 55.5, ties: 0.9, total: 56.4 },
- 'A2': { wins: 54.7, ties: 0.9, total: 55.6 },
- 'KQs': { wins: 62.5, ties: 0.9, total: 63.4 },
- 'KJs': { wins: 61.7, ties: 0.9, total: 62.6 },
- 'KTs': { wins: 60.9, ties: 0.9, total: 61.8 },
- 'K9s': { wins: 59.1, ties: 0.9, total: 60.0 },
- 'K8s': { wins: 57.6, ties: 0.9, total: 58.5 },
- 'K7s': { wins: 56.9, ties: 0.9, total: 57.8 },
- 'K6s': { wins: 55.9, ties: 0.9, total: 56.8 },
- 'K5s': { wins: 55.1, ties: 0.9, total: 56.0 },
- 'K4s': { wins: 54.3, ties: 0.9, total: 55.2 },
- 'K3s': { wins: 53.6, ties: 0.9, total: 54.5 },
- 'K2s': { wins: 52.9, ties: 0.9, total: 53.8 },
- 'KQ': { wins: 60.9, ties: 0.9, total: 61.8 },
- 'KJ': { wins: 60.0, ties: 0.9, total: 60.9 },
- 'KT': { wins: 59.1, ties: 0.9, total: 60.0 },
- 'K9': { wins: 57.3, ties: 0.9, total: 58.2 },
- 'K8': { wins: 55.9, ties: 0.9, total: 56.8 },
- 'K7': { wins: 55.1, ties: 0.9, total: 56.0 },
- 'K6': { wins: 54.1, ties: 0.9, total: 55.0 },
- 'K5': { wins: 53.3, ties: 0.9, total: 54.2 },
- 'K4': { wins: 52.5, ties: 0.9, total: 53.4 },
- 'K3': { wins: 51.7, ties: 0.9, total: 52.6 },
- 'K2': { wins: 51.0, ties: 0.9, total: 51.9 },
- 'QJs': { wins: 59.4, ties: 0.9, total: 60.3 },
- 'QTs': { wins: 58.6, ties: 0.9, total: 59.5 },
- 'Q9s': { wins: 56.7, ties: 0.9, total: 57.6 },
- 'Q8s': { wins: 55.3, ties: 0.9, total: 56.2 },
- 'Q7s': { wins: 54.2, ties: 0.9, total: 55.1 },
- 'Q6s': { wins: 53.4, ties: 0.9, total: 54.3 },
- 'Q5s': { wins: 52.6, ties: 0.9, total: 53.5 },
- 'Q4s': { wins: 51.7, ties: 0.9, total: 52.6 },
- 'Q3s': { wins: 51.0, ties: 0.9, total: 51.9 },
- 'Q2s': { wins: 50.3, ties: 0.9, total: 51.2 },
- 'QJ': { wins: 57.6, ties: 0.9, total: 58.5 },
- 'QT': { wins: 56.8, ties: 0.9, total: 57.7 },
- 'Q9': { wins: 55.0, ties: 0.9, total: 55.9 },
- 'Q8': { wins: 53.5, ties: 0.9, total: 54.4 },
- 'Q7': { wins: 52.3, ties: 0.9, total: 53.2 },
- 'Q6': { wins: 51.5, ties: 0.9, total: 52.4 },
- 'Q5': { wins: 50.7, ties: 0.9, total: 51.6 },
- 'Q4': { wins: 49.8, ties: 0.9, total: 50.7 },
- 'Q3': { wins: 49.1, ties: 0.9, total: 50.0 },
- 'Q2': { wins: 48.4, ties: 0.9, total: 49.3 },
- 'JTs': { wins: 57.1, ties: 0.9, total: 58.0 },
- 'J9s': { wins: 55.1, ties: 0.9, total: 56.0 },
- 'J8s': { wins: 53.7, ties: 0.9, total: 54.6 },
- 'J7s': { wins: 52.1, ties: 0.9, total: 53.0 },
- 'J6s': { wins: 51.0, ties: 0.9, total: 51.9 },
- 'J5s': { wins: 50.1, ties: 0.9, total: 51.0 },
- 'J4s': { wins: 49.2, ties: 0.9, total: 50.1 },
- 'J3s': { wins: 48.5, ties: 0.9, total: 49.4 },
- 'J2s': { wins: 47.8, ties: 0.9, total: 48.7 },
- 'JT': { wins: 55.3, ties: 0.9, total: 56.2 },
- 'J9': { wins: 53.2, ties: 0.9, total: 54.1 },
- 'J8': { wins: 51.7, ties: 0.9, total: 52.6 },
- 'J7': { wins: 50.5, ties: 0.9, total: 51.4 },
- 'J6': { wins: 49.2, ties: 0.9, total: 50.1 },
- 'J5': { wins: 48.3, ties: 0.9, total: 49.2 },
- 'J4': { wins: 47.4, ties: 0.9, total: 48.3 },
- 'J3': { wins: 46.7, ties: 0.9, total: 47.6 },
- 'J2': { wins: 46.0, ties: 0.9, total: 46.9 },
- 'T9s': { wins: 53.4, ties: 0.9, total: 54.3 },
- 'T8s': { wins: 51.9, ties: 0.9, total: 52.8 },
- 'T7s': { wins: 50.5, ties: 0.9, total: 51.4 },
- 'T6s': { wins: 49.0, ties: 0.9, total: 49.9 },
- 'T5s': { wins: 48.0, ties: 0.9, total: 48.9 },
- 'T4s': { wins: 47.1, ties: 0.9, total: 48.0 },
- 'T3s': { wins: 46.4, ties: 0.9, total: 47.3 },
- 'T2s': { wins: 45.7, ties: 0.9, total: 46.6 },
- 'T9': { wins: 51.4, ties: 0.9, total: 52.3 },
- 'T8': { wins: 49.9, ties: 0.9, total: 50.8 },
- 'T7': { wins: 48.6, ties: 0.9, total: 49.5 },
- 'T6': { wins: 47.3, ties: 0.9, total: 48.2 },
- 'T5': { wins: 46.1, ties: 0.9, total: 47.0 },
- 'T4': { wins: 45.2, ties: 0.9, total: 46.1 },
- 'T3': { wins: 44.5, ties: 0.9, total: 45.4 },
- 'T2': { wins: 43.8, ties: 0.9, total: 44.7 },
- '98s': { wins: 51.4, ties: 0.9, total: 52.3 },
- '97s': { wins: 49.9, ties: 0.9, total: 50.8 },
- '96s': { wins: 48.4, ties: 0.9, total: 49.3 },
- '95s': { wins: 46.9, ties: 0.9, total: 47.8 },
- '94s': { wins: 46.0, ties: 0.9, total: 46.9 },
- '93s': { wins: 45.3, ties: 0.9, total: 46.2 },
- '92s': { wins: 44.6, ties: 0.9, total: 45.5 },
- '98': { wins: 48.1, ties: 0.9, total: 49.0 },
- '97': { wins: 46.9, ties: 0.9, total: 47.8 },
- '96': { wins: 45.5, ties: 0.9, total: 46.4 },
- '95': { wins: 44.2, ties: 0.9, total: 45.1 },
- '94': { wins: 43.3, ties: 0.9, total: 44.2 },
- '93': { wins: 42.6, ties: 0.9, total: 43.5 },
- '92': { wins: 41.9, ties: 0.9, total: 42.8 },
- '87s': { wins: 49.0, ties: 0.9, total: 49.9 },
- '86s': { wins: 47.5, ties: 0.9, total: 48.4 },
- '85s': { wins: 46.1, ties: 0.9, total: 47.0 },
- '84s': { wins: 45.0, ties: 0.9, total: 45.9 },
- '83s': { wins: 44.3, ties: 0.9, total: 45.2 },
- '82s': { wins: 43.6, ties: 0.9, total: 44.5 },
- '87': { wins: 46.1, ties: 0.9, total: 47.0 },
- '86': { wins: 44.4, ties: 0.9, total: 45.3 },
- '85': { wins: 43.2, ties: 0.9, total: 44.1 },
- '84': { wins: 42.1, ties: 0.9, total: 43.0 },
- '83': { wins: 41.4, ties: 0.9, total: 42.3 },
- '82': { wins: 40.7, ties: 0.9, total: 41.6 },
- '76s': { wins: 46.4, ties: 0.9, total: 47.3 },
- '75s': { wins: 44.9, ties: 0.9, total: 45.8 },
- '74s': { wins: 43.7, ties: 0.9, total: 44.6 },
- '73s': { wins: 43.0, ties: 0.9, total: 43.9 },
- '72s': { wins: 42.3, ties: 0.9, total: 43.2 },
- '76': { wins: 43.6, ties: 0.9, total: 44.5 },
- '75': { wins: 41.8, ties: 0.9, total: 42.7 },
- '74': { wins: 40.7, ties: 0.9, total: 41.6 },
- '73': { wins: 40.0, ties: 0.9, total: 40.9 },
- '72': { wins: 39.3, ties: 0.9, total: 40.2 },
- '65s': { wins: 44.1, ties: 0.9, total: 45.0 },
- '64s': { wins: 42.8, ties: 0.9, total: 43.7 },
- '63s': { wins: 42.2, ties: 0.9, total: 43.1 },
- '62s': { wins: 41.5, ties: 0.9, total: 42.4 },
- '65': { wins: 41.0, ties: 0.9, total: 41.9 },
- '64': { wins: 39.9, ties: 0.9, total: 40.8 },
- '63': { wins: 39.2, ties: 0.9, total: 40.1 },
- '62': { wins: 38.5, ties: 0.9, total: 39.4 },
- '54s': { wins: 42.3, ties: 0.9, total: 43.2 },
- '53s': { wins: 41.7, ties: 0.9, total: 42.6 },
- '52s': { wins: 41.0, ties: 0.9, total: 41.9 },
- '54': { wins: 39.4, ties: 0.9, total: 40.3 },
- '53': { wins: 38.7, ties: 0.9, total: 39.6 },
- '52': { wins: 38.0, ties: 0.9, total: 38.9 },
- '43s': { wins: 40.9, ties: 0.9, total: 41.8 },
- '42s': { wins: 40.2, ties: 0.9, total: 41.1 },
- '43': { wins: 37.7, ties: 0.9, total: 38.6 },
- '42': { wins: 37.0, ties: 0.9, total: 37.9 },
- '32s': { wins: 39.4, ties: 0.9, total: 40.3 },
- '32': { wins: 36.3, ties: 0.9, total: 37.2 }
- };
- this.lastBetAmount = 0;
- this.lastRecommendation = "";
- this.lastPlayerDecision = null;
- this.lastCommunityCount = 0;
- this.messageBoxObserverStarted = false;
- this.lastUpdateCall = 0;
- this.history = {
- correctRecommendations: 0,
- totalHands: 0,
- adjustThresholds() {
- if (this.totalHands > 0) {
- const successRate = this.correctRecommendations / this.totalHands;
- if (successRate < 0.5) {
- this.raiseThreshold -= 5;
- this.foldThreshold += 5;
- } else if (successRate > 0.7) {
- this.raiseThreshold += 5;
- this.foldThreshold -= 5;
- }
- }
- }
- };
- this.addStyle();
- try {
- const savedOpponents = localStorage.getItem('pokerHelperOpponentStats');
- if (savedOpponents) {
- this.opponentStats = new Map(JSON.parse(savedOpponents));
- }
- } catch (e) {
- console.error("Failed to load saved data:", e);
- }
- this.update = this.update.bind(this);
- this.detectHandEnd = this.detectHandEnd.bind(this);
- }
- update() {
- const now = Date.now();
- if (this.lastUpdateTime && now - this.lastUpdateTime < 1000) {
- setTimeout(this.update.bind(this), 1000);
- return;
- }
- this.lastUpdateTime = now;
- let allCards = this.getFullDeck();
- let knownCards = Array.from(document.querySelectorAll("[class*='flipper___'] > div[class*='front___'] > div")).map(e => {
- var card = (e.classList[1] || "null-0").split("_")[0]
- .replace("-A", "-14")
- .replace("-K", "-13")
- .replace("-Q", "-12")
- .replace("-J", "-11");
- if (card == "cardSize") card = "null-0";
- return card;
- });
- let communityCards = knownCards.slice(0, 5);
- let communityCardsCount = communityCards.filter(e => !e.includes("null")).length;
- let isPreFlop = communityCardsCount === 0;
- allCards = this.filterDeck(allCards, communityCards.filter(e => !e.includes("null")));
- if (knownCards.filter(e => !e.includes("null")).length === 0 && communityCardsCount === 0) {
- if (this.isHandActive) {
- this.detectHandEnd();
- this.isHandActive = false;
- document.getElementById("pokerCalc-action").textContent = "Waiting for cards...";
- document.querySelector("#pokerCalc-myHand tbody").innerHTML = "";
- document.querySelector("#pokerCalc-upgrades tbody").innerHTML = "";
- document.querySelector("#pokerCalc-oppPossHands tbody").innerHTML = "";
- document.querySelector("#pokerCalc-preflop tbody").innerHTML = "";
- document.getElementById("pokerCalc-preflop").style.display = "none";
- document.getElementById("pokerCalc-myHand").style.display = "table";
- document.getElementById("pokerCalc-upgrades").style.display = "table";
- document.getElementById("pokerCalc-oppPossHands").style.display = "table";
- }
- setTimeout(this.update.bind(this), 1000);
- return;
- } else {
- this.isHandActive = true;
- }
- if (JSON.stringify(knownCards).length != this.lastLength || communityCardsCount !== this.lastCommunityCount) {
- this.lastCommunityCount = communityCardsCount;
- document.querySelector("#pokerCalc-myHand tbody").innerHTML = "";
- document.querySelector("#pokerCalc-upgrades tbody").innerHTML = "";
- document.querySelector("#pokerCalc-oppPossHands tbody").innerHTML = "";
- document.querySelector("#pokerCalc-preflop tbody").innerHTML = "";
- if (isPreFlop) {
- document.getElementById("pokerCalc-preflop").style.display = "table";
- document.getElementById("pokerCalc-myHand").style.display = "none";
- document.getElementById("pokerCalc-upgrades").style.display = "none";
- document.getElementById("pokerCalc-oppPossHands").style.display = "none";
- this.processPreFlopStats();
- } else {
- document.getElementById("pokerCalc-preflop").style.display = "none";
- document.getElementById("pokerCalc-myHand").style.display = "table";
- document.getElementById("pokerCalc-upgrades").style.display = "table";
- document.getElementById("pokerCalc-oppPossHands").style.display = "table";
- this.processPostFlopStats(knownCards, communityCards, allCards);
- }
- this.lastLength = JSON.stringify(knownCards).length;
- }
- setTimeout(this.update.bind(this), 1000);
- }
- detectHandEnd() {
- this.lastBetAmount = 0;
- this.lastRecommendation = "";
- }
- calculatePreFlopPotential(holeCards, handNotation) {
- const card1Value = parseInt(holeCards[0].split("-")[1]);
- const card2Value = parseInt(holeCards[1].split("-")[1]);
- const hasPair = card1Value === card2Value;
- const isSuited = handNotation.endsWith('s');
- let pairChance = hasPair ? 100 : 32;
- let twoPairChance = 0;
- let tripsChance = hasPair ? 12 : 0;
- let fullHouseChance = 0;
- let straightChance = 0;
- let flushChance = 0;
- let quadsChance = hasPair ? 3 : 0;
- let straightFlushChance = 0;
- let royalFlushChance = 0;
- if (!hasPair) {
- const card1Rank = card1Value;
- const card2Rank = card2Value;
- const gap = Math.abs(card1Rank - card2Rank);
- twoPairChance = 4;
- if (gap <= 4) {
- straightChance = 12 - (gap * 2);
- }
- if (isSuited) {
- flushChance = 6;
- if (gap <= 4) {
- straightFlushChance = 0.5;
- if (card1Rank >= 10 && card2Rank >= 10) {
- royalFlushChance = 0.2;
- }
- }
- }
- }
- return {
- pairChance,
- twoPairChance,
- tripsChance,
- fullHouseChance,
- straightChance,
- flushChance,
- quadsChance,
- straightFlushChance,
- royalFlushChance
- };
- }
- calculateDrawPotential(holeCards, communityCards) {
- const allCards = [...holeCards, ...communityCards].filter(c => !c.includes("null"));
- let pairChance = 0;
- let twoPairChance = 0;
- let tripsChance = 0;
- let fullHouseChance = 0;
- let straightChance = 0;
- let flushChance = 0;
- let quadsChance = 0;
- let straightFlushChance = 0;
- let royalFlushChance = 0;
- if (allCards.length < 2) return {
- pairChance, twoPairChance, tripsChance, fullHouseChance,
- straightChance, flushChance, quadsChance, straightFlushChance, royalFlushChance
- };
- const ranks = allCards.map(card => parseInt(card.split("-")[1]));
- const suits = allCards.map(card => card.split("-")[0]);
- const rankCounts = {};
- ranks.forEach(rank => {
- rankCounts[rank] = (rankCounts[rank] || 0) + 1;
- });
- const suitCounts = {};
- suits.forEach(suit => {
- suitCounts[suit] = (suitCounts[suit] || 0) + 1;
- });
- const handObject = this.makeHandObject(allCards);
- const hasPair = this.hasPair(allCards, handObject);
- const hasTwoPair = this.hasTwoPairs(allCards, handObject);
- const hasTrips = this.hasThreeOfAKind(allCards, handObject);
- const hasQuads = this.hasFourOfAKind(allCards, handObject);
- const hasFullHouse = this.hasFullHouse(allCards, handObject);
- const hasStraight = this.hasStraight(allCards, handObject);
- const hasFlush = this.hasFlush(allCards, handObject);
- const hasStraightFlush = this.hasStraightFlush(allCards, handObject);
- const hasRoyalFlush = this.hasRoyalFlush(allCards, handObject);
- if (hasPair) pairChance = 100;
- if (hasTwoPair) twoPairChance = 100;
- if (hasTrips) tripsChance = 100;
- if (hasQuads) quadsChance = 100;
- if (hasFullHouse) fullHouseChance = 100;
- if (hasStraight) straightChance = 100;
- if (hasFlush) flushChance = 100;
- if (hasStraightFlush) straightFlushChance = 100;
- if (hasRoyalFlush) royalFlushChance = 100;
- const communityCount = communityCards.filter(c => !c.includes("null")).length;
- const cardsToBeDealt = 5 - communityCount;
- if (cardsToBeDealt > 0 && !hasRoyalFlush) {
- if (!hasPair && !hasTwoPair && !hasTrips) {
- const pairOptions = Object.values(rankCounts).filter(count => count === 1).length;
- pairChance = Math.min(100, (pairOptions * 3 * cardsToBeDealt / 47) * 100);
- }
- if (hasPair && !hasTwoPair && !hasFullHouse) {
- const unpaired = ranks.filter(rank => rankCounts[rank] === 1);
- twoPairChance = Math.min(100, (unpaired.length * 3 * cardsToBeDealt / 47) * 100);
- }
- if ((hasPair || hasTwoPair) && !hasTrips) {
- const pairRanks = Object.entries(rankCounts)
- .filter(([rank, count]) => count === 2)
- .map(([rank]) => parseInt(rank));
- if (pairRanks.length > 0) {
- tripsChance = Math.min(100, (pairRanks.length * 2 * cardsToBeDealt / 47) * 100);
- }
- }
- if (hasTrips && !hasQuads) {
- const tripRanks = Object.entries(rankCounts)
- .filter(([rank, count]) => count === 3)
- .map(([rank]) => parseInt(rank));
- if (tripRanks.length > 0) {
- quadsChance = Math.min(100, (tripRanks.length * cardsToBeDealt / 47) * 100);
- }
- }
- if (hasTrips && !hasFullHouse) {
- const singleCards = Object.entries(rankCounts)
- .filter(([rank, count]) => count === 1)
- .length;
- fullHouseChance = Math.min(100, (singleCards * 3 * cardsToBeDealt / 47) * 100);
- } else if (hasTwoPair && !hasFullHouse) {
- fullHouseChance = Math.min(100, (4 * cardsToBeDealt / 47) * 100);
- }
- if (!hasStraight) {
- const uniqueRanks = [...new Set(ranks)].sort((a, b) => a - b);
- if (uniqueRanks.includes(14)) {
- uniqueRanks.push(1);
- }
- let outCount = 0;
- for (let i = 0; i <= uniqueRanks.length - 4; i++) {
- if (uniqueRanks[i+3] - uniqueRanks[i] <= 4) {
- const gap = uniqueRanks[i+3] - uniqueRanks[i] - 3;
- if (gap === 0) {
- outCount = Math.max(outCount, 8);
- } else if (gap === 1) {
- outCount = Math.max(outCount, 4);
- }
- }
- }
- straightChance = Math.min(100, (outCount * cardsToBeDealt / 47) * 100);
- }
- if (!hasFlush) {
- const maxSuitCount = Math.max(...Object.values(suitCounts).map(count => count || 0));
- if (maxSuitCount >= 4) {
- flushChance = Math.min(100, (9 * cardsToBeDealt / 47) * 100);
- } else if (maxSuitCount === 3 && communityCount <= 3) {
- flushChance = Math.min(100, (cardsToBeDealt / 47) * 100);
- }
- }
- if (!hasStraightFlush && flushChance > 0 && straightChance > 0) {
- const flushSuit = Object.entries(suitCounts)
- .filter(([suit, count]) => count >= 3)
- .map(([suit]) => suit);
- if (flushSuit.length > 0) {
- const suitedCards = allCards.filter(card => card.startsWith(flushSuit[0]));
- const suitedRanks = suitedCards.map(card => parseInt(card.split("-")[1]));
- const uniqueRanks = [...new Set(suitedRanks)].sort((a, b) => a - b);
- for (let i = 0; i <= uniqueRanks.length - 3; i++) {
- if (uniqueRanks[i+2] - uniqueRanks[i] <= 4) {
- straightFlushChance = Math.min(100, (cardsToBeDealt / 47) * 25);
- if (uniqueRanks.includes(10) && uniqueRanks.includes(11) &&
- uniqueRanks.includes(12) && uniqueRanks.includes(13) &&
- uniqueRanks.includes(14)) {
- royalFlushChance = 100;
- } else if (uniqueRanks.some(r => r >= 10) &&
- uniqueRanks.every(r => r <= 14)) {
- royalFlushChance = Math.min(100, (cardsToBeDealt / 47) * 5);
- }
- break;
- }
- }
- }
- }
- }
- return {
- pairChance: Math.min(100, Math.max(0, Math.round(pairChance))),
- twoPairChance: Math.min(100, Math.max(0, Math.round(twoPairChance))),
- tripsChance: Math.min(100, Math.max(0, Math.round(tripsChance))),
- fullHouseChance: Math.min(100, Math.max(0, Math.round(fullHouseChance))),
- straightChance: Math.min(100, Math.max(0, Math.round(straightChance))),
- flushChance: Math.min(100, Math.max(0, Math.round(flushChance))),
- quadsChance: Math.min(100, Math.max(0, Math.round(quadsChance))),
- straightFlushChance: Math.min(100, Math.max(0, Math.round(straightFlushChance))),
- royalFlushChance: Math.min(100, Math.max(0, Math.round(royalFlushChance)))
- };
- }
- processPreFlopStats() {
- let playerNodes = document.querySelectorAll("[class*='playerMeGateway___']");
- if (!playerNodes || playerNodes.length === 0) return;
- let holeCards = Array.from(playerNodes[0].querySelectorAll("div[class*='front___'] > div")).map(e => {
- var card = (e.classList[1] || "null-0").split("_")[0]
- .replace("-A", "-14")
- .replace("-K", "-13")
- .replace("-Q", "-12")
- .replace("-J", "-11");
- if (card == "cardSize") card = "null-0";
- return card;
- }).filter(c => !c.includes("null"));
- if (holeCards.length !== 2) return;
- const card1 = this.convertToNotation(holeCards[0]);
- const card2 = this.convertToNotation(holeCards[1]);
- if (!card1 || !card2) return;
- const card1Value = parseInt(holeCards[0].split("-")[1]);
- const card2Value = parseInt(holeCards[1].split("-")[1]);
- const hasPair = card1Value === card2Value;
- const suited = holeCards[0].split("-")[0] === holeCards[1].split("-")[0];
- let handNotation;
- if (card1 === card2) {
- handNotation = card1 + card1;
- } else {
- const sortedCards = [card1, card2].sort((a, b) => {
- const values = { 'A': 14, 'K': 13, 'Q': 12, 'J': 11, 'T': 10 };
- const valA = values[a] || parseInt(a);
- const valB = values[b] || parseInt(b);
- return valB - valA;
- });
- handNotation = sortedCards[0] + sortedCards[1];
- if (suited) handNotation += 's';
- }
- const winEquity = this.calculatePreflopEquity(handNotation);
- const drawPotentials = this.calculatePreFlopPotential(holeCards, handNotation);
- this.updateProbabilityMeter(winEquity, drawPotentials);
- const handStats = this.preflopStats[handNotation];
- if (handStats) {
- const recommendation = this.getPreFlopRecommendation(handStats.total);
- const actionButton = document.getElementById("pokerCalc-action");
- actionButton.textContent = recommendation;
- actionButton.classList.remove("action-raise", "action-call", "action-fold");
- if (recommendation.includes("Raise")) {
- actionButton.classList.add("action-raise");
- } else if (recommendation.includes("Call")) {
- actionButton.classList.add("action-call");
- } else if (recommendation.includes("Fold")) {
- actionButton.classList.add("action-fold");
- }
- this.lastRecommendation = recommendation;
- let statsHTML = `<tr>
- <td>${handNotation}</td>
- <td>${handStats.wins.toFixed(2)}%</td>
- <td>${handStats.ties.toFixed(2)}%</td>
- <td>${handStats.total.toFixed(2)}%</td>
- <td>${this.getPreflopHandTier(handStats.total)}</td>
- </tr>`;
- document.querySelector("#pokerCalc-preflop tbody").innerHTML = statsHTML;
- } else {
- this.estimateHandStats(handNotation);
- }
- }
- processPostFlopStats(knownCards, communityCards, allCards) {
- let playerNodes = document.querySelectorAll("[class*='playerMeGateway___']");
- playerNodes.forEach(player => {
- let myCards = Array.from(player.querySelectorAll("div[class*='front___'] > div")).map(e => {
- var card = (e.classList[1] || "null-0").split("_")[0]
- .replace("-A", "-14")
- .replace("-K", "-13")
- .replace("-Q", "-12")
- .replace("-J", "-11");
- if (card == "cardSize") card = "null-0";
- return card;
- });
- let myHand = this.getHandScore(communityCards.concat(myCards));
- if (myHand.score > 0) {
- const drawPotentials = this.calculateDrawPotential(myCards, communityCards);
- let myRank = this.calculateHandRank(myHand, communityCards, allCards);
- const potSize = parseInt(document.querySelector(".pot-display")?.textContent || 0);
- const betToCall = parseInt(document.querySelector(".bet-to-call")?.textContent || 0);
- const potOdds = betToCall / (potSize + betToCall);
- const recommendation = this.getRecommendation(myRank.topNumber, potOdds);
- const actionButton = document.getElementById("pokerCalc-action");
- actionButton.textContent = recommendation;
- actionButton.classList.remove("action-raise", "action-call", "action-fold");
- if (recommendation.includes("Raise") || recommendation.includes("Bet")) {
- actionButton.classList.add("action-raise");
- } else if (recommendation.includes("Call") || recommendation.includes("Check")) {
- actionButton.classList.add("action-call");
- } else if (recommendation.includes("Fold")) {
- actionButton.classList.add("action-fold");
- }
- this.lastRecommendation = recommendation;
- document.querySelector("#pokerCalc-myHand tbody").innerHTML += `<tr><td>Me</td><td>${myHand.description}</td><td>${myRank.rank}</td><td>${myRank.top}</td></tr>`;
- let myUpgrades = {};
- let bestOppHands = {};
- let additionalCards = [];
- let additionalOppCards = [];
- if (communityCards.filter(e => !e.includes("null")).length == 3) {
- for (let a of allCards) {
- for (let b of allCards) {
- if (a > b) additionalCards.push([a, b]);
- }
- }
- } else if (communityCards.filter(e => !e.includes("null")).length == 4) {
- for (let a of allCards) additionalCards.push([a]);
- } else if (communityCards.filter(e => !e.includes("null")).length == 5) {
- for (let a of allCards) {
- for (let b of allCards) {
- if (a > b) additionalOppCards.push([a, b]);
- }
- }
- }
- for (let cards of additionalCards) {
- let thisHand = this.getHandScore(communityCards.concat(cards).concat(myCards));
- if (thisHand.score > myHand.score) {
- let type = thisHand.description.split(":")[0];
- if (thisHand.description.includes("Four of a kind") || thisHand.description.includes("Three of a kind") || thisHand.description.includes("Pair")) {
- type += ": " + thisHand.description.split("</span>")[1].split("<span")[0].trim() + "s";
- } else if (thisHand.description.includes("Full house")) {
- type += ": " + thisHand.description.split("</span>")[1].split("<span")[0].trim() + "s full of " + thisHand.description.split("</span>").reverse()[0].split("</td>")[0] + "s";
- } else if (thisHand.description.includes("Straight")) {
- type += ": " + thisHand.description.split("</span>")[1].split("<span")[0].trim() + "-high";
- } else if (thisHand.description.includes("Two pairs")) {
- type += ": " + thisHand.description.split("</span>")[1].split("<span")[0].trim() + "s and " + thisHand.description.split("</span>")[3].split("<span")[0].trim() + "s";
- }
- if (!myUpgrades.hasOwnProperty(type)) {
- myUpgrades[type] = { hand: thisHand, type: type, cards: cards, score: thisHand.score, duplicates: 0, chance: 0 };
- }
- myUpgrades[type].description = thisHand.description;
- myUpgrades[type].duplicates++;
- }
- }
- let topUpgrades = Object.values(myUpgrades).map(e => {
- const chance = (e.duplicates / additionalCards.length) * 100;
- const newCommunity = communityCards.concat(e.cards);
- const remainingDeck = this.filterDeck(allCards, [...e.cards, ...myCards]);
- const handRank = this.calculateHandRank(e.hand, newCommunity, remainingDeck);
- return {
- ...e,
- chance: chance,
- rank: handRank.rank || "N/A",
- top: handRank.top || "N/A"
- };
- });
- let aggregatedChances = {
- pairChance: 0,
- twoPairChance: 0,
- tripsChance: 0,
- fullHouseChance: 0,
- straightChance: 0,
- flushChance: 0,
- quadsChance: 0,
- straightFlushChance: 0,
- royalFlushChance: 0
- };
- topUpgrades.forEach(upgrade => {
- const type = upgrade.type.split(':')[0].trim();
- if (type.includes('Pair') && !type.includes('Two')) {
- aggregatedChances.pairChance += upgrade.chance;
- } else if (type.includes('Two pairs')) {
- aggregatedChances.twoPairChance += upgrade.chance;
- } else if (type.includes('Three of a kind')) {
- aggregatedChances.tripsChance += upgrade.chance;
- } else if (type.includes('Full house')) {
- aggregatedChances.fullHouseChance += upgrade.chance;
- } else if (type.includes('Four of a kind')) {
- aggregatedChances.quadsChance += upgrade.chance;
- } else if (type.includes('Straight') && !type.includes('flush')) {
- aggregatedChances.straightChance += upgrade.chance;
- } else if (type.includes('Flush') && !type.includes('Straight') && !type.includes('Royal')) {
- aggregatedChances.flushChance += upgrade.chance;
- } else if (type.includes('Straight flush') && !type.includes('Royal')) {
- aggregatedChances.straightFlushChance += upgrade.chance;
- } else if (type.includes('Royal flush')) {
- aggregatedChances.royalFlushChance += upgrade.chance;
- }
- });
- if (myHand.description.includes('Pair') && !myHand.description.includes('Two')) {
- aggregatedChances.pairChance = 100;
- } else if (myHand.description.includes('Two pairs')) {
- aggregatedChances.pairChance = 100;
- aggregatedChances.twoPairChance = 100;
- } else if (myHand.description.includes('Three of a kind')) {
- aggregatedChances.pairChance = 100;
- aggregatedChances.tripsChance = 100;
- } else if (myHand.description.includes('Straight') && !myHand.description.includes('flush')) {
- aggregatedChances.straightChance = 100;
- } else if (myHand.description.includes('Flush') && !myHand.description.includes('Straight') && !myHand.description.includes('Royal')) {
- aggregatedChances.flushChance = 100;
- } else if (myHand.description.includes('Full house')) {
- aggregatedChances.pairChance = 100;
- aggregatedChances.twoPairChance = 100;
- aggregatedChances.tripsChance = 100;
- aggregatedChances.fullHouseChance = 100;
- } else if (myHand.description.includes('Four of a kind')) {
- aggregatedChances.pairChance = 100;
- aggregatedChances.tripsChance = 100;
- aggregatedChances.quadsChance = 100;
- } else if (myHand.description.includes('Straight flush') && !myHand.description.includes('Royal')) {
- aggregatedChances.straightChance = 100;
- aggregatedChances.flushChance = 100;
- aggregatedChances.straightFlushChance = 100;
- } else if (myHand.description.includes('Royal flush')) {
- aggregatedChances.straightChance = 100;
- aggregatedChances.flushChance = 100;
- aggregatedChances.straightFlushChance = 100;
- aggregatedChances.royalFlushChance = 100;
- }
- Object.keys(aggregatedChances).forEach(key => {
- aggregatedChances[key] = Math.min(100, aggregatedChances[key]);
- });
- this.updateProbabilityMeter(myRank.winProbability, aggregatedChances);
- let bestUpgradeChance = -1;
- let bestUpgradeIndex = -1;
- for (let i = 0; i < topUpgrades.length; i++) {
- if (topUpgrades[i].chance > bestUpgradeChance) {
- bestUpgradeChance = topUpgrades[i].chance;
- bestUpgradeIndex = i;
- }
- }
- let upgradeString = "";
- for (let i = 0; i < topUpgrades.length; i++) {
- const upgrade = topUpgrades[i];
- const isBestHand = i === bestUpgradeIndex;
- upgradeString += `<tr class="${isBestHand ? 'best-hand' : ''}">`;
- upgradeString += `<td>${upgrade.chance.toFixed(2)}%</td><td>${upgrade.type}</td><td>${upgrade.rank}</td><td>${upgrade.top}</td>`;
- upgradeString += "</tr>";
- }
- document.querySelector("#pokerCalc-upgrades tbody").innerHTML = upgradeString;
- for (let cards of additionalOppCards) {
- let oppPossHand = this.getHandScore(communityCards.concat(cards));
- let type = oppPossHand.description.split(":")[0];
- if (oppPossHand.description.includes("Four of a kind") || oppPossHand.description.includes("Three of a kind") || oppPossHand.description.includes("Pair")) {
- type += ": " + oppPossHand.description.split("</span>")[1].split("<span")[0].trim() + "s";
- } else if (oppPossHand.description.includes("Full house")) {
- type += ": " + oppPossHand.description.split("</span>")[1].split("<span")[0].trim() + "s full of " + oppPossHand.description.split("</span>").reverse()[0].split("</td>")[0] + "s";
- } else if (oppPossHand.description.includes("Straight")) {
- type += ": " + oppPossHand.description.split("</span>")[1].split("<span")[0].trim() + "-high";
- } else if (oppPossHand.description.includes("Two pairs")) {
- type += ": " + oppPossHand.description.split("</span>")[1].split("<span")[0].trim() + "s and " + oppPossHand.description.split("</span>")[3].split("<span")[0].trim() + "s";
- }
- if (!bestOppHands.hasOwnProperty(type)) {
- bestOppHands[type] = { hand: oppPossHand, type: type, cards: cards, score: oppPossHand.score, duplicates: 0, chance: 0 };
- }
- bestOppHands[type].description = oppPossHand.description;
- bestOppHands[type].duplicates++;
- }
- let topOppHands = Object.values(bestOppHands);
- topOppHands.forEach(e => {
- e.chance = (e.duplicates / additionalOppCards.length) * 100;
- });
- topOppHands = topOppHands
- .sort((a, b) => b.score - a.score)
- .slice(0, this.upgradesToShow);
- topOppHands.forEach(e => {
- const newCommunity = communityCards.concat(e.cards);
- const remainingDeck = this.filterDeck(allCards, e.cards);
- const thisRank = this.calculateHandRank(e.hand, newCommunity, remainingDeck);
- e.rank = thisRank.rank;
- e.top = thisRank.top;
- });
- let bestOppHandScore = -1;
- let bestOppHandIndex = -1;
- for (let i = 0; i < topOppHands.length; i++) {
- if (topOppHands[i].score > bestOppHandScore) {
- bestOppHandScore = topOppHands[i].score;
- bestOppHandIndex = i;
- }
- }
- let oppHandString = "";
- for (let i = 0; i < topOppHands.length; i++) {
- const upgrade = topOppHands[i];
- const isBestHand = i === bestOppHandIndex;
- oppHandString += `<tr class="${isBestHand ? 'best-opp-hand' : ''}">`;
- oppHandString += `<td>${upgrade.chance.toFixed(2)}%</td><td>${upgrade.type}</td><td>${upgrade.rank}</td><td>${upgrade.top}</td>`;
- oppHandString += "</tr>";
- }
- document.querySelector("#pokerCalc-oppPossHands tbody").innerHTML = oppHandString;
- }
- });
- }
- getRecommendation(topNumber, potOdds) {
- const topPercent = topNumber * 100;
- const thresholds = {
- 'pre-flop': { raise: 25, fold: 60 },
- 'flop': { raise: 20, fold: 50 },
- 'turn': { raise: 15, fold: 40 },
- 'river': { raise: 10, fold: 30 }
- };
- const betRound = this.getBettingRound();
- const { raise, fold } = thresholds[betRound] || { raise: 20, fold: 50 };
- if (topPercent <= raise) {
- return "Raise/Bet - Strong Hand";
- } else if (topPercent <= fold) {
- if (potOdds > 0 && potOdds < topNumber) {
- return "Call - Pot Odds Favorable";
- }
- return "Call/Check - Moderate";
- } else {
- return "Fold - Weak Hand";
- }
- }
- getBettingRound() {
- const communityCards = Array.from(document.querySelectorAll("[class*='flipper___'] > div[class*='front___'] > div")).slice(0, 5);
- const numCards = communityCards.filter(e => !e.classList.contains("null-0")).length;
- switch (numCards) {
- case 0: return 'pre-flop';
- case 3: return 'flop';
- case 4: return 'turn';
- case 5: return 'river';
- default: return 'unknown';
- }
- }
- addStatisticsTable() {
- const observer = new MutationObserver((mutations, obs) => {
- const reactRoot = document.querySelector("#react-root");
- if (reactRoot) {
- if (!document.getElementById("pokerCalc-div")) {
- const div = document.createElement("div");
- div.id = "pokerCalc-div";
- div.style.position = "relative";
- div.innerHTML = `
- <div class="suits-container"></div>
- <div class="flopmaster-header">
- <div id="pokerCalc-recommendations">
- <div class="action-chip">
- <div class="chip-inner">
- <span id="pokerCalc-action">Waiting for cards...</span>
- </div>
- <div class="chip-edge"></div>
- </div>
- </div>
- <div id="flopmaster-logo">
- <div class="logo-container">
- <div class="logo-card">
- <span class="logo-text">FlopMaster</span>
- <div class="logo-suits">
- <span class="suit hearts">♥</span>
- <span class="suit spades">♠</span>
- <span class="suit diamonds">♦</span>
- <span class="suit clubs">♣</span>
- </div>
- </div>
- </div>
- </div>
- </div>
- <div class="win-probability-meter">
- <div class="meter-label">Win Probability</div>
- <div class="meter-container">
- <div class="meter-groove"></div>
- <div class="meter-bar" style="width: 0%"></div>
- <div class="meter-value">0%</div>
- </div>
- </div>
- <div class="mini-meters-container">
- <div class="mini-meter pair-meter">
- <div class="mini-meter-label">Pair</div>
- <div class="mini-meter-container">
- <div class="mini-meter-groove"></div>
- <div class="mini-meter-bar" data-type="pair" style="width: 0%"></div>
- <div class="mini-meter-value">0%</div>
- </div>
- </div>
- <div class="mini-meter two-pair-meter">
- <div class="mini-meter-label">Two Pair</div>
- <div class="mini-meter-container">
- <div class="mini-meter-groove"></div>
- <div class="mini-meter-bar" data-type="twoPair" style="width: 0%"></div>
- <div class="mini-meter-value">0%</div>
- </div>
- </div>
- <div class="mini-meter trips-meter">
- <div class="mini-meter-label">Three of a Kind</div>
- <div class="mini-meter-container">
- <div class="mini-meter-groove"></div>
- <div class="mini-meter-bar" data-type="trips" style="width: 0%"></div>
- <div class="mini-meter-value">0%</div>
- </div>
- </div>
- <div class="mini-meter straight-meter">
- <div class="mini-meter-label">Straight</div>
- <div class="mini-meter-container">
- <div class="mini-meter-groove"></div>
- <div class="mini-meter-bar" data-type="straight" style="width: 0%"></div>
- <div class="mini-meter-value">0%</div>
- </div>
- </div>
- <div class="mini-meter flush-meter">
- <div class="mini-meter-label">Flush</div>
- <div class="mini-meter-container">
- <div class="mini-meter-groove"></div>
- <div class="mini-meter-bar" data-type="flush" style="width: 0%"></div>
- <div class="mini-meter-value">0%</div>
- </div>
- </div>
- <div class="mini-meter full-house-meter">
- <div class="mini-meter-label">Full House</div>
- <div class="mini-meter-container">
- <div class="mini-meter-groove"></div>
- <div class="mini-meter-bar" data-type="fullHouse" style="width: 0%"></div>
- <div class="mini-meter-value">0%</div>
- </div>
- </div>
- <div class="mini-meter quads-meter">
- <div class="mini-meter-label">Four of a Kind</div>
- <div class="mini-meter-container">
- <div class="mini-meter-groove"></div>
- <div class="mini-meter-bar" data-type="quads" style="width: 0%"></div>
- <div class="mini-meter-value">0%</div>
- </div>
- </div>
- <div class="mini-meter straight-flush-meter">
- <div class="mini-meter-label">Straight Flush</div>
- <div class="mini-meter-container">
- <div class="mini-meter-groove"></div>
- <div class="mini-meter-bar" data-type="straightFlush" style="width: 0%"></div>
- <div class="mini-meter-value">0%</div>
- </div>
- </div>
- <div class="mini-meter royal-flush-meter">
- <div class="mini-meter-label">Royal Flush</div>
- <div class="mini-meter-container">
- <div class="mini-meter-groove"></div>
- <div class="mini-meter-bar" data-type="royalFlush" style="width: 0%"></div>
- <div class="mini-meter-value">0%</div>
- </div>
- </div>
- </div>
- <table id="pokerCalc-preflop" style="display: none;">
- <caption>Pre-Flop Hand Statistics</caption>
- <thead>
- <tr>
- <th>Hand</th>
- <th>Win %</th>
- <th>Tie %</th>
- <th>Total %</th>
- <th>Tier</th>
- </tr>
- </thead>
- <tbody></tbody>
- </table>
- <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>
- `;
- reactRoot.after(div);
- this.initProbabilityMeter();
- setTimeout(() => {
- const suitsContainer = document.querySelector('.suits-container');
- if (suitsContainer) {
- const floatingSuits = new FloatingSuits(suitsContainer, {
- suitCount: 25,
- minSize: 18,
- maxSize: 42,
- minSpeed: 10,
- maxSpeed: 80
- });
- window.pokerFloatingSuits = floatingSuits;
- floatingSuits.start();
- setTimeout(() => {
- const fps = window.performance?.now ? 60 : 30; // Simplified - assume 60 FPS for modern browsers
- if (fps < 40) {
- floatingSuits.setDensity(20); // Reduce number of suits on lower-end devices
- }
- }, 3000);
- }
- }, 500);
- }
- obs.disconnect();
- this.update();
- }
- });
- observer.observe(document, { childList: true, subtree: true });
- }
- initProbabilityMeter() {
- const updateMeter = (percentage = 0, handStats = null) => {
- const meterBar = document.querySelector('.meter-bar');
- const meterValue = document.querySelector('.meter-value');
- if (meterBar && meterValue) {
- meterBar.style.transition = 'width 0.8s ease-in-out';
- meterBar.style.width = `${percentage}%`;
- meterValue.textContent = `${percentage.toFixed(1)}%`;
- if (percentage >= 70) {
- meterBar.style.backgroundColor = '#4CAF50';
- } else if (percentage >= 40) {
- meterBar.style.backgroundColor = '#FFC107';
- } else {
- meterBar.style.backgroundColor = '#F44336';
- }
- if (handStats) {
- this.updateMiniMeters(handStats);
- }
- }
- };
- this.updateProbabilityMeter = updateMeter;
- updateMeter(0);
- }
- updateMiniMeters(stats = {}) {
- const pairChance = stats.pairChance || 0;
- const twoPairChance = stats.twoPairChance || 0;
- const tripsChance = stats.tripsChance || 0;
- const fullHouseChance = stats.fullHouseChance || 0;
- const straightChance = stats.straightChance || 0;
- const flushChance = stats.flushChance || 0;
- const quadsChance = stats.quadsChance || 0;
- const straightFlushChance = stats.straightFlushChance || 0;
- const royalFlushChance = stats.royalFlushChance || 0;
- const pairBar = document.querySelector('.mini-meter-bar[data-type="pair"]');
- const pairValue = pairBar?.parentNode.querySelector('.mini-meter-value');
- if (pairBar && pairValue) {
- pairBar.style.width = `${pairChance}%`;
- pairValue.textContent = `${pairChance.toFixed(1)}%`;
- }
- const twoPairBar = document.querySelector('.mini-meter-bar[data-type="twoPair"]');
- const twoPairValue = twoPairBar?.parentNode.querySelector('.mini-meter-value');
- if (twoPairBar && twoPairValue) {
- twoPairBar.style.width = `${twoPairChance}%`;
- twoPairValue.textContent = `${twoPairChance.toFixed(1)}%`;
- }
- const tripsBar = document.querySelector('.mini-meter-bar[data-type="trips"]');
- const tripsValue = tripsBar?.parentNode.querySelector('.mini-meter-value');
- if (tripsBar && tripsValue) {
- tripsBar.style.width = `${tripsChance}%`;
- tripsValue.textContent = `${tripsChance.toFixed(1)}%`;
- }
- const quadsBar = document.querySelector('.mini-meter-bar[data-type="quads"]');
- const quadsValue = quadsBar?.parentNode.querySelector('.mini-meter-value');
- if (quadsBar && quadsValue) {
- quadsBar.style.width = `${quadsChance}%`;
- quadsValue.textContent = `${quadsChance.toFixed(1)}%`;
- }
- const fullHouseBar = document.querySelector('.mini-meter-bar[data-type="fullHouse"]');
- const fullHouseValue = fullHouseBar?.parentNode.querySelector('.mini-meter-value');
- if (fullHouseBar && fullHouseValue) {
- fullHouseBar.style.width = `${fullHouseChance}%`;
- fullHouseValue.textContent = `${fullHouseChance.toFixed(1)}%`;
- }
- const straightBar = document.querySelector('.mini-meter-bar[data-type="straight"]');
- const straightValue = straightBar?.parentNode.querySelector('.mini-meter-value');
- if (straightBar && straightValue) {
- straightBar.style.width = `${straightChance}%`;
- straightValue.textContent = `${straightChance.toFixed(1)}%`;
- }
- const flushBar = document.querySelector('.mini-meter-bar[data-type="flush"]');
- const flushValue = flushBar?.parentNode.querySelector('.mini-meter-value');
- if (flushBar && flushValue) {
- flushBar.style.width = `${flushChance}%`;
- flushValue.textContent = `${flushChance.toFixed(1)}%`;
- }
- const straightFlushBar = document.querySelector('.mini-meter-bar[data-type="straightFlush"]');
- const straightFlushValue = straightFlushBar?.parentNode.querySelector('.mini-meter-value');
- if (straightFlushBar && straightFlushValue) {
- straightFlushBar.style.width = `${straightFlushChance}%`;
- straightFlushValue.textContent = `${straightFlushChance.toFixed(1)}%`;
- }
- const royalFlushBar = document.querySelector('.mini-meter-bar[data-type="royalFlush"]');
- const royalFlushValue = royalFlushBar?.parentNode.querySelector('.mini-meter-value');
- if (royalFlushBar && royalFlushValue) {
- royalFlushBar.style.width = `${royalFlushChance}%`;
- royalFlushValue.textContent = `${royalFlushChance.toFixed(1)}%`;
- }
- }
- calculatePreflopEquity(handNotation) {
- if (this.preflopStats[handNotation]) {
- return this.preflopStats[handNotation].total;
- }
- const isPair = handNotation.length === 2;
- const isSuited = handNotation.endsWith('s');
- let card1, card2;
- if (isPair) {
- card1 = card2 = this.getCardRank(handNotation[0]);
- } else {
- card1 = this.getCardRank(handNotation[0]);
- card2 = this.getCardRank(handNotation[1].replace('s', ''));
- }
- let equity = 0;
- if (isPair) {
- equity = 50 + (card1 * 2.5);
- } else {
- let baseEquity = (card1 + card2) * 1.5;
- const isConnected = Math.abs(card1 - card2) <= 2;
- if (isSuited) baseEquity += 5;
- if (isConnected) baseEquity += 3;
- equity = baseEquity;
- }
- return Math.min(Math.max(equity, 30), 85);
- }
- getCardRank(cardValue) {
- const values = { 'A': 14, 'K': 13, 'Q': 12, 'J': 11, 'T': 10 };
- return values[cardValue] || parseInt(cardValue);
- }
- estimateHandStats(handNotation) {
- const winEquity = this.calculatePreflopEquity(handNotation);
- const isPocketPair = handNotation.length === 2 && handNotation[0] === handNotation[1];
- const isSuited = handNotation.endsWith('s');
- let pairChance = isPocketPair ? 100 : 40;
- let twoPairChance = isPocketPair ? 20 : 5;
- let tripsChance = isPocketPair ? 25 : 0;
- let fullHouseChance = isPocketPair ? 5 : 0;
- let straightChance = 0;
- let flushChance = 0;
- let quadsChance = isPocketPair ? 3 : 0;
- let straightFlushChance = 0;
- let royalFlushChance = 0;
- if (!isPocketPair) {
- const card1 = this.getCardRank(handNotation[0]);
- const card2 = this.getCardRank(handNotation[1].replace('s', ''));
- const isConnected = Math.abs(card1 - card2) <= 3;
- if (isConnected) {
- straightChance = 20 - (Math.abs(card1 - card2) * 5);
- }
- if (isSuited) {
- flushChance = 15;
- if (isConnected) {
- straightFlushChance = 1;
- if (card1 >= 10 && card2 >= 10) {
- royalFlushChance = 0.5;
- }
- }
- }
- }
- const recommendation = this.getPreFlopRecommendation(winEquity);
- const actionButton = document.getElementById("pokerCalc-action");
- actionButton.textContent = recommendation;
- actionButton.classList.remove("action-raise", "action-call", "action-fold");
- if (recommendation.includes("Raise")) {
- actionButton.classList.add("action-raise");
- } else if (recommendation.includes("Call")) {
- actionButton.classList.add("action-call");
- } else if (recommendation.includes("Fold")) {
- actionButton.classList.add("action-fold");
- }
- this.lastRecommendation = recommendation;
- let statsHTML = `<tr>
- <td>${handNotation}</td>
- <td>${(winEquity * 0.95).toFixed(2)}%</td>
- <td>${(winEquity * 0.05).toFixed(2)}%</td>
- <td>${winEquity.toFixed(2)}%</td>
- <td>${this.getPreflopHandTier(winEquity)}</td>
- </tr>`;
- document.querySelector("#pokerCalc-preflop tbody").innerHTML = statsHTML;
- this.updateProbabilityMeter(winEquity, {
- pairChance,
- twoPairChance,
- tripsChance,
- fullHouseChance,
- straightChance,
- flushChance,
- quadsChance,
- straightFlushChance,
- royalFlushChance
- });
- }
- convertToNotation(card) {
- if (!card || card === "null-0") return null;
- const value = card.split("-")[1];
- switch (value) {
- case "14": return "A";
- case "13": return "K";
- case "12": return "Q";
- case "11": return "J";
- case "10": return "T";
- default: return value;
- }
- }
- getPreflopHandTier(winPercentage) {
- if (winPercentage >= 75) return "Premium";
- if (winPercentage >= 65) return "Strong";
- if (winPercentage >= 55) return "Playable";
- if (winPercentage >= 45) return "Speculative";
- return "Weak";
- }
- getPreFlopRecommendation(winPercentage) {
- const position = this.getPlayerPosition();
- let raiseThreshold, callThreshold;
- switch (position) {
- case 'early':
- raiseThreshold = 70;
- callThreshold = 60;
- break;
- case 'middle':
- raiseThreshold = 65;
- callThreshold = 55;
- break;
- case 'late':
- raiseThreshold = 60;
- callThreshold = 50;
- break;
- case 'button':
- case 'smallBlind':
- raiseThreshold = 55;
- callThreshold = 45;
- break;
- case 'bigBlind':
- raiseThreshold = 60;
- callThreshold = 40;
- break;
- default:
- raiseThreshold = 65;
- callThreshold = 50;
- }
- if (winPercentage < 30) {
- return "Fold - Weak Hand";
- }
- if (winPercentage >= raiseThreshold) {
- return "Raise - Strong Hand";
- } else if (winPercentage >= callThreshold) {
- return "Call - Moderate Hand";
- } else {
- return "Fold - Weak Hand";
- }
- }
- getPlayerPosition() {
- return 'middle';
- }
- getFullDeck() {
- let result = [];
- for (let suit of ["hearts", "diamonds", "spades", "clubs"]) {
- for (let value of [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]) {
- result.push(suit + "-" + value);
- }
- }
- return result;
- }
- filterDeck(deck, cards) {
- for (let card of cards) {
- let index = deck.indexOf(card);
- if (index != -1) {
- delete deck[index];
- }
- }
- return deck.filter(e => e != "empty");
- }
- calculateHandRank(myHand, communityCards, allCards) {
- if (!myHand?.score || !Array.isArray(communityCards) || !Array.isArray(allCards) ||
- communityCards.length === 0 || allCards.length === 0) {
- return {
- rank: "N/A",
- top: "N/A",
- topNumber: 0,
- betterHands: 0,
- equalHands: 0,
- worseHands: 0,
- totalHands: 0
- };
- }
- let betterHands = 0;
- let equalHands = 0;
- let worseHands = 0;
- let totalHands = 0;
- const availableCards = allCards.filter(card =>
- card &&
- typeof card === 'string' &&
- !communityCards.includes(card) &&
- !myHand.result.includes(card)
- );
- if (availableCards.length < 2) {
- return {
- rank: "N/A",
- top: "N/A",
- topNumber: 0.5
- };
- }
- for (let i = 0; i < availableCards.length - 1; i++) {
- for (let j = i + 1; j < availableCards.length; j++) {
- const combo = [availableCards[i], availableCards[j]];
- let thisHand = this.getHandScore(communityCards.concat(combo));
- if (thisHand.score > myHand.score) {
- betterHands++;
- } else if (thisHand.score === myHand.score) {
- const tieBreaker = this.compareTiedHands(myHand, thisHand);
- if (tieBreaker > 0) betterHands++;
- else if (tieBreaker < 0) worseHands++;
- else equalHands++;
- } else {
- worseHands++;
- }
- totalHands++;
- }
- }
- if (totalHands === 0) {
- return {
- rank: "N/A",
- top: "N/A",
- topNumber: 0.5
- };
- }
- const trueRank = betterHands + Math.ceil(equalHands / 2);
- const percentile = ((betterHands + equalHands / 2) / totalHands) * 100;
- const winProbability = 100 - percentile;
- return {
- rank: `${trueRank + 1} / ${totalHands}`,
- top: `${percentile.toFixed(1)}%`,
- topNumber: percentile / 100,
- winProbability: winProbability
- };
- }
- compareTiedHands(hand1, hand2) {
- const kickers1 = hand1.result.map(card => parseInt(card.split('-')[1])).sort((a, b) => b - a);
- const kickers2 = hand2.result.map(card => parseInt(card.split('-')[1])).sort((a, b) => b - a);
- for (let i = 0; i < kickers1.length; i++) {
- if (kickers1[i] !== kickers2[i]) {
- return kickers2[i] - kickers1[i];
- }
- }
- return 0;
- }
- prettifyHand(hand) {
- let resultText = "";
- for (let card of hand) {
- if (card != "null-0") {
- resultText += " " + card
- .replace("diamonds", "<span class='diamonds'>♦</span>")
- .replace("spades", "<span class='spades'>♠</span>")
- .replace("hearts", "<span class='hearts'>♥</span>")
- .replace("clubs", "<span class='clubs'>♣</span>")
- .replace("-14", "-A")
- .replace("-13", "K")
- .replace("-12", "Q")
- .replace("-11", "J")
- .replace("-", "");
- }
- }
- return resultText;
- }
- getHandScore(hand) {
- hand = hand.filter(e => !e.includes("null"));
- if (hand.length < 5) { return { description: "", score: 0 }; }
- let resultString = "";
- let resultText = "";
- let handResult;
- let handObject = this.makeHandObject(hand);
- if (handResult = this.hasFourOfAKind(hand, handObject)) {
- resultString += "7";
- resultText += "Four of a kind:";
- } else if (handResult = this.hasFullHouse(hand, handObject)) {
- resultString += "6";
- resultText += "Full house:";
- } else if (handResult = this.hasFlush(hand, handObject)) {
- let isRoyal = this.hasRoyalFlush(hand, handObject);
- if (isRoyal) {
- handResult = isRoyal;
- resultString += "9";
- resultText += "Royal flush:";
- } else {
- let isStraight = this.hasStraightFlush(hand, handObject);
- if (isStraight) {
- handResult = isStraight;
- resultString += "8";
- resultText += "Straight flush:";
- } else {
- resultString += "5";
- resultText += "Flush:";
- }
- }
- } else if (handResult = this.hasStraight(hand, handObject)) {
- resultString += "4";
- resultText += "Straight:";
- } else if (handResult = this.hasThreeOfAKind(hand, handObject)) {
- resultString += "3";
- resultText += "Three of a kind:";
- } else if (handResult = this.hasTwoPairs(hand, handObject)) {
- resultString += "2";
- resultText += "Two pairs:";
- } else if (handResult = this.hasPair(hand, handObject)) {
- resultString += "1";
- resultText += "Pair:";
- } else {
- resultString += "0";
- resultText += "High card:";
- handResult = hand.slice(0, 5);
- }
- for (let card of handResult) {
- resultString += parseInt(card.split("-")[1]).toString(16);
- }
- resultText += this.prettifyHand(handResult);
- return { description: resultText, result: handResult, score: parseInt(resultString, 16) };
- }
- makeHandObject(hand) {
- let resultMap = { cards: hand, suits: {}, values: {} };
- hand.sort((a, b) => parseInt(b.split("-")[1]) - parseInt(a.split("-")[1])).filter(e => e != "null-0").forEach(e => {
- let suit = e.split("-")[0];
- let value = e.split("-")[1];
- if (!resultMap.suits.hasOwnProperty(suit)) {
- resultMap.suits[suit] = [];
- }
- if (!resultMap.values.hasOwnProperty(value)) {
- resultMap.values[value] = [];
- }
- resultMap.suits[suit].push(e);
- resultMap.values[value].push(e);
- });
- return resultMap;
- }
- hasRoyalFlush(hand, handObject) {
- for (let 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 (let suit in handObject.suits) {
- const suitCards = handObject.suits[suit];
- if (suitCards.length >= 5) {
- const straightFlush = this.hasStraight(suitCards, this.makeHandObject(suitCards));
- if (straightFlush) {
- return straightFlush;
- }
- }
- }
- return null;
- }
- hasFourOfAKind(hand, handObject) {
- let quadruplets = Object.values(handObject.values).filter(e => e.length == 4);
- if (quadruplets.length > 0) {
- delete hand[hand.indexOf(quadruplets[0][0])];
- delete hand[hand.indexOf(quadruplets[0][1])];
- delete hand[hand.indexOf(quadruplets[0][2])];
- delete hand[hand.indexOf(quadruplets[0][3])];
- hand = hand.filter(e => e != "empty");
- return quadruplets[0].concat(hand).slice(0, 5);
- }
- return null;
- }
- hasFullHouse(hand, handObject) {
- let triplets = Object.values(handObject.values)
- .filter(e => e.length === 3)
- .sort((a, b) => parseInt(b[0].split("-")[1]) - parseInt(a[0].split("-")[1]));
- if (triplets.length === 0) {
- return null;
- }
- for (let threeOfKind of triplets) {
- const threeValue = parseInt(threeOfKind[0].split("-")[1]);
- const pairs = Object.values(handObject.values)
- .filter(e => e.length >= 2 && parseInt(e[0].split("-")[1]) !== threeValue)
- .sort((a, b) => parseInt(b[0].split("-")[1]) - parseInt(a[0].split("-")[1]));
- if (pairs.length > 0) {
- return threeOfKind.slice(0, 3).concat(pairs[0].slice(0, 2));
- }
- }
- return null;
- }
- hasFlush(hand, handObject) {
- for (let 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 valueMap = new Map();
- hand.forEach(card => {
- const value = parseInt(card.split("-")[1]);
- if (!valueMap.has(value) || parseInt(valueMap.get(value).split("-")[1]) < value) {
- valueMap.set(value, card);
- }
- });
- const uniqueValues = Array.from(valueMap.keys()).sort((a, b) => b - a);
- for (let i = 0; i <= uniqueValues.length - 5; i++) {
- const possibleStraight = uniqueValues.slice(i, i + 5);
- if (possibleStraight[0] - possibleStraight[4] === 4) {
- return possibleStraight.map(value => valueMap.get(value));
- }
- }
- if (uniqueValues.includes(14) &&
- uniqueValues.includes(2) &&
- uniqueValues.includes(3) &&
- uniqueValues.includes(4) &&
- uniqueValues.includes(5)) {
- return [
- valueMap.get(5),
- valueMap.get(4),
- valueMap.get(3),
- valueMap.get(2),
- valueMap.get(14)
- ];
- }
- return null;
- }
- hasThreeOfAKind(hand, handObject) {
- let triplets = Object.values(handObject.values).filter(e => e.length == 3);
- if (triplets.length > 0) {
- delete hand[hand.indexOf(triplets[0][0])];
- delete hand[hand.indexOf(triplets[0][1])];
- delete hand[hand.indexOf(triplets[0][2])];
- hand = hand.filter(e => e != "empty");
- return triplets[0].concat(hand).slice(0, 5);
- }
- return null;
- }
- hasTwoPairs(hand, handObject) {
- let pairs = Object.values(handObject.values).filter(e => e.length == 2);
- if (pairs.length > 1) {
- delete hand[hand.indexOf(pairs[0][0])];
- delete hand[hand.indexOf(pairs[0][1])];
- delete hand[hand.indexOf(pairs[1][0])];
- delete hand[hand.indexOf(pairs[1][1])];
- hand = hand.filter(e => e != "empty");
- if (parseInt(pairs[0][0].split("-")[1]) > parseInt(pairs[1][0].split("-")[1])) {
- return pairs[0].concat(pairs[1].concat(hand)).slice(0, 5);
- } else {
- return pairs[1].concat(pairs[0].concat(hand)).slice(0, 5);
- }
- }
- return null;
- }
- hasPair(hand, handObject) {
- let pairs = Object.values(handObject.values).filter(e => e.length == 2);
- if (pairs.length > 0) {
- delete hand[hand.indexOf(pairs[0][0])];
- delete hand[hand.indexOf(pairs[0][1])];
- hand = hand.filter(e => e != "empty");
- return pairs[0].concat(hand).slice(0, 5);
- }
- return null;
- }
- addStyle() {
- try {
- const styleText = `
- @import url('https://fonts.googleapis.com/css2?family=Playfair+Display:wght@600;700&family=Roboto:wght@400;500;700&display=swap');
- #pokerCalc-div * {
- font-family: 'Roboto', 'Arial', sans-serif;
- box-sizing: border-box;
- color: #fbf7d5;
- }
- .flopmaster-header {
- display: flex;
- flex-direction: row;
- align-items: center;
- justify-content: space-between;
- margin-bottom: 20px;
- gap: 20px;
- }
- #flopmaster-logo {
- text-align: right;
- padding: 5px 0;
- position: relative;
- flex: 0 0 auto;
- }
- .logo-container {
- position: relative;
- display: inline-block;
- perspective: 1200px;
- transform-style: preserve-3d;
- }
- .logo-card {
- position: relative;
- background: linear-gradient(145deg, #0e7a38 0%, #054122 90%);
- border-radius: 12px;
- padding: 15px 25px;
- box-shadow:
- 0 10px 25px rgba(0,0,0,0.6),
- 0 0 20px rgba(255,255,255,0.15) inset;
- border: 2px solid rgba(255,215,0,0.4);
- overflow: hidden;
- transform-style: preserve-3d;
- transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
- min-width: 240px;
- animation: floatCard 5s ease-in-out infinite alternate;
- }
- .logo-card::before {
- content: '';
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjAiIGhlaWdodD0iNjAiIHZpZXdCb3g9IjAgMCA2MCA2MCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxwYXRoIGQ9Ik0zNiAzNGMwLTEuMTA1LS44OTUtMi0yLTJzLTIgLjg5NS0yIDJjMCAxLjEwNC44OTUgMiAyIDJzMi0uODk2IDItMnoiIGZpbGw9InJnYmEoMjU1LDI1NSwyNTUsMC4wNSkiLz48L2c+PC9zdmc+');
- opacity: 0.2;
- z-index: 0;
- }
- .logo-card:hover {
- transform: translateY(-5px) rotateX(5deg) rotateY(5deg);
- box-shadow:
- 0 15px 25px rgba(0,0,0,0.6),
- 0 0 30px rgba(212, 175, 55, 0.4);
- cursor: pointer;
- }
- @keyframes floatCard {
- 0% { transform: translateY(0) rotateZ(0deg); }
- 50% { transform: translateY(-5px) rotateZ(0.5deg); }
- 100% { transform: translateY(-3px) rotateZ(-0.5deg); }
- }
- @keyframes softGreenPulse {
- 0% { filter: hue-rotate(-10deg) brightness(0.95); }
- 33% { filter: hue-rotate(0deg) brightness(1.05); }
- 66% { filter: hue-rotate(10deg) brightness(1.1); }
- 100% { filter: hue-rotate(0deg) brightness(1); }
- }
- .logo-text {
- font-size: 35px;
- font-weight: 800;
- font-family: 'Playfair Display', serif;
- letter-spacing: 1px;
- display: block;
- width: 100%;
- text-align: center;
- position: relative;
- z-index: 2;
- color: #fff6c8;
- margin: 5px 0;
- text-shadow:
- 0 0 5px #fff,
- 0 0 10px #fff,
- 0 0 20px #fff,
- 0 0 40px #ffb700,
- 0 0 80px #ffb700;
- animation: neonGoldGlow 1.5s ease-in-out infinite alternate;
- }
- .logo-suits {
- position: relative;
- display: flex;
- justify-content: space-around;
- padding: 2px 10px;
- margin-top: 5px;
- }
- .logo-suits .suit {
- font-size: 14px;
- transform-origin: center;
- animation: pulsate 3s infinite;
- filter: drop-shadow(0 3px 4px rgba(0,0,0,0.7));
- transition: all 0.3s ease;
- background-image: none !important;
- -webkit-text-fill-color: initial !important;
- -webkit-background-clip: initial !important;
- background-clip: initial !important;
- }
- .logo-suits .suit:hover {
- transform: scale(1.3) translateY(-3px);
- filter: drop-shadow(0 5px 10px rgba(0,0,0,0.8));
- }
- .logo-suits .suit.hearts,
- .logo-suits .suit.diamonds {
- color: #e62222 !important;
- text-shadow: 0 0 5px rgba(230, 34, 34, 0.7);
- }
- .logo-suits .suit.clubs,
- .logo-suits .suit.spades {
- color: #000000 !important;
- text-shadow: 0 0 5px rgba(255, 255, 255, 0.5);
- }
- .suit.hearts { animation-delay: 0s; }
- .suit.diamonds { animation-delay: 0.75s; }
- .suit.clubs { animation-delay: 1.5s; }
- .suit.spades { animation-delay: 2.25s; }
- @keyframes pulsate {
- 0%, 100% { transform: scale(1); opacity: 0.8; }
- 50% { transform: scale(2); opacity: 1; }
- }
- /* Floating suits animation */
- .suits-container {
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- overflow: hidden;
- pointer-events: none;
- z-index: -1;
- }
- .floating-suit {
- position: absolute;
- display: inline-block;
- opacity: 0.12;
- user-select: none;
- transition: transform 0.3s ease;
- z-index: -1;
- will-change: transform, top, left;
- }
- .floating-suit.hearts,
- .floating-suit.diamonds {
- color: #e62222;
- text-shadow: 0 0 3px rgba(230, 34, 34, 0.3);
- }
- .floating-suit.clubs,
- .floating-suit.spades {
- color: #000;
- text-shadow: 0 0 3px rgba(0, 0, 0, 0.2);
- }
- .floating-suit.gold {
- color: #d4af37;
- text-shadow: 0 0 4px rgba(212, 175, 55, 0.5);
- }
- #pokerCalc-div::after {
- content: "";
- position: absolute;
- bottom: 0;
- left: 50%;
- transform: translateX(-50%);
- width: 80%;
- height: 1px;
- background: linear-gradient(to right, transparent, rgba(212, 175, 55, 0.5), transparent);
- }
- #pokerCalc-div {
- background: linear-gradient(160deg, #0a5f2a 0%, #052517 100%);
- color: #fbf7d5;
- padding: 25px;
- margin: 15px;
- border-radius: 16px;
- box-shadow:
- 0 10px 35px rgba(0,0,0,0.5),
- 0 0 40px rgba(0,0,0,0.1) inset;
- border: 3px solid;
- border-image: linear-gradient(45deg, #d4af37, #f1c736, #d4af37) 1;
- position: relative;
- overflow: hidden;
- z-index: 1;
- }
- #pokerCalc-div::before {
- content: "";
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background-image:
- url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><rect width="100" height="100" fill="none" stroke="%23d4af37" stroke-width="0.5" stroke-dasharray="5,5"/></svg>'),
- url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1IiBoZWlnaHQ9IjUiPgo8cmVjdCB3aWR0aD0iNSIgaGVpZ2h0PSI1IiBmaWxsPSIjMGE1ZjJhIj48L3JlY3Q+CjxwYXRoIGQ9Ik0wIDVMNSAwWk02IDRMNCA2Wk0tMSAxTDEgLTFaIiBzdHJva2U9IiMwNzRhMjMiIHN0cm9rZS13aWR0aD0iMSI+PC9wYXRoPgo8L3N2Zz4='),
- radial-gradient(circle at 100% 100%, rgba(7,74,35,0.6) 0%, transparent 50%),
- radial-gradient(circle at 0% 0%, rgba(20,140,60,0.3) 0%, transparent 50%);
- opacity: 0.5;
- z-index: -1;
- pointer-events: none;
- animation: moveGrid 20s linear infinite;
- }
- @keyframes moveGrid {
- 0% { background-position: 0 0, 0 0, 0 0, 0 0; }
- 100% { background-position: 100px 100px, 0 0, 0 0, 0 0; }
- }
- /* 3D Enhanced Win Probability Meter */
- .win-probability-meter {
- margin: 25px auto 15px;
- max-width: 90%;
- position: relative;
- }
- .meter-label {
- font-size: 14px;
- font-weight: 500;
- margin-bottom: 5px;
- color: #fbf7d5;
- text-align: center;
- letter-spacing: 0.5px;
- text-shadow: 0 2px 4px rgba(0,0,0,0.7);
- position: relative;
- }
- .meter-container {
- height: 25px;
- position: relative;
- border-radius: 12px;
- background: linear-gradient(to bottom, #052517, #0a5f2a);
- padding: 4px;
- box-shadow:
- 0 4px 10px rgba(0,0,0,0.7),
- 0 10px 20px rgba(0,0,0,0.3);
- position: relative;
- overflow: hidden;
- border: 1px solid rgba(212, 175, 55, 0.6);
- transform-style: preserve-3d;
- perspective: 500px;
- }
- .meter-groove {
- position: absolute;
- top: 4px;
- left: 4px;
- right: 4px;
- bottom: 4px;
- background: rgba(0,0,0,0.6);
- border-radius: 9px;
- box-shadow:
- inset 0 2px 6px rgba(0,0,0,0.8),
- inset 0 0 3px rgba(0,0,0,0.6);
- background-image:
- linear-gradient(rgba(10,10,10,0.6) 1px, transparent 1px),
- linear-gradient(90deg, rgba(10,10,10,0.6) 1px, transparent 1px);
- background-size: 5px 5px;
- z-index: 1;
- }
- .meter-bar {
- height: 17px;
- margin-top: 0;
- width: 0%;
- background: linear-gradient(to bottom,
- rgba(255,255,255,0.15) 0%,
- rgba(255,255,255,0) 40%,
- rgba(0,0,0,0.3) 100%),
- linear-gradient(to right, #F44336, #FFC107, #4CAF50);
- border-radius: 8px;
- transition: width 0.8s cubic-bezier(0.22, 1, 0.36, 1);
- position: relative;
- box-shadow:
- 0 0 10px rgba(255,255,255,0.3),
- 0 1px 1px rgba(255,255,255,0.5) inset,
- 0 -1px 1px rgba(0,0,0,0.5) inset;
- z-index: 2;
- transform: translateZ(3px);
- }
- .meter-bar::after {
- content: '';
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- height: 8px;
- background: linear-gradient(to bottom, rgba(255,255,255,0.3), transparent);
- border-radius: 8px 8px 0 0;
- z-index: 3;
- }
- .meter-container::before {
- content: '';
- position: absolute;
- top: -5px;
- left: 0;
- right: 0;
- height: 10px;
- background: linear-gradient(to bottom, transparent, rgba(0,0,0,0.3));
- z-index: 0;
- transform: rotateX(45deg);
- transform-origin: bottom;
- }
- .meter-container::after {
- content: '';
- position: absolute;
- bottom: -5px;
- left: 0;
- right: 0;
- height: 10px;
- background: linear-gradient(to top, transparent, rgba(0,0,0,0.3));
- z-index: 0;
- transform: rotateX(-45deg);
- transform-origin: top;
- }
- .meter-value {
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- display: flex;
- align-items: center;
- justify-content: center;
- color: white;
- font-weight: bold;
- font-size: 14px;
- text-shadow: 0 1px 2px rgba(0,0,0,0.9), 0 0 10px rgba(0,0,0,0.5);
- z-index: 4;
- transform: translateZ(5px);
- }
- /* Mini Meters Styling */
- .mini-meters-container {
- display: flex;
- flex-wrap: wrap;
- justify-content: space-between;
- gap: 10px;
- margin: 5px auto 15px;
- max-width: 94%;
- }
- .mini-meter {
- flex: 1 1 30%;
- min-width: 120px;
- margin-bottom: 5px;
- }
- .mini-meter-label {
- font-size: 11px;
- font-weight: 500;
- margin-bottom: 3px;
- color: #fbf7d5;
- text-align: center;
- letter-spacing: 0.5px;
- text-shadow: 0 1px 3px rgba(0,0,0,0.7);
- }
- .mini-meter-container {
- height: 16px;
- position: relative;
- border-radius: 8px;
- background: linear-gradient(to bottom, #052517, #0a5f2a);
- padding: 3px;
- box-shadow:
- 0 2px 6px rgba(0,0,0,0.6),
- 0 6px 10px rgba(0,0,0,0.2);
- position: relative;
- overflow: hidden;
- border: 1px solid rgba(212, 175, 55, 0.4);
- transform-style: preserve-3d;
- }
- .mini-meter-groove {
- position: absolute;
- top: 3px;
- left: 3px;
- right: 3px;
- bottom: 3px;
- background: rgba(0,0,0,0.6);
- border-radius: 6px;
- box-shadow: inset 0 1px 4px rgba(0,0,0,0.8);
- background-image:
- linear-gradient(rgba(10,10,10,0.6) 1px, transparent 1px),
- linear-gradient(90deg, rgba(10,10,10,0.6) 1px, transparent 1px);
- background-size: 4px 4px;
- z-index: 1;
- }
- .mini-meter-bar {
- height: 10px;
- margin-top: 0;
- width: 0%;
- border-radius: 5px;
- transition: width 0.6s cubic-bezier(0.22, 1, 0.36, 1);
- position: relative;
- box-shadow:
- 0 0 6px rgba(255,255,255,0.2),
- 0 1px 1px rgba(255,255,255,0.3) inset;
- z-index: 2;
- transform: translateZ(2px);
- }
- /* More vibrant color scheme */
- .mini-meter-bar[data-type="pair"] {
- background: #5C6BC0;
- }
- .mini-meter-bar[data-type="twoPair"] {
- background: #42A5F5;
- }
- .mini-meter-bar[data-type="trips"] {
- background: #AB47BC;
- }
- .mini-meter-bar[data-type="fullHouse"] {
- background: #7E57C2;
- }
- .mini-meter-bar[data-type="straight"] {
- background: #FFA726;
- }
- .mini-meter-bar[data-type="flush"] {
- background: #66BB6A;
- }
- .mini-meter-bar[data-type="quads"] {
- background: #EC407A;
- }
- .mini-meter-bar[data-type="straightFlush"] {
- background: #26C6DA;
- }
- .mini-meter-bar[data-type="royalFlush"] {
- background: linear-gradient(45deg, #FFEB3B, #FFC107, #FF9800);
- box-shadow: 0 0 10px 2px rgba(255, 215, 0, 0.7);
- animation: royal-glow 2s infinite alternate;
- }
- @keyframes royal-glow {
- 0% { box-shadow: 0 0 5px 1px rgba(255, 215, 0, 0.5); }
- 100% { box-shadow: 0 0 15px 3px rgba(255, 215, 0, 0.9); }
- }
- .mini-meter-bar {
- transition:
- width 0.8s cubic-bezier(0.22, 1, 0.36, 1),
- background-color 0.5s ease;
- }
- /* Pulse animation for high percentages */
- .mini-meter-bar[style*="90%"] {
- animation: high-percent-pulse 1.5s infinite;
- }
- @keyframes high-percent-pulse {
- 0%, 100% { opacity: 1; }
- 50% { opacity: 0.8; }
- }
- .mini-meter-value {
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- display: flex;
- align-items: center;
- justify-content: center;
- color: white;
- font-weight: bold;
- font-size: 10px;
- text-shadow: 0 1px 2px rgba(0,0,0,0.9);
- z-index: 4;
- transform: translateZ(3px);
- }
- #pokerCalc-recommendations {
- flex: 1;
- display: flex;
- align-items: center;
- justify-content: flex-start;
- margin-right: 20px;
- }
- .action-chip {
- position: relative;
- width: 280px;
- height: 80px;
- cursor: pointer;
- overflow: visible;
- }
- .chip-inner {
- position: absolute;
- width: 100%;
- height: 100%;
- border-radius: 40px;
- background: linear-gradient(145deg, #2d2d2d, #151515);
- box-shadow: 0 5px 15px rgba(0,0,0,0.6);
- overflow: hidden;
- display: flex;
- align-items: center;
- justify-content: center;
- z-index: 2;
- transition: all 0.3s ease;
- border: 8px dashed rgba(255,255,255,0.15);
- }
- .action-chip:hover .chip-inner {
- box-shadow: 0 8px 25px rgba(0,0,0,0.7);
- }
- .chip-inner::after {
- content: "";
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- height: 40%;
- background: linear-gradient(to bottom, rgba(255,255,255,0.15), transparent);
- pointer-events: none;
- border-radius: 40px 40px 0 0;
- }
- .chip-inner::before {
- content: "";
- position: absolute;
- inset: 8px;
- border-radius: 32px;
- background: radial-gradient(circle at center, #262626, #111111);
- z-index: -1;
- }
- .chip-edge {
- position: absolute;
- width: calc(100% - 16px);
- height: calc(100% - 16px);
- top: 8px;
- left: 8px;
- border-radius: 32px;
- z-index: 1;
- transition: all 0.3s ease;
- }
- #pokerCalc-action {
- color: #fff;
- font-weight: bold;
- font-size: 18px;
- padding: 8px 20px;
- display: inline-flex;
- align-items: center;
- justify-content: center;
- letter-spacing: 0.5px;
- transition: all 0.3s ease;
- text-align: center;
- font-family: 'Roboto', 'Arial Black', Arial, sans-serif;
- position: relative;
- line-height: 1.3;
- }
- #pokerCalc-action::before,
- #pokerCalc-action::after {
- content: "";
- position: absolute;
- width: 10px;
- height: 10px;
- border-radius: 50%;
- background-color: currentColor;
- opacity: 0.7;
- transition: all 0.3s ease;
- box-shadow: 0 0 10px currentColor;
- }
- #pokerCalc-action::before {
- left: -25px;
- }
- #pokerCalc-action::after {
- right: -25px;
- }
- #pokerCalc-action.action-raise {
- color: #50e150;
- animation: neonGreenGlow 1.5s ease-in-out infinite alternate;
- text-shadow:
- 0 0 5px #fff,
- 0 0 10px #fff,
- 0 0 20px #fff,
- 0 0 40px #50e150,
- 0 0 80px #50e150;
- }
- #pokerCalc-action.action-raise ~ .chip-edge {
- box-shadow: 0 0 20px 5px rgba(80, 225, 80, 0.4);
- animation: pulse-chip-green 2s infinite alternate;
- }
- #pokerCalc-action.action-call {
- color: #f0ad4e;
- animation: neonOrangeGlow 1.5s ease-in-out infinite alternate;
- text-shadow:
- 0 0 5px #fff,
- 0 0 10px #fff,
- 0 0 20px #fff,
- 0 0 40px #f0ad4e,
- 0 0 80px #f0ad4e;
- }
- #pokerCalc-action.action-call ~ .chip-edge {
- box-shadow: 0 0 20px 5px rgba(240, 173, 78, 0.4);
- animation: pulse-chip-orange 2s infinite alternate;
- }
- #pokerCalc-action.action-fold {
- color: #f05050;
- animation: neonRedGlow 1.5s ease-in-out infinite alternate;
- text-shadow:
- 0 0 5px #fff,
- 0 0 10px #fff,
- 0 0 20px #fff,
- 0 0 40px #f05050,
- 0 0 80px #f05050;
- }
- #pokerCalc-action.action-fold ~ .chip-edge {
- box-shadow: 0 0 20px 5px rgba(240, 80, 80, 0.4);
- animation: pulse-chip-red 2s infinite alternate;
- }
- @keyframes pulse-chip-green {
- 0% { box-shadow: 0 0 10px 5px rgba(80, 225, 80, 0.4); }
- 100% { box-shadow: 0 0 25px 8px rgba(80, 225, 80, 0.7); }
- }
- @keyframes pulse-chip-orange {
- 0% { box-shadow: 0 0 10px 5px rgba(240, 173, 78, 0.4); }
- 100% { box-shadow: 0 0 25px 8px rgba(240, 173, 78, 0.7); }
- }
- @keyframes pulse-chip-red {
- 0% { box-shadow: 0 0 10px 5px rgba(240, 80, 80, 0.4); }
- 100% { box-shadow: 0 0 25px 8px rgba(240, 80, 80, 0.7); }
- }
- /* Enhanced Holographic Table Effect */
- #pokerCalc-div table {
- border-collapse: separate;
- border-spacing: 0;
- margin: 25px 0;
- width: 100%;
- border-radius: 12px;
- transition: all 0.3s ease;
- background: linear-gradient(
- 135deg,
- rgba(10, 30, 20, 0.8) 0%,
- rgba(20, 60, 40, 0.8) 50%,
- rgba(10, 30, 20, 0.8) 100%
- );
- backdrop-filter: blur(5px);
- border: 1px solid rgba(212, 175, 55, 0.5);
- box-shadow:
- 0 0 20px rgba(212, 175, 55, 0.3),
- 0 0 40px rgba(212, 175, 55, 0.2);
- position: relative;
- overflow: hidden;
- }
- /* Primary shimmer layer - more visible but still seamless */
- #pokerCalc-div table::before {
- content: '';
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background: linear-gradient(
- 70deg,
- rgba(255,255,255,0) 0%,
- rgba(255,255,255,0) 35%,
- rgba(255,255,255,0.15) 45%,
- rgba(255,255,255,0.2) 50%,
- rgba(255,255,255,0.15) 55%,
- rgba(255,255,255,0) 65%,
- rgba(255,255,255,0) 100%
- );
- background-size: 200% 200%;
- background-position: 0% 0;
- animation: hologram-flow 12s linear infinite;
- pointer-events: none;
- z-index: 1;
- }
- /* Secondary subtle gold shimmer layer */
- #pokerCalc-div table::after {
- content: '';
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background: linear-gradient(
- 110deg,
- rgba(212, 175, 55, 0) 0%,
- rgba(212, 175, 55, 0) 40%,
- rgba(212, 175, 55, 0.07) 47%,
- rgba(212, 175, 55, 0.1) 50%,
- rgba(212, 175, 55, 0.07) 53%,
- rgba(212, 175, 55, 0) 60%,
- rgba(212, 175, 55, 0) 100%
- );
- background-size: 200% 200%;
- background-position: 100% 0;
- animation: hologram-flow 18s linear infinite;
- pointer-events: none;
- z-index: 1;
- opacity: 0.9;
- }
- @keyframes hologram-flow {
- 0% { background-position: 100% 0; }
- 100% { background-position: -100% 0; }
- }
- #pokerCalc-div table:hover {
- box-shadow:
- 0 12px 20px rgba(0, 0, 0, 0.3),
- 0 0 20px rgba(212, 175, 55, 0.4);
- transform: translateY(-3px);
- }
- #pokerCalc-div th {
- background: linear-gradient(145deg, #272727, #1c1c1c);
- color: #fbf7d5;
- padding: 15px 12px;
- font-weight: 600;
- font-size: 14px;
- text-transform: uppercase;
- letter-spacing: 1.5px;
- border-bottom: 2px solid rgba(212, 175, 55, 0.3);
- position: relative;
- overflow: hidden;
- text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8);
- z-index: 2;
- }
- #pokerCalc-div th::after {
- content: '';
- position: absolute;
- bottom: 0;
- left: 0;
- right: 0;
- height: 1px;
- background: linear-gradient(to right, transparent, rgba(212, 175, 55, 0.5), transparent);
- }
- #pokerCalc-div td {
- background: rgba(26, 26, 26, 0.8);
- padding: 12px;
- border-bottom: 1px solid rgba(255, 255, 255, 0.05);
- position: relative;
- transition: all 0.3s ease;
- color: #fbf7d5;
- font-size: 14px;
- z-index: 2;
- }
- #pokerCalc-div tr:hover td {
- background: rgba(40, 40, 40, 0.8);
- transform: translateX(3px);
- box-shadow: -3px 0 10px rgba(0, 0, 0, 0.2);
- }
- #pokerCalc-div tr:last-child td {
- border-bottom: none;
- }
- #pokerCalc-div tbody tr {
- position: relative;
- transition: all 0.3s ease;
- }
- #pokerCalc-div tbody tr:hover {
- background: rgba(212, 175, 55, 0.1);
- transform: scale(1.01);
- z-index: 2;
- }
- #pokerCalc-div caption {
- color: #fbf7d5;
- font-size: 1.2em;
- font-weight: bold;
- margin: 15px 0 10px;
- text-align: left;
- letter-spacing: 1px;
- position: relative;
- padding-left: 40px;
- text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
- font-family: 'Playfair Display', serif;
- animation: neonGoldGlow 3s ease-in-out infinite alternate;
- }
- #pokerCalc-div caption::before {
- content: '♠♣♥♦';
- position: absolute;
- left: 3px;
- top: 50%;
- transform: translateY(-50%);
- font-size: 0.9em;
- letter-spacing: 2px;
- }
- #pokerCalc-div caption::before {
- text-shadow: 0 0 5px rgba(0, 0, 0, 0.5);
- }
- /* Style the individual suit icons in captions */
- #pokerCalc-myHand caption::before,
- #pokerCalc-preflop caption::before {
- content: '♠♣♥♦';
- }
- #pokerCalc-upgrades caption::before {
- content: '♠♣♥♦';
- }
- #pokerCalc-oppPossHands caption::before {
- content: '♠♣♥♦';
- }
- /* Make caption icons black and red */
- #pokerCalc-div caption::before {
- background: linear-gradient(to right,
- black 0%, black 25%,
- black 25%, black 50%,
- #e62222 50%, #e62222 75%,
- #e62222 75%, #e62222 100%);
- -webkit-background-clip: text;
- -webkit-text-fill-color: transparent;
- background-clip: text;
- }
- #pokerCalc-div td:nth-child(1) {
- font-weight: bold;
- color: #fbf7d5;
- position: relative;
- background: rgba(30, 30, 30, 0.9);
- }
- #pokerCalc-div td:nth-child(3),
- #pokerCalc-div td:nth-child(4) {
- font-weight: bold;
- color: #fbf7d5;
- }
- .rank-up {
- color: #70ff70 !important;
- text-shadow: 0 0 5px rgba(112, 255, 112, 0.5);
- }
- .rank-down {
- color: #ff7070 !important;
- text-shadow: 0 0 5px rgba(255, 112, 112, 0.5);
- }
- .similar-hand td {
- opacity: 0.7;
- }
- .best-hand {
- background: rgba(64, 195, 64, 0.2) !important;
- border-left: 4px solid #40c340 !important;
- animation: glowGreen 2s infinite alternate;
- }
- @keyframes glowGreen {
- 0% { box-shadow: inset 0 0 5px rgba(64, 195, 64, 0.5); }
- 100% { box-shadow: inset 0 0 15px rgba(64, 195, 64, 0.8); }
- }
- .best-hand td {
- color: #fbf7d5 !important;
- font-weight: bold;
- }
- .best-hand td:first-child {
- position: relative;
- }
- .best-hand td:first-child::before {
- content: '★';
- position: absolute;
- left: -20px;
- top: 50%;
- transform: translateY(-50%);
- color: #40c340;
- animation: starPulse 1.5s infinite alternate;
- }
- @keyframes starPulse {
- 0% { transform: translateY(-50%) scale(1); opacity: 0.7; }
- 100% { transform: translateY(-50%) scale(1.3); opacity: 1; }
- }
- .potential-best-hand {
- background: rgba(64, 64, 195, 0.2) !important;
- border-left: 4px solid #4040c3 !important;
- animation: glowBlue 2s infinite alternate;
- }
- @keyframes glowBlue {
- 0% { box-shadow: inset 0 0 5px rgba(64, 64, 195, 0.5); }
- 100% { box-shadow: inset 0 0 15px rgba(64, 64, 195, 0.8); }
- }
- .potential-best-hand td {
- color: #fbf7d5 !important;
- font-weight: bold;
- }
- .best-opp-hand {
- background: rgba(195, 64, 64, 0.2) !important;
- border-left: 4px solid #c34040 !important;
- animation: glowRed 2s infinite alternate;
- }
- @keyframes glowRed {
- 0% { box-shadow: inset 0 0 5px rgba(195, 64, 64, 0.5); }
- 100% { box-shadow: inset 0 0 15px rgba(195, 64, 64, 0.8); }
- }
- .best-opp-hand td {
- color: #fbf7d5 !important;
- font-weight: bold;
- }
- .ev-positive {
- border-left: 4px solid #40c340 !important;
- box-shadow: inset 3px 0 10px rgba(64, 195, 64, 0.3);
- }
- .ev-negative {
- border-left: 4px solid #c34040 !important;
- box-shadow: inset 3px 0 10px rgba(195, 64, 64, 0.3);
- }
- .bluff-alert {
- animation: pulse-red 1.5s infinite;
- position: relative;
- }
- .bluff-alert::before {
- content: '⚠️';
- position: absolute;
- left: -25px;
- animation: shakeWarning 0.8s infinite;
- }
- @keyframes shakeWarning {
- 0%, 100% { transform: translateX(0); }
- 25% { transform: translateX(-2px); }
- 75% { transform: translateX(2px); }
- }
- @keyframes pulse-red {
- 0%, 100% {
- border-color: #c34040;
- box-shadow: 0 0 5px rgba(195, 64, 64, 0.6);
- }
- 50% {
- border-color: #ff4040;
- box-shadow: 0 0 15px rgba(255, 64, 64, 0.9);
- }
- }
- @keyframes float {
- 0%, 100% {
- transform: translateY(0);
- }
- 50% {
- transform: translateY(-8px);
- }
- }
- /* Card Icons in Statistics */
- span[class*="spades"], span[class*="hearts"],
- span[class*="clubs"], span[class*="diamonds"] {
- display: inline-block;
- font-size: 16px;
- margin: 0 1px;
- vertical-align: middle;
- filter: drop-shadow(0 2px 2px rgba(0,0,0,0.5));
- }
- span[class*="hearts"], span[class*="diamonds"] {
- color: #ff5555 !important;
- }
- span[class*="spades"], span[class*="clubs"] {
- color: #ffffff !important;
- }
- /* Neon Glow Effects */
- .neon-text {
- text-shadow:
- 0 0 5px #fff,
- 0 0 10px #fff,
- 0 0 20px #fff,
- 0 0 40px #0ff,
- 0 0 80px #0ff,
- 0 0 90px #0ff,
- 0 0 100px #0ff,
- 0 0 150px #0ff;
- animation: neonGlow 1.5s ease-in-out infinite alternate;
- }
- @keyframes neonGlow {
- from {
- text-shadow:
- 0 0 5px #fff,
- 0 0 10px #fff,
- 0 0 20px #fff,
- 0 0 40px #0ff,
- 0 0 80px #0ff,
- 0 0 90px #0ff,
- 0 0 100px #0ff,
- 0 0 150px #0ff;
- }
- to {
- text-shadow:
- 0 0 2px #fff,
- 0 0 5px #fff,
- 0 0 10px #fff,
- 0 0 20px #0ff,
- 0 0 40px #0ff,
- 0 0 60px #0ff,
- 0 0 70px #0ff,
- 0 0 100px #0ff;
- }
- }
- @keyframes neonGoldGlow {
- from {
- text-shadow:
- 0 0 2px #fff,
- 0 0 5px #fff,
- 0 0 10px #ffb700,
- 0 0 20px rgba(255, 183, 0, 0.5);
- }
- to {
- text-shadow:
- 0 0 1px #fff,
- 0 0 3px #fff,
- 0 0 5px #ffb700,
- 0 0 10px rgba(255, 183, 0, 0.3);
- }
- }
- @keyframes neonGreenGlow {
- from {
- text-shadow:
- 0 0 2px #fff,
- 0 0 5px #fff,
- 0 0 10px rgba(80, 225, 80, 0.4);
- }
- to {
- text-shadow:
- 0 0 1px #fff,
- 0 0 3px #fff,
- 0 0 5px rgba(80, 225, 80, 0.2);
- }
- }
- @keyframes neonOrangeGlow {
- from {
- text-shadow:
- 0 0 2px #fff,
- 0 0 5px #fff,
- 0 0 10px rgba(240, 173, 78, 0.4);
- }
- to {
- text-shadow:
- 0 0 1px #fff,
- 0 0 3px #fff,
- 0 0 5px rgba(240, 173, 78, 0.2);
- }
- }
- @keyframes neonRedGlow {
- from {
- text-shadow:
- 0 0 2px #fff,
- 0 0 5px #fff,
- 0 0 10px rgba(240, 80, 80, 0.4);
- }
- to {
- text-shadow:
- 0 0 1px #fff,
- 0 0 3px #fff,
- 0 0 5px rgba(240, 80, 80, 0.2);
- }
- }
- `;
- const style = document.createElement("style");
- style.type = "text/css";
- if (style.styleSheet) {
- style.styleSheet.cssText = styleText;
- } else {
- style.appendChild(document.createTextNode(styleText));
- }
- document.head.appendChild(style);
- } catch (e) {
- console.error("Error adding styles:", e);
- const minimalStyle = document.createElement("style");
- minimalStyle.textContent = "#pokerCalc-div { font-family: Arial; color: gold; background: #0a5f2a; padding: 20px; }";
- document.head.appendChild(minimalStyle);
- }
- }
- }
- window.pokerCalculator = new PokerCalculatorModule();
- window.pokerCalculator.addStatisticsTable();
- window.addEventListener("hashchange", () => {
- if (window.location.href.includes("sid=holdem")) {
- if (!document.getElementById("pokerCalc-div")) {
- window.pokerCalculator = new PokerCalculatorModule();
- window.pokerCalculator.addStatisticsTable();
- }
- }
- });
- window.addEventListener("error", (e) => {
- if (e.message && e.message.includes("pokerCalculator")) {
- console.log("Poker Helper error detected, attempting to recover...");
- try {
- window.pokerCalculator = new PokerCalculatorModule();
- window.pokerCalculator.addStatisticsTable();
- } catch (err) {
- console.error("Could not recover poker helper:", err);
- }
- }
- });
- (() => {
- setTimeout(() => {
- const div = document.getElementById("pokerCalc-div");
- if (div) {
- const versionInfo = document.createElement("div");
- versionInfo.style.fontSize = "11px";
- versionInfo.style.color = "#d4af37";
- versionInfo.style.textAlign = "right";
- versionInfo.style.marginTop = "10px";
- versionInfo.style.letterSpacing = "0.5px";
- versionInfo.style.fontFamily = "'Roboto', sans-serif";
- versionInfo.style.textShadow = "0 1px 2px rgba(0,0,0,0.8)";
- versionInfo.innerHTML = "FlopMaster v1.0 <span style='opacity:0.7;'></span> <span style='font-size:9px;opacity:0.8;'></span>";
- div.appendChild(versionInfo);
- }
- }, 3000);
- })();