0% ont trouvé ce document utile (0 vote)
21 vues26 pages

Simulation Monte Carlo pour Football

Le document présente un moteur de simulation Monte Carlo pour le football, permettant de simuler des matchs en utilisant des statistiques d'équipes et des facteurs contextuels comme le type de match et les conditions météorologiques. Il inclut des méthodes pour exécuter des simulations, préparer les données des équipes, et générer des résultats détaillés tels que les buts, les tirs, et les fautes. La simulation est configurable et peut être exécutée plusieurs fois pour obtenir des résultats statistiques significatifs.

Transféré par

rubenboga04
Copyright
© All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats TXT, PDF, TXT ou lisez en ligne sur Scribd
0% ont trouvé ce document utile (0 vote)
21 vues26 pages

Simulation Monte Carlo pour Football

Le document présente un moteur de simulation Monte Carlo pour le football, permettant de simuler des matchs en utilisant des statistiques d'équipes et des facteurs contextuels comme le type de match et les conditions météorologiques. Il inclut des méthodes pour exécuter des simulations, préparer les données des équipes, et générer des résultats détaillés tels que les buts, les tirs, et les fautes. La simulation est configurable et peut être exécutée plusieurs fois pour obtenir des résultats statistiques significatifs.

Transféré par

rubenboga04
Copyright
© All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats TXT, PDF, TXT ou lisez en ligne sur Scribd

// ================================================

// 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 => ![Link](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');
}
}

Vous aimerez peut-être aussi