您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Clean and simple poker helper for Torn City
// ==UserScript== // @name Torn Poker Helper // @namespace http://tampermonkey.net/ // @version 1.4.2 // @description Clean and simple poker helper for Torn City // @author JESUUS [2353554] // @match https://www.torn.com/page.php?sid=holdem* // @grant none // @license MIT // ==/UserScript== (function () { 'use strict'; const ranks = '23456789TJQKA'.split(''); const expertMode = false; const showProbabilities = true; const cache = new Map(); let lastGameState = null; const translations = { en: { yourCards: 'Your cards', board: 'Board', combination: 'Combination', advice: 'Advice', draws: 'Draws', activePlayers: 'Active players', waiting: 'Waiting...', empty: 'Empty', analyzing: 'Analyzing...', outOf: 'outs •', chanceOf: '% chance', highCard: 'High Card', onePair: 'One Pair', twoPair: 'Two Pair', threeOfAKind: 'Three of a Kind', straight: 'Straight', flush: 'Flush', fullHouse: 'Full House', fourOfAKind: 'Four of a Kind', straightFlush: 'Straight Flush', royalFlush: 'Royal Flush', weakHandDrawTemplate: '🤔 Weak hand but possible draw ({probability}% chance). {position}', inPosition: 'In position, you can call or raise small', outOfPosition: 'Out of position, be careful', weakHandFollow: '🤔 Weak hand but possible draw. Call or check if cheap', allIn: '💰 You can go all-in (very strong hand)', raiseStrong: '🔥 You can raise big (good hand in position)', raiseNormal: '🔥 You can raise (good hand)', callOrRaise: '🙂 You can call or small raise (few opponents)', callOnly: '🙂 You can call', checkInPosition: '🤏 You can check in position', foldOrCheck: '🕊️ Wait and see (or fold)', fold: '🚫 I advise you to fold', winProbability: 'Win chance', folded: 'Folded', assistant: 'Poker Assistant', helper: 'Torn City Helper', language: 'Language', minimize: 'Minimize', maximize: 'Maximize', toggleView: 'Toggle view', }, fr: { yourCards: 'Tes cartes', board: 'Plateau', combination: 'Combinaison', advice: 'Conseil', draws: 'Tirages', activePlayers: 'Joueurs actifs', waiting: 'En attente...', empty: 'Vide', analyzing: 'Analyse en cours...', outOf: 'outs •', chanceOf: '% de chances', highCard: 'Aucune combinaison', onePair: 'Une paire', twoPair: 'Deux paires', threeOfAKind: 'Un brelan', straight: 'Une suite', flush: 'Une couleur', fullHouse: 'Un full', fourOfAKind: 'Un carré', straightFlush: 'Suite couleur', royalFlush: 'Quinte flush royale', weakHandDrawTemplate: '🤔 Main faible mais tirage possible ({probability}% chance). {position}', inPosition: 'En position, tu peux suivre ou relancer petit', outOfPosition: 'Hors position, prudence', weakHandFollow: '🤔 Main faible mais tirage possible. Suis ou check si pas cher', allIn: '💰 Tu peux tout mettre (très forte main)', raiseStrong: '🔥 Tu peux relancer fort (bonne main en position)', raiseNormal: '🔥 Tu peux relancer (bonne main)', callOrRaise: "🙂 Tu peux suivre ou relancer léger (peu d'adversaires)", callOnly: '🙂 Tu peux suivre (call)', checkInPosition: '🤏 Tu peux checker en position', foldOrCheck: '🕊️ Attends de voir (ou couche-toi)', fold: '🚫 Je te conseille de te coucher', winProbability: 'Chance de victoire', folded: 'Couché', assistant: 'Assistant Poker', helper: 'Aide Torn City', language: 'Langue', minimize: 'Réduire', maximize: 'Agrandir', toggleView: 'Changer la vue', }, de: { yourCards: 'Deine Karten', board: 'Board', combination: 'Kombination', advice: 'Ratschlag', draws: 'Draws', activePlayers: 'Aktive Spieler', waiting: 'Warten...', empty: 'Leer', analyzing: 'Analysieren...', outOf: 'Outs •', chanceOf: '% Chance', highCard: 'Höchste Karte', onePair: 'Ein Paar', twoPair: 'Zwei Paare', threeOfAKind: 'Drilling', straight: 'Straße', flush: 'Flush', fullHouse: 'Full House', fourOfAKind: 'Vierling', straightFlush: 'Straight Flush', royalFlush: 'Royal Flush', weakHandDrawTemplate: '🤔 Schwache Hand aber möglicher Draw ({probability}% Chance). {position}', inPosition: 'In Position, du kannst callen oder klein raisen', outOfPosition: 'Außerhalb der Position, sei vorsichtig', weakHandFollow: '🤔 Schwache Hand aber möglicher Draw. Calle oder checke wenn billig', allIn: '💰 Du kannst All-in gehen (sehr starke Hand)', raiseStrong: '🔥 Du kannst stark erhöhen (gute Hand in Position)', raiseNormal: '🔥 Du kannst erhöhen (gute Hand)', callOrRaise: '🙂 Du kannst callen oder leicht erhöhen (wenige Gegner)', callOnly: '🙂 Du kannst callen', checkInPosition: '🤏 Du kannst in Position checken', foldOrCheck: '🕊️ Warte ab (oder folde)', fold: '🚫 Ich rate dir zu folden', winProbability: 'Gewinnchance', folded: 'Gefoldet', assistant: 'Poker Assistent', helper: 'Torn City Helfer', language: 'Sprache', minimize: 'Minimieren', maximize: 'Maximieren', toggleView: 'Ansicht umschalten', }, es: { yourCards: 'Tus cartas', board: 'Mesa', combination: 'Combinación', advice: 'Consejo', draws: 'Posibilidades', activePlayers: 'Jugadores activos', waiting: 'Esperando...', empty: 'Vacío', analyzing: 'Analizando...', outOf: 'outs •', chanceOf: '% de probabilidad', highCard: 'Carta alta', onePair: 'Una pareja', twoPair: 'Dos parejas', threeOfAKind: 'Trío', straight: 'Escalera', flush: 'Color', fullHouse: 'Full', fourOfAKind: 'Póker', straightFlush: 'Escalera de color', royalFlush: 'Escalera real', weakHandDrawTemplate: '🤔 Mano débil pero posible proyecto ({probability}% probabilidad). {position}', inPosition: 'En posición, puedes ver o subir poco', outOfPosition: 'Fuera de posición, ten cuidado', weakHandFollow: '🤔 Mano débil pero posible proyecto. Ve o pasa si es barato', allIn: '💰 Puedes ir all-in (mano muy fuerte)', raiseStrong: '🔥 Puedes subir fuerte (buena mano en posición)', raiseNormal: '🔥 Puedes subir (buena mano)', callOrRaise: '🙂 Puedes ver o subir poco (pocos oponentes)', callOnly: '🙂 Puedes ver', checkInPosition: '🤏 Puedes pasar en posición', foldOrCheck: '🕊️ Espera (o retírate)', fold: '🚫 Te aconsejo retirarte', winProbability: 'Probabilidad de ganar', folded: 'Retirado', assistant: 'Asistente de Póker', helper: 'Ayudante de Torn City', language: 'Idioma', minimize: 'Minimizar', maximize: 'Maximizar', toggleView: 'Cambiar vista', }, }; let currentLang = localStorage.getItem('tornPokerLanguage') || 'en'; let isMinimized = localStorage.getItem('tornPokerMinimized') === 'true'; let isMobileMode = false; let mobilePosition = localStorage.getItem('tornPokerMobilePosition') || 'bottom-right'; function detectMobileMode() { isMobileMode = window.innerWidth <= 768; return isMobileMode; } window.addEventListener('resize', function () { const wasMobile = isMobileMode; const isMobileNow = detectMobileMode(); if (wasMobile !== isMobileNow) { const main = lireCartesJoueur(); const board = lireCartesPlateau(); afficherInfos(main, board); } }); function t(key, replacements = {}) { const text = translations[currentLang][key] || translations['en'][key] || key; return Object.entries(replacements).reduce((result, [key, value]) => { return result.replace(new RegExp('{' + key + '}', 'g'), value); }, text); } function changerLangue(lang) { if (translations[lang]) { currentLang = lang; localStorage.setItem('tornPokerLanguage', lang); const main = lireCartesJoueur(); const board = lireCartesPlateau(); afficherInfos(main, board); } } function changerPositionMobile() { const positions = [ 'top-left', 'top-right', 'bottom-right', 'bottom-left', ]; const currentIndex = positions.indexOf(mobilePosition); const nextIndex = (currentIndex + 1) % positions.length; mobilePosition = positions[nextIndex]; localStorage.setItem('tornPokerMobilePosition', mobilePosition); const main = lireCartesJoueur(); const board = lireCartesPlateau(); afficherInfos(main, board); } function getPositionMobileStyles() { switch (mobilePosition) { case 'top-left': return 'top: 10px; left: 10px;'; case 'top-right': return 'top: 10px; right: 10px;'; case 'bottom-left': return 'bottom: 30px; left: 10px;'; case 'bottom-right': default: return 'bottom: 30px; right: 10px;'; } } const htmlTemplates = { mobileCard: (label, value, color = '#e2e8f0', dataAttr = '') => ` <div style=" background: rgba(255, 255, 255, 0.05); border-radius: 6px; padding: 5px 8px; margin-bottom: 4px; display: flex; justify-content: space-between; align-items: center; "> <span style="font-size: 11px; color: ${color}; opacity: 0.9;">${label}</span> <span ${dataAttr} style="font-family: 'Courier New', monospace; font-weight: 600; color: ${color}; font-size: 12px;"> ${value} </span> </div> `, desktopCard: ( label, value, color = '#e2e8f0', bgColor = 'rgba(255, 255, 255, 0.05)', dataAttr = '' ) => ` <div style=" background: ${bgColor}; border-radius: 8px; padding: 10px 12px; margin-bottom: 16px; "> <div style="display: flex; align-items: center; gap: 8px; margin-bottom: 8px;"> <div style="width: 6px; height: 6px; background: ${color}; border-radius: 50%; box-shadow: 0 0 8px ${color}60;"></div> <span style="font-weight: 600; font-size: 13px; color: #e2e8f0;">${label}</span> </div> <div ${dataAttr} style=" background: ${bgColor}; border: 1px solid ${color}20; border-radius: 8px; padding: 10px 12px; font-family: 'Courier New', monospace; font-weight: 600; color: ${color}; font-size: 15px; ">${value}</div> </div> `, langOption: (code, flag, name, isActive) => ` <div class="langOption" data-lang="${code}" style=" display: flex; align-items: center; gap: 6px; padding: 6px 8px; color: ${isActive ? '#60a5fa' : '#e2e8f0'}; font-weight: ${isActive ? '600' : '500'}; font-size: 11px; cursor: pointer; transition: background 0.2s ease; ${isActive ? 'background: rgba(59, 130, 246, 0.1);' : ''} "> <span style="font-size: 12px;">${flag}</span> <span>${name}</span> </div> `, generateMobileInterface: ( couleurAccent, main, board, nomFinal, conseilFinal, outs, nbJoueurs, isMinimized, winProbability ) => { if (isMinimized) { return ` <div id="toggleMinimize" style=" width: 40px; height: 40px; display: flex; align-items: center; justify-content: center; cursor: pointer; background: linear-gradient(135deg, ${couleurAccent}50, ${couleurAccent}30); border-radius: 10px; position: relative; "> <div style=" width: 28px; height: 28px; background: linear-gradient(135deg, ${couleurAccent}, ${couleurAccent}80); border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 14px; ">🃏</div> <div style=" position: absolute; bottom: -1px; right: -1px; width: 10px; height: 10px; background: rgba(255, 255, 255, 0.9); border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 7px; color: #333; ">📍</div> </div> `; } return ` <div style="display: flex; flex-direction: column;"> <div style=" display: flex; align-items: center; justify-content: space-between; padding: 6px 8px; border-bottom: 1px solid rgba(255, 255, 255, 0.1); "> <div style="display: flex; align-items: center; gap: 6px;"> <div style=" width: 20px; height: 20px; background: linear-gradient(135deg, ${couleurAccent}, ${couleurAccent}80); border-radius: 5px; display: flex; align-items: center; justify-content: center; font-size: 11px; ">🃏</div> <div style="font-weight: 600; font-size: 11px; color: white; opacity: 0.95;">${t( 'assistant' )}</div> </div> <div style="display: flex; gap: 4px;"> <div id="positionButton" style=" width: 20px; height: 20px; display: flex; align-items: center; justify-content: center; background: rgba(255, 255, 255, 0.1); border-radius: 5px; cursor: pointer; font-size: 9px; ">📍</div> <div id="langSelector" style="position: relative;"> <div id="langButton" style=" width: 20px; height: 20px; display: flex; align-items: center; justify-content: center; background: rgba(255, 255, 255, 0.1); border-radius: 5px; cursor: pointer; font-size: 9px; ">${langConfig[currentLang].flag}</div> <div id="langOptions" style=" display: none; position: absolute; top: 100%; right: 0; margin-top: 2px; background: rgba(15, 23, 42, 0.98); border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 5px; overflow: hidden; box-shadow: 0 4px 12px -2px rgba(0, 0, 0, 0.8); width: 90px; z-index: 100000; "> ${Object.entries(langConfig) .map( ([code, { flag, name }]) => ` <div class="langOption" data-lang="${code}" style=" display: flex; align-items: center; gap: 5px; padding: 5px 8px; color: ${code === currentLang ? '#60a5fa' : '#e2e8f0'}; font-weight: ${code === currentLang ? '600' : '500'}; font-size: 9px; cursor: pointer; transition: background 0.2s ease; ${code === currentLang ? 'background: rgba(59, 130, 246, 0.1);' : ''} "> <span style="font-size: 9px;">${flag}</span> <span>${name}</span> </div> ` ) .join('')} </div> </div> <div id="toggleMinimize" style=" width: 20px; height: 20px; display: flex; align-items: center; justify-content: center; background: rgba(255, 255, 255, 0.1); border-radius: 5px; cursor: pointer; font-size: 11px; ">–</div> </div> </div> <div style="padding: 6px 8px;"> ${htmlTemplates.mobileCard( t('yourCards'), main.length ? main.join(' ') : t('waiting'), 'white', 'data-player-cards' )} ${htmlTemplates.mobileCard( t('board'), board.length ? board.join(' ') : t('empty'), '#10b981', 'data-board-cards' )} ${htmlTemplates.mobileCard( t('combination'), nomFinal || t('analyzing'), '#8b5cf6', 'data-combination' )} ${ winProbability > 0 ? htmlTemplates.mobileCard( t('winProbability'), `${Math.round(winProbability * 100)}%`, '#3b82f6', 'data-win-probability' ) : '' } <div data-advice style=" background: linear-gradient(135deg, ${couleurAccent}30, ${couleurAccent}15); border: 1px solid ${couleurAccent}50; border-radius: 6px; padding: 5px 8px; color: white; font-weight: 600; font-size: 11px; line-height: 1.3; text-align: center; margin-bottom: 4px; ">${conseilFinal}</div> ${ outs && outs.nombre > 0 ? htmlTemplates.mobileCard( t('draws'), `${outs.nombre} ${t( 'outOf' )} ${Math.round( outs.probability * 100 )}${t('chanceOf')}`, '#f59e0b', 'data-outs' ) : '' } </div> </div> `; }, generateLangSelector: (couleurAccent) => ` <div id="langSelector" style="position: relative;"> <div id="langButton" style=" width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; background: rgba(255, 255, 255, 0.1); border-radius: 6px; cursor: pointer; font-size: 12px; ">${langConfig[currentLang].flag}</div> <div id="langOptions" style=" display: none; position: absolute; top: 100%; right: 0; margin-top: 4px; background: rgba(15, 23, 42, 0.98); border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 6px; overflow: hidden; box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.7); width: 100px; z-index: 100000; "> ${Object.entries(langConfig) .map(([code, { flag, name }]) => htmlTemplates.langOption( code, flag, name, code === currentLang ) ) .join('')} </div> </div> `, generateDesktopInterface: ( couleurAccent, main, board, nomFinal, conseilFinal, outs, nbJoueurs, winProbability ) => ` <div data-drag-handle style=" background: linear-gradient(135deg, ${couleurAccent}20, ${couleurAccent}10); padding: 16px 20px; border-radius: 14px 14px 0 0; border-bottom: 1px solid ${couleurAccent}40; position: relative; "> <div style=" display: flex; align-items: center; gap: 12px; margin-bottom: 12px; "> <div style=" width: 40px; height: 40px; background: linear-gradient(135deg, ${couleurAccent}, ${couleurAccent}80); border-radius: 12px; display: flex; align-items: center; justify-content: center; font-size: 20px; box-shadow: 0 4px 12px ${couleurAccent}40; ">🃏</div> <div> <div style="font-weight: 700; font-size: 16px; color: white;">${t( 'assistant' )}</div> <div style="font-size: 12px; color: ${couleurAccent}; opacity: 0.8;">${t( 'helper' )}</div> </div> ${htmlTemplates.generateDesktopLangSelector(couleurAccent)} </div> </div> <div style="padding: 20px;"> ${htmlTemplates.desktopCard( t('yourCards'), main.length ? main.join(' • ') : '🎴 ' + t('waiting'), 'white', 'rgba(255, 255, 255, 0.05)', 'data-player-cards' )} ${htmlTemplates.desktopCard( t('board'), board.length ? board.join(' • ') : '🟢 ' + t('empty'), '#10b981', 'rgba(16, 185, 129, 0.1)', 'data-board-cards' )} ${htmlTemplates.desktopCard( t('combination'), nomFinal || '🔍 ' + t('analyzing'), '#8b5cf6', 'linear-gradient(135deg, rgba(139, 92, 246, 0.15), rgba(139, 92, 246, 0.05))', 'data-combination' )} ${ winProbability > 0 ? htmlTemplates.desktopCard( t('winProbability'), `🎯 ${Math.round(winProbability * 100)}%`, '#3b82f6', 'linear-gradient(135deg, rgba(59, 130, 246, 0.15), rgba(59, 130, 246, 0.05))', 'data-win-probability' ) : '' } <div style="margin-bottom: ${outs ? '16px' : '0'};"> <div style=" display: flex; align-items: center; gap: 8px; margin-bottom: 8px; "> <div style=" width: 6px; height: 6px; background: ${couleurAccent}; border-radius: 50%; box-shadow: 0 0 8px ${couleurAccent}60; "></div> <span style="font-weight: 600; font-size: 13px; color: #e2e8f0;">${t( 'advice' )}</span> </div> <div data-advice style=" background: linear-gradient(135deg, ${couleurAccent}20, ${couleurAccent}10); border: 1px solid ${couleurAccent}40; border-radius: 8px; padding: 12px; color: white; font-weight: 600; font-size: 14px; line-height: 1.4; ">${conseilFinal}</div> </div> ${ outs && outs.nombre > 0 ? ` <div style="margin-bottom: 16px;"> <div style=" display: flex; align-items: center; gap: 8px; margin-bottom: 8px; "> <div style=" width: 6px; height: 6px; background: #f59e0b; border-radius: 50%; box-shadow: 0 0 8px #f59e0b60; "></div> <span style="font-weight: 600; font-size: 13px; color: #e2e8f0;">${t( 'draws' )}</span> </div> <div data-outs style=" background: linear-gradient(135deg, rgba(245, 158, 11, 0.15), rgba(245, 158, 11, 0.05)); border: 1px solid rgba(245, 158, 11, 0.3); border-radius: 8px; padding: 12px; color: #fbbf24; font-weight: 600; font-size: 14px; "> ${outs.nombre} ${t('outOf')} ${Math.round(outs.probability * 100)}${t( 'chanceOf' )} <div style="color: #94a3b8; font-size: 12px; line-height: 1.3; margin-top: 4px;"> ${outs.details.slice(0, 3).join(' • ')}${outs.details.length > 3 ? '...' : ''} </div> </div> </div> ` : '' } ${ nbJoueurs > 0 ? ` <div style=" margin-top: 16px; padding-top: 16px; border-top: 1px solid rgba(255, 255, 255, 0.1); "> <div style=" display: flex; justify-content: space-between; align-items: center; "> <div style=" display: flex; align-items: center; gap: 8px; "> <span style="font-size: 16px;">👥</span> <span style="color: #94a3b8; font-size: 13px;">${t('activePlayers')}</span> </div> <div data-active-players style=" background: rgba(255, 255, 255, 0.1); border-radius: 12px; padding: 4px 12px; font-weight: 700; color: white; font-size: 13px; ">${nbJoueurs}</div> </div> </div> ` : '' } </div> `, generateDesktopLangSelector: (couleurAccent) => ` <div id="langSelector" style=" position: absolute; top: 16px; right: 16px; z-index: 2; "> <div id="langButton" style=" display: flex; align-items: center; justify-content: center; width: 32px; height: 32px; background: rgba(255, 255, 255, 0.1); border-radius: 8px; cursor: pointer; user-select: none; transition: all 0.2s ease; "> <span style="font-size: 16px;">${langConfig[currentLang].flag}</span> </div> <div id="langOptions" style=" display: none; position: absolute; top: 100%; right: 0; margin-top: 8px; background: rgba(15, 23, 42, 0.95); backdrop-filter: blur(12px); border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 8px; overflow: hidden; box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.7); width: 120px; z-index: 3; "> ${Object.entries(langConfig) .map( ([code, { flag, name }]) => ` <div class="langOption" data-lang="${code}" style=" display: flex; align-items: center; gap: 8px; padding: 10px 12px; color: ${code === currentLang ? '#60a5fa' : '#e2e8f0'}; font-weight: ${code === currentLang ? '700' : '500'}; font-size: 13px; cursor: pointer; transition: background 0.2s ease; ${code === currentLang ? 'background: rgba(59, 130, 246, 0.1);' : ''} "> <span style="font-size: 16px;">${flag}</span> <span>${name}</span> </div> ` ) .join('')} </div> </div> `, }; function updateContentOnly(main, board, box) { const isMobile = window.innerWidth <= 768; const joueurAFold = detecterSiJoueurAFold(); const playerCardsElement = box.querySelector('[data-player-cards]'); if (playerCardsElement) { if (isMobile) { playerCardsElement.textContent = main.length ? main.join(' ') : t('waiting'); } else { playerCardsElement.textContent = main.length ? main.join(' • ') : '🎴 ' + t('waiting'); } } const boardCardsElement = box.querySelector('[data-board-cards]'); if (boardCardsElement) { if (isMobile) { boardCardsElement.textContent = board.length ? board.join(' ') : t('empty'); } else { boardCardsElement.textContent = board.length ? board.join(' • ') : '🟢 ' + t('empty'); } } const nbJoueurs = compterJoueursActifs(); const activePlayersElement = box.querySelector('[data-active-players]'); if (activePlayersElement) { activePlayersElement.textContent = nbJoueurs; } if (joueurAFold) { const winProbabilityElement = box.querySelector( '[data-win-probability]' ); if (winProbabilityElement) { winProbabilityElement.textContent = t('folded'); } const adviceElement = box.querySelector('[data-advice]'); if (adviceElement) { adviceElement.textContent = t('folded'); } const combinationElement = box.querySelector('[data-combination]'); if (combinationElement) { combinationElement.textContent = ''; } } else if (main.length >= 2) { const toutesCartes = [...main, ...board]; const evaluation = evaluerMain(toutesCartes, true); const position = detecterPosition(); let outs = null; if (evaluation.tirage && main.length >= 2 && board.length >= 3) { outs = calculerOuts(main, board); } const winProbability = calculerProbabiliteVictoire( main, board, nbJoueurs, 500 ); const nomFinal = expertMode ? evaluation.nom : simplifierMain(evaluation.nom); const conseilFinal = simplifierConseil( evaluation.conseil, position, nbJoueurs, evaluation.tirage, outs ); const combinationElement = box.querySelector('[data-combination]'); if (combinationElement) { if (isMobile) { combinationElement.textContent = nomFinal || t('analyzing'); } else { combinationElement.textContent = nomFinal || '🔍 ' + t('analyzing'); } } const winProbabilityElement = box.querySelector( '[data-win-probability]' ); if (winProbabilityElement) { if (isMobile) { winProbabilityElement.textContent = `${Math.round( winProbability * 100 )}%`; } else { winProbabilityElement.textContent = `🎯 ${Math.round( winProbability * 100 )}%`; } } const adviceElement = box.querySelector('[data-advice]'); if (adviceElement) { adviceElement.textContent = conseilFinal; } const outsElement = box.querySelector('[data-outs]'); if (outsElement && outs && outs.nombre > 0) { outsElement.textContent = `${outs.nombre} ${t( 'outOf' )} ${Math.round(outs.probability * 100)}${t('chanceOf')}`; } } } function extraireValeurEtCouleur(className) { const regex = /(clubs|spades|hearts|diamonds)-([0-9TJQKA]+)/; const match = className.match(regex); if (!match) { return null; } const couleurMap = { clubs: '♣', spades: '♠', hearts: '♥', diamonds: '♦', }; const couleur = couleurMap[match[1]]; const valeur = match[2].toUpperCase(); return `${valeur}${couleur}`; } function lireCartesJoueur() { const cartes = document.querySelectorAll( '.playerMeGateway___AEI5_ .hand___aOp4l .card___t7csZ .front___osz1p > div' ); return Array.from(cartes) .map((c) => extraireValeurEtCouleur(c.className)) .filter(Boolean); } function lireCartesPlateau() { const cartes = document.querySelectorAll( '.communityCards___cGHD3 .front___osz1p > div' ); return Array.from(cartes) .map((c) => extraireValeurEtCouleur(c.className)) .filter(Boolean); } function compterJoueursActifs() { let joueursTable = document.querySelectorAll('[class*="opponent___"]'); if (joueursTable.length === 0) { joueursTable = document.querySelectorAll('[id*="player-"]'); } if (joueursTable.length === 0) { joueursTable = Array.from( document.querySelectorAll('[class*="name___"]') ) .map((nom) => nom.closest( 'div[class*="player"], div[class*="opponent"], div' ) ) .filter(Boolean); } let joueursActifs = 0; if (joueursTable.length > 0) { joueursTable.forEach((joueur, index) => { const nom = joueur.querySelector ? joueur.querySelector('[class*="name___"]') : null; const texteComplet = joueur.textContent ? joueur.textContent.toLowerCase() : ''; const estInactif = texteComplet.includes('sitting out') || texteComplet.includes('waiting bb') || texteComplet.includes('waiting') || texteComplet.includes('folded') || texteComplet.includes('fold'); if (!estInactif) { joueursActifs++; } }); } else { const nomsJoueurs = document.querySelectorAll('[class*="name___"]'); nomsJoueurs.forEach((nom, index) => { let conteneur = nom.parentElement; while ( conteneur && !conteneur.textContent.includes('Sitting out') && !conteneur.textContent.includes('Waiting') && conteneur.parentElement ) { conteneur = conteneur.parentElement; } const texteComplet = conteneur ? conteneur.textContent.toLowerCase() : ''; const estInactif = texteComplet.includes('sitting out') || texteComplet.includes('waiting bb') || texteComplet.includes('waiting') || texteComplet.includes('folded') || texteComplet.includes('fold'); if (!estInactif) { joueursActifs++; } }); } let votreStatut = ''; const scriptStatus = document.querySelector('[data-testid="poker-assistant"]') ?.textContent || document.querySelector('.advice')?.textContent || document.querySelector('[class*="advice"]')?.textContent || ''; const votreJoueur = document.querySelector('.playerMeGateway___AEI5_'); const playerStatus = votreJoueur ? votreJoueur.textContent.toLowerCase() : ''; let yourSpecificStatus = ''; if (votreJoueur) { const yourStatusSpan = votreJoueur.querySelector('span'); yourSpecificStatus = yourStatusSpan ? yourStatusSpan.textContent.toLowerCase() : ''; } votreStatut = ( scriptStatus + ' ' + playerStatus + ' ' + yourSpecificStatus ).toLowerCase(); const vosCartes = document.querySelectorAll( '.playerMeGateway___AEI5_ .hand___aOp4l .card___t7csZ' ); const avezDesCartes = vosCartes.length >= 2; const votreZone = document.querySelector('.playerMeGateway___AEI5_'); const votreZoneText = votreZone ? votreZone.textContent.toLowerCase() : ''; let votreZoneComplete = votreZoneText; if (votreZone) { const spans = votreZone.querySelectorAll('span, div'); spans.forEach((span) => { votreZoneComplete += ' ' + span.textContent.toLowerCase(); }); } const vousAvezFolde = votreZoneComplete.includes('folded') || votreStatut.includes('folded') || votreStatut.includes('fold'); const vousEtesActif = avezDesCartes && !vousAvezFolde && !votreStatut.includes('waiting') && !votreStatut.includes('sitting out'); if (vousEtesActif) { joueursActifs++; } if (joueursActifs === 0) { const positionsAvecCartes = document.querySelectorAll( '[class*="hand___"], [class*="card___"]' ).length; const positionsAvecJetons = document.querySelectorAll( '[class*="bet"], [class*="chip"]' ).length; joueursActifs = Math.max( Math.floor(positionsAvecCartes / 2), Math.floor(positionsAvecJetons / 2), 3 ); } const resultat = Math.max(joueursActifs, 2); if (window.lastPlayerCount !== resultat) { window.lastPlayerCount = resultat; } return resultat; } function detecterSiJoueurAFold() { const vosCartes = document.querySelectorAll( '.playerMeGateway___AEI5_ .hand___aOp4l .card___t7csZ' ); const avezDesCartes = vosCartes.length >= 2; const votreZone = document.querySelector('.playerMeGateway___AEI5_'); const votreZoneText = votreZone ? votreZone.textContent.toLowerCase() : ''; let votreZoneComplete = votreZoneText; if (votreZone) { const spans = votreZone.querySelectorAll('span, div'); spans.forEach((span) => { votreZoneComplete += ' ' + span.textContent.toLowerCase(); }); } const votreJoueur = document.querySelector('.playerMeGateway___AEI5_'); const playerStatus = votreJoueur ? votreJoueur.textContent.toLowerCase() : ''; const vousAvezFolde = votreZoneComplete.includes('folded') || playerStatus.includes('folded') || playerStatus.includes('fold'); return vousAvezFolde; } function detecterPosition() { const bouton = document.querySelector('.yourTurn___b2sZp'); return bouton && bouton.textContent.includes('Your turn'); } function detecterPositionTable() { const tousJoueurs = document.querySelectorAll('.player___Z25g2'); const monIndex = Array.from(tousJoueurs).findIndex((p) => p.classList.contains('playerMeGateway___AEI5_') ); if (monIndex === -1) { return 0; } const totalJoueurs = tousJoueurs.length; if (monIndex === totalJoueurs - 1) { return 2; // Button/Dealer } if (monIndex === 0 || monIndex === 1) { return 0; // SB/BB } return 1; // Position médiane } function simplifierMain(nom) { return t(nom.replace(/\s+/g, '').toLowerCase()) || nom; } function trouverMeilleureMain(toutesCartes) { if (toutesCartes.length < 5) { return toutesCartes; } const combinations = []; function getCombinations(arr, size) { if (size === 1) { return arr.map((el) => [el]); } const result = []; arr.forEach((el, i) => { const rest = arr.slice(i + 1); const combos = getCombinations(rest, size - 1); combos.forEach((combo) => { result.push([el, ...combo]); }); }); return result; } const combos = getCombinations(toutesCartes, 5); let meilleureCombo = combos[0]; let meilleureEval = evaluerMain5Cartes(combos[0]); combos.forEach((combo) => { const evaluate = evaluerMain5Cartes(combo); if (comparerMains(evaluate, meilleureEval) > 0) { meilleureCombo = combo; meilleureEval = evaluate; } }); return { cartes: meilleureCombo, evaluation: meilleureEval }; } function comparerMains(main1, main2) { if (main1.force !== main2.force) { return main1.force - main2.force; } if (main1.valeurPrincipale !== main2.valeurPrincipale) { return main1.valeurPrincipale - main2.valeurPrincipale; } for ( let i = 0; i < Math.max(main1.kickers.length, main2.kickers.length); i++ ) { const k1 = main1.kickers[i] || -1; const k2 = main2.kickers[i] || -1; if (k1 !== k2) { return k1 - k2; } } return 0; } function evaluerMain5Cartes(cartes5) { const suits = { '♠': [], '♥': [], '♦': [], '♣': [] }; const values = {}; const allVals = []; cartes5.forEach((card) => { const match = card.match(/^([0-9TJQKA]+)(.)$/); if (!match) { return; } const val = match[1]; const suit = match[2]; suits[suit].push(val); values[val] = (values[val] || 0) + 1; allVals.push(val); }); const countList = Object.values(values).sort((a, b) => b - a); const allRanks = allVals .map((v) => ranks.indexOf(v)) .sort((a, b) => b - a); const flushSuit = Object.entries(suits).find( ([_, list]) => list.length === 5 ); const isFlush = !!flushSuit; const uniqueRanks = [...new Set(allRanks)].sort((a, b) => a - b); let straightFound = false; let straightHighCard = 0; // Vérifier toutes les suites possibles de 5 cartes consécutives for (let i = 0; i <= uniqueRanks.length - 5; i++) { const seq = uniqueRanks.slice(i, i + 5); // Vérifier si c'est une suite (différence de 4 entre la première et dernière carte) if (seq[4] - seq[0] === 4) { straightFound = true; straightHighCard = seq[4]; break; } } if (!straightFound) { // Vérifier la wheel (A-2-3-4-5) const wheelRanks = [ ranks.indexOf('A'), ranks.indexOf('2'), ranks.indexOf('3'), ranks.indexOf('4'), ranks.indexOf('5'), ]; const hasWheel = wheelRanks.every((rank) => uniqueRanks.includes(rank) ); if (hasWheel) { straightFound = true; straightHighCard = ranks.indexOf('5'); } } const royalFlush = isFlush && straightFound && straightHighCard === ranks.indexOf('A'); const valeurPair = Object.entries(values) .filter(([_, count]) => count === 2) .map(([val, _]) => ranks.indexOf(val)) .sort((a, b) => b - a); const valeurBrelan = Object.entries(values) .filter(([_, count]) => count === 3) .map(([val, _]) => ranks.indexOf(val))[0]; const valeurCarre = Object.entries(values) .filter(([_, count]) => count === 4) .map(([val, _]) => ranks.indexOf(val))[0]; let kickers = []; let force = 0; let valeurPrincipale = 0; let nom = ''; if (royalFlush) { force = 9; valeurPrincipale = ranks.indexOf('A'); nom = 'Royal Flush'; kickers = []; } else if (isFlush && straightFound) { force = 8; valeurPrincipale = straightHighCard; nom = 'Straight Flush'; kickers = []; } else if (countList[0] === 4) { force = 7; valeurPrincipale = valeurCarre; nom = 'Four of a Kind'; kickers = allRanks.filter((r) => r !== valeurCarre).slice(0, 1); } else if (countList[0] === 3 && countList[1] === 2) { force = 6; valeurPrincipale = valeurBrelan; nom = 'Full House'; kickers = valeurPair.slice(0, 1); } else if (isFlush) { force = 5; nom = 'Flush'; const flushCards = suits[flushSuit[0]] .map((v) => ranks.indexOf(v)) .sort((a, b) => b - a); valeurPrincipale = flushCards[0]; kickers = flushCards.slice(1, 5); } else if (straightFound) { force = 4; valeurPrincipale = straightHighCard; nom = 'Straight'; kickers = []; } else if (countList[0] === 3) { force = 3; valeurPrincipale = valeurBrelan; nom = 'Three of a Kind'; kickers = allRanks.filter((r) => r !== valeurBrelan).slice(0, 2); } else if (countList[0] === 2 && countList[1] === 2) { force = 2; valeurPrincipale = valeurPair[0]; nom = 'Two Pair'; kickers = [ valeurPair[1], ...allRanks.filter((r) => !valeurPair.includes(r)).slice(0, 1), ]; } else if (countList[0] === 2) { force = 1; valeurPrincipale = valeurPair[0]; nom = 'One Pair'; kickers = allRanks.filter((r) => r !== valeurPair[0]).slice(0, 3); } else { force = 0; valeurPrincipale = allRanks[0]; nom = 'High Card'; kickers = allRanks.slice(1, 5); } return { nom, force, valeurPrincipale, kickers, cartes: cartes5, }; } function calculerOuts(main, board) { const toutesCartes = [...main, ...board]; const cartesUtilisees = new Set(toutesCartes); const outs = new Set(); const outDetails = []; // Analyser les tirages possibles const flushOuts = calculerFlushOuts(main, board, cartesUtilisees); const straightOuts = calculerStraightOuts(main, board, cartesUtilisees); // Ajouter les outs de flush flushOuts.forEach((carte) => { outs.add(carte); outDetails.push(`${carte} → Flush`); }); // Ajouter les outs de suite straightOuts.forEach((carte) => { if (!outs.has(carte)) { outDetails.push(`${carte} → Straight`); } outs.add(carte); }); return { nombre: outs.size, details: outDetails, probability: calculerProbabilite(outs.size, board.length), }; } function calculerFlushOuts(main, board, cartesUtilisees) { const outs = []; const toutesCartes = [...main, ...board]; // Compter les cartes par couleur const suits = { '♠': [], '♥': [], '♦': [], '♣': [] }; toutesCartes.forEach((carte) => { const match = carte.match(/^([0-9TJQKA]+)(.)$/); if (match) { suits[match[2]].push(match[1]); } }); // Chercher un tirage couleur (4 cartes de même couleur) Object.entries(suits).forEach(([suit, cartes]) => { if (cartes.length === 4) { // Ajouter toutes les cartes restantes de cette couleur const allRanks = '23456789TJQKA'.split(''); allRanks.forEach((rank) => { const carte = `${rank}${suit}`; if (!cartesUtilisees.has(carte)) { outs.push(carte); } }); } }); return outs; } function calculerStraightOuts(main, board, cartesUtilisees) { const outs = []; const toutesCartes = [...main, ...board]; // Obtenir les rangs uniques const rangs = new Set(); toutesCartes.forEach((carte) => { const match = carte.match(/^([0-9TJQKA]+)(.)$/); if (match) { rangs.add(ranks.indexOf(match[1])); } }); const rangsArray = Array.from(rangs).sort((a, b) => a - b); const allSuits = ['♠', '♥', '♦', '♣']; // Chercher tous les tirages de suite possibles // Pour chaque séquence possible de 5 cartes consécutives for (let start = 0; start <= ranks.length - 5; start++) { const sequence = [ start, start + 1, start + 2, start + 3, start + 4, ]; // Compter combien de cartes de cette séquence on a const cartesPresentes = sequence.filter((rang) => rangsArray.includes(rang) ); // Si on a exactement 4 cartes de la séquence if (cartesPresentes.length === 4) { // Trouver la carte manquante const carteManquante = sequence.find( (rang) => !rangsArray.includes(rang) ); // Ajouter toutes les variantes de couleur de cette carte allSuits.forEach((suit) => { const carte = `${ranks[carteManquante]}${suit}`; if (!cartesUtilisees.has(carte)) { outs.push(carte); } }); } } // Cas spécial pour la wheel (A-2-3-4-5) const wheelSequence = [ ranks.indexOf('A'), ranks.indexOf('2'), ranks.indexOf('3'), ranks.indexOf('4'), ranks.indexOf('5'), ]; const wheelPresentes = wheelSequence.filter((rang) => rangsArray.includes(rang) ); if (wheelPresentes.length === 4) { const wheelManquante = wheelSequence.find( (rang) => !rangsArray.includes(rang) ); allSuits.forEach((suit) => { const carte = `${ranks[wheelManquante]}${suit}`; if (!cartesUtilisees.has(carte)) { outs.push(carte); } }); } // Cas spécial pour la suite royale (T-J-Q-K-A) const royalSequence = [ ranks.indexOf('T'), ranks.indexOf('J'), ranks.indexOf('Q'), ranks.indexOf('K'), ranks.indexOf('A'), ]; const royalPresentes = royalSequence.filter((rang) => rangsArray.includes(rang) ); if (royalPresentes.length === 4) { const royalManquante = royalSequence.find( (rang) => !rangsArray.includes(rang) ); allSuits.forEach((suit) => { const carte = `${ranks[royalManquante]}${suit}`; if (!cartesUtilisees.has(carte)) { outs.push(carte); } }); } return outs; } function calculerProbabilite(outs, boardSize) { // Formules corrigées pour les probabilités de tirage if (boardSize === 3) { // Flop vers turn et river (2 cartes restantes) // Formule: 1 - ((47-outs)/47) * ((46-outs)/46) const cartesRestantes = 52 - 5; // 47 cartes (52 - 2 du joueur - 3 du flop) return 1 - Math.pow((cartesRestantes - outs) / cartesRestantes, 2); } else if (boardSize === 4) { // Turn vers river (1 carte restante) const cartesRestantes = 52 - 6; // 46 cartes (52 - 2 du joueur - 4 du board) return outs / cartesRestantes; } else if (boardSize === 0) { // Preflop vers river (5 cartes à venir) // Approximation simplifiée pour preflop const cartesRestantes = 52 - 2; // 50 cartes return 1 - Math.pow((cartesRestantes - outs) / cartesRestantes, 5); } return 0; } function calculerProbabiliteVictoire( main, board, nbJoueurs = 2, simulations = 1000 ) { if (main.length < 2) { return 0; } const cartesUtilisees = new Set([...main, ...board]); const cartesRestantes = []; // Créer le deck des cartes restantes const allRanks = '23456789TJQKA'.split(''); const allSuits = ['♠', '♥', '♦', '♣']; allRanks.forEach((rank) => { allSuits.forEach((suit) => { const carte = `${rank}${suit}`; if (!cartesUtilisees.has(carte)) { cartesRestantes.push(carte); } }); }); let victoires = 0; for (let sim = 0; sim < simulations; sim++) { // Mélanger les cartes restantes const deck = [...cartesRestantes]; for (let i = deck.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [deck[i], deck[j]] = [deck[j], deck[i]]; } // Calculer les cartes nécessaires pour board et adversaires const cartesNecessairesBoard = Math.max(0, 5 - board.length); const cartesNecessairesAdversaires = (nbJoueurs - 1) * 2; const cartesTotalesNecessaires = cartesNecessairesBoard + cartesNecessairesAdversaires; // Vérifier qu'il y a assez de cartes if (deck.length < cartesTotalesNecessaires) { continue; // Passer cette simulation } // Compléter le board si nécessaire const boardComplet = [...board]; for (let i = 0; i < cartesNecessairesBoard; i++) { boardComplet.push(deck.pop()); } // Évaluer notre main const notreMeilleureMain = trouverMeilleureMain([ ...main, ...boardComplet, ]); // Simuler les mains des adversaires let gagne = true; let deckIndex = 0; for (let adversaire = 1; adversaire < nbJoueurs; adversaire++) { const mainAdversaire = [deck[deckIndex], deck[deckIndex + 1]]; deckIndex += 2; const mainAdversaireComplete = trouverMeilleureMain([ ...mainAdversaire, ...boardComplet, ]); if ( comparerMains( mainAdversaireComplete.evaluation, notreMeilleureMain.evaluation ) > 0 ) { gagne = false; break; } } if (gagne) { victoires++; } } return victoires / simulations; } function analyserTirages(toutesCartes) { const suits = { '♠': [], '♥': [], '♦': [], '♣': [] }; const values = {}; const allVals = []; toutesCartes.forEach((card) => { const match = card.match(/^([0-9TJQKA]+)(.)$/); if (!match) { return; } const val = match[1]; const suit = match[2]; suits[suit].push(val); values[val] = (values[val] || 0) + 1; allVals.push(val); }); const countList = Object.values(values).sort((a, b) => b - a); const allRanks = allVals .map((v) => ranks.indexOf(v)) .sort((a, b) => a - b); const uniqueRanks = [...new Set(allRanks)].sort((a, b) => a - b); const flushDraw = Object.values(suits).some( (list) => list.length === 4 ); const doubleTwayFlushDraw = Object.values(suits).filter((list) => list.length === 3).length >= 2; let openEndedStraight = false; let gutShot = false; for (let i = 0; i <= uniqueRanks.length - 4; i++) { const seq = uniqueRanks.slice(i, i + 4); if (seq[3] - seq[0] === 3) { openEndedStraight = true; break; } } if (!openEndedStraight) { for (let i = 0; i <= uniqueRanks.length - 4; i++) { const seq = uniqueRanks.slice(i, i + 4); if (seq[3] - seq[0] === 4) { gutShot = true; break; } } } const nombrePair = countList[0] >= 2 ? Object.entries(values).filter(([_, count]) => count >= 2) .length : 0; const doublePair = nombrePair >= 2; return { flushDraw, doubleTwayFlushDraw, openEndedStraight, gutShot, nombrePair, doublePair, possibleStraight: openEndedStraight || gutShot, possibleFlush: flushDraw || doubleTwayFlushDraw, }; } function simplifierConseil(conseil, position, joueurs, tirage, outs) { if (expertMode) { return conseil; } const positionTable = detecterPositionTable(); const estEnPosition = positionTable === 2; const peuJoueurs = joueurs <= 3; const bonneProba = outs && outs.probability > 0.3; // Gestion des tirages if (conseil.includes('tirage')) { if (bonneProba) { return t('weakHandDrawTemplate', { probability: Math.round(outs.probability * 100), position: estEnPosition ? t('inPosition') : t('outOfPosition'), }); } return t('weakHandFollow'); } // Gestion des conseils forts if (conseil === 'All-in') { return t('allIn'); } if (conseil === 'RAISE fort') { return estEnPosition ? t('raiseStrong') : t('raiseNormal'); } if (conseil === 'RAISE') { return estEnPosition ? t('raiseStrong') : t('raiseNormal'); } if (conseil === 'Call') { return peuJoueurs && estEnPosition ? t('callOrRaise') : t('callOnly'); } if (conseil === 'Check / Call') { return position ? t('checkInPosition') : t('foldOrCheck'); } if (conseil === 'Fold') { return t('fold'); } // Si aucune condition n'est remplie, retourner le conseil traduit ou original return conseil; } function evaluerMainPreflop(main) { if (main.length !== 2) { return null; } // Extraire les valeurs et couleurs const cartes = main.map((carte) => { const match = carte.match(/^([0-9TJQKA]+)(.)$/); return { valeur: match[1], couleur: match[2], rang: ranks.indexOf(match[1]), }; }); const valeur1 = cartes[0].rang; const valeur2 = cartes[1].rang; const memeCouleur = cartes[0].couleur === cartes[1].couleur; // Paire if (valeur1 === valeur2) { const valeurPaire = Math.max(valeur1, valeur2); // Stratégie TAG moderne: plus agressive avec les top pairs if (valeurPaire >= ranks.indexOf('A')) { // AA return { nom: 'One Pair', conseil: 'All-in', force: 8, valeurPrincipale: valeurPaire, tirage: false, kickers: [], }; } else if (valeurPaire >= ranks.indexOf('K')) { // KK return { nom: 'One Pair', conseil: 'RAISE fort', force: 7, valeurPrincipale: valeurPaire, tirage: false, kickers: [], }; } else if (valeurPaire >= ranks.indexOf('Q')) { // QQ return { nom: 'One Pair', conseil: 'RAISE fort', force: 6, valeurPrincipale: valeurPaire, tirage: false, kickers: [], }; } else if (valeurPaire >= ranks.indexOf('J')) { // JJ return { nom: 'One Pair', conseil: 'RAISE fort', force: 6, valeurPrincipale: valeurPaire, tirage: false, kickers: [], }; } else if (valeurPaire >= ranks.indexOf('T')) { // TT return { nom: 'One Pair', conseil: 'RAISE', force: 5, valeurPrincipale: valeurPaire, tirage: false, kickers: [], }; } else if (valeurPaire >= ranks.indexOf('9')) { // 99 return { nom: 'One Pair', conseil: 'RAISE', force: 4, valeurPrincipale: valeurPaire, tirage: false, kickers: [], }; } else if (valeurPaire >= ranks.indexOf('7')) { // 88-77 return { nom: 'One Pair', conseil: 'Call', // Position dependent force: 3, valeurPrincipale: valeurPaire, tirage: false, kickers: [], }; } else { // 66-22 - plus strict return { nom: 'One Pair', conseil: 'Call', // En position seulement force: 2, valeurPrincipale: valeurPaire, tirage: false, kickers: [], }; } } // Cartes non appariées - Stratégie TAG moderne const hauteValeur = Math.max(valeur1, valeur2); const basseValeur = Math.min(valeur1, valeur2); // AK - Main premium absolue if ( hauteValeur >= ranks.indexOf('A') && basseValeur >= ranks.indexOf('K') ) { return { nom: 'High Card', conseil: 'RAISE fort', force: 7, valeurPrincipale: hauteValeur, tirage: false, kickers: [basseValeur], }; } // AQ - Très forte mais pas premium if ( hauteValeur >= ranks.indexOf('A') && basseValeur >= ranks.indexOf('Q') ) { return { nom: 'High Card', conseil: 'RAISE', force: 5, valeurPrincipale: hauteValeur, tirage: false, kickers: [basseValeur], }; } // AJ - Bonne main, position dépendante if ( hauteValeur >= ranks.indexOf('A') && basseValeur >= ranks.indexOf('J') ) { return { nom: 'High Card', conseil: memeCouleur ? 'RAISE' : 'Call', force: memeCouleur ? 4 : 3, valeurPrincipale: hauteValeur, tirage: false, kickers: [basseValeur], }; } // AT - Main marginale, suited only if ( hauteValeur >= ranks.indexOf('A') && basseValeur >= ranks.indexOf('T') ) { return { nom: 'High Card', conseil: memeCouleur ? 'Call' : 'Fold', force: memeCouleur ? 2 : 0, valeurPrincipale: hauteValeur, tirage: false, kickers: [basseValeur], }; } // KQ - Main solide if ( hauteValeur >= ranks.indexOf('K') && basseValeur >= ranks.indexOf('Q') ) { return { nom: 'High Card', conseil: memeCouleur ? 'Call' : 'Fold', force: memeCouleur ? 3 : 1, valeurPrincipale: hauteValeur, tirage: false, kickers: [basseValeur], }; } // Connecteurs assortis 65s+ (pour le potentiel post-flop) if ( memeCouleur && Math.abs(hauteValeur - basseValeur) <= 4 && hauteValeur >= ranks.indexOf('9') && basseValeur >= ranks.indexOf('5') ) { return { nom: 'High Card', conseil: 'Call', force: 2, valeurPrincipale: hauteValeur, tirage: true, kickers: [basseValeur], }; } // Autres mains - fold strict return { nom: 'High Card', conseil: 'Fold', force: 0, valeurPrincipale: hauteValeur, tirage: false, kickers: [basseValeur], }; } function evaluerMain(toutesCartes, inclureDetails = false) { const cacheKey = toutesCartes.join(','); if (cache.has(cacheKey)) { return cache.get(cacheKey); } let result; if (toutesCartes.length < 5) { // Si on est preflop (exactement 2 cartes), utiliser l'évaluation preflop if (toutesCartes.length === 2) { result = evaluerMainPreflop(toutesCartes); } else { // Sinon utiliser l'analyse de tirage existante const tirage = analyserTirages(toutesCartes); const isTirage = tirage.possibleFlush || tirage.possibleStraight; if (isTirage) { let detailTirage = ''; if (tirage.openEndedStraight) { detailTirage += 'Quinte ouverte (8 outs)'; } if (tirage.gutShot && !tirage.openEndedStraight) { detailTirage += 'Gutshot (4 outs)'; } if (tirage.flushDraw) { detailTirage += (detailTirage ? ', ' : '') + 'Tirage couleur (9 outs)'; } result = { nom: 'High Card', conseil: 'Main faible avec tirage' + (detailTirage ? ': ' + detailTirage : ''), force: 0.5, valeurPrincipale: 0, tirage: true, detailTirage, kickers: [], }; } else { result = { nom: 'High Card', conseil: 'Fold', force: 0, valeurPrincipale: 0, tirage: false, kickers: [], }; } } } else { const meilleureMain = trouverMeilleureMain(toutesCartes); const evaluation = meilleureMain.evaluation; const conseilMap = { 9: 'All-in', // Royal Flush 8: 'All-in', // Straight Flush 7: 'All-in', // Four of a Kind 6: 'RAISE fort', // Full House 5: 'RAISE fort', // Flush 4: 'RAISE fort', // Straight 3: 'RAISE', // Three of a Kind 2: 'Call', // Two Pair 1: 'Check / Call', // One Pair 0: 'Fold', // High Card }; let conseil = 'Fold'; const forces = Object.keys(conseilMap) .map((k) => parseInt(k)) .sort((a, b) => b - a); for (const minForce of forces) { if (evaluation.force >= minForce) { conseil = conseilMap[minForce]; break; } } result = { nom: evaluation.nom, conseil, force: evaluation.force, valeurPrincipale: evaluation.valeurPrincipale, tirage: false, kickers: evaluation.kickers, }; } cache.set(cacheKey, result); if (cache.size > 100) { const firstKey = cache.keys().next().value; cache.delete(firstKey); } return result; } const getThemeColors = (force) => { const themes = { 9: { accent: '#dc2626', fond: 'rgba(127, 29, 29, 0.95)', bordure: '#dc2626', }, 8: { accent: '#dc2626', fond: 'rgba(127, 29, 29, 0.95)', bordure: '#dc2626', }, 7: { accent: '#dc2626', fond: 'rgba(127, 29, 29, 0.95)', bordure: '#dc2626', }, 6: { accent: '#ea580c', fond: 'rgba(124, 45, 18, 0.95)', bordure: '#ea580c', }, 5: { accent: '#f59e0b', fond: 'rgba(120, 53, 15, 0.95)', bordure: '#f59e0b', }, 4: { accent: '#f59e0b', fond: 'rgba(120, 53, 15, 0.95)', bordure: '#f59e0b', }, 3: { accent: '#ca8a04', fond: 'rgba(113, 63, 18, 0.95)', bordure: '#ca8a04', }, 2: { accent: '#10b981', fond: 'rgba(6, 95, 70, 0.95)', bordure: '#10b981', }, 1: { accent: '#059669', fond: 'rgba(6, 78, 59, 0.95)', bordure: '#059669', }, 0: { accent: '#6b7280', fond: 'rgba(15, 23, 42, 0.95)', bordure: '#374151', }, }; for (const [minForce, theme] of Object.entries(themes)) { if (force >= parseInt(minForce)) { return theme; } } return themes[0]; }; const langConfig = { en: { flag: '🇬🇧', name: 'English' }, fr: { flag: '🇫🇷', name: 'Français' }, de: { flag: '🇩🇪', name: 'Deutsch' }, es: { flag: '🇪🇸', name: 'Español' }, }; function afficherInfos(main, board) { const nbJoueurs = compterJoueursActifs(); const joueurAFold = detecterSiJoueurAFold(); const gameStateKey = `${main.join(',')}-${board.join( ',' )}-${currentLang}-${isMobileMode}-${isMinimized}-${mobilePosition}-${nbJoueurs}-${joueurAFold}`; if (lastGameState === gameStateKey) { return; } let box = document.getElementById('mainPokerBox'); if (!box) { box = document.createElement('div'); box.id = 'mainPokerBox'; document.body.appendChild(box); } const langOptions = document.getElementById('langOptions'); const dropdownIsOpen = langOptions && langOptions.style.display === 'block'; if (dropdownIsOpen && box.innerHTML) { updateContentOnly(main, board, box); return; } lastGameState = gameStateKey; let evaluation, winProbability, nomFinal, conseilFinal, outs; if (joueurAFold) { evaluation = { nom: '', conseil: t('folded'), tirage: false, force: 0, }; winProbability = 0; nomFinal = ''; conseilFinal = t('folded'); outs = null; } else { evaluation = main.length < 2 ? { nom: '', conseil: t('waiting'), tirage: false, force: 0, } : evaluerMain([...main, ...board], true); winProbability = main.length >= 2 ? calculerProbabiliteVictoire(main, board, nbJoueurs, 500) : 0; nomFinal = expertMode ? evaluation.nom : simplifierMain(evaluation.nom); // Calculer les outs AVANT simplifierConseil pour les tirages const outs = !joueurAFold && evaluation.tirage && main.length >= 2 && board.length >= 3 ? calculerOuts(main, board) : null; conseilFinal = simplifierConseil( evaluation.conseil, detecterPosition(), nbJoueurs, evaluation.tirage, outs ); } const { accent: couleurAccent, fond: couleurFond, bordure: couleurBordure, } = getThemeColors(evaluation.force); detectMobileMode(); box.style.cssText = ` position: fixed; ${ isMobileMode ? getPositionMobileStyles() : `top: ${currentPosition.y}px; left: ${currentPosition.x}px;` } width: ${isMobileMode ? (isMinimized ? '40px' : '220px') : '350px'}; background: ${couleurFond}; backdrop-filter: blur(12px); border: ${isMobileMode ? '1px' : '2px'} solid ${couleurBordure}; border-radius: ${isMobileMode ? '8px' : '16px'}; padding: 0; font-family: 'Segoe UI', -apple-system, BlinkMacSystemFont, sans-serif; font-size: ${isMobileMode ? '12px' : '14px'}; color: white; z-index: 99999; box-shadow: 0 8px 20px -4px rgba(0, 0, 0, 0.6); transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); `; if (isMobileMode) { box.innerHTML = htmlTemplates.generateMobileInterface( couleurAccent, main, board, nomFinal, conseilFinal, outs, nbJoueurs, isMinimized, winProbability ); } else { box.innerHTML = htmlTemplates.generateDesktopInterface( couleurAccent, main, board, nomFinal, conseilFinal, outs, nbJoueurs, winProbability ); } if (!box.dataset.animated) { box.style.transform = 'translateX(100%) scale(0.8)'; box.style.opacity = '0'; setTimeout(() => { box.style.transform = 'translateX(0) scale(1)'; box.style.opacity = '1'; }, 100); box.dataset.animated = 'true'; } const activePlayersElement = box.querySelector('[data-active-players]'); if (activePlayersElement) { activePlayersElement.textContent = nbJoueurs; } rendreModaleDraggable(box); eventHandlers.setupUIEvents(box); } let isDragging = false; const dragOffset = { x: 0, y: 0 }; const currentPosition = { x: 20, y: 20 }; let tapCount = 0; let tapTimer = null; const eventHandlers = { setupUIEvents: (box) => { setTimeout(() => { const elements = { langButton: document.getElementById('langButton'), langOptions: document.getElementById('langOptions'), toggleMinimize: document.getElementById('toggleMinimize'), positionButton: document.getElementById('positionButton'), }; if (elements.toggleMinimize) { elements.toggleMinimize.addEventListener('click', (e) => { e.stopPropagation(); eventHandlers.handleToggleMinimize(); }); } if (elements.positionButton && isMobileMode) { elements.positionButton.addEventListener('click', (e) => { e.stopPropagation(); changerPositionMobile(); }); } if (elements.langButton && elements.langOptions) { eventHandlers.setupLanguageSelector( elements.langButton, elements.langOptions ); } }, 100); }, handleToggleMinimize: () => { if (isMobileMode && isMinimized) { tapCount++; if (tapCount === 1) { tapTimer = setTimeout(() => { isMinimized = false; localStorage.setItem('tornPokerMinimized', isMinimized); eventHandlers.refreshInterface(); tapCount = 0; }, 300); } else if (tapCount === 2) { clearTimeout(tapTimer); changerPositionMobile(); tapCount = 0; } } else { isMinimized = !isMinimized; localStorage.setItem('tornPokerMinimized', isMinimized); eventHandlers.refreshInterface(); } }, setupLanguageSelector: (langButton, langOptions) => { const newLangButton = langButton.cloneNode(true); langButton.parentNode.replaceChild(newLangButton, langButton); newLangButton.addEventListener('click', (e) => { e.stopPropagation(); const isDisplayed = langOptions.style.display === 'block'; langOptions.style.display = isDisplayed ? 'none' : 'block'; }); const closeDropdown = (e) => { if (!e.target.closest('#langSelector')) { langOptions.style.display = 'none'; } }; document.removeEventListener('click', closeDropdown); document.addEventListener('click', closeDropdown); langOptions.addEventListener('click', (e) => e.stopPropagation()); setTimeout(() => { document.querySelectorAll('.langOption').forEach((option) => { const newOption = option.cloneNode(true); option.parentNode.replaceChild(newOption, option); newOption.addEventListener('click', (e) => { e.stopPropagation(); const lang = newOption.getAttribute('data-lang'); changerLangue(lang); langOptions.style.display = 'none'; }); newOption.addEventListener('mouseover', () => { if ( newOption.getAttribute('data-lang') !== currentLang ) { newOption.style.background = 'rgba(255, 255, 255, 0.05)'; } }); newOption.addEventListener('mouseout', () => { if ( newOption.getAttribute('data-lang') !== currentLang ) { newOption.style.background = 'transparent'; } }); }); }, 50); }, refreshInterface: () => { const main = lireCartesJoueur(); const board = lireCartesPlateau(); afficherInfos(main, board); }, }; function rendreModaleDraggable(box) { if (isMobileMode) { return; } const header = box.querySelector('[data-drag-handle]'); if (!header) { return; } header.style.cursor = 'move'; header.style.userSelect = 'none'; header.addEventListener('mousedown', function (e) { if ( e.target.closest('#langButton') || e.target.closest('#langOptions') ) { return; } isDragging = true; const rect = box.getBoundingClientRect(); dragOffset.x = e.clientX - rect.left; dragOffset.y = e.clientY - rect.top; box.style.transition = 'none'; e.preventDefault(); }); document.addEventListener('mousemove', function (e) { if (!isDragging || isMobileMode) { return; } const newX = e.clientX - dragOffset.x; const newY = e.clientY - dragOffset.y; const maxX = window.innerWidth - box.offsetWidth; const maxY = window.innerHeight - box.offsetHeight; currentPosition.x = Math.max(0, Math.min(newX, maxX)); currentPosition.y = Math.max(0, Math.min(newY, maxY)); box.style.left = currentPosition.x + 'px'; box.style.top = currentPosition.y + 'px'; box.style.right = 'auto'; box.style.bottom = 'auto'; }); document.addEventListener('mouseup', function () { if (isDragging) { isDragging = false; box.style.transition = 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)'; } }); } function ajouterStylesGlobaux() { if (document.getElementById('pokerHelperStyles')) { return; } const styles = document.createElement('style'); styles.id = 'pokerHelperStyles'; styles.textContent = ` #mainPokerBox { transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important; } #mainPokerBox:hover { transform: translateY(-2px) !important; } #langButton:hover { background: rgba(255, 255, 255, 0.2) !important; transform: scale(1.05) !important; } .langOption:hover { background: rgba(255, 255, 255, 0.05) !important; } [data-drag-handle] { cursor: move !important; } [data-drag-handle]:active { cursor: grabbing !important; } @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.7; } } @keyframes slideIn { from { transform: translateX(100%) scale(0.8); opacity: 0; } to { transform: translateX(0) scale(1); opacity: 1; } } @media (max-width: 768px) { #mainPokerBox { font-size: 11px !important; } [data-drag-handle] { cursor: default !important; } } `; document.head.appendChild(styles); } const gameLoop = { isRunning: false, intervalId: null, lastPlayerCount: null, start: () => { if (gameLoop.isRunning) { return; } ajouterStylesGlobaux(); detectMobileMode(); gameLoop.isRunning = true; gameLoop.intervalId = setInterval(() => { const currentPlayerCount = compterJoueursActifs(); if (gameLoop.lastPlayerCount !== currentPlayerCount) { gameLoop.lastPlayerCount = currentPlayerCount; cache.clear(); eventHandlers.refreshInterface(); } const mainCards = document.querySelectorAll( '.playerMeGateway___AEI5_ .hand___aOp4l .card___t7csZ .front___osz1p > div' ); if (mainCards.length >= 2) { eventHandlers.refreshInterface(); } }, 1000); }, stop: () => { if (gameLoop.intervalId) { clearInterval(gameLoop.intervalId); gameLoop.intervalId = null; gameLoop.isRunning = false; } }, }; function attendreEtExecuter() { gameLoop.start(); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', attendreEtExecuter); } else { attendreEtExecuter(); } window.addEventListener('load', attendreEtExecuter); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址