// ==UserScript==
// @name Torn Poker Helper
// @namespace http://tampermonkey.net/
// @version 1.4.1
// @description Clean and simple poker helper for Torn City
// @author JESUUS [2353554]
// @match https://www.torn.com/page.php?sid=holdem*
// @grant none
// @license MITon le
// ==/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 = 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);
}
});
}
return outs;
}
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 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: 2,
valeurPrincipale: valeurPaire,
tirage: false,
kickers: []
};
} else { // 66-22 - plus strict
return {
nom: "One Pair",
conseil: "Fold", // Fold hors position, call en position favorable seulement
force: 1,
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 = {
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);
})();