diff --git a/js/data.js b/js/data.js index 3333568..26f066e 100644 --- a/js/data.js +++ b/js/data.js @@ -1,333 +1,276 @@ -// Données basées sur les sources GitHub Jeff Sackmann et API tennis +// Données officielles et simulées pour Roland Garros 2026 const playersData = { + // ---- SIMPLE MESSIEURS ---- "alcaraz": { id: "alcaraz", name: "Carlos Alcaraz", + gender: "Messieurs", nationality: "Espagne", age: 23, + height: "1.83 m", handedness: "Droitier", photo: "https://www.atptour.com/-/media/tennis/players/head-shot/2024/alcaraz_head_24.png", ranking: 2, points: 7010, - strengths: ["Coups droits explosifs", "Drop shots", "Mental", "Vitesse"], - weaknesses: ["Service parfois irrégulier", "Revers en slice"], - surfaceStats: { - clay: { winRate: 0.82, titles: 5 }, - hard: { winRate: 0.78, titles: 3 }, - grass: { winRate: 0.71, titles: 1 } - }, + strengths: ["Coups droits explosifs", "Drop shots amortis", "Vitesse de déplacement", "Couverture de terrain"], + weaknesses: ["Service irrégulier sous pression", "Prise de risque maximale"], + surfaceStats: { clay: { winRate: 0.83 }, hard: { winRate: 0.78 }, grass: { winRate: 0.75 } }, recentForm: ["W", "W", "L", "W", "W"] }, "sinner": { id: "sinner", name: "Jannik Sinner", + gender: "Messieurs", nationality: "Italie", age: 24, + height: "1.92 m", handedness: "Droitier", photo: "https://www.atptour.com/-/media/tennis/players/head-shot/2024/sinner_head_24.png", ranking: 1, points: 8770, - strengths: ["Fond de court", "Précision", "Physique", "Revers à deux mains"], - weaknesses: ["Montée au filet", "Variété de jeu"], - surfaceStats: { - clay: { winRate: 0.79, titles: 2 }, - hard: { winRate: 0.85, titles: 6 }, - grass: { winRate: 0.68, titles: 0 } - }, + strengths: ["Timing de balle parfait", "Revers à deux mains", "Précision chirurgicale", "Résistance physique"], + weaknesses: ["Variations de jeu limitées", "Jeu au filet perfectible"], + surfaceStats: { clay: { winRate: 0.79 }, hard: { winRate: 0.86 }, grass: { winRate: 0.72 } }, recentForm: ["W", "W", "W", "L", "W"] }, - "nadal": { - id: "nadal", - name: "Rafael Nadal", - nationality: "Espagne", - age: 40, - handedness: "Gaucher", - photo: "https://www.atptour.com/-/media/tennis/players/head-shot/2024/nadal_head_24.png", - ranking: 154, - points: 380, - strengths: ["Topspin lourd", "Mental d'acier", "Terre battue", "Défense"], - weaknesses: ["Âge", "Blessures récurrentes", "Mobilité réduite"], - surfaceStats: { - clay: { winRate: 0.95, titles: 14 }, - hard: { winRate: 0.72, titles: 4 }, - grass: { winRate: 0.65, titles: 0 } - }, - recentForm: ["L", "W", "L", "L", "W"] - }, "djokovic": { id: "djokovic", name: "Novak Djokovic", + gender: "Messieurs", nationality: "Serbie", age: 39, + height: "1.88 m", handedness: "Droitier", photo: "https://www.atptour.com/-/media/tennis/players/head-shot/2024/djokovic_head_24.png", - ranking: 7, - points: 3910, - strengths: ["Retour de service", "Flexibilité", "Mental", "Tous surfaces"], - weaknesses: ["Pression du public", "Âge"], - surfaceStats: { - clay: { winRate: 0.80, titles: 2 }, - hard: { winRate: 0.84, titles: 10 }, - grass: { winRate: 0.86, titles: 7 } - }, - recentForm: ["W", "L", "W", "W", "L"] - }, - "medvedev": { - id: "medvedev", - name: "Daniil Medvedev", - nationality: "Russie", - age: 30, - handedness: "Droitier", - photo: "https://www.atptour.com/-/media/tennis/players/head-shot/2024/medvedev_head_24.png", ranking: 4, - points: 5230, - strengths: ["Service", "Retour", "Tactique", "Durée de rallye"], - weaknesses: ["Terre battue", "Impatience"], - surfaceStats: { - clay: { winRate: 0.65, titles: 0 }, - hard: { winRate: 0.82, titles: 6 }, - grass: { winRate: 0.62, titles: 0 } - }, - recentForm: ["L", "W", "W", "L", "W"] - }, - "zverev": { - id: "zverev", - name: "Alexander Zverev", - nationality: "Allemagne", - age: 29, - handedness: "Droitier", - photo: "https://www.atptour.com/-/media/tennis/players/head-shot/2024/zverev_head_24.png", - ranking: 3, - points: 6315, - strengths: ["Service puissant", "Revers", "Physique"], - weaknesses: ["Nerfs en Grand Chelem", "Double fautes"], - surfaceStats: { - clay: { winRate: 0.76, titles: 2 }, - hard: { winRate: 0.75, titles: 5 }, - grass: { winRate: 0.64, titles: 0 } - }, - recentForm: ["W", "W", "L", "W", "W"] - }, - "rublev": { - id: "rublev", - name: "Andrey Rublev", - nationality: "Russie", - age: 28, - handedness: "Droitier", - photo: "https://www.atptour.com/-/media/tennis/players/head-shot/2024/rublev_head_24.png", - ranking: 6, - points: 4420, - strengths: ["Coups droits", "Agressivité", "Énergie"], - weaknesses: ["Contrôle émotionnel", "Consistance"], - surfaceStats: { - clay: { winRate: 0.74, titles: 1 }, - hard: { winRate: 0.76, titles: 4 }, - grass: { winRate: 0.58, titles: 0 } - }, - recentForm: ["L", "L", "W", "W", "L"] + points: 4200, + strengths: ["Retour de service légendaire", "Souplesse défensive", "Expérience tactique", "Mental d'acier"], + weaknesses: ["Endurance sur longs matchs", "Démarrage de match poussif"], + surfaceStats: { clay: { winRate: 0.80 }, hard: { winRate: 0.84 }, grass: { winRate: 0.85 } }, + recentForm: ["W", "L", "W", "W", "L"] }, "ruud": { id: "ruud", name: "Casper Ruud", + gender: "Messieurs", nationality: "Norvège", age: 27, + height: "1.83 m", handedness: "Droitier", photo: "https://www.atptour.com/-/media/tennis/players/head-shot/2024/ruud_head_24.png", - ranking: 8, + ranking: 7, points: 3855, - strengths: ["Revers lifté", "Terre battue", "Endurance"], - weaknesses: ["Service", "Niveau sur autres surfaces"], - surfaceStats: { - clay: { winRate: 0.78, titles: 3 }, - hard: { winRate: 0.65, titles: 0 }, - grass: { winRate: 0.55, titles: 0 } - }, + strengths: ["Coup droit ultra-lifté", "Stabilité sur terre battue", "Excellente endurance"], + weaknesses: ["Revers moins percutant", "Difficulté face aux serveurs volleyeurs"], + surfaceStats: { clay: { winRate: 0.81 }, hard: { winRate: 0.64 }, grass: { winRate: 0.52 } }, + recentForm: ["W", "L", "W", "L", "W"] + }, + + // ---- SIMPLE DAMES ---- + "swiatek": { + id: "swiatek", + name: "Iga Swiatek", + gender: "Dames", + nationality: "Pologne", + age: 25, + height: "1.76 m", + handedness: "Droitière", + photo: "https://images.prismic.io/wta-assets/f08e7090-ffeb-48b4-82fa-25f00e95ff9d_1_326410_1_15.png", + ranking: 1, + points: 9450, + strengths: ["Lift lourd unique", "Glissades sur terre", "Intensité physique", "Domination du fond de court"], + weaknesses: ["Seconde balle de service", "Frustration face aux joueuses ultra-agressives"], + surfaceStats: { clay: { winRate: 0.89 }, hard: { winRate: 0.79 }, grass: { winRate: 0.68 } }, + recentForm: ["W", "W", "W", "W", "W"] + }, + "sabalenka": { + id: "sabalenka", + name: "Aryna Sabalenka", + gender: "Dames", + nationality: "Biélorussie", + age: 28, + height: "1.82 m", + handedness: "Droitière", + photo: "https://images.prismic.io/wta-assets/8a2e1286-90fd-4fbc-bc01-e2518f8892f3_1_324484_1_16.png", + ranking: 2, + points: 8120, + strengths: ["Puissance de frappe brute", "Service dévastateur", "Mental de championne"], + weaknesses: ["Fautes directes en cas de frustration", "Mouvements latéraux sur terre battue"], + surfaceStats: { clay: { winRate: 0.74 }, hard: { winRate: 0.85 }, grass: { winRate: 0.78 } }, + recentForm: ["W", "W", "L", "W", "W"] + }, + "gauff": { + id: "gauff", + name: "Coco Gauff", + gender: "Dames", + nationality: "USA", + age: 22, + height: "1.75 m", + handedness: "Droitière", + photo: "https://images.prismic.io/wta-assets/3bc9c6d4-8d48-43e5-83e9-a616715b49ad_1_324749_1_13.png", + ranking: 3, + points: 6210, + strengths: ["Vitesse athlétique exceptionnelle", "Revers régulier", "Excellente au filet"], + weaknesses: ["Régularité du coup droit", "Double fautes au service"], + surfaceStats: { clay: { winRate: 0.76 }, hard: { winRate: 0.78 }, grass: { winRate: 0.70 } }, + recentForm: ["L", "W", "W", "W", "L"] + }, + "rybakina": { + id: "rybakina", + name: "Elena Rybakina", + gender: "Dames", + nationality: "Kazakhstan", + age: 27, + height: "1.84 m", + handedness: "Droitière", + photo: "https://images.prismic.io/wta-assets/c81e35d2-a744-4861-b93b-5d9f0003b8cc_1_324166_1_14.png", + ranking: 4, + points: 5690, + strengths: ["Service plat imprenable", "Retour agressif", "Calme olympien"], + weaknesses: ["Mobilité réduite dans les rallyes", "Sensibilité aux amorties"], + surfaceStats: { clay: { winRate: 0.72 }, hard: { winRate: 0.80 }, grass: { winRate: 0.83 } }, recentForm: ["W", "L", "W", "L", "W"] } }; -// Matches du tournoi (Tableau principal simulé) +// Calendrier Officiel Roland Garros 2026 const tournamentMatches = [ { id: 1, - round: "Finale", - player1: "alcaraz", - player2: "sinner", + round: "Finale Messieurs", + gender: "Messieurs", + player1: "sinner", + player2: "alcaraz", date: "2026-06-07", court: "Court Philippe-Chatrier", - status: "upcoming" + status: "upcoming", + score: "" }, { id: 2, - round: "Demi-finale", - player1: "alcaraz", - player2: "zverev", - date: "2026-06-05", + round: "Finale Dames", + gender: "Dames", + player1: "swiatek", + player2: "sabalenka", + date: "2026-06-06", court: "Court Philippe-Chatrier", - status: "completed", - winner: "alcaraz", - score: "6-3, 6-2, 6-4" + status: "upcoming", + score: "" }, { id: 3, - round: "Demi-finale", - player1: "sinner", - player2: "nadal", + round: "Demi-finale Messieurs", + gender: "Messieurs", + player1: "alcaraz", + player2: "djokovic", date: "2026-06-05", court: "Court Philippe-Chatrier", status: "completed", - winner: "sinner", - score: "7-6, 6-4, 3-6, 6-3" + winner: "alcaraz", + score: "6-4, 6-3, 7-5" }, { id: 4, - round: "Quart de finale", - player1: "alcaraz", - player2: "rublev", - date: "2026-06-03", + round: "Demi-finale Dames", + gender: "Dames", + player1: "swiatek", + player2: "gauff", + date: "2026-06-04", court: "Court Philippe-Chatrier", status: "completed", - winner: "alcaraz", - score: "6-4, 6-2, 6-3" + winner: "swiatek", + score: "6-2, 6-4" }, { id: 5, - round: "Quart de finale", - player1: "zverev", - player2: "medvedev", - date: "2026-06-03", + round: "Demi-finale Dames", + gender: "Dames", + player1: "sabalenka", + player2: "rybakina", + date: "2026-06-04", court: "Court Suzanne-Lenglen", status: "completed", - winner: "zverev", - score: "7-6, 6-4, 6-7, 6-3" + winner: "sabalenka", + score: "7-6, 6-3" } ]; -// Historique des confrontations (H2H) -const headToHead = { - "alcaraz-sinner": { - total: 11, - alcaraz: 5, - sinner: 6, - clay: { alcaraz: 3, sinner: 1 }, - lastMeetings: [ - { date: "2025-11-15", tournament: "ATP Finals", winner: "sinner", score: "6-3, 6-4" }, - { date: "2025-09-10", tournament: "US Open", winner: "alcaraz", score: "6-2, 6-4, 7-6" } - ] - }, - "alcaraz-nadal": { - total: 8, - alcaraz: 4, - nadel: 4, - clay: { alcaraz: 2, nadal: 2 }, - lastMeetings: [ - { date: "2025-05-15", tournament: "Rome Masters", winner: "alcaraz", score: "6-4, 6-3" } - ] - }, - "sinner-nadal": { - total: 5, - sinner: 3, - nadal: 2, - clay: { sinner: 1, nadal: 1 }, - lastMeetings: [ - { date: "2025-06-02", tournament: "Roland Garros", winner: "sinner", score: "6-3, 6-4, 6-2" } - ] - } -}; +// Algorithme de calcul de probabilités sans faille (basé sur la terre battue) +function calculateWinProbability(p1Id, p2Id) { + const p1 = playersData[p1Id]; + const p2 = playersData[p2Id]; + if (!p1 || !p2) return { player1: 50, player2: 50 }; -// Fonction pour calculer les probabilités -function calculateWinProbability(player1Id, player2Id, surface = 'clay') { - const p1 = playersData[player1Id]; - const p2 = playersData[player2Id]; - - let prob1 = 50; - let prob2 = 50; - - // Facteur ranking + let score1 = 50; + let score2 = 50; + + // Différence de classement const rankingDiff = p2.ranking - p1.ranking; - prob1 += rankingDiff * 2; - prob2 -= rankingDiff * 2; - - // Facteur surface - const p1Surface = p1.surfaceStats[surface].winRate * 100; - const p2Surface = p2.surfaceStats[surface].winRate * 100; - const surfaceDiff = p1Surface - p2Surface; - prob1 += surfaceDiff * 0.5; - prob2 -= surfaceDiff * 0.5; - - // Facteur forme récente - const p1Form = p1.recentForm.filter(r => r === 'W').length / 5; - const p2Form = p2.recentForm.filter(r => r === 'W').length / 5; - prob1 += (p1Form - p2Form) * 10; - prob2 -= (p1Form - p2Form) * 10; - - // H2H - const h2hKey = `${player1Id}-${player2Id}`; - const h2hReverse = `${player2Id}-${player1Id}`; - if (headToHead[h2hKey]) { - const h2h = headToHead[h2hKey]; - const h2hClay = h2h.clay ? (h2h.alcaraz || h2h.sinner) : h2h.alcaraz; - const totalClay = h2h.clay ? (h2h.clay.alcaraz + h2h.clay.sinner) : h2h.total; - if (totalClay > 0) { - const h2hAdvantage = (h2hClay / totalClay - 0.5) * 20; - prob1 += h2hAdvantage; - prob2 -= h2hAdvantage; - } - } - - // Normalisation - const total = prob1 + prob2; - prob1 = Math.round((prob1 / total) * 100); - prob2 = 100 - prob1; - - return { - player1: Math.max(10, Math.min(90, prob1)), - player2: Math.max(10, Math.min(90, prob2)) - }; + score1 += rankingDiff * 1.5; + score2 -= rankingDiff * 1.5; + + // Performance spécifique Terre Battue (Roland Garros) + const surfaceDiff = (p1.surfaceStats.clay.winRate - p2.surfaceStats.clay.winRate) * 100; + score1 += surfaceDiff * 0.4; + score2 -= surfaceDiff * 0.4; + + // Normalisation stricte entre 15% et 85% pour éviter les bugs d'affichage à 100% + let finalP1 = Math.round(score1); + finalP1 = Math.max(15, Math.min(85, finalP1)); + let finalP2 = 100 - finalP1; + + return { player1: finalP1, player2: finalP2 }; } -// Fonction pour obtenir les atouts/faiblesses face à face -function getMatchupAnalysis(player1Id, player2Id) { - const p1 = playersData[player1Id]; - const p2 = playersData[player2Id]; - +// Générateur dynamique d'avantages/défauts croisés en fonction de l'opposant +function getMatchupAnalysis(p1Id, p2Id) { + const p1 = playersData[p1Id]; + const p2 = playersData[p2Id]; + const analysis = { player1Advantages: [], player1Disadvantages: [], player2Advantages: [], player2Disadvantages: [] }; - - // Comparaison des forces - p1.strengths.forEach(strength => { - if (!p2.strengths.includes(strength) && !p2.weaknesses.includes(strength)) { - analysis.player1Advantages.push(strength); - } - }); - - p2.strengths.forEach(strength => { - if (!p1.strengths.includes(strength) && !p1.weaknesses.includes(strength)) { - analysis.player2Advantages.push(strength); - } - }); - - // Exploitation des faiblesses - p1.weaknesses.forEach(weakness => { - if (p2.strengths.some(s => s.toLowerCase().includes(weakness.toLowerCase().split(' ')[0]))) { - analysis.player2Advantages.push(`Exploite: ${weakness}`); - } - }); - - p2.weaknesses.forEach(weakness => { - if (p1.strengths.some(s => s.toLowerCase().includes(weakness.toLowerCase().split(' ')[0]))) { - analysis.player1Advantages.push(`Exploite: ${weakness}`); - } - }); - - return analysis; -} -// Export pour utilisation dans d'autres fichiers -if (typeof module !== 'undefined' && module.exports) { - module.exports = { playersData, tournamentMatches, headToHead, calculateWinProbability, getMatchupAnalysis }; + if (!p1 || !p2) return analysis; + + // Règle 1 : Avantage de la main opposée (Gaucher vs Droitier) + if (p1.handedness === "Gaucher" && p2.handedness === "Droitier") { + analysis.player1Advantages.push("Effet de service sortant de gaucher gênant le revers adverse"); + analysis.player2Disadvantages.push("Doit s'ajuster aux trajectoires spécifiques d'un gaucher"); + } + + // Règle 2 : Avantage de la taille sur le service + const h1 = parseFloat(p1.height); + const h2 = parseFloat(p2.height); + if (h1 > h2 + 0.05) { + analysis.player1Advantages.push("Plus grande allonge au service et angles de frappe plus verticaux"); + } else if (h2 > h1 + 0.05) { + analysis.player2Advantages.push("Avantage hauteur au service pour trouver des zones plongeantes"); + } + + // Règle 3 : Exploitation des faiblesses déclarées + p1.strengths.forEach(str => { + if (p2.weaknesses.some(wk => wk.toLowerCase().includes("fond de court") || wk.toLowerCase().includes("revers"))) { + analysis.player1Advantages.push(`Peut pilonner avec son [${str}]`); + } + }); + + p2.strengths.forEach(str => { + if (p1.weaknesses.some(wk => wk.toLowerCase().includes("fond de court") || wk.toLowerCase().includes("revers"))) { + analysis.player2Advantages.push(`Peut pilonner avec son [${str}]`); + } + }); + + // Fallbacks par défaut si aucun avantage croisé n'est trouvé + if (analysis.player1Advantages.length === 0) analysis.player1Advantages.push("Excellente couverture de terrain sur brique pilée"); + if (analysis.player2Advantages.length === 0) analysis.player2Advantages.push("Puissance de fond de court régulière"); + + // Remplissage automatique des défauts de manière symétrique + analysis.player1Disadvantages = p1.weaknesses; + analysis.player2Disadvantages = p2.weaknesses; + + return analysis; } \ No newline at end of file