您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Clean and simple poker helper for Torn City
当前为
// ==UserScript== // @name Torn Poker Helper // @namespace http://tampermonkey.net/ // @version 1.3.1 // @description Clean and simple poker helper for Torn City // @author JESUUS [2353554] // @match https://www.torn.com/* // @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) => b - a); let straightFound = false; let straightHighCard = 0; for (let i = 0; i <= uniqueRanks.length - 5; i++) { const seq = uniqueRanks.slice(i, i + 5).sort((a, b) => a - b); if (seq[4] - seq[0] === 4) { straightFound = true; straightHighCard = seq[4]; break; } } if (!straightFound) { 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 = []; const outDetails = []; const allRanks = "23456789TJQKA".split(''); const allSuits = ['♠', '♥', '♦', '♣']; const toutesCartesPossibles = []; allRanks.forEach(rank => { allSuits.forEach(suit => { toutesCartesPossibles.push(`${rank}${suit}`); }); }); const meilleureMainActuelle = trouverMeilleureMain(toutesCartes); toutesCartesPossibles.forEach(carte => { if (cartesUtilisees.has(carte)) { return; } const mainTest = [...toutesCartes, carte]; const meilleureMainTest = trouverMeilleureMain(mainTest); if (comparerMains(meilleureMainTest.evaluation, meilleureMainActuelle.evaluation) > 0) { outs.push(carte); outDetails.push(`${carte} → ${meilleureMainTest.evaluation.nom}`); } }); return { nombre: outs.length, details: outDetails, probability: calculerProbabilite(outs.length, board.length) }; } function calculerProbabilite(outs, boardSize) { if (boardSize === 0) { return 1 - Math.pow((52 - outs - 2) / (52 - 2), 3); } else if (boardSize === 3) { return 1 - Math.pow((52 - outs - 5) / (52 - 5), 2); } else if (boardSize === 4) { return outs / (52 - 6); } 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]]; } // Compléter le board si nécessaire const boardComplet = [...board]; while (boardComplet.length < 5) { boardComplet.push(deck.pop()); } // Évaluer notre main const notreMeilleureMain = trouverMeilleureMain([...main, ...boardComplet]); // Simuler les mains des adversaires let gagne = true; let deckIndex = 0; // Vérifier qu'il y a assez de cartes pour tous les adversaires const cartesNecessaires = (nbJoueurs - 1) * 2; if (deck.length < cartesNecessaires) { continue; // Passer cette simulation } 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 t('allIn'); } 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 evaluerMain(toutesCartes, inclureDetails = false) { const cacheKey = toutesCartes.join(','); if (cache.has(cacheKey)) { return cache.get(cacheKey); } let result; if (toutesCartes.length < 5) { 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 = { 7: "All-in", 6: "RAISE fort", 4: "RAISE fort", 3: "RAISE", 2: "Call", 1: "Check / Call", 0: "Fold" }; 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 = { 7: { accent: '#dc2626', fond: 'rgba(127, 29, 29, 0.95)', bordure: '#dc2626' }, 6: { accent: '#ea580c', fond: 'rgba(124, 45, 18, 0.95)', bordure: '#ea580c' }, 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; if (joueurAFold) { evaluation = { nom: "", conseil: t('folded'), tirage: false, force: 0 }; winProbability = 0; nomFinal = ""; conseilFinal = t('folded'); } 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); conseilFinal = simplifierConseil( evaluation.conseil, detecterPosition(), nbJoueurs, evaluation.tirage, null ); } const outs = (!joueurAFold && evaluation.tirage && main.length >= 2 && board.length >= 3) ? calculerOuts(main, board) : null; 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); })();