// ================================================
// MOTEUR DE SIMULATION MONTE CARLO POUR FOOTBALL
// ================================================
// Classe principale pour le moteur de simulation
class FootballSimulationEngine {
constructor() {
// Facteurs de base pour les simulations
this.DEFAULT_HOME_ADVANTAGE = 1.3;
this.DEFAULT_GOAL_VARIANCE = 0.2;
this.DEFAULT_XG_CONVERSION = 0.75;
this.MATCH_MINUTES = 90;
// Constantes pour les événements de match
this.EVENT_RATES = {
SHOT: 0.11, // Probabilité de tir par minute de possession
CORNER: 0.035, // Probabilité de corner par minute
FOUL: 0.09, // Probabilité de faute par minute
YELLOW: 0.15, // Probabilité de carton jaune après une faute
RED: 0.02, // Probabilité de carton rouge après une faute
INJURY: 0.005, // Probabilité de blessure par minute
WOODWORK: 0.15 // Probabilité de tirer sur les montants
(parmi les tirs)
};
// Facteurs d'impact des conditions
this.CONDITION_FACTORS = {
// Types de match
matchType: {
league: { homeAdvantage: 1.0, intensity: 1.0 },
cup: { homeAdvantage: 1.1, intensity: 1.2 },
derby: { homeAdvantage: 1.2, intensity: 1.3 },
friendly: { homeAdvantage: 0.7, intensity: 0.8 },
european: { homeAdvantage: 0.9, intensity: 1.15 }
},
// Météo
weather: {
normal: { attackReduction: 0, passAccuracyFactor: 1.0 },
rainy: { attackReduction: 0.1, passAccuracyFactor: 0.9 },
snowy: { attackReduction: 0.15, passAccuracyFactor: 0.85 },
windy: { attackReduction: 0.08, passAccuracyFactor: 0.92 },
hot: { attackReduction: 0.05, passAccuracyFactor: 0.97 }
},
// Importance du match
importance: {
low: { intensityFactor: 0.8, defenseBoost: 0.0 },
normal: { intensityFactor: 1.0, defenseBoost: 0.0 },
high: { intensityFactor: 1.2, defenseBoost: 0.05 },
'very-high': { intensityFactor: 1.3, defenseBoost: 0.1 }
},
// Style d'arbitrage
refStyle: {
lenient: { foulRate: 0.8, cardRate: 0.7 },
balanced: { foulRate: 1.0, cardRate: 1.0 },
strict: { foulRate: 1.0, cardRate: 1.3 }
}
};
}
// Fonction principale pour exécuter une simulation complète
runSimulation(config) {
const {
homeTeam,
awayTeam,
homeTeamData,
awayTeamData,
matchType = 'league',
weather = 'normal',
importance = 'normal',
refStyle = 'balanced',
simulationCount = 10000,
homePlayers = [],
awayPlayers = [],
homeGoalkeeper = null,
awayGoalkeeper = null
} = config;
[Link](`Démarrage de la simulation: ${homeTeam} vs $
{awayTeam}`);
[Link](`Nombre de simulations: ${simulationCount}`);
// Résultats cumulatifs
const results = {
homeWins: 0,
draws: 0,
awayWins: 0,
homeGoals: 0,
awayGoals: 0,
btts: 0, // Les deux équipes marquent
over05: 0, // Plus de 0.5 buts
over15: 0, // Plus de 1.5 buts
over25: 0, // Plus de 2.5 buts
over35: 0, // Plus de 3.5 buts
homeCleanSheet: 0, // Clean sheet pour l'équipe à domicile
awayCleanSheet: 0, // Clean sheet pour l'équipe à l'extérieur
exactScores: {}, // Décompte des scores exacts
xG: { // Expected Goals moyen
home: 0,
away: 0
},
events: { // Statistiques des événements
corners: { home: 0, away: 0 },
shots: { home: 0, away: 0 },
shotsOnTarget: { home: 0, away: 0 },
fouls: { home: 0, away: 0 },
yellowCards: { home: 0, away: 0 },
redCards: { home: 0, away: 0 }
}
};
// Préparer les caractéristiques des équipes
const teamStats = this._prepareTeamStats(homeTeamData,
awayTeamData, config);
// Exécuter les simulations
for (let i = 0; i < simulationCount; i++) {
const matchResult = this._simulateSingleMatch(teamStats,
config);
this._aggregateResults(results, matchResult);
// Mettre à jour la progression si une fonction de callback est
fournie
if ([Link] && i % [Link](1,
[Link](simulationCount / 100)) === 0) {
const progress = (i / simulationCount) * 100;
[Link](progress, i, simulationCount);
}
}
// Calculer les moyennes et les probabilités
const processedResults = this._processResults(results,
simulationCount);
[Link]("Simulation terminée", processedResults);
return processedResults;
}
// Prépare les données statistiques des équipes pour la simulation
_prepareTeamStats(homeTeamData, awayTeamData, config) {
// Paramètres du match
const matchType = [Link] || 'league';
const weather = [Link] || 'normal';
const importance = [Link] || 'normal';
const refStyle = [Link] || 'balanced';
// Facteurs basés sur les conditions du match
const homeAdvantage = this.DEFAULT_HOME_ADVANTAGE *
this.CONDITION_FACTORS.matchType[matchType].homeAdvantage;
const intensityFactor =
this.CONDITION_FACTORS.matchType[matchType].intensity *
this.CONDITION_FACTORS.importance[importance].intensityFactor;
const attackReduction =
this.CONDITION_FACTORS.weather[weather].attackReduction;
const passAccuracyFactor =
this.CONDITION_FACTORS.weather[weather].passAccuracyFactor;
const foulRate =
this.CONDITION_FACTORS.refStyle[refStyle].foulRate;
const cardRate =
this.CONDITION_FACTORS.refStyle[refStyle].cardRate;
// Extraction et transformation des statistiques clés
const extractStat = (data, key, defaultValue = 1.0) => {
if (!data) return defaultValue;
const value = parseFloat(data[key]);
return isNaN(value) ? defaultValue : value;
};
// Statistiques offensives
const homeGoalsPer90 = extractStat(homeTeamData, 'Gls', 1.2) /
extractStat(homeTeamData, '90s', 38);
const awayGoalsPer90 = extractStat(awayTeamData, 'Gls', 1.0) /
extractStat(awayTeamData, '90s', 38);
const homeXGPer90 = extractStat(homeTeamData, 'xG', 1.3) /
extractStat(homeTeamData, '90s', 38);
const awayXGPer90 = extractStat(awayTeamData, 'xG', 1.1) /
extractStat(awayTeamData, '90s', 38);
const homeShotsPer90 = extractStat(homeTeamData, 'Sh/90', 12);
const awayShotsPer90 = extractStat(awayTeamData, 'Sh/90', 10);
const homeShotsOnTargetRate = extractStat(homeTeamData, 'SoT%', 35)
/ 100;
const awayShotsOnTargetRate = extractStat(awayTeamData, 'SoT%', 33)
/ 100;
// Statistiques défensives (à partir de données opposées si
disponibles)
// Si GA n'est pas disponible, on utilise la moyenne de la ligue
const homeGoalsAgainstPer90 = extractStat(homeTeamData, 'GA',
1.1) / extractStat(homeTeamData, '90s', 38);
const awayGoalsAgainstPer90 = extractStat(awayTeamData, 'GA',
1.3) / extractStat(awayTeamData, '90s', 38);
// Possession et style de jeu
const homePossession = extractStat(homeTeamData, 'Poss', 50);
const awayPossession = extractStat(awayTeamData, 'Poss', 50);
// Ajuster la possession relative au match
const totalPossession = homePossession + awayPossession;
const adjustedHomePossession = (homePossession / totalPossession) *
100 * homeAdvantage;
const adjustedAwayPossession = 100 - adjustedHomePossession;
// Calcul des xG de base pour le match
// Les valeurs de xG sont ajustées en fonction de l'adversaire, de
l'avantage à domicile et des conditions
const baseHomeXG = homeXGPer90 * (1 - attackReduction) *
(awayGoalsAgainstPer90 / 1.1) * // Facteur basé
sur la défense adverse
homeAdvantage; // Avantage domicile
const baseAwayXG = awayXGPer90 * (1 - attackReduction) *
(homeGoalsAgainstPer90 / 1.1); // Facteur basé sur
la défense adverse
// Statistiques pour les corners, fautes, etc.
const homeCornerRate = extractStat(homeTeamData, 'CK', 5) /
extractStat(homeTeamData, '90s', 38);
const awayCornerRate = extractStat(awayTeamData, 'CK', 4) /
extractStat(awayTeamData, '90s', 38);
// Calcul des taux de conversion
const homeGoalConversionRate = homeGoalsPer90 / homeShotsPer90 ||
0.1;
const awayGoalConversionRate = awayGoalsPer90 / awayShotsPer90 ||
0.09;
// Retourner l'objet avec toutes les statistiques préparées
return {
home: {
name: [Link],
baseXG: baseHomeXG,
goalsPer90: homeGoalsPer90,
shotsPer90: homeShotsPer90 * (1 - attackReduction),
shotsOnTargetRate: homeShotsOnTargetRate *
passAccuracyFactor,
goalConversionRate: homeGoalConversionRate,
cornerRate: homeCornerRate,
possession: adjustedHomePossession
},
away: {
name: [Link],
baseXG: baseAwayXG,
goalsPer90: awayGoalsPer90,
shotsPer90: awayShotsPer90 * (1 - attackReduction),
shotsOnTargetRate: awayShotsOnTargetRate *
passAccuracyFactor,
goalConversionRate: awayGoalConversionRate,
cornerRate: awayCornerRate,
possession: adjustedAwayPossession
},
matchFactors: {
homeAdvantage,
intensityFactor,
attackReduction,
passAccuracyFactor,
foulRate,
cardRate
}
};
}
// Simule un match unique en utilisant la méthode Monte Carlo
_simulateSingleMatch(teamStats, config) {
// Variables de suivi pour ce match
const matchStats = {
homeGoals: 0,
awayGoals: 0,
homeXG: 0,
awayXG: 0,
homeShots: 0,
awayShots: 0,
homeShotsOnTarget: 0,
awayShotsOnTarget: 0,
homeCorners: 0,
awayCorners: 0,
homeFouls: 0,
awayFouls: 0,
homeYellowCards: 0,
awayYellowCards: 0,
homeRedCards: 0,
awayRedCards: 0,
minuteByMinute: []
};
// Calcul des Expected Goals ajusté avec variance
const homeXG = this._generateXGWithVariance([Link]);
const awayXG = this._generateXGWithVariance([Link]);
[Link] = homeXG;
[Link] = awayXG;
// Distribution poissonnienne de buts basée sur xG
[Link] = this._generatePoissonRandom(homeXG);
[Link] = this._generatePoissonRandom(awayXG);
// Simulation des tirs
[Link] = [Link]([Link].shotsPer90 *
(1 + this._randomVariance(0.2))); // Variance de 20%
[Link] = [Link]([Link].shotsPer90 *
(1 + this._randomVariance(0.2)));
// Tirs cadrés
[Link] = [Link](
[Link],
[Link]([Link] *
[Link] *
(1 + this._randomVariance(0.1)))
);
[Link] = [Link](
[Link],
[Link]([Link] *
[Link] *
(1 + this._randomVariance(0.1)))
);
// Corners
const cornerFactor = 1 + ([Link] +
[Link] - 22) / 50;
[Link] = [Link](0,
[Link]([Link] * cornerFactor *
(1 + this._randomVariance(0.3))));
[Link] = [Link](0,
[Link]([Link] * cornerFactor *
(1 + this._randomVariance(0.3))));
// Fautes et cartons
const foulFactor = [Link] *
[Link];
[Link] = [Link](8 * foulFactor * (1 +
this._randomVariance(0.25)));
[Link] = [Link](10 * foulFactor * (1 +
this._randomVariance(0.25))); // Équipe extérieure commet habituellement plus de
fautes
const cardFactor = [Link];
[Link] =
this._generateBinomialRandom([Link], 0.12 * cardFactor);
[Link] =
this._generateBinomialRandom([Link], 0.15 * cardFactor);
[Link] =
this._generateBinomialRandom([Link], 0.05 * cardFactor);
[Link] =
this._generateBinomialRandom([Link], 0.05 * cardFactor);
// Simulation minute par minute (si détaillée est demandée)
if ([Link]) {
[Link] = this._simulateMatchFlow(
teamStats,
[Link],
[Link],
[Link],
[Link],
[Link],
[Link],
[Link],
[Link],
[Link],
[Link],
[Link],
[Link]
);
}
return matchStats;
}
// Simulation détaillée du flux du match minute par minute
_simulateMatchFlow(teamStats, totalHomeGoals, totalAwayGoals,
totalHomeShots, totalAwayShots,
totalHomeCorners, totalAwayCorners, totalHomeFouls,
totalAwayFouls,
totalHomeYellowCards, totalAwayYellowCards,
totalHomeRedCards, totalAwayRedCards) {
const minuteByMinute = [];
const homeTeam = [Link];
const awayTeam = [Link];
// Préparer les événements à répartir dans le match
const events = {
homeGoals: this._createEventDistribution(totalHomeGoals),
awayGoals: this._createEventDistribution(totalAwayGoals),
homeShots: this._createEventDistribution(totalHomeShots,
totalHomeGoals),
awayShots: this._createEventDistribution(totalAwayShots,
totalAwayGoals),
homeCorners: this._createEventDistribution(totalHomeCorners),
awayCorners: this._createEventDistribution(totalAwayCorners),
homeFouls: this._createEventDistribution(totalHomeFouls),
awayFouls: this._createEventDistribution(totalAwayFouls),
homeYellowCards:
this._createEventDistribution(totalHomeYellowCards),
awayYellowCards:
this._createEventDistribution(totalAwayYellowCards),
homeRedCards: this._createEventDistribution(totalHomeRedCards),
awayRedCards: this._createEventDistribution(totalAwayRedCards)
};
// Indicateurs d'état du match
let currentScore = { home: 0, away: 0 };
let possession = 'home'; // Qui possède le ballon au départ
let momentum = 0; // -100 à 100, négatif = momentum à l'extérieur,
positif = momentum à domicile
let intensity = 50; // 0 à 100, augmente avec les buts, cartons,
etc.
// Minute par minute
for (let minute = 1; minute <= this.MATCH_MINUTES; minute++) {
// Événements spéciaux pour certaines minutes
if (minute === 1) {
[Link]({
minute,
type: 'kickoff',
text: `Le match entre ${homeTeam} et ${awayTeam}
commence!`
});
} else if (minute === 45) {
[Link]({
minute,
type: 'half-time',
text: `C'est la mi-temps. Score: ${[Link]}-$
{[Link]}.`
});
// Reset du momentum à la mi-temps
momentum = momentum * 0.5;
continue;
} else if (minute === 46) {
[Link]({
minute,
type: 'kickoff',
text: `La seconde mi-temps commence.`
});
} else if (minute === 90) {
[Link]({
minute,
type: 'full-time',
text: `Le match est terminé! Résultat final: $
{homeTeam} ${[Link]}-${[Link]} ${awayTeam}.`
});
}
// Changement de possession
if ([Link]() < 0.3) { // 30% de chance de changement
possession = possession === 'home' ? 'away' : 'home';
}
// Traitement des événements pour cette minute
const minuteEvents = [];
// Buts
if ([Link](minute)) {
[Link]++;
[Link]({
type: 'goal',
team: 'home',
text: `BUT! ${homeTeam} marque! Le score est maintenant
${[Link]}-${[Link]}.`
});
momentum += 20; // Boost du momentum pour l'équipe à
domicile
intensity += 15;
}
if ([Link](minute)) {
[Link]++;
[Link]({
type: 'goal',
team: 'away',
text: `BUT! ${awayTeam} marque! Le score est maintenant
${[Link]}-${[Link]}.`
});
momentum -= 20; // Boost du momentum pour l'équipe à
l'extérieur
intensity += 15;
}
// Tirs
if ([Link](minute) && !
[Link](minute)) {
[Link]({
type: 'shot',
team: 'home',
text: `Tir de ${homeTeam}!`
});
momentum += 5;
}
if ([Link](minute) && !
[Link](minute)) {
[Link]({
type: 'shot',
team: 'away',
text: `Tir de ${awayTeam}!`
});
momentum -= 5;
}
// Corners
if ([Link](minute)) {
[Link]({
type: 'corner',
team: 'home',
text: `Corner pour ${homeTeam}.`
});
momentum += 2;
}
if ([Link](minute)) {
[Link]({
type: 'corner',
team: 'away',
text: `Corner pour ${awayTeam}.`
});
momentum -= 2;
}
// Fautes et cartons
if ([Link](minute)) {
[Link]({
type: 'foul',
team: 'home',
text: `Faute commise par ${homeTeam}.`
});
momentum -= 1;
}
if ([Link](minute)) {
[Link]({
type: 'foul',
team: 'away',
text: `Faute commise par ${awayTeam}.`
});
momentum += 1;
}
if ([Link](minute)) {
[Link]({
type: 'yellow',
team: 'home',
text: `Carton jaune pour un joueur de ${homeTeam}!`
});
momentum -= 5;
intensity += 10;
}
if ([Link](minute)) {
[Link]({
type: 'yellow',
team: 'away',
text: `Carton jaune pour un joueur de ${awayTeam}!`
});
momentum += 5;
intensity += 10;
}
if ([Link](minute)) {
[Link]({
type: 'red',
team: 'home',
text: `CARTON ROUGE! Un joueur de ${homeTeam} est
expulsé!`
});
momentum -= 30;
intensity += 25;
}
if ([Link](minute)) {
[Link]({
type: 'red',
team: 'away',
text: `CARTON ROUGE! Un joueur de ${awayTeam} est
expulsé!`
});
momentum += 30;
intensity += 25;
}
// S'il y a des événements pour cette minute
if ([Link] > 0) {
[Link]({
minute,
events: minuteEvents,
possession,
momentum,
intensity,
score: { ...currentScore }
});
}
// Si aucun événement mais on veut quand même suivre l'état du
match
else if (minute % 5 === 0) { // Tous les 5 minutes
[Link]({
minute,
type: 'update',
possession,
momentum,
intensity,
score: { ...currentScore },
text: this._generateCommentary(minute, currentScore,
possession, momentum, intensity, homeTeam, awayTeam)
});
}
// Évolution naturelle du momentum (retour à l'équilibre)
momentum = momentum * 0.95;
// L'intensité augmente naturellement au fil du match
if (minute > 75) {
intensity = [Link](100, intensity + 1);
}
}
return minuteByMinute;
}
// Fonction pour générer un commentaire narratif basé sur l'état du
match
_generateCommentary(minute, score, possession, momentum, intensity,
homeTeam, awayTeam) {
const possessingTeam = possession === 'home' ? homeTeam : awayTeam;
const scoreDiff = [Link] - [Link];
const absScoreDiff = [Link](scoreDiff);
// Fin de match approche
if (minute >= 85) {
if (scoreDiff === 0) {
return `Le match est serré alors que nous approchons de la
fin du temps réglementaire.`;
} else if (absScoreDiff === 1) {
const leadingTeam = scoreDiff > 0 ? homeTeam : awayTeam;
const trailingTeam = scoreDiff > 0 ? awayTeam : homeTeam;
return `${leadingTeam} tient à son avance d'un but. $
{trailingTeam} pousse pour égaliser.`;
} else {
const leadingTeam = scoreDiff > 0 ? homeTeam : awayTeam;
return `${leadingTeam} semble avoir le match bien en main
avec une avance de ${absScoreDiff} buts.`;
}
}
// Mi-temps approche
if (minute >= 40 && minute < 45) {
return `Nous approchons de la mi-temps. Score: ${[Link]}-$
{[Link]}.`;
}
// Match équilibré ou dominé
if ([Link](momentum) < 20) {
return `Le jeu est équilibré, ${possessingTeam} en possession
du ballon.`;
} else {
const dominantTeam = momentum > 0 ? homeTeam : awayTeam;
return `${dominantTeam} domine cette phase de jeu.`;
}
}
// Création d'une distribution d'événements pour un match
_createEventDistribution(totalEvents, reservedMinutes = []) {
const eventMinutes = [];
const availableMinutes = [Link]({length: this.MATCH_MINUTES},
(_, i) => i + 1)
.filter(min => );
// Distribution spéciale pour les buts
if ([Link] === 0) { // Pour les buts
// Répartition par période avec plus de buts en seconde mi-
temps
const firstHalfEvents = [Link](totalEvents * 0.4);
const secondHalfEvents = totalEvents - firstHalfEvents;
// Première mi-temps
this._distributeEventsInRange(eventMinutes, firstHalfEvents, 1,
45);
// Seconde mi-temps
this._distributeEventsInRange(eventMinutes, secondHalfEvents,
46, 90);
} else {
// Distribution simple pour d'autres événements
for (let i = 0; i < totalEvents; i++) {
if ([Link] === 0) break;
const randomIndex = [Link]([Link]() *
[Link]);
const minute = availableMinutes[randomIndex];
[Link](minute);
// Retirer la minute pour éviter les doublons
[Link](randomIndex, 1);
}
}
return [Link]((a, b) => a - b);
}
// Distribue des événements dans une période du match
_distributeEventsInRange(eventMinutes, count, start, end) {
const minutesInRange = end - start + 1;
// Créer une distribution de probabilité favorisant les dernières
minutes
const probabilities = Array(minutesInRange).fill(0);
// Fonction de distribution: augmentation progressive de la
probabilité
for (let i = 0; i < minutesInRange; i++) {
// La formule ci-dessous donne une augmentation progressive
// avec un pic dans les 10 dernières minutes de chaque période
const minutePosition = i / minutesInRange; // 0 à 1
probabilities[i] = 0.5 + 0.5 * [Link](minutePosition,
1.5); // Plus de poids vers la fin
// Bonus pour les 5 dernières minutes de chaque période
if (i >= minutesInRange - 5) {
probabilities[i] *= 1.5;
}
}
// Normaliser les probabilités
const sum = [Link]((a, b) => a + b, 0);
for (let i = 0; i < [Link]; i++) {
probabilities[i] /= sum;
}
// Sélectionner les minutes selon cette distribution
for (let i = 0; i < count; i++) {
let selectedMinute = -1;
const rand = [Link]();
let cumulative = 0;
for (let j = 0; j < [Link]; j++) {
cumulative += probabilities[j];
if (rand < cumulative) {
selectedMinute = start + j;
break;
}
}
if (selectedMinute !== -1) {
[Link](selectedMinute);
}
}
}
// Agrège les résultats d'un match simulé dans les résultats globaux
_aggregateResults(results, matchResult) {
// Résultat du match
if ([Link] > [Link]) {
[Link]++;
} else if ([Link] < [Link]) {
[Link]++;
} else {
[Link]++;
}
// Statistiques globales
[Link] += [Link];
[Link] += [Link];
[Link] += [Link];
[Link] += [Link];
// Statistiques d'événements
[Link] += [Link];
[Link] += [Link];
[Link] += [Link];
[Link] += [Link];
[Link] += [Link];
[Link] += [Link];
[Link] += [Link];
[Link] += [Link];
[Link] += [Link];
[Link] += [Link];
[Link] += [Link];
[Link] += [Link];
// Les deux équipes marquent
if ([Link] > 0 && [Link] > 0) {
[Link]++;
}
// Clean sheets
if ([Link] === 0) {
[Link]++;
}
if ([Link] === 0) {
[Link]++;
}
// Over/Under
const totalGoals = [Link] + [Link];
if (totalGoals > 0.5) results.over05++;
if (totalGoals > 1.5) results.over15++;
if (totalGoals > 2.5) results.over25++;
if (totalGoals > 3.5) results.over35++;
// Score exact
const scoreKey = `${[Link]}-$
{[Link]}`;
[Link][scoreKey] = ([Link][scoreKey] ||
0) + 1;
}
// Traite les résultats de simulation pour obtenir les probabilités
finales
_processResults(results, simulationCount) {
// Scores exacts triés par fréquence
const sortedExactScores = [Link]([Link])
.map(([score, count]) => ({
score,
count,
probability: (count / simulationCount) * 100
}))
.sort((a, b) => [Link] - [Link]);
// Extraction des 10 scores les plus probables
const topScores = [Link](0, 10);
return {
matchProbabilities: {
homeWin: ([Link] / simulationCount) * 100,
draw: ([Link] / simulationCount) * 100,
awayWin: ([Link] / simulationCount) * 100
},
goalExpectancy: {
home: [Link] / simulationCount,
away: [Link] / simulationCount,
total: ([Link] + [Link]) /
simulationCount
},
xG: {
home: [Link] / simulationCount,
away: [Link] / simulationCount
},
btts: {
yes: ([Link] / simulationCount) * 100,
no: 100 - ([Link] / simulationCount) * 100
},
cleanSheet: {
home: ([Link] / simulationCount) * 100,
away: ([Link] / simulationCount) * 100
},
overUnder: {
over05: (results.over05 / simulationCount) * 100,
over15: (results.over15 / simulationCount) * 100,
over25: (results.over25 / simulationCount) * 100,
over35: (results.over35 / simulationCount) * 100,
under05: 100 - (results.over05 / simulationCount) * 100,
under15: 100 - (results.over15 / simulationCount) * 100,
under25: 100 - (results.over25 / simulationCount) * 100,
under35: 100 - (results.over35 / simulationCount) * 100
},
exactScores: topScores,
eventStats: {
shots: {
home: [Link] / simulationCount,
away: [Link] / simulationCount
},
shotsOnTarget: {
home: [Link] /
simulationCount,
away: [Link] /
simulationCount
},
corners: {
home: [Link] / simulationCount,
away: [Link] / simulationCount
},
fouls: {
home: [Link] / simulationCount,
away: [Link] / simulationCount
},
cards: {
homeYellow: [Link] /
simulationCount,
awayYellow: [Link] /
simulationCount,
homeRed: [Link] /
simulationCount,
awayRed: [Link] / simulationCount
}
}
};
}
// Génère une valeur d'expected goals avec une variance
_generateXGWithVariance(baseXG) {
return baseXG * (1 +
this._randomVariance(this.DEFAULT_GOAL_VARIANCE));
}
// Génère un nombre aléatoire suivant une distribution de Poisson
_generatePoissonRandom(lambda) {
const L = [Link](-lambda);
let k = 0;
let p = 1;
do {
k++;
p *= [Link]();
} while (p > L);
return k - 1;
}
// Génère un nombre aléatoire suivant une distribution binomiale
_generateBinomialRandom(n, p) {
let successes = 0;
for (let i = 0; i < n; i++) {
if ([Link]() < p) {
successes++;
}
}
return successes;
}
// Génère une variance aléatoire centrée sur 0
_randomVariance(magnitude) {
return ([Link]() * 2 - 1) * magnitude;
}
}
// ================================================
// INTÉGRATION AVEC L'INTERFACE UTILISATEUR
// ================================================
// Mise à jour de l'état global de l'application pour inclure le moteur de
simulation
[Link]('DOMContentLoaded', function() {
// Initialisation précédente
initTheme();
initTabs();
initEventListeners();
loadStoredData();
// Initialiser le moteur de simulation
[Link] = new FootballSimulationEngine();
[Link]("Moteur de simulation Monte Carlo initialisé");
});
// Mise à jour de la fonction startSimulation pour utiliser le moteur Monte
Carlo
function startSimulation() {
const homeTeam = [Link]('homeTeam').value;
const awayTeam = [Link]('awayTeam').value;
if (!homeTeam || !awayTeam) {
alert('Veuillez sélectionner les deux équipes pour la
simulation.');
return;
}
if (homeTeam === awayTeam) {
alert('Veuillez sélectionner deux équipes différentes.');
return;
}
// Récupération des données d'équipes
const homeTeamData = [Link](team => [Link] ===
homeTeam);
const awayTeamData = [Link](team => [Link] ===
awayTeam);
if (!homeTeamData || !awayTeamData) {
alert('Données insuffisantes pour ces équipes. Importez des données
complètes.');
return;
}
const simulationCount =
parseInt([Link]('simulationCount').value);
const matchType = [Link]('matchType').value;
const weather = [Link]('weatherCondition').value;
const importance = [Link]('matchImportance').value;
const refStyle = [Link]('refStyle').value;
// Masquer le message d'absence de simulation
[Link]('noSimulationData').[Link]('hidden');
[Link]('noLiveMatch').[Link]('hidden');
// Afficher l'indicateur de progression
[Link]('simulationInProgress').[Link]('hidden');
[Link]('simulationResults').[Link]('hidden');
// Réinitialiser la barre de progression
const progressBar = [Link]('.simulation-progress-bar');
[Link] = '0%';
[Link]('simulationProgressText').textContent = `0 / $
{simulationCount} simulations (0%)`;
// Désactiver le bouton de démarrage
[Link]('startSimulationBtn').disabled = true;
// Configuration de la simulation
const simulationConfig = {
homeTeam,
awayTeam,
homeTeamData,
awayTeamData,
matchType,
weather,
importance,
refStyle,
simulationCount,
detailedSimulation: true, // Pour la simulation minute par minute
onProgress: (progress, currentSimulation, totalSimulations) => {
// Mise à jour de la barre de progression
[Link] = `${progress}%`;
[Link]('simulationProgressText').textContent =
`${currentSimulation} / ${totalSimulations} simulations ($
{[Link](progress)}%)`;
}
};
// Lancer la simulation asynchrone
setTimeout(() => {
try {
// Exécuter la simulation
const results =
[Link](simulationConfig);
// Enregistrer les résultats dans l'état de l'application
[Link] = results;
// Masquer la progression et afficher les résultats
[Link]('simulationInProgress').[Link]('hidden');
[Link]('simulationResults').[Link]('hidden');
[Link]('liveMatchContainer').[Link]('hidden');
// Réactiver le bouton de démarrage
[Link]('startSimulationBtn').disabled = false;
// Afficher les résultats
displaySimulationResults(homeTeam, awayTeam, simulationCount,
results);
// Lancer la simulation de match en direct basée sur les
résultats
startLiveMatchSimulation(homeTeam, awayTeam, results);
// Ajouter à l'historique des simulations
const simulationDate = new Date();
[Link]({
date: simulationDate,
homeTeam,
awayTeam,
homeScore: [Link]([Link]),
awayScore: [Link]([Link]),
homeWinProb: [Link],
drawProb: [Link],
awayWinProb: [Link],
bttsProb: [Link],
over25Prob: [Link].over25,
simulationCount,
fullResults: results
});
// Limiter l'historique à 50 simulations
if ([Link] > 50) {
[Link] =
[Link](0, 50);
}
// Sauvegarder dans localStorage
[Link]('simulationHistory', [Link](
[Link](item => {
// Faire une copie sans les résultats complets pour
économiser de l'espace
const { fullResults, ...rest } = item;
return rest;
})
));
// Mettre à jour le compteur de matchs
updateMatchCount();
} catch (error) {
[Link]("Erreur pendant la simulation:", error);
[Link]('simulationInProgress').[Link]('hidden');
[Link]('startSimulationBtn').disabled = false;
alert(`Erreur pendant la simulation: ${[Link]}`);
}
}, 100);
}
// Fonction améliorée pour afficher les résultats de simulation
function displaySimulationResults(homeTeam, awayTeam, simulationCount,
results) {
// Mise à jour de l'interface avec les données réelles de simulation
[Link]('homeTeamName').textContent = homeTeam;
[Link]('awayTeamName').textContent = awayTeam;
// Calcul du score le plus probable
const mostLikelyScore = [Link][0];
const [homeScore, awayScore] =
[Link]('-').map(Number);
[Link]('homeTeamScore').textContent = homeScore;
[Link]('awayTeamScore').textContent = awayScore;
[Link]('homeTeamxG').textContent = `xG: $
{[Link](2)}`;
[Link]('awayTeamxG').textContent = `xG: $
{[Link](2)}`;
// Probabilités de résultat
[Link]('homeWinProb').textContent = `$
{[Link](1)}%`;
[Link]('drawProb').textContent = `$
{[Link](1)}%`;
[Link]('awayWinProb').textContent = `$
{[Link](1)}%`;
// Autres statistiques
[Link]('bttsProb').textContent = `$
{[Link](1)}%`;
[Link]('over25Prob').textContent = `$
{[Link](1)}%`;
[Link]('homeCleanSheetProb').textContent = `$
{[Link](1)}%`;
[Link]('awayCleanSheetProb').textContent = `$
{[Link](1)}%`;
[Link]('matchSimulationCount').textContent = `$
{[Link]()} simulations`;
// Affichage des scores probables
const topScoresDiv = [Link]('topScores');
[Link] = '';
[Link](score => {
const scoreElement = [Link]('div');
[Link] = 'bg-gray-100 p-2 rounded text-center';
[Link] = `
<div class="font-medium">${[Link]}</div>
<div class="text-xs text-gray-500">$
{[Link](1)}%</div>
`;
[Link](scoreElement);
});
}
// Fonction améliorée pour la simulation en direct
function startLiveMatchSimulation(homeTeam, awayTeam, results) {
// Réinitialiser l'interface
[Link]('liveHomeTeam').textContent = homeTeam;
[Link]('liveAwayTeam').textContent = awayTeam;
[Link]('liveHomeScore').textContent = '0';
[Link]('liveAwayScore').textContent = '0';
[Link]('liveMatchTime').textContent = "0'";
[Link]('homeShots').textContent = '0';
[Link]('awayShots').textContent = '0';
[Link]('homeShotsOnTarget').textContent = '0';
[Link]('awayShotsOnTarget').textContent = '0';
[Link]('homeCorners').textContent = '0';
[Link]('awayCorners').textContent = '0';
[Link]('homeFouls').textContent = '0';
[Link]('awayFouls').textContent = '0';
[Link]('homeYellowCards').textContent = '0';
[Link]('awayYellowCards').textContent = '0';
// Réinitialiser les commentaires
const commentaryDiv = [Link]('matchCommentary');
[Link] = '';
// Ajouter le commentaire de début de match
addMatchComment('début', `Le match entre ${homeTeam} et ${awayTeam}
commence!`);
// Générer un match simulé
// Pour la démonstration, nous allons générer un score qui correspond
approximativement aux résultats
const expectedHomeGoals = [Link];
const expectedAwayGoals = [Link];
// Calculer le nombre approximatif de tirs
const expectedHomeShots = [Link];
const expectedAwayShots = [Link];
const expectedHomeShotsOnTarget =
[Link];
const expectedAwayShotsOnTarget =
[Link];
// Calculer le nombre approximatif d'autres événements
const expectedHomeCorners = [Link];
const expectedAwayCorners = [Link];
const expectedHomeFouls = [Link];
const expectedAwayFouls = [Link];
const expectedHomeYellowCards = [Link];
const expectedAwayYellowCards = [Link];
// Créer une série d'événements pour le match
// Format: [minute, type, équipe, détails]
const matchEvents = [];
// Ajouter les buts (distribués aléatoirement mais avec préférence pour
la fin des mi-temps)
const homeGoals = [Link](expectedHomeGoals);
const awayGoals = [Link](expectedAwayGoals);
addMatchEvents(matchEvents, homeGoals, 'goal', 'home', homeTeam,
awayTeam);
addMatchEvents(matchEvents, awayGoals, 'goal', 'away', homeTeam,
awayTeam);
// Ajouter les tirs (qui ne sont pas des buts)
const homeShots = [Link](0, [Link](expectedHomeShots) -
homeGoals);
const awayShots = [Link](0, [Link](expectedAwayShots) -
awayGoals);
addMatchEvents(matchEvents, homeShots, 'shot', 'home', homeTeam,
awayTeam);
addMatchEvents(matchEvents, awayShots, 'shot', 'away', homeTeam,
awayTeam);
// Ajouter les corners
addMatchEvents(matchEvents, [Link](expectedHomeCorners), 'corner',
'home', homeTeam, awayTeam);
addMatchEvents(matchEvents, [Link](expectedAwayCorners), 'corner',
'away', homeTeam, awayTeam);
// Ajouter les fautes
addMatchEvents(matchEvents, [Link](expectedHomeFouls), 'foul',
'home', homeTeam, awayTeam);
addMatchEvents(matchEvents, [Link](expectedAwayFouls), 'foul',
'away', homeTeam, awayTeam);
// Ajouter les cartons jaunes
addMatchEvents(matchEvents, [Link](expectedHomeYellowCards),
'yellow', 'home', homeTeam, awayTeam);
addMatchEvents(matchEvents, [Link](expectedAwayYellowCards),
'yellow', 'away', homeTeam, awayTeam);
// Trier les événements par minute
[Link]((a, b) => a[0] - b[0]);
// Variables pour suivre l'état du match
const matchData = {
currentMinute: 0,
homeScore: 0,
awayScore: 0,
homeShots: 0,
awayShots: 0,
homeShotsOnTarget: 0,
awayShotsOnTarget: 0,
homeCorners: 0,
awayCorners: 0,
homeFouls: 0,
awayFouls: 0,
homeYellowCards: 0,
awayYellowCards: 0,
homeRedCards: 0,
awayRedCards: 0,
homePossession: 50,
awayPossession: 50
};
// Arrêter toute simulation précédente
if ([Link]) {
clearInterval([Link]);
}
// Simuler le déroulement du match
[Link] = true;
[Link] = 1;
[Link] = false;
// Chaque intervalle représente une minute de jeu
[Link] = setInterval(() => {
if ([Link]) return;
[Link]++;
// Mettre à jour le temps
[Link]('liveMatchTime').textContent = `$
{[Link]}'`;
// Traiter les événements de cette minute
const minuteEvents = [Link](event => event[0] ===
[Link]);
for (const event of minuteEvents) {
const [minute, type, team, details] = event;
if (type === 'goal') {
if (team === 'home') {
[Link]++;
[Link]++;
[Link]++;
} else {
[Link]++;
[Link]++;
[Link]++;
}
} else if (type === 'shot') {
if (team === 'home') {
[Link]++;
// 33% de chance que le tir soit cadré
if ([Link]() < 0.33) {
[Link]++;
}
} else {
[Link]++;
// 33% de chance que le tir soit cadré
if ([Link]() < 0.33) {
[Link]++;
}
}
} else if (type === 'corner') {
if (team === 'home') {
[Link]++;
} else {
[Link]++;
}
} else if (type === 'foul') {
if (team === 'home') {
[Link]++;
} else {
[Link]++;
}
} else if (type === 'yellow') {
if (team === 'home') {
[Link]++;
} else {
[Link]++;
}
} else if (type === 'red') {
if (team === 'home') {
[Link]++;
} else {
[Link]++;
}
}
// Ajouter un commentaire pour cet événement
addEventCommentary([Link], type, team,
details, matchData);
}
// Simuler la possession
const possessionChange = [Link]() * 6 - 3; // -3 à 3
[Link] += possessionChange;
[Link] = [Link](30, [Link](70,
[Link])); // Limiter entre 30% et 70%
[Link] = 100 - [Link];
// Mettre à jour l'affichage de la possession
[Link]('homePossession').textContent =
[Link]([Link]);
[Link]('awayPossession').textContent =
[Link]([Link]);
[Link]('homePossessionBar').[Link] = `$
{[Link]}%`;
[Link]('awayPossessionBar').[Link] = `$
{[Link]}%`;
// Mettre à jour l'interface
updateLiveMatchInterface(matchData);
// Ajouter des commentaires génériques pour certaines minutes
if ([Link] === 1) {
// Le commentaire de début a déjà été ajouté
} else if ([Link] === 45) {
addMatchComment([Link], `C'est la fin de la
première mi-temps ! Score : ${[Link]}-${[Link]}`);
} else if ([Link] === 46) {
addMatchComment([Link], `Début de la seconde
mi-temps !`);
} else if ([Link] === 90) {
addMatchComment([Link], `C'est la fin du match
! Score final : ${[Link]}-${[Link]}`);
endMatchSimulation();
} else if ([Link] % 15 === 0) {
// Commentaire toutes les 15 minutes si aucun événement
if ([Link] === 0) {
addMatchComment([Link], `$
{[Link]}' - Le score est de ${[Link]}-$
{[Link]}.`);
}
}
// Vérifier si le match est terminé
if ([Link] >= 90) {
endMatchSimulation();
}
}, 1000 / [Link]);
}
// Fonction pour ajouter des événements au match
function addMatchEvents(matchEvents, count, type, team, homeTeam, awayTeam)
{
// Distribution des minutes pour les événements
const availableMinutes = [];
// Différentes distributions selon le type d'événement
if (type === 'goal') {
// Distribution spéciale pour les buts avec plus d'occurrences en
fin de mi-temps
for (let i = 1; i <= 90; i++) {
// Pondération plus élevée pour les 10 dernières minutes de
chaque mi-temps
let weight = 1;
if ((i >= 36 && i <= 45) || (i >= 80 && i <= 90)) {
weight = 2.5;
} else if ((i >= 26 && i <= 35) || (i >= 70 && i <= 79)) {
weight = 1.5;
}
// Ajouter les minutes selon leur pondération
for (let j = 0; j < weight; j++) {
[Link](i);
}
}
} else {
// Distribution uniforme pour les autres types d'événements
for (let i = 1; i <= 90; i++) {
[Link](i);
}
}
// Générer les événements
for (let i = 0; i < count; i++) {
const randomIndex = [Link]([Link]() *
[Link]);
const minute = availableMinutes[randomIndex];
// Détails spécifiques selon le type d'événement
let details = '';
if (type === 'goal') {
details = team === 'home' ? `BUT! ${homeTeam} marque!` : `BUT!
${awayTeam} marque!`;
} else if (type === 'shot') {
details = team === 'home' ? `Tir de ${homeTeam}!` : `Tir de $
{awayTeam}!`;
} else if (type === 'corner') {
details = team === 'home' ? `Corner pour ${homeTeam}.` :
`Corner pour ${awayTeam}.`;
} else if (type === 'foul') {
details = team === 'home' ? `Faute commise par ${homeTeam}.` :
`Faute commise par ${awayTeam}.`;
} else if (type === 'yellow') {
details = team === 'home' ? `Carton jaune pour un joueur de $
{homeTeam}!` : `Carton jaune pour un joueur de ${awayTeam}!`;
} else if (type === 'red') {
details = team === 'home' ? `CARTON ROUGE! Un joueur de $
{homeTeam} est expulsé!` : `CARTON ROUGE! Un joueur de ${awayTeam} est expulsé!`;
}
[Link]([minute, type, team, details]);
// Retirer cette minute pour éviter trop d'événements au même
moment
[Link](randomIndex, 1);
// S'il n'y a plus de minutes disponibles, sortir de la boucle
if ([Link] === 0) break;
}
}
// Fonction pour ajouter un commentaire d'événement
function addEventCommentary(minute, type, team, details, matchData) {
if (type === 'goal') {
const score = team === 'home' ?
`${[Link]}-${[Link]}` :
`${[Link]}-${[Link]}`;
addMatchComment(minute, `${details} Le score est maintenant $
{score}.`, 'goal');
} else if (type === 'shot') {
addMatchComment(minute, details);
} else if (type === 'corner') {
addMatchComment(minute, details);
} else if (type === 'foul') {
addMatchComment(minute, details);
} else if (type === 'yellow') {
addMatchComment(minute, details, 'card');
} else if (type === 'red') {
addMatchComment(minute, details, 'red-card');
}
}