0% found this document useful (0 votes)
3 views115 pages

Text

The document is an HTML template for a football match simulator, featuring a responsive design with various UI components styled using CSS and Tailwind CSS. It includes sections for match statistics, commentary, and team categories, utilizing JavaScript libraries like Chart.js for data visualization. The template is designed to be visually appealing with a dark mode option and various interactive elements.

Uploaded by

rubenboga04
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
3 views115 pages

Text

The document is an HTML template for a football match simulator, featuring a responsive design with various UI components styled using CSS and Tailwind CSS. It includes sections for match statistics, commentary, and team categories, utilizing JavaScript libraries like Chart.js for data visualization. The template is designed to be visually appealing with a dark mode option and various interactive elements.

Uploaded by

rubenboga04
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
You are on page 1/ 115

<!

DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="theme-color" content="#5D5CDE">
<title>Simulateur Pro de Matchs de Football</title>
<link rel="manifest" href="#" id="manifestPlaceholder">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?
family=Montserrat:wght@400;500;600;700&family=Roboto:wght@300;400;500;700&display=s
wap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-
awesome/6.4.0/css/all.min.css">
<link rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/[email protected]/dist/tailwind.min.css">
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.min.js"></
script>
<style>
:root {
/* Couleurs de base */
--primary: #5D5CDE;
--primary-light: #7675EF;
--primary-dark: #3E3DB0;
--accent: #F39C12;
--accent-light: #F7B541;
--accent-dark: #E67E22;
--success: #2ECC71;
--danger: #E74C3C;
--warning: #F39C12;
--info: #3498DB;

/* Couleurs du thème clair */


--bg-main: #F8F9FA;
--bg-card: #FFFFFF;
--bg-input: #FFFFFF;
--text-primary: #181838;
--text-secondary: #5E5E8C;
--text-muted: #8E8EA9;
--border-color: #E2E2F0;
--shadow-color: rgba(0, 0, 0, 0.05);

/* Animations */
--transition-speed: 0.3s;

/* Rayons de bordure */
--border-radius-sm: 0.25rem;
--border-radius: 0.5rem;
--border-radius-lg: 1rem;
--border-radius-xl: 1.5rem;

/* Espacement */
--spacing-xs: 0.25rem;
--spacing-sm: 0.5rem;
--spacing-md: 1rem;
--spacing-lg: 1.5rem;
--spacing-xl: 2rem;
}
/* Thème sombre */
.dark {
--bg-main: #121220;
--bg-card: #1E1E30;
--bg-input: #252538;
--text-primary: #F7F7FC;
--text-secondary: #AEAECE;
--text-muted: #8E8EA9;
--border-color: #323252;
--shadow-color: rgba(0, 0, 0, 0.2);
}

* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

body {
font-family: 'Roboto', sans-serif;
background-color: var(--bg-main);
color: var(--text-primary);
transition: background-color var(--transition-speed), color var(--
transition-speed);
min-height: 100vh;
line-height: 1.6;
}

h1, h2, h3, h4, h5, h6 {


font-family: 'Montserrat', sans-serif;
font-weight: 600;
}

.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 var(--spacing-md);
}

.card {
background-color: var(--bg-card);
border-radius: var(--border-radius-lg);
padding: var(--spacing-lg);
margin-bottom: var(--spacing-lg);
box-shadow: 0 4px 16px var(--shadow-color);
border: 1px solid var(--border-color);
transition: transform 0.3s ease, box-shadow 0.3s ease;
}

.card:hover {
transform: translateY(-2px);
box-shadow: 0 8px 24px var(--shadow-color);
}

.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--spacing-md);
border-bottom: 1px solid var(--border-color);
padding-bottom: var(--spacing-md);
}

.card-title {
font-size: 1.25rem;
font-weight: 600;
color: var(--text-primary);
}

.card-body {
position: relative;
}

/* Formulaires */
.form-group {
margin-bottom: var(--spacing-md);
}

.form-group label {
display: block;
margin-bottom: var(--spacing-xs);
color: var(--text-secondary);
font-weight: 500;
font-size: 0.9rem;
}

.form-control {
width: 100%;
padding: var(--spacing-md);
border: 1px solid var(--border-color);
border-radius: var(--border-radius);
background-color: var(--bg-input);
color: var(--text-primary);
font-size: 1rem;
transition: border-color 0.2s ease, box-shadow 0.2s ease;
}

.form-control:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(93, 92, 222, 0.15);
}

.form-control::placeholder {
color: var(--text-muted);
}

select.form-control {
appearance: none;
background-image: url("data:image/svg+xml,%3Csvg
xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%238E8EA9'
viewBox='0 0 16 16'%3E%3Cpath d='M7.247 11.14L2.451 5.658C1.885 5.013 2.345 4 3.204
4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 12px center;
padding-right: 32px;
}
.form-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: var(--spacing-md);
}

.form-row {
display: flex;
gap: var(--spacing-md);
}

.form-row > * {
flex: 1;
}

.form-actions {
display: flex;
justify-content: flex-end;
gap: var(--spacing-md);
margin-top: var(--spacing-lg);
}

/* Boutons */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--spacing-sm);
padding: var(--spacing-md) var(--spacing-lg);
border-radius: var(--border-radius);
font-weight: 500;
cursor: pointer;
transition: all var(--transition-speed);
text-align: center;
border: none;
font-size: 0.95rem;
}

.btn-primary {
background-color: var(--primary);
color: white;
box-shadow: 0 4px 6px rgba(93, 92, 222, 0.25);
}

.btn-primary:hover {
background-color: var(--primary-light);
box-shadow: 0 6px 8px rgba(93, 92, 222, 0.3);
}

.btn-accent {
background-color: var(--accent);
color: white;
box-shadow: 0 4px 6px rgba(243, 156, 18, 0.25);
}

.btn-accent:hover {
background-color: var(--accent-light);
box-shadow: 0 6px 8px rgba(243, 156, 18, 0.3);
}

.btn-secondary {
background-color: var(--bg-card);
color: var(--text-secondary);
border: 1px solid var(--border-color);
}

.btn-secondary:hover {
background-color: var(--border-color);
color: var(--text-primary);
}

.btn-success {
background-color: var(--success);
color: white;
box-shadow: 0 4px 6px rgba(46, 204, 113, 0.25);
}

.btn-success:hover {
background-color: var(--success);
box-shadow: 0 6px 8px rgba(46, 204, 113, 0.3);
}

.btn-danger {
background-color: var(--danger);
color: white;
box-shadow: 0 4px 6px rgba(231, 76, 60, 0.25);
}

.btn-danger:hover {
background-color: var(--danger);
box-shadow: 0 6px 8px rgba(231, 76, 60, 0.3);
}

.btn-sm {
padding: var(--spacing-sm) var(--spacing-md);
font-size: 0.85rem;
}

.btn-lg {
padding: var(--spacing-lg) var(--spacing-xl);
font-size: 1.1rem;
}

/* Barre de vitesse */
.speed-control {
display: flex;
align-items: center;
gap: var(--spacing-md);
margin-bottom: var(--spacing-md);
}

.speed-label {
font-weight: 500;
color: var(--text-secondary);
}

.speed-options {
display: flex;
gap: var(--spacing-xs);
}

.speed-option {
padding: var(--spacing-xs) var(--spacing-sm);
border-radius: var(--border-radius-sm);
background-color: var(--bg-card);
border: 1px solid var(--border-color);
cursor: pointer;
transition: all var(--transition-speed);
}

.speed-option:hover {
border-color: var(--primary);
color: var(--primary);
}

.speed-option.active {
background-color: var(--primary);
color: white;
border-color: var(--primary);
}

/* Match Style */
.match-field {
background-color: #1b5e20;
border-radius: var(--border-radius);
padding: var(--spacing-xl);
margin-bottom: var(--spacing-lg);
position: relative;
min-height: 200px;
color: white;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
overflow: hidden;
}

.match-field-lines {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border: 4px solid rgba(255, 255, 255, 0.3);
pointer-events: none;
}

.match-field-center {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 60px;
height: 60px;
border-radius: 50%;
border: 4px solid rgba(255, 255, 255, 0.3);
pointer-events: none;
}
.match-field-center::before {
content: '';
position: absolute;
top: 50%;
left: -100px;
right: -100px;
height: 4px;
background-color: rgba(255, 255, 255, 0.3);
transform: translateY(-50%);
}

.match-scoreboard {
background-color: var(--bg-card);
border-radius: var(--border-radius);
padding: var(--spacing-lg);
text-align: center;
box-shadow: 0 4px 16px var(--shadow-color);
margin-bottom: var(--spacing-lg);
}

.match-teams {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--spacing-md);
}

.match-team {
flex: 1;
text-align: center;
}

.match-score {
font-size: 2.5rem;
font-weight: 700;
padding: 0 var(--spacing-md);
}

.match-time {
font-size: 1.2rem;
font-weight: 600;
color: var(--primary);
margin-bottom: var(--spacing-sm);
}

.match-stats {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: var(--spacing-sm);
margin-top: var(--spacing-lg);
padding-top: var(--spacing-md);
border-top: 1px solid var(--border-color);
}

.match-stat {
text-align: center;
}

.stat-label {
font-size: 0.8rem;
color: var(--text-secondary);
margin-bottom: var(--spacing-xs);
}

.stat-values {
display: flex;
justify-content: space-between;
align-items: center;
}

.stat-value {
font-weight: 600;
flex: 1;
}

.stat-bar {
height: 6px;
background-color: var(--border-color);
border-radius: 3px;
margin-top: var(--spacing-xs);
position: relative;
overflow: hidden;
}

.stat-fill {
position: absolute;
top: 0;
left: 0;
height: 100%;
background-color: var(--primary);
transition: width 0.5s ease-out;
}

/* Match Commentary */
.match-commentary {
background-color: var(--bg-card);
border-radius: var(--border-radius);
padding: var(--spacing-md);
box-shadow: 0 4px 16px var(--shadow-color);
margin-bottom: var(--spacing-lg);
height: 300px;
overflow-y: auto;
}

.commentary-title {
font-size: 1.1rem;
font-weight: 600;
margin-bottom: var(--spacing-md);
padding-bottom: var(--spacing-sm);
border-bottom: 1px solid var(--border-color);
}

.commentary-list {
list-style: none;
}

.commentary-item {
padding: var(--spacing-sm) 0;
border-bottom: 1px dashed var(--border-color);
}

.commentary-time {
font-weight: 600;
color: var(--primary);
}

/* Badges */
.badge {
display: inline-block;
padding: var(--spacing-xs) var(--spacing-sm);
border-radius: var(--border-radius-sm);
font-size: 0.75rem;
font-weight: 500;
}

.badge-primary {
background-color: rgba(93, 92, 222, 0.1);
color: var(--primary);
}

.badge-success {
background-color: rgba(46, 204, 113, 0.1);
color: var(--success);
}

.badge-danger {
background-color: rgba(231, 76, 60, 0.1);
color: var(--danger);
}

.badge-warning {
background-color: rgba(243, 156, 18, 0.1);
color: var(--warning);
}

/* Team Category */
.team-category {
padding: var(--spacing-xs) var(--spacing-sm);
border-radius: var(--border-radius-sm);
font-size: 0.75rem;
font-weight: 500;
display: inline-block;
margin-top: var(--spacing-xs);
}

.category-grande {
background-color: rgba(46, 204, 113, 0.1);
color: var(--success);
}

.category-moyenne {
background-color: rgba(243, 156, 18, 0.1);
color: var(--warning);
}

.category-petite {
background-color: rgba(231, 76, 60, 0.1);
color: var(--danger);
}

/* Team Styles */
.team-style {
padding: var(--spacing-xs) var(--spacing-sm);
border-radius: var(--border-radius-sm);
font-size: 0.75rem;
font-weight: 500;
display: inline-block;
margin-top: var(--spacing-xs);
}

.style-offensive {
background-color: rgba(231, 76, 60, 0.1);
color: var(--danger);
}

.style-balanced {
background-color: rgba(243, 156, 18, 0.1);
color: var(--warning);
}

.style-defensive {
background-color: rgba(46, 204, 113, 0.1);
color: var(--success);
}

/* Toggle */
.toggle-container {
display: flex;
align-items: center;
gap: var(--spacing-sm);
}

.toggle-switch {
position: relative;
display: inline-block;
width: 50px;
height: 26px;
}

.toggle-switch input {
opacity: 0;
width: 0;
height: 0;
}

.toggle-slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: var(--border-color);
transition: .4s;
border-radius: 26px;
}
.toggle-slider:before {
position: absolute;
content: "";
height: 20px;
width: 20px;
left: 3px;
bottom: 3px;
background-color: white;
transition: .4s;
border-radius: 50%;
}

input:checked + .toggle-slider {
background-color: var(--primary);
}

input:checked + .toggle-slider:before {
transform: translateX(24px);
}

/* Utilitaires */
.mb-1 { margin-bottom: var(--spacing-xs); }
.mb-2 { margin-bottom: var(--spacing-sm); }
.mb-3 { margin-bottom: var(--spacing-md); }
.mb-4 { margin-bottom: var(--spacing-lg); }
.mb-5 { margin-bottom: var(--spacing-xl); }

.mt-1 { margin-top: var(--spacing-xs); }


.mt-2 { margin-top: var(--spacing-sm); }
.mt-3 { margin-top: var(--spacing-md); }
.mt-4 { margin-top: var(--spacing-lg); }
.mt-5 { margin-top: var(--spacing-xl); }

.flex { display: flex; }


.flex-col { flex-direction: column; }
.justify-between { justify-content: space-between; }
.items-center { align-items: center; }
.gap-1 { gap: var(--spacing-xs); }
.gap-2 { gap: var(--spacing-sm); }
.gap-3 { gap: var(--spacing-md); }

.text-center { text-align: center; }


.font-bold { font-weight: 700; }
.text-sm { font-size: 0.85rem; }
.text-lg { font-size: 1.1rem; }
.text-xl { font-size: 1.25rem; }
.text-2xl { font-size: 1.5rem; }

.text-success { color: var(--success); }


.text-danger { color: var(--danger); }
.text-warning { color: var(--warning); }
.text-muted { color: var(--text-muted); }

/* Animations */
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}

@keyframes pulse {
0% {
transform: scale(0.95);
box-shadow: 0 0 0 0 rgba(93, 92, 222, 0.7);
}
70% {
transform: scale(1);
box-shadow: 0 0 0 5px rgba(93, 92, 222, 0);
}
100% {
transform: scale(0.95);
box-shadow: 0 0 0 0 rgba(93, 92, 222, 0);
}
}

/* Goal Animation */
.goal-animation {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.7);
display: flex;
justify-content: center;
align-items: center;
z-index: 100;
animation: goalFadeIn 0.3s forwards;
}

.goal-content {
padding: var(--spacing-xl);
background-color: rgba(93, 92, 222, 0.9);
border-radius: var(--border-radius-lg);
text-align: center;
color: white;
transform: scale(0.8);
animation: goalScale 0.5s forwards;
}

.goal-text {
font-size: 3rem;
font-weight: 700;
margin-bottom: var(--spacing-lg);
text-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
}

.goal-team {
font-size: 1.5rem;
font-weight: 600;
}
@keyframes goalFadeIn {
from { opacity: 0; }
to { opacity: 1; }
}

@keyframes goalScale {
from { transform: scale(0.8); }
to { transform: scale(1); }
}

/* Notifications */
#notifications {
position: fixed;
top: var(--spacing-lg);
right: var(--spacing-lg);
z-index: 1000;
display: flex;
flex-direction: column;
gap: var(--spacing-sm);
max-width: 350px;
}

.notification {
display: flex;
align-items: center;
gap: var(--spacing-md);
padding: var(--spacing-md);
background-color: var(--bg-card);
border-radius: var(--border-radius);
box-shadow: 0 4px 12px var(--shadow-color);
animation: slideIn 0.3s ease;
border-left: 4px solid var(--primary);
}

.notification.success {
border-color: var(--success);
}

.notification.error {
border-color: var(--danger);
}

.notification.warning {
border-color: var(--warning);
}

.notification.info {
border-color: var(--primary);
}

.notification i {
font-size: 1.2rem;
}

.notification.success i {
color: var(--success);
}

.notification.error i {
color: var(--danger);
}

.notification.warning i {
color: var(--warning);
}

.notification.info i {
color: var(--primary);
}

@keyframes slideIn {
from {
opacity: 0;
transform: translateX(30px);
}
to {
opacity: 1;
transform: translateX(0);
}
}

@keyframes fadeout {
from {
opacity: 1;
transform: translateX(0);
}
to {
opacity: 0;
transform: translateX(30px);
}
}

/* Match History */
.match-history-container {
margin-top: var(--spacing-xl);
}

.match-history-list {
max-height: 500px;
overflow-y: auto;
}

.match-history-item {
background-color: var(--bg-card);
border-radius: var(--border-radius);
padding: var(--spacing-md);
margin-bottom: var(--spacing-sm);
border: 1px solid var(--border-color);
transition: transform 0.2s ease;
}

.match-history-item:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px var(--shadow-color);
}

.match-result {
display: flex;
justify-content: space-between;
align-items: center;
}

.match-details {
padding-top: var(--spacing-sm);
margin-top: var(--spacing-sm);
border-top: 1px solid var(--border-color);
font-size: 0.9rem;
color: var(--text-secondary);
}

/* Switch sombre/clair */
.theme-switch {
position: fixed;
bottom: 20px;
right: 20px;
z-index: 100;
}

.theme-switch-button {
width: 40px;
height: 40px;
border-radius: 50%;
background-color: var(--bg-card);
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4px 8px var(--shadow-color);
cursor: pointer;
border: none;
color: var(--text-primary);
}

/* Import Data Card */


.import-data-card {
border: 2px dashed var(--border-color);
border-radius: var(--border-radius);
padding: var(--spacing-lg);
margin-bottom: var(--spacing-lg);
text-align: center;
}

.import-data-card textarea {
min-height: 120px;
resize: vertical;
}

/* Scheduled Matches */
.scheduled-matches {
margin-top: var(--spacing-lg);
}

.scheduled-match-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--spacing-md);
background-color: var(--bg-card);
border-radius: var(--border-radius);
margin-bottom: var(--spacing-sm);
border: 1px solid var(--border-color);
}

.scheduled-match-teams {
font-weight: 600;
}

.scheduled-match-time {
color: var(--text-secondary);
font-size: 0.9rem;
}

/* Nouveau sélecteur d'équipe avec badges */


.team-selector {
position: relative;
display: flex;
flex-direction: column;
}

.team-selector .selected-team {
display: flex;
align-items: center;
padding: var(--spacing-sm);
border: 1px solid var(--border-color);
background-color: var(--bg-input);
border-radius: var(--border-radius);
gap: var(--spacing-sm);
cursor: pointer;
}

.team-selector .team-list {
position: absolute;
top: 100%;
left: 0;
right: 0;
max-height: 250px;
overflow-y: auto;
background-color: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: var(--border-radius);
z-index: 10;
box-shadow: 0 4px 12px var(--shadow-color);
display: none;
}

.team-selector .team-item {
padding: var(--spacing-sm);
cursor: pointer;
transition: background-color 0.2s ease;
display: flex;
align-items: center;
}

.team-selector .team-item:hover {
background-color: rgba(93, 92, 222, 0.1);
}
.team-league-badge {
display: inline-flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
border-radius: 50%;
font-size: 0.75rem;
font-weight: 600;
margin-right: var(--spacing-sm);
}

.team-league-badge.L1 {
background-color: #0053a0;
color: white;
}

.team-league-badge.PL {
background-color: #3d185c;
color: white;
}

.team-league-badge.LL {
background-color: #ee8707;
color: white;
}

.team-league-badge.BL {
background-color: #d3010c;
color: white;
}

.team-league-badge.SA {
background-color: #008756;
color: white;
}

/* Onglets */
.tabs {
display: flex;
gap: 2px;
margin-bottom: var(--spacing-md);
border-bottom: 1px solid var(--border-color);
}

.tab {
padding: var(--spacing-sm) var(--spacing-lg);
cursor: pointer;
background-color: transparent;
border: none;
color: var(--text-secondary);
border-bottom: 2px solid transparent;
transition: all 0.2s ease;
font-weight: 500;
}

.tab:hover {
color: var(--primary);
}
.tab.active {
color: var(--primary);
border-bottom-color: var(--primary);
}

.tab-content {
display: none;
}

.tab-content.active {
display: block;
animation: fadeIn 0.3s ease;
}

/* Planification des matchs */


.datetime-picker {
display: flex;
gap: var(--spacing-md);
}

.datetime-picker input {
flex: 1;
}

.scheduled-matches-list {
max-height: 300px;
overflow-y: auto;
}

.scheduled-match-buttons {
display: flex;
gap: var(--spacing-sm);
}

/* Loader */
.loader-container {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
}

.loader {
width: 50px;
height: 50px;
border: 5px solid var(--bg-card);
border-radius: 50%;
border-top-color: var(--primary);
animation: spin 1s linear infinite;
}

@keyframes spin {
100% {
transform: rotate(360deg);
}
}

/* Style pour appareils mobiles */


@media (max-width: 768px) {
.container {
padding: 0 var(--spacing-sm);
}

.card {
padding: var(--spacing-md);
}

.match-scoreboard {
padding: var(--spacing-md);
}

.match-score {
font-size: 2rem;
}

.match-stats {
grid-template-columns: 1fr 1fr;
gap: var(--spacing-md);
}

.form-grid {
grid-template-columns: 1fr;
}

.form-row {
flex-direction: column;
}

.btn {
padding: var(--spacing-sm) var(--spacing-md);
}

.tabs {
overflow-x: auto;
padding-bottom: var(--spacing-xs);
}

.tab {
padding: var(--spacing-sm) var(--spacing-md);
white-space: nowrap;
}
}

/* Style pour très petits écrans */


@media (max-width: 480px) {
.match-teams {
flex-direction: column;
gap: var(--spacing-md);
}

.match-stats {
grid-template-columns: 1fr;
}

.datetime-picker {
flex-direction: column;
}
}
</style>
</head>
<body>
<div class="container mt-4 mb-5">
<header class="text-center mb-5">
<h1 class="text-2xl font-bold mb-2">Simulateur Pro de Matchs de
Football</h1>
<p class="text-muted">Simulez des matchs en temps réel avec analyses
statistiques avancées</p>
</header>

<div class="tabs">
<button class="tab active" data-tab="import-data">Importer
Données</button>
<button class="tab" data-tab="match-setup">Configuration Match</button>
<button class="tab" data-tab="scheduled-matches">Matchs
Planifiés</button>
<button class="tab" data-tab="match-history">Historique</button>
</div>

<div id="tab-import-data" class="tab-content active">


<div class="card">
<div class="card-header">
<h2 class="card-title">Importer les données d'équipes</h2>
</div>
<div class="card-body">
<p class="mb-3">Collez les données tabulaires des équipes ci-
dessous pour commencer (format avec tabs/tabulations).</p>

<div class="form-group">
<label for="dataInput">Données d'équipes</label>
<textarea id="dataInput" class="form-control"
placeholder="Collez vos données (format avec tabs/tabulations)"></textarea>
</div>

<div id="dataLoadedIndicator" class="mb-3 p-3 bg-success bg-


opacity-10 rounded" style="display: none;">
<div class="flex items-center">
<i class="fas fa-check-circle text-success mr-2"></i>
<div>
<span class="font-bold">Données chargées avec
succès</span>
<p class="text-sm mb-0"><span
id="teamsLoadedCount">0</span> équipes importées</p>
</div>
</div>
</div>

<div class="flex justify-end">


<button id="clearDataBtn" class="btn btn-secondary mr-3">
<i class="fas fa-trash"></i> Effacer données
</button>
<button id="loadDemoDataBtn" class="btn btn-primary">
<i class="fas fa-database"></i> Charger données de démo
</button>
</div>
</div>
</div>

<div class="card">
<div class="card-header">
<h2 class="card-title">Équipes importées</h2>
</div>
<div class="card-body">
<div class="form-group">
<input type="text" id="teamSearch" class="form-control"
placeholder="Rechercher une équipe...">
</div>

<div id="teamsContainer" class="grid grid-cols-1 md:grid-cols-2


lg:grid-cols-3 gap-3">
<!-- Les équipes seront ajoutées ici -->
<div class="text-center text-muted py-4">
<i class="fas fa-database fa-2x mb-2"></i>
<p>Aucune équipe importée</p>
<p class="text-sm">Importez des données pour
commencer</p>
</div>
</div>
</div>
</div>
</div>

<div id="tab-match-setup" class="tab-content">


<div class="card" id="matchSetupCard">
<div class="card-header">
<h2 class="card-title">Configuration du Match</h2>
</div>
<div class="card-body">
<form id="matchSetupForm">
<div class="form-grid">
<div class="form-group">
<label for="team1">Équipe Domicile</label>
<select id="team1" class="form-control" required>
<option value="">Sélectionner une
équipe</option>
<!-- Options générées dynamiquement -->
</select>
<div class="flex gap-2 mt-2">
<span id="team1Category" class="team-
category"></span>
<span id="team1Style"
class="team-style"></span>
</div>
</div>

<div class="form-group">
<label for="team2">Équipe Extérieur</label>
<select id="team2" class="form-control" required>
<option value="">Sélectionner une
équipe</option>
<!-- Options générées dynamiquement -->
</select>
<div class="flex gap-2 mt-2">
<span id="team2Category" class="team-
category"></span>
<span id="team2Style"
class="team-style"></span>
</div>
</div>
</div>

<div class="form-grid">
<div class="form-group">
<label for="matchType">Type de Match</label>
<select id="matchType" class="form-control"
required>
<option
value="Championnat">Championnat</option>
<option value="Coupe">Coupe</option>
<option value="Derby">Derby</option>
<option value="Amical">Amical</option>
</select>
</div>

<div class="form-group">
<label for="venue">Stade</label>
<select id="venue" class="form-control">
<option value="home">Domicile</option>
<option value="away">Extérieur</option>
<option value="neutral">Terrain neutre</option>
</select>
</div>
</div>

<div class="form-grid">
<div class="form-group">
<label for="weather">Conditions Météo</label>
<select id="weather" class="form-control">
<option value="clear">Ensoleillé</option>
<option value="cloudy">Nuageux</option>
<option value="rainy">Pluvieux</option>
<option value="snowy">Neigeux</option>
<option value="foggy">Brumeux</option>
</select>
</div>

<div class="form-group">
<label for="crowd">Affluence</label>
<select id="crowd" class="form-control">
<option value="full">Complet</option>
<option value="high">Élevée</option>
<option value="medium"
selected>Moyenne</option>
<option value="low">Faible</option>
<option value="empty">Huis clos</option>
</select>
</div>
</div>
<div class="form-grid">
<div class="form-group">
<label for="pastMatches">Confrontations directes
(optionnel)</label>
<input type="text" id="pastMatches" class="form-
control" placeholder="Ex: 2-1,0-0,3-2">
<div class="text-sm text-muted mt-1">Format:
résultats séparés par virgules</div>
</div>

<div class="form-group">
<label>Forme récente (optionnel)</label>
<div class="flex gap-2">
<input type="text" id="last5Team1" class="form-
control" placeholder="Équipe 1: V,N,D,V,V">
<input type="text" id="last5Team2" class="form-
control" placeholder="Équipe 2: D,V,V,N,D">
</div>
<div class="text-sm text-muted mt-1">Format: V
(victoire), N (nul), D (défaite)</div>
</div>
</div>

<div class="form-group mt-4">


<label class="mb-2 font-bold">Options avancées</label>
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
<div class="toggle-container">
<label class="toggle-switch">
<input type="checkbox"
id="commentaryEnabled" checked>
<span class="toggle-slider"></span>
</label>
<span>Commentaire détaillé</span>
</div>

<div class="toggle-container">
<label class="toggle-switch">
<input type="checkbox" id="advancedStats"
checked>
<span class="toggle-slider"></span>
</label>
<span>Statistiques avancées</span>
</div>

<div class="toggle-container">
<label class="toggle-switch">
<input type="checkbox" id="realismMode"
checked>
<span class="toggle-slider"></span>
</label>
<span>Mode réalisme</span>
</div>

<div class="toggle-container">
<label class="toggle-switch">
<input type="checkbox"
id="backgroundNotifications" checked>
<span class="toggle-slider"></span>
</label>
<span>Notifications en arrière-plan</span>
</div>

<div class="toggle-container">
<label class="toggle-switch">
<input type="checkbox"
id="intensityFactor">
<span class="toggle-slider"></span>
</label>
<span>Facteur d'intensité aléatoire</span>
</div>

<div class="toggle-container">
<label class="toggle-switch">
<input type="checkbox"
id="scheduleForLater">
<span class="toggle-slider"></span>
</label>
<span>Planifier pour plus tard</span>
</div>
</div>
</div>

<div id="scheduleDatetimeContainer" class="form-group mt-3"


style="display: none;">
<label for="scheduleDateTime">Date et heure du
match</label>
<input type="datetime-local" id="scheduleDateTime"
class="form-control">
</div>

<div class="speed-control mt-4">


<div class="speed-label">Vitesse de simulation:</div>
<div class="speed-options">
<button type="button" class="speed-option" data-
speed="real">Temps réel (1h30)</button>
<button type="button" class="speed-option" data-
speed="fast">Rapide (7min30)</button>
<button type="button" class="speed-option active"
data-speed="very-fast">Très rapide (1min30)</button>
</div>
</div>

<div class="form-actions">
<button type="button" id="resetBtn" class="btn btn-
secondary">
<i class="fas fa-undo"></i> Réinitialiser
</button>
<button type="button" id="startSimulationBtn"
class="btn btn-primary">
<i class="fas fa-play"></i> Lancer la simulation
</button>
</div>
</form>
</div>
</div>

<div id="simulationContainer" style="display: none;">


<div class="match-scoreboard">
<div class="match-time" id="matchTime">00:00</div>
<div class="match-teams">
<div class="match-team" id="homeTeam">Équipe Domicile</div>
<div class="match-score">
<span id="homeScore">0</span> - <span
id="awayScore">0</span>
</div>
<div class="match-team" id="awayTeam">Équipe
Extérieur</div>
</div>
<div class="flex justify-between items-center mt-2 text-sm">
<span class="badge badge-primary"
id="matchType">Type</span>
<span class="badge badge-warning"
id="matchInfo">Informations</span>
</div>
</div>

<div class="grid grid-cols-1 md:grid-cols-2 gap-4">


<div class="match-commentary">
<div class="commentary-title">
<i class="fas fa-microphone"></i> Commentaire du match
</div>
<ul class="commentary-list" id="commentaryList">
<!-- Événements générés dynamiquement -->
</ul>
</div>

<div class="card">
<div class="card-header">
<h3 class="card-title">Statistiques du match</h3>
</div>
<div class="card-body">
<div class="match-stats">
<div class="match-stat">
<div class="stat-label">Possession</div>
<div class="stat-values">
<div class="stat-value"
id="homePossession">50%</div>
<div class="stat-value"
id="awayPossession">50%</div>
</div>
<div class="stat-bar">
<div class="stat-fill" id="possessionBar"
style="width: 50%;"></div>
</div>
</div>

<div class="match-stat">
<div class="stat-label">Tirs</div>
<div class="stat-values">
<div class="stat-value"
id="homeShots">0</div>
<div class="stat-value"
id="awayShots">0</div>
</div>
<div class="stat-bar">
<div class="stat-fill" id="shotsBar"
style="width: 50%;"></div>
</div>
</div>

<div class="match-stat">
<div class="stat-label">Tirs cadrés</div>
<div class="stat-values">
<div class="stat-value"
id="homeShotsOnTarget">0</div>
<div class="stat-value"
id="awayShotsOnTarget">0</div>
</div>
<div class="stat-bar">
<div class="stat-fill"
id="shotsOnTargetBar" style="width: 50%;"></div>
</div>
</div>

<div class="match-stat">
<div class="stat-label">Corners</div>
<div class="stat-values">
<div class="stat-value"
id="homeCorners">0</div>
<div class="stat-value"
id="awayCorners">0</div>
</div>
<div class="stat-bar">
<div class="stat-fill" id="cornersBar"
style="width: 50%;"></div>
</div>
</div>

<div class="match-stat">
<div class="stat-label">Cartons jaunes</div>
<div class="stat-values">
<div class="stat-value"
id="homeYellowCards">0</div>
<div class="stat-value"
id="awayYellowCards">0</div>
</div>
</div>

<div class="match-stat">
<div class="stat-label">Cartons rouges</div>
<div class="stat-values">
<div class="stat-value"
id="homeRedCards">0</div>
<div class="stat-value"
id="awayRedCards">0</div>
</div>
</div>
</div>

<div id="advancedStatsContainer" class="mt-4">


<h4 class="font-bold mb-2">Statistiques
avancées</h4>
<div class="grid grid-cols-2 gap-2">
<div class="match-stat">
<div class="stat-label">xG (Expected
Goals)</div>
<div class="stat-values">
<div class="stat-value"
id="homeXG">0.00</div>
<div class="stat-value"
id="awayXG">0.00</div>
</div>
<div class="stat-bar">
<div class="stat-fill" id="xgBar"
style="width: 50%;"></div>
</div>
</div>

<div class="match-stat">
<div class="stat-label">Attaques
dangereuses</div>
<div class="stat-values">
<div class="stat-value"
id="homeDangerousAttacks">0</div>
<div class="stat-value"
id="awayDangerousAttacks">0</div>
</div>
<div class="stat-bar">
<div class="stat-fill"
id="dangerousAttacksBar" style="width: 50%;"></div>
</div>
</div>

<div class="match-stat">
<div class="stat-label">Momentum</div>
<div class="stat-values">
<div class="stat-value"
id="homeMomentum">0</div>
<div class="stat-value"
id="awayMomentum">0</div>
</div>
<div class="stat-bar">
<div class="stat-fill" id="momentumBar"
style="width: 50%;"></div>
</div>
</div>

<div class="match-stat">
<div class="stat-label">Fatigue</div>
<div class="stat-values">
<div class="stat-value"
id="homeFatigue">100%</div>
<div class="stat-value"
id="awayFatigue">100%</div>
</div>
<div class="stat-bar">
<div class="stat-fill" id="fatigueBar"
style="width: 50%;"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="flex justify-between mt-4">
<button id="pauseResumeBtn" class="btn btn-secondary">
<i class="fas fa-pause"></i> Pause
</button>
<button id="stopSimulationBtn" class="btn btn-danger">
<i class="fas fa-stop"></i> Arrêter
</button>
</div>
</div>

<div id="matchSummary" class="card mt-4" style="display: none;">


<div class="card-header">
<h3 class="card-title">Résumé du Match</h3>
</div>
<div class="card-body" id="matchSummaryContent">
<!-- Contenu généré dynamiquement -->
</div>
<div class="flex justify-between mt-4">
<button id="newSimulationBtn" class="btn btn-secondary">
<i class="fas fa-plus"></i> Nouvelle simulation
</button>
<button id="saveResultBtn" class="btn btn-primary">
<i class="fas fa-save"></i> Sauvegarder le résultat
</button>
</div>
</div>
</div>

<div id="tab-scheduled-matches" class="tab-content">


<div class="card">
<div class="card-header">
<h2 class="card-title">Matchs Planifiés</h2>
</div>
<div class="card-body">
<div id="scheduledMatchesEmpty" class="text-center text-muted
py-4">
<i class="fas fa-calendar-alt fa-2x mb-2"></i>
<p>Aucun match planifié</p>
<p class="text-sm">Utilisez l'option "Planifier pour plus
tard" lors de la configuration d'un match</p>
</div>

<div id="scheduledMatchesList" class="scheduled-matches-list">


<!-- Les matchs planifiés seront ajoutés ici -->
</div>
</div>
</div>
</div>

<div id="tab-match-history" class="tab-content">


<div class="card">
<div class="card-header">
<h3 class="card-title">Historique des Matchs</h3>
<button id="clearHistoryBtn" class="btn btn-sm btn-danger">
<i class="fas fa-trash"></i> Effacer
</button>
</div>
<div class="card-body">
<div id="matchHistoryList" class="match-history-list">
<!-- Contenu généré dynamiquement -->
<div class="text-center text-muted py-4">
<i class="fas fa-history fa-2x mb-2"></i>
<p>Aucun match dans l'historique</p>
<p class="text-sm">Lancez une simulation pour
commencer</p>
</div>
</div>
</div>
</div>
</div>
</div>

<!-- Notifications -->


<div id="notifications"></div>

<!-- Bouton de thème -->


<div class="theme-switch">
<button id="themeToggle" class="theme-switch-button">
<i class="fas fa-moon"></i>
</button>
</div>

<!-- Animation de but -->


<div id="goalAnimation" style="display: none;" class="goal-animation">
<div class="goal-content">
<div class="goal-text">BUUUUT!</div>
<div class="goal-team" id="goalTeam"></div>
</div>
</div>

<!-- Loader -->


<div id="loaderContainer" class="loader-container" style="display: none;">
<div class="loader"></div>
</div>

<script>
/* =========================================================
INITIALISATION ET GESTION DES DONNÉES
========================================================= */

// Variables globales
let teamData = {}; // Stockage des données d'équipes
let matchHistory = []; // Historique des matchs
let currentMatch = null; // Match en cours de simulation
let simulationTimer = null; // Timer pour la simulation
let simulationPaused = false; // État de pause
let scheduledMatches = []; // Matchs planifiés
let scheduledMatchesTimers = {}; // Timers pour les matchs planifiés

// Constantes pour la simulation


const DEFAULT_SIMULATION_SPEED = 60000; // 60 secondes = 1 minute
(simulation en temps quasi-réel)
const FAST_SIMULATION_SPEED = 5000; // 5 secondes = 1 minute
(simulation rapide)
const VERY_FAST_SIMULATION_SPEED = 1000; // 1 seconde = 1 minute
(simulation très rapide)
let CURRENT_SIMULATION_SPEED = VERY_FAST_SIMULATION_SPEED;
const MAX_MATCH_MINUTES = 90; // Durée standard d'un match

// Création du manifeste pour l'installation PWA


const manifestObj = {
"name": "Football Match Simulator Pro",
"short_name": "FootSim",
"description": "Application de simulation de matches de football avec
statistiques avancées",
"start_url": "./",
"display": "standalone",
"background_color": "#5D5CDE",
"theme_color": "#5D5CDE",
"icons": [
{
"src":
"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZp
ZXdCb3g9IjAgMCA1MTIgNTEyIj48cGF0aCBmaWxsPSIjNUQ1Q0RFIiBkPSJNNDE3LjMgMjA0LjJjLTE4Ljg
tMTQuNy00Mi45LTIyLjQtNjguOS0yMi40aC03LjFjLTI2IDAtNTAuMSA3LjctNjguOSAyMi40LTE5LjIgMT
UtMjkuOSAzNC44LTI5LjkgNTUuNXY0MGMwIDIwLjYgMTAuNyA0MC41IDI5LjkgNTUuNSAxOC44IDE0LjcgN
DIuOSAyMi40IDY4LjkgMjIuNGg3LjFjMjYgMCA1MC4xLTcuNyA2OC45LTIyLjQgMTkuMi0xNSAyOS45LTM0
LjggMjkuOS01NS41di00MGMwLTIwLjYtMTAuNy00MC41LTI5LjktNTUuNXoiPjwvcGF0aD48cGF0aCBmaWx
sPSIjRkZGRkZGIiBkPSJNMzIxLjQgMzI1LjdjMC02LjQgNS4yLTExLjcgMTEuNy0xMS43aDM2LjdjNi40ID
AgMTEuNyA1LjIgMTEuNyAxMS43djEwYzAgNi40LTUuMiAxMS43LTExLjcgMTEuN2gtMzYuN2MtNi40IDAtM
TEuNy01LjItMTEuNy0xMS43di0xMHptLTE1OC43LTExLjdoMzYuN2M2LjQgMCAxMS43IDUuMiAxMS43IDEx
Ljd2MTBjMCA2LjQtNS4yIDExLjctMTEuNyAxMS43aC0zNi43Yy02LjQgMC0xMS43LTUuMi0xMS43LTExLjd
2LTEwYzAtNi40IDUuMi0xMS43IDExLjctMTEuN3oiPjwvcGF0aD48cGF0aCBmaWxsPSIjMDAwMDAwIiBkPS
JNMjU2IDgwQzE0OSA4MCA2NCAxNjUgNjQgMjcxLjYgNjQgMzc5IDEzNyA0NTMgMjU2IDQ1M3MxOTItNzQgM
TkyLTE4MS40QzQ0OCAxNjUgMzYzIDgwIDI1NiA4MHptMCAyOTguN2MtMjQgMC00Ni4zLTUuNy02Ni4zLTE1
LjVDMTgxIDM1OCAxNzYuOCAzNTAgMTc2LjggMzQyLjV2LTM2LjZjMC0zLjIuOS02LjMgMi43LTkuMWwyLS4
yYzI4LjItMjcuMSA0NC45LTUxLjUgNTAuMy03MiAzLjEtMTEuOS44LTI0LjgtNi4yLTM0LjktNy4zLTEwLj
YtMTAuNi0xOS41LTEwLTI3IDEuMi0xNC43IDE2LjItMjIuNCAyOC4xLTE0LjcgMTIuOCA4LjMgMjUuOSAyM
S41IDM2IDM2LjJsLjQuMWMzNS44IDIgNjYuMSAyNi40IDgwLjQgNjUuMSAxMC42IDI4LjggNC4xIDYwLjUg
MTYuNyA4MiAxMSAxOC44IDI5LjQgMjcuMy00LjQgNTUuNEMzMzYuNyAzNjkuOSAyOTcuNiAzNzguNyAyNTY
gMzc4Ljd6Ii8+PHBhdGggZmlsbD0iI0ZGRkZGRiIgZD0iTTI1NiAyMzQuMmMxOSAwIDM0LjQtMTUuNCAzNC
40LTM0LjQgMC0xOS0xNS40LTM0LjQtMzQuNC0zNC40LTE5IDAtMzQuNCAxNS40LTM0LjQgMzQuNCAwIDE5I
DE1LjQgMzQuNCAzNC40IDM0LjR6Ii8+PC9zdmc+",
"sizes": "512x512",
"type": "image/svg+xml",
"purpose": "any maskable"
}
]
};

const manifestStr = URL.createObjectURL(


new Blob([JSON.stringify(manifestObj)], {type: "application/json"})
);
document.getElementById("manifestPlaceholder").setAttribute("href",
manifestStr);

// Service Worker pour les notifications en arrière-plan


const swContent = `
self.addEventListener('install', (event) => {
self.skipWaiting();
});

self.addEventListener('activate', (event) => {


event.waitUntil(clients.claim());
});
// Écouter les messages de l'application
self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'SCHEDULE_NOTIFICATION') {
const {id, title, body, icon, timestamp} = event.data.payload;
const delay = timestamp - Date.now();

// Programmer la notification
if (delay > 0) {
setTimeout(() => {
self.registration.showNotification(title, {
body,
icon,
badge: icon,
tag: id,
renotify: true,
data: { id }
});
}, delay);
}
} else if (event.data && event.data.type === 'SHOW_NOTIFICATION') {
const {id, title, body, icon} = event.data.payload;

// Afficher immédiatement la notification


self.registration.showNotification(title, {
body,
icon,
badge: icon,
tag: id,
renotify: true,
data: { id }
});
}
});

// Gérer les clics sur les notifications


self.addEventListener('notificationclick', (event) => {
event.notification.close();

// Ouvrir/activer l'application
event.waitUntil(
clients.matchAll({type: 'window'}).then(clientList => {
for (const client of clientList) {
if ('focus' in client) {
return client.focus();
}
}
if (clients.openWindow) {
return clients.openWindow('./');
}
})
);
});
`;

// Création d'un Blob URL pour le service worker


const swBlob = new Blob([swContent], {type: 'text/javascript'});
const swUrl = URL.createObjectURL(swBlob);

// Initialiser l'application
document.addEventListener('DOMContentLoaded', function() {
// Demander la permission pour les notifications
if ('Notification' in window) {
if (Notification.permission !== 'granted' &&
Notification.permission !== 'denied') {
Notification.requestPermission();
}
}

// Enregistrer le service worker pour les notifications en arrière-plan


if ('serviceWorker' in navigator) {
navigator.serviceWorker.register(swUrl)
.then(registration => {
console.log('Service Worker enregistré avec succès:',
registration);
})
.catch(error => {
console.error('Erreur lors de l\'enregistrement du Service
Worker:', error);
});
}

// Charger les données depuis le stockage local


loadData();

// Initialiser les gestionnaires d'événements


initEventHandlers();

// Vérifier le thème
checkTheme();

// Afficher une notification de bienvenue


showNotification('Bienvenue sur le Simulateur Pro de Matchs de
Football!', 'info');

// Configurer le traitement des données collées par l'utilisateur


document.getElementById('dataInput').addEventListener('input',
function(event) {
const data = event.target.value;
if (data && data.trim() !== '') {
parseData(data);
populateTeamOptions();
updateTeamsContainer();

// Afficher l'indicateur de chargement réussi


const teamsCount = Object.keys(teamData).length;
document.getElementById('teamsLoadedCount').textContent =
teamsCount;
document.getElementById('dataLoadedIndicator').style.display =
'block';

showNotification('Données chargées avec succès', 'success');


} else {
document.getElementById('dataLoadedIndicator').style.display =
'none';
}
});

// Configurer la recherche d'équipes


document.getElementById('teamSearch').addEventListener('input',
function(event) {
updateTeamsContainer(event.target.value);
});

// Configurer l'option de planification


document.getElementById('scheduleForLater').addEventListener('change',
function() {
document.getElementById('scheduleDatetimeContainer').style.display
= this.checked ? 'block' : 'none';

// Régler la date/heure par défaut à maintenant + 1 heure


if (this.checked) {
const now = new Date();
now.setHours(now.getHours() + 1);

// Format YYYY-MM-DDThh:mm
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');

document.getElementById('scheduleDateTime').value = `${year}-$
{month}-${day}T${hours}:${minutes}`;
}
});

// Initialiser les onglets


initTabs();

// Vérifier et démarrer les matchs planifiés


checkScheduledMatches();
});

function initTabs() {
const tabs = document.querySelectorAll('.tab');

tabs.forEach(tab => {
tab.addEventListener('click', function() {
const tabId = this.getAttribute('data-tab');

// Désactiver tous les onglets et leur contenu


document.querySelectorAll('.tab').forEach(t =>
t.classList.remove('active'));
document.querySelectorAll('.tab-content').forEach(c =>
c.classList.remove('active'));

// Activer l'onglet cliqué et son contenu


this.classList.add('active');
document.getElementById(`tab-$
{tabId}`).classList.add('active');
});
});
}

// Charger les données depuis le stockage local


function loadData() {
try {
const storedTeamData = localStorage.getItem('simulatorTeamData');
if (storedTeamData) {
teamData = JSON.parse(storedTeamData);
populateTeamOptions();
updateTeamsContainer();

// Mettre à jour l'indicateur de données chargées


const teamsCount = Object.keys(teamData).length;
if (teamsCount > 0) {
document.getElementById('teamsLoadedCount').textContent =
teamsCount;

document.getElementById('dataLoadedIndicator').style.display = 'block';
}
}

const storedMatchHistory =
localStorage.getItem('simulatorMatchHistory');
if (storedMatchHistory) {
matchHistory = JSON.parse(storedMatchHistory);
updateMatchHistory();
}

const storedScheduledMatches =
localStorage.getItem('simulatorScheduledMatches');
if (storedScheduledMatches) {
scheduledMatches = JSON.parse(storedScheduledMatches);
updateScheduledMatchesDisplay();
}
} catch (error) {
console.error('Erreur lors du chargement des données:', error);
showNotification('Erreur lors du chargement des données', 'error');
}
}

// Sauvegarder les données dans le stockage local


function saveData() {
try {
localStorage.setItem('simulatorTeamData',
JSON.stringify(teamData));
localStorage.setItem('simulatorMatchHistory',
JSON.stringify(matchHistory));
localStorage.setItem('simulatorScheduledMatches',
JSON.stringify(scheduledMatches));
} catch (error) {
console.error('Erreur lors de la sauvegarde des données:', error);
showNotification('Erreur lors de la sauvegarde des données',
'error');
}
}

// Initialiser les gestionnaires d'événements


function initEventHandlers() {
// Gestionnaires pour le formulaire de configuration
document.getElementById('team1').addEventListener('change',
updateTeamInfo);
document.getElementById('team2').addEventListener('change',
updateTeamInfo);
document.getElementById('resetBtn').addEventListener('click',
resetForm);
document.getElementById('startSimulationBtn').addEventListener('click',
startMatch);
document.getElementById('clearDataBtn').addEventListener('click',
clearData);
document.getElementById('loadDemoDataBtn').addEventListener('click',
loadDemoData);

// Gestionnaires pour les contrôles de simulation


document.getElementById('pauseResumeBtn').addEventListener('click',
togglePauseResume);
document.getElementById('stopSimulationBtn').addEventListener('click',
stopSimulation);
document.getElementById('newSimulationBtn').addEventListener('click',
newSimulation);
document.getElementById('saveResultBtn').addEventListener('click',
saveMatchResult);
document.getElementById('clearHistoryBtn').addEventListener('click',
clearMatchHistory);

// Gestionnaires pour les options de vitesse


const speedButtons = document.querySelectorAll('.speed-option');
speedButtons.forEach(button => {
button.addEventListener('click', function() {
speedButtons.forEach(btn => btn.classList.remove('active'));
this.classList.add('active');
changeSimulationSpeed(this.getAttribute('data-speed'));
});
});

// Gestionnaire pour le bouton de thème


document.getElementById('themeToggle').addEventListener('click',
toggleTheme);
}

// Mettre à jour les infos de l'équipe sélectionnée


function updateTeamInfo() {
const team1 = document.getElementById('team1').value;
const team2 = document.getElementById('team2').value;

// Mettre à jour les catégories et styles


if (team1) {
const team1Category = classifyTeam(team1);
const team1Style = identifyTeamStyle(team1);

const categoryElement = document.getElementById('team1Category');


categoryElement.textContent = team1Category;
categoryElement.className = `team-category category-$
{team1Category.toLowerCase()}`;

const styleElement = document.getElementById('team1Style');


styleElement.textContent = team1Style;
styleElement.className = `team-style style-$
{team1Style.toLowerCase()}`;
}

if (team2) {
const team2Category = classifyTeam(team2);
const team2Style = identifyTeamStyle(team2);
const categoryElement = document.getElementById('team2Category');
categoryElement.textContent = team2Category;
categoryElement.className = `team-category category-$
{team2Category.toLowerCase()}`;

const styleElement = document.getElementById('team2Style');


styleElement.textContent = team2Style;
styleElement.className = `team-style style-$
{team2Style.toLowerCase()}`;
}

// Vérifier si les deux équipes sont identiques


if (team1 && team2 && team1 === team2) {
showNotification('Veuillez sélectionner deux équipes différentes',
'warning');
document.getElementById('startSimulationBtn').disabled = true;
} else {
document.getElementById('startSimulationBtn').disabled = false;
}
}

// Réinitialiser le formulaire
function resetForm() {
document.getElementById('matchSetupForm').reset();
document.getElementById('team1Category').textContent = '';
document.getElementById('team1Style').textContent = '';
document.getElementById('team2Category').textContent = '';
document.getElementById('team2Style').textContent = '';
document.getElementById('startSimulationBtn').disabled = false;
document.getElementById('scheduleDatetimeContainer').style.display =
'none';
}

// Peupler les sélecteurs d'équipes


function populateTeamOptions() {
const team1Select = document.getElementById('team1');
const team2Select = document.getElementById('team2');

// Sauvegarder les valeurs sélectionnées actuellement


const team1Value = team1Select.value;
const team2Value = team2Select.value;

// Vider et repeupler les sélecteurs


team1Select.innerHTML = '<option value="">Sélectionner une
équipe</option>';
team2Select.innerHTML = '<option value="">Sélectionner une
équipe</option>';

const sortedTeams = Object.keys(teamData).sort();

for (const team of sortedTeams) {


// Récupérer la ligue/compétition si disponible
const league = teamData[team].Comp || "";

// Ajouter aux sélecteurs


const option1 = document.createElement('option');
option1.value = team;
option1.textContent = team + (league ? ` (${league})` : '');
team1Select.appendChild(option1);

const option2 = document.createElement('option');


option2.value = team;
option2.textContent = team + (league ? ` (${league})` : '');
team2Select.appendChild(option2);
}

// Restaurer les valeurs précédemment sélectionnées


if (team1Value && sortedTeams.includes(team1Value)) team1Select.value =
team1Value;
if (team2Value && sortedTeams.includes(team2Value)) team2Select.value =
team2Value;

// Mettre à jour les informations affichées


updateTeamInfo();
}

// Mettre à jour l'affichage des équipes


function updateTeamsContainer(filterText = '') {
const teamsContainer = document.getElementById('teamsContainer');
const teams = Object.keys(teamData).sort();

if (teams.length === 0) {
teamsContainer.innerHTML = `
<div class="text-center text-muted py-4">
<i class="fas fa-database fa-2x mb-2"></i>
<p>Aucune équipe importée</p>
<p class="text-sm">Importez des données pour commencer</p>
</div>
`;
return;
}

// Filtrer les équipes si nécessaire


let filteredTeams = teams;
if (filterText) {
const lowerFilter = filterText.toLowerCase();
filteredTeams = teams.filter(team =>
team.toLowerCase().includes(lowerFilter) ||
(teamData[team].Comp || '').toLowerCase().includes(lowerFilter)
);
}

if (filteredTeams.length === 0) {
teamsContainer.innerHTML = `
<div class="text-center text-muted py-4">
<i class="fas fa-search fa-2x mb-2"></i>
<p>Aucune équipe ne correspond à votre recherche</p>
<p class="text-sm">Essayez un autre terme de recherche</p>
</div>
`;
return;
}

// Créer les cartes d'équipe


let html = '';
filteredTeams.forEach(team => {
const data = teamData[team];
const category = classifyTeam(team);
const style = identifyTeamStyle(team);
const league = data.Comp || "";
const leagueBadgeHtml = league ? `<span class="team-league-badge $
{league}">${league}</span>` : '';

html += `
<div class="p-3 bg-card rounded shadow-sm border border-color
hover:shadow-md transition-all">
<div class="flex items-center justify-between mb-2">
<div class="flex items-center">
${leagueBadgeHtml}
<span class="font-bold">${team}</span>
</div>
<div class="flex gap-1">
<span class="team-category category-$
{category.toLowerCase()}">${category}</span>
<span class="team-style style-$
{style.toLowerCase()}">${style}</span>
</div>
</div>
<div class="grid grid-cols-2 gap-2 text-sm">
<div>
<span class="text-muted">xG/90:</span>
<span class="font-bold">${(data["xG/90"] ||
0).toFixed(2)}</span>
</div>
<div>
<span class="text-muted">Poss:</span>
<span class="font-bold">${data["Poss"] ||
0}%</span>
</div>
<div>
<span class="text-muted">Âge:</span>
<span class="font-bold">${data["Age"] || 0}</span>
</div>
<div>
<span class="text-muted">Discipline:</span>
<span class="font-bold">$
{evaluateDiscipline(team)}/5</span>
</div>
</div>
</div>
`;
});

teamsContainer.innerHTML = html;
}

// Effacer les données


function clearData() {
showConfirmation(
'Êtes-vous sûr de vouloir effacer toutes les données d\'équipes ?',
function() {
teamData = {};
saveData();
populateTeamOptions();
updateTeamsContainer();
document.getElementById('dataInput').value = '';
document.getElementById('dataLoadedIndicator').style.display =
'none';

showNotification('Données effacées avec succès', 'info');


}
);
}

// Parser les données collées


function parseData(data) {
teamData = {};
const lines = data.split('\n');
const headers = lines[0].split('\t'); // Split by tab

for (let i = 1; i < lines.length; i++) {


const values = lines[i].split('\t'); // Split by tab
if (values.length >= 30) { // Assurons-nous qu'il y a suffisamment
de colonnes
const teamName = values[1]; // Squad name est dans la deuxième
colonne
if (teamName && teamName.trim() !== "") {
try {
teamData[teamName] = {
"Age": parseFloat(values[4]) || 26.0,
"Poss": parseFloat(values[5]) || 0,
"MP": parseInt(values[6]) || 0,
"Starts": parseInt(values[7]) || 0,
"Min": parseInt((values[8] || "0").replace(",",
"")) || 0,
"Gls": parseFloat(values[10]) || 0,
"Ast": parseFloat(values[11]) || 0,
"G+A": parseFloat(values[12]) || 0,
"G-PK": parseFloat(values[13]) || 0,
"PK": parseFloat(values[14]) || 0,
"PKatt": parseFloat(values[15]) || 0,
"CrdY": parseFloat(values[16]) || 0,
"CrdR": parseFloat(values[17]) || 0,
"xG": parseFloat(values[18]) || 0,
"npxG": parseFloat(values[19]) || 0,
"xAG": parseFloat(values[20]) || 0,
"npxG+xAG": parseFloat(values[21]) || 0,
"PrgC": parseFloat(values[22]) || 0,
"PrgP": parseFloat(values[23]) || 0,
"xG/90": parseFloat(values[28]) || 0,
"xAG/90": parseFloat(values[29]) || 0,
// Ajoutons des champs calculés pour la défense
"GA": parseFloat(values[10]) || 1.5, // Utilisons
les buts comme approximation des GA
"xGA": parseFloat(values[18]) || 0, // En supposant
que c'est dans les données
"Comp": values[2] || "" // Ajout de la
compétition/ligue
};
} catch (error) {
console.error(`Erreur de parsing pour l'équipe $
{teamName}:`, error);
}
}
}
}

// Sauvegarder les données


saveData();
}

// Charger des données de démo


function loadDemoData() {
// Quelques équipes de démo avec des données réalistes
const demoTeams = {
"Paris SG": {
"Age": 27.5,
"Poss": 62.3,
"MP": 38,
"Starts": 418,
"Min": 37620,
"Gls": 89,
"Ast": 53,
"G+A": 142,
"G-PK": 79,
"PK": 10,
"PKatt": 12,
"CrdY": 75,
"CrdR": 4,
"xG": 85.2,
"npxG": 75.8,
"xAG": 51.4,
"npxG+xAG": 127.2,
"PrgC": 920,
"PrgP": 1560,
"xG/90": 2.03,
"xAG/90": 1.23,
"GA": 32,
"League": "Ligue 1",
"Comp": "L1"
},
"Marseille": {
"Age": 26.8,
"Poss": 58.1,
"MP": 38,
"Starts": 418,
"Min": 37620,
"Gls": 70,
"Ast": 48,
"G+A": 118,
"G-PK": 65,
"PK": 5,
"PKatt": 7,
"CrdY": 82,
"CrdR": 6,
"xG": 72.8,
"npxG": 67.9,
"xAG": 49.6,
"npxG+xAG": 117.5,
"PrgC": 865,
"PrgP": 1428,
"xG/90": 1.74,
"xAG/90": 1.18,
"GA": 41,
"League": "Ligue 1",
"Comp": "L1"
},
"Lyon": {
"Age": 25.9,
"Poss": 56.4,
"MP": 38,
"Starts": 418,
"Min": 37620,
"Gls": 67,
"Ast": 43,
"G+A": 110,
"G-PK": 60,
"PK": 7,
"PKatt": 10,
"CrdY": 70,
"CrdR": 3,
"xG": 68.2,
"npxG": 61.2,
"xAG": 44.8,
"npxG+xAG": 106.0,
"PrgC": 832,
"PrgP": 1376,
"xG/90": 1.63,
"xAG/90": 1.07,
"GA": 44,
"League": "Ligue 1",
"Comp": "L1"
},
"Monaco": {
"Age": 25.2,
"Poss": 54.3,
"MP": 38,
"Starts": 418,
"Min": 37620,
"Gls": 65,
"Ast": 40,
"G+A": 105,
"G-PK": 59,
"PK": 6,
"PKatt": 8,
"CrdY": 68,
"CrdR": 2,
"xG": 64.7,
"npxG": 58.9,
"xAG": 42.5,
"npxG+xAG": 101.4,
"PrgC": 810,
"PrgP": 1342,
"xG/90": 1.55,
"xAG/90": 1.02,
"GA": 40,
"League": "Ligue 1",
"Comp": "L1"
},
"Lille": {
"Age": 25.7,
"Poss": 52.8,
"MP": 38,
"Starts": 418,
"Min": 37620,
"Gls": 58,
"Ast": 38,
"G+A": 96,
"G-PK": 53,
"PK": 5,
"PKatt": 7,
"CrdY": 64,
"CrdR": 2,
"xG": 61.2,
"npxG": 56.4,
"xAG": 39.9,
"npxG+xAG": 96.3,
"PrgC": 788,
"PrgP": 1305,
"xG/90": 1.46,
"xAG/90": 0.95,
"GA": 38,
"League": "Ligue 1",
"Comp": "L1"
},
"Barcelona": {
"Age": 26.8,
"Poss": 63.2,
"MP": 38,
"Starts": 418,
"Min": 37620,
"Gls": 85,
"Ast": 55,
"G+A": 140,
"G-PK": 77,
"PK": 8,
"PKatt": 10,
"CrdY": 69,
"CrdR": 3,
"xG": 83.7,
"npxG": 75.9,
"xAG": 54.1,
"npxG+xAG": 130.0,
"PrgC": 905,
"PrgP": 1580,
"xG/90": 2.00,
"xAG/90": 1.29,
"GA": 30,
"League": "La Liga",
"Comp": "LL"
},
"Real Madrid": {
"Age": 27.9,
"Poss": 61.5,
"MP": 38,
"Starts": 418,
"Min": 37620,
"Gls": 82,
"Ast": 52,
"G+A": 134,
"G-PK": 73,
"PK": 9,
"PKatt": 11,
"CrdY": 72,
"CrdR": 2,
"xG": 80.4,
"npxG": 71.6,
"xAG": 51.8,
"npxG+xAG": 123.4,
"PrgC": 890,
"PrgP": 1540,
"xG/90": 1.92,
"xAG/90": 1.24,
"GA": 33,
"League": "La Liga",
"Comp": "LL"
},
"Bayern Munich": {
"Age": 26.7,
"Poss": 63.8,
"MP": 34,
"Starts": 374,
"Min": 33660,
"Gls": 92,
"Ast": 62,
"G+A": 154,
"G-PK": 84,
"PK": 8,
"PKatt": 9,
"CrdY": 58,
"CrdR": 2,
"xG": 90.5,
"npxG": 82.7,
"xAG": 61.4,
"npxG+xAG": 144.1,
"PrgC": 940,
"PrgP": 1620,
"xG/90": 2.42,
"xAG/90": 1.64,
"GA": 28,
"League": "Bundesliga",
"Comp": "BL"
},
"Borussia Dortmund": {
"Age": 25.9,
"Poss": 58.2,
"MP": 34,
"Starts": 374,
"Min": 33660,
"Gls": 83,
"Ast": 54,
"G+A": 137,
"G-PK": 77,
"PK": 6,
"PKatt": 8,
"CrdY": 62,
"CrdR": 3,
"xG": 81.6,
"npxG": 75.8,
"xAG": 53.6,
"npxG+xAG": 129.4,
"PrgC": 890,
"PrgP": 1520,
"xG/90": 2.18,
"xAG/90": 1.43,
"GA": 36,
"League": "Bundesliga",
"Comp": "BL"
},
"Liverpool": {
"Age": 26.5,
"Poss": 59.4,
"MP": 38,
"Starts": 418,
"Min": 37620,
"Gls": 87,
"Ast": 58,
"G+A": 145,
"G-PK": 79,
"PK": 8,
"PKatt": 10,
"CrdY": 65,
"CrdR": 2,
"xG": 85.3,
"npxG": 77.5,
"xAG": 57.4,
"npxG+xAG": 134.9,
"PrgC": 910,
"PrgP": 1550,
"xG/90": 2.04,
"xAG/90": 1.37,
"GA": 31,
"League": "Premier League",
"Comp": "PL"
},
"Manchester City": {
"Age": 27.2,
"Poss": 64.1,
"MP": 38,
"Starts": 418,
"Min": 37620,
"Gls": 94,
"Ast": 65,
"G+A": 159,
"G-PK": 85,
"PK": 9,
"PKatt": 11,
"CrdY": 60,
"CrdR": 1,
"xG": 92.7,
"npxG": 83.9,
"xAG": 64.3,
"npxG+xAG": 148.2,
"PrgC": 950,
"PrgP": 1650,
"xG/90": 2.22,
"xAG/90": 1.54,
"GA": 26,
"League": "Premier League",
"Comp": "PL"
},
"Juventus": {
"Age": 27.8,
"Poss": 57.6,
"MP": 38,
"Starts": 418,
"Min": 37620,
"Gls": 76,
"Ast": 49,
"G+A": 125,
"G-PK": 68,
"PK": 8,
"PKatt": 10,
"CrdY": 70,
"CrdR": 3,
"xG": 74.1,
"npxG": 66.3,
"xAG": 48.5,
"npxG+xAG": 114.8,
"PrgC": 850,
"PrgP": 1480,
"xG/90": 1.77,
"xAG/90": 1.16,
"GA": 35,
"League": "Serie A",
"Comp": "SA"
},
"Inter": {
"Age": 28.1,
"Poss": 56.2,
"MP": 38,
"Starts": 418,
"Min": 37620,
"Gls": 81,
"Ast": 52,
"G+A": 133,
"G-PK": 71,
"PK": 10,
"PKatt": 12,
"CrdY": 73,
"CrdR": 4,
"xG": 79.2,
"npxG": 69.4,
"xAG": 51.3,
"npxG+xAG": 120.7,
"PrgC": 880,
"PrgP": 1510,
"xG/90": 1.89,
"xAG/90": 1.23,
"GA": 34,
"League": "Serie A",
"Comp": "SA"
}
};

teamData = demoTeams;
populateTeamOptions();
updateTeamsContainer();
saveData();

// Mettre à jour l'indicateur de données chargées


const teamsCount = Object.keys(teamData).length;
document.getElementById('teamsLoadedCount').textContent = teamsCount;
document.getElementById('dataLoadedIndicator').style.display = 'block';

showNotification('Données de démo chargées avec succès', 'success');


}

/* =========================================================
LOGIQUE DE SIMULATION DE MATCH
========================================================= */

// Fonction de limite pour les valeurs de xG


function limit_xG_final(xG) {
return Math.min(Math.max(xG, 0.1), 2.8); // Min 0.1, max 2.8 (plus
réaliste)
}

// Classification des équipes en catégories


function classifyTeam(team) {
const data = teamData[team];
if (!data) return "Moyenne"; // Par défaut, équipe moyenne

const possession = data["Poss"] || 50;


const xG = data["xG/90"] || 1.5;
const GA = data["GA"] / (data["MP"] || 1) || 1.5;

if (possession > 55 && xG > 2.0 && GA < 1.2) return "Grande";
if (possession < 45 && xG < 1.2 && GA > 1.8) return "Petite";
return "Moyenne";
}

// Identifier le style de jeu d'une équipe


function identifyTeamStyle(team) {
const data = teamData[team];
if (!data) return "Balanced";

const prgP = data["PrgP"] / Math.max(data["MP"], 1) || 0;


const prgC = data["PrgC"] / Math.max(data["MP"], 1) || 0;
const possPerc = data["Poss"] || 50;
const goalsPer90 = data["Gls"] / Math.max(data["MP"], 1) || 0;
const xGPer90 = data["xG/90"] || 1.2;

// Scoring system for offensive tendency


let offensiveScore = 0;
if (prgP > 50) offensiveScore += 2;
else if (prgP > 40) offensiveScore += 1;

if (prgC > 40) offensiveScore += 2;


else if (prgC > 30) offensiveScore += 1;

if (possPerc > 55) offensiveScore += 2;


else if (possPerc > 48) offensiveScore += 1;

if (goalsPer90 > 1.8) offensiveScore += 2;


else if (goalsPer90 > 1.3) offensiveScore += 1;
if (xGPer90 > 1.7) offensiveScore += 2;
else if (xGPer90 > 1.3) offensiveScore += 1;

// Determine style based on score


if (offensiveScore >= 7) return "Offensive";
if (offensiveScore <= 3) return "Defensive";
return "Balanced";
}

// Évaluer la discipline d'une équipe (1-5)


function evaluateDiscipline(team) {
const data = teamData[team];
if (!data) return 3; // Discipline moyenne par défaut

const yellowsPer90 = data["CrdY"] / Math.max(data["MP"], 1) || 0;


const redsPer90 = data["CrdR"] / Math.max(data["MP"], 1) || 0;

// Pondération: un carton rouge vaut 5 cartons jaunes


const disciplineScore = yellowsPer90 + (redsPer90 * 5);

// Inverser l'échelle: plus le score est bas, meilleure est la


discipline
if (disciplineScore < 1.0) return 5; // Excellente discipline
if (disciplineScore < 1.8) return 4; // Bonne discipline
if (disciplineScore < 2.5) return 3; // Discipline moyenne
if (disciplineScore < 3.2) return 2; // Discipline médiocre
return 1; // Mauvaise discipline
}

// Calcul des buts attendus (xG)


function predictGoals(team, opponent, matchType = "Championnat") {
const data = teamData[team];
const opponentData = teamData[opponent];

if (!data || !opponentData) {
console.error('Données d\'équipe manquantes pour le calcul xG');
return 0.8; // Valeur par défaut
}

// Calcul de base des xG par match


let baseXgPerMatch = 0;
if (data["MP"] > 0) {
baseXgPerMatch = data["xG"] / Math.max(1, data["MP"]);
} else if (data["xG/90"] > 0) {
baseXgPerMatch = data["xG/90"];
} else {
baseXgPerMatch = 0.8; // Valeur par défaut plus raisonnable
}

// Limiter la valeur de base


baseXgPerMatch = limit_xG_final(baseXgPerMatch);

// Calcul dynamique du xGA (capacité défensive adverse)


const opponentXG = opponentData["xG/90"] || 1.5;
const teamDefense = data["GA"] / Math.max(data["MP"] || 1, 1) || 1.5;
const defenseQuality = Math.min(Math.max(teamDefense / 1.5, 0.7), 1.3);
const dynamicXGA = limit_xG_final(opponentXG * defenseQuality);

// Ajustement en fonction des facteurs offensifs et défensifs


const xAG_for = data["xAG"] / Math.max(1, data["MP"]) / 15;
const PrgP_for = data["PrgP"] / Math.max(1, data["MP"]) / 150;
const PrgC_for = data["PrgC"] / Math.max(1, data["MP"]) / 150;
const GA_against = opponentData["Gls"] / Math.max(1,
opponentData["MP"]);

// Pondération des facteurs


let attack_boost = (xAG_for * 0.04) + (PrgP_for * 0.01) + (PrgC_for *
0.01);
let defense_penalty = Math.min(((GA_against + dynamicXGA) / 2 - 0.8) /
5, 0.4);

// Calcul avec facteurs limités


let adjusted_xG = baseXgPerMatch * (1 + Math.min(attack_boost, 0.2) -
Math.max(defense_penalty, -0.1));

// Réduction pour plus de réalisme


adjusted_xG = adjusted_xG * 0.9;

// Ajustements supplémentaires
const teamCategory = classifyTeam(team);

// Ajustement selon la catégorie


const category_factors = {
"Grande": 1.1, // Boost léger pour les grandes équipes (+10%)
"Moyenne": 1.0, // Pas de changement
"Petite": 0.85 // Réduction pour les petites équipes (-15%)
};
adjusted_xG *= (category_factors[teamCategory] || 1.0);

// Ajustement selon le style de jeu


const teamStyle = identifyTeamStyle(team);
let offensiveBonus = 1.0;
if (teamStyle === "Offensive") {
offensiveBonus = 1.15; // +15% pour style très offensif
} else if (teamStyle === "Defensive") {
offensiveBonus = 0.92; // -8% pour style défensif
}
adjusted_xG *= offensiveBonus;

// Ajustement selon le type de match


const match_factors = {
"Derby": 1.1, // +10% pour les derbys (plus d'intensité)
"Coupe": 0.95, // -5% pour les coupes (plus de prudence)
"Championnat": 1.0, // Valeur de référence
"Amical": 0.9 // -10% pour les amicaux (moins d'enjeu)
};
adjusted_xG *= (match_factors[matchType] || 1.0);

// Limiter à nouveau avec la limite finale


adjusted_xG = limit_xG_final(adjusted_xG);

return adjusted_xG;
}

// Récupérer les résultats des confrontations directes


function getPastMatchResults() {
let pastMatchesInput = document.getElementById("pastMatches").value;
if (!pastMatchesInput) return [];
return pastMatchesInput.split(",").map(match => match.trim());
}

// Analyser les confrontations directes


function analyzeHeadToHead(pastMatches) {
if (!pastMatches || pastMatches.length === 0) return null;

let stats = {
matchCount: pastMatches.length,
team1Wins: 0,
team2Wins: 0,
draws: 0,
team1Goals: 0,
team2Goals: 0,
over25: 0,
under25: 0,
btts: 0,
avgTeam1Goals: 0,
avgTeam2Goals: 0
};

pastMatches.forEach(score => {
let [goals1, goals2] = score.split("-").map(Number);
if (isNaN(goals1) || isNaN(goals2)) return;

stats.team1Goals += goals1;
stats.team2Goals += goals2;

if (goals1 > goals2) stats.team1Wins++;


else if (goals1 < goals2) stats.team2Wins++;
else stats.draws++;

if (goals1 + goals2 > 2.5) stats.over25++;


else stats.under25++;

if (goals1 > 0 && goals2 > 0) stats.btts++;


});

stats.avgTeam1Goals = stats.team1Goals / stats.matchCount;


stats.avgTeam2Goals = stats.team2Goals / stats.matchCount;

return stats;
}

// Analyser la forme récente


function analyzeRecentForm(formInput) {
if (!formInput) return null;

let results = formInput.toUpperCase().split(",");


let win = 0, draw = 0, loss = 0;

results.forEach(result => {
if (result === "V") win++;
else if (result === "N") draw++;
else if (result === "D") loss++;
});

let totalMatches = win + draw + loss;


if (totalMatches === 0) return null;

return {
results: results,
winRate: (win / totalMatches) * 100,
drawRate: (draw / totalMatches) * 100,
lossRate: (loss / totalMatches) * 100
};
}

// Generate a random number from Poisson distribution with mean lambda


function poissonRandom(lambda) {
let L = Math.exp(-lambda);
let p = 1.0;
let k = 0;

do {
k++;
p *= Math.random();
} while (p > L);

return k - 1;
}

// --------------------------------
// FONCTIONS DE SIMULATION DE MATCH
// --------------------------------

// Initialiser une nouvelle simulation de match


function initializeMatchSimulation(team1, team2, matchType = "Championnat")
{
// Vérifier que les équipes existent dans les données
if (!teamData[team1] || !teamData[team2]) {
showNotification('Équipe(s) non trouvée(s) dans les données',
'error');
return null;
}

// Récupérer les données de forme récente si disponibles


const formTeam1 =
analyzeRecentForm(document.getElementById("last5Team1")?.value || "");
const formTeam2 =
analyzeRecentForm(document.getElementById("last5Team2")?.value || "");

// Récupérer les confrontations directes si disponibles


const pastMatches = getPastMatchResults();
const h2hStats = pastMatches.length > 0 ?
analyzeHeadToHead(pastMatches) : null;

// Utiliser directement les fonctions de prédiction existantes


let xgTeam1 = predictGoals(team1, team2, matchType);
let xgTeam2 = predictGoals(team2, team1, matchType);

// Ajuster selon la forme récente


if (formTeam1) {
const formFactor = formTeam1.winRate / 100; // Convertir en facteur
0-1
xgTeam1 *= (0.85 + formFactor * 0.3); // Entre 0.85 et 1.15
}
if (formTeam2) {
const formFactor = formTeam2.winRate / 100;
xgTeam2 *= (0.85 + formFactor * 0.3);
}

// Récupérer les données supplémentaires depuis le formulaire


const venue = document.getElementById('venue').value;
const weather = document.getElementById('weather').value;
const crowd = document.getElementById('crowd').value;

// Ajuster en fonction du lieu du match


if (venue === 'home') {
xgTeam1 *= 1.1; // +10% pour équipe à domicile
xgTeam2 *= 0.9; // -10% pour équipe à l'extérieur
} else if (venue === 'away') {
xgTeam1 *= 0.9; // -10% pour équipe à l'extérieur
xgTeam2 *= 1.1; // +10% pour équipe à domicile
}

// Ajuster en fonction de la météo


if (weather === 'rainy' || weather === 'snowy') {
// Conditions difficiles réduisent légèrement les buts
xgTeam1 *= 0.9;
xgTeam2 *= 0.9;
}

// Ajuster en fonction de l'affluence


if (crowd === 'full') {
// Stade plein favorise l'équipe à domicile si pertinent
if (venue === 'home') {
xgTeam1 *= 1.05;
} else if (venue === 'away') {
xgTeam2 *= 1.05;
}
} else if (crowd === 'empty') {
// À huis clos, moins d'impact du facteur domicile
if (venue === 'home') {
xgTeam1 *= 0.95;
} else if (venue === 'away') {
xgTeam2 *= 0.95;
}
}

// Données statistiques réelles depuis teamData


const team1Stats = teamData[team1];
const team2Stats = teamData[team2];

// Données de possession basées sur données réelles


const team1Possession = team1Stats.Poss || 50;
const team2Possession = team2Stats.Poss || 50;

// Calculer la domination attendue basée sur la possession réelle


const totalPoss = team1Possession + team2Possession;
const dominationBase = Math.round((team1Possession / totalPoss) * 100);

// Style de jeu des équipes


const team1Style = identifyTeamStyle(team1) || "Balanced";
const team2Style = identifyTeamStyle(team2) || "Balanced";
// Discipline des équipes (basée sur les cartons dans teamData)
const team1Discipline = evaluateDiscipline(team1) || 3;
const team2Discipline = evaluateDiscipline(team2) || 3;

// Catégories des équipes


const team1Category = classifyTeam(team1);
const team2Category = classifyTeam(team2);

// Statistiques attendues basées sur les xG et les données réelles


const expectedShotsTeam1 = Math.max(5, Math.round(xgTeam1 * 4)); // ~4
tirs par xG, au moins 5
const expectedShotsTeam2 = Math.max(5, Math.round(xgTeam2 * 4));

const expectedCornersTeam1 = Math.max(3, Math.round(xgTeam1 * 2.5)); //


~2.5 corners par xG, au moins 3
const expectedCornersTeam2 = Math.max(3, Math.round(xgTeam2 * 2.5));

// Force relative des équipes (entre 0.7 et 1.3) basée sur la catégorie
let team1StrengthFactor = 1.0;
let team2StrengthFactor = 1.0;

if (team1Category === "Grande") team1StrengthFactor = 1.3;


else if (team1Category === "Petite") team1StrengthFactor = 0.7;

if (team2Category === "Grande") team2StrengthFactor = 1.3;


else if (team2Category === "Petite") team2StrengthFactor = 0.7;

// Options de simulation avancées


const commentaryEnabled =
document.getElementById('commentaryEnabled').checked;
const advancedStats = document.getElementById('advancedStats').checked;
const realismMode = document.getElementById('realismMode').checked;
const intensityFactor =
document.getElementById('intensityFactor').checked;
const backgroundNotifications =
document.getElementById('backgroundNotifications').checked;

// Créer l'objet du match


return {
id: `match_${Date.now()}_${Math.floor(Math.random() * 1000)}`,
team1: team1,
team2: team2,
team1Category: team1Category,
team2Category: team2Category,
matchType: matchType,
xgTeam1: xgTeam1,
xgTeam2: xgTeam2,

// Paramètres supplémentaires
venue: venue,
weather: weather,
crowd: crowd,

// Options avancées
commentaryEnabled: commentaryEnabled,
advancedStats: advancedStats,
realismMode: realismMode,
intensityFactor: intensityFactor,
backgroundNotifications: backgroundNotifications,

// Données additionnelles
formTeam1: formTeam1,
formTeam2: formTeam2,
h2hStats: h2hStats,
pastMatches: pastMatches,

// Données statistiques
team1Stats: Object.assign({}, team1Stats),
team2Stats: Object.assign({}, team2Stats),

// Facteurs de force
team1StrengthFactor: team1StrengthFactor,
team2StrengthFactor: team2StrengthFactor,

expectedStats: {
shots: { team1: expectedShotsTeam1, team2:
expectedShotsTeam2 },
corners: { team1: expectedCornersTeam1, team2:
expectedCornersTeam2 }
},

// Données de match
score1: 0,
score2: 0,
minute: 0,
status: "En attente",
possessionTeam: dominationBase > 50 ? team1 : team2,
intensity: 1.0,
team1Style: team1Style,
team2Style: team2Style,
team1Discipline: team1Discipline,
team2Discipline: team2Discipline,

// Facteurs dynamiques
fatigue: {
team1: 1.0,
team2: 1.0
},
momentum: {
team1: 0,
team2: 0
},

// Historique d'événements
events: [],
displayedEvents: [],

// Statistiques de match
stats: {
yellowCards: {
team1: 0,
team2: 0
},
redCards: {
team1: 0,
team2: 0
},
dangerousAttacks: {
team1: 0,
team2: 0
},
shotsOnTarget: {
team1: 0,
team2: 0
},
shots: {
team1: 0,
team2: 0
},
corners: {
team1: 0,
team2: 0
},
possession: {
team1: team1Possession,
team2: team2Possession,
current: dominationBase
},
dominationIndex: dominationBase
},

// Timing
startTime: new Date(),
endTime: null
};
}

// Démarrer la simulation du match


function startMatch() {
// Récupérer les équipes sélectionnées
const team1 = document.getElementById('team1').value;
const team2 = document.getElementById('team2').value;
const matchType = document.getElementById('matchType').value;

// Validation
if (!team1 || !team2) {
showNotification('Veuillez sélectionner les deux équipes',
'warning');
return;
}

if (team1 === team2) {


showNotification('Veuillez sélectionner deux équipes différentes',
'warning');
return;
}

// Vérifier si le match doit être planifié


if (document.getElementById('scheduleForLater').checked) {
const scheduledDateTime =
document.getElementById('scheduleDateTime').value;

if (!scheduledDateTime) {
showNotification('Veuillez spécifier une date et heure pour le
match planifié', 'warning');
return;
}

// Convertir en timestamp
const scheduledTimestamp = new Date(scheduledDateTime).getTime();
const now = Date.now();

// Vérifier que la date est dans le futur


if (scheduledTimestamp <= now) {
showNotification('La date et heure planifiées doivent être dans
le futur', 'warning');
return;
}

// Créer l'objet de match planifié


const scheduledMatch = {
id: `scheduled_${Date.now()}_${Math.floor(Math.random() *
1000)}`,
team1: team1,
team2: team2,
matchType: matchType,
venue: document.getElementById('venue').value,
weather: document.getElementById('weather').value,
crowd: document.getElementById('crowd').value,
pastMatches: getPastMatchResults(),
last5Team1: document.getElementById('last5Team1').value,
last5Team2: document.getElementById('last5Team2').value,
commentaryEnabled:
document.getElementById('commentaryEnabled').checked,
advancedStats:
document.getElementById('advancedStats').checked,
realismMode: document.getElementById('realismMode').checked,
intensityFactor:
document.getElementById('intensityFactor').checked,
backgroundNotifications:
document.getElementById('backgroundNotifications').checked,
simulationSpeed: CURRENT_SIMULATION_SPEED,
scheduledAt: scheduledTimestamp
};

// Ajouter à la liste des matchs planifiés


scheduledMatches.push(scheduledMatch);
saveData();

// Mettre à jour l'affichage des matchs planifiés


updateScheduledMatchesDisplay();

// Planifier la notification
scheduleMatchNotification(scheduledMatch);

showNotification(`Match ${team1} vs ${team2} planifié pour le ${new


Date(scheduledTimestamp).toLocaleString()}`, 'success');

// Réinitialiser le formulaire
resetForm();

// Passer à l'onglet des matchs planifiés


document.querySelector('.tab[data-tab="scheduled-
matches"]').click();
return;
}

// Initialiser la simulation
currentMatch = initializeMatchSimulation(team1, team2, matchType);

if (!currentMatch) {
showNotification('Erreur lors de l\'initialisation de la
simulation', 'error');
return;
}

// Cacher le formulaire et afficher la simulation


document.getElementById('matchSetupCard').style.display = 'none';
document.getElementById('simulationContainer').style.display = 'block';

// Mettre à jour l'interface


updateMatchDisplay();

// Démarrer la simulation
currentMatch.status = "En cours";
simulationPaused = false;

// Notification de démarrage du match


showInAppNotification(`🏁 Le match ${team1} vs ${team2} commence!`,
'info', true);

// Ajouter un événement de début de match


addMatchEvent(currentMatch, "🏁 Coup d'envoi!");

// Démarrer la simulation minute par minute


simulateMatchMinutes();

showNotification(`Match ${team1} vs ${team2} démarré!`, 'success');


}

// Simuler les minutes du match


function simulateMatchMinutes() {
if (!currentMatch || simulationPaused) {
return;
}

// Si le match est terminé, arrêter la simulation


if (currentMatch.status === "Terminé" || currentMatch.minute >=
MAX_MATCH_MINUTES) {
finishMatch();
return;
}

// Passer à la minute suivante


currentMatch.minute++;

// Pause mi-temps (45 minutes)


if (currentMatch.minute === 45) {
addMatchEvent(currentMatch, "🏁 Mi-temps! Les équipes rentrent aux
vestiaires.");
updateMatchDisplay();

// Notification de mi-temps
showInAppNotification(` Mi-temps : ${currentMatch.team1} $
{currentMatch.score1}-${currentMatch.score2} ${currentMatch.team2}`, 'info', true);

// Planifier la reprise après 3 secondes (simule la mi-temps)


setTimeout(() => {
if (currentMatch && currentMatch.status === "En cours") {
currentMatch.minute++; // Avancer à la 46e minute
addMatchEvent(currentMatch, "🏁 Début de la seconde mi-
temps!");

// Notification de reprise
showInAppNotification(`🏁 2ème mi-temps : $
{currentMatch.team1} ${currentMatch.score1}-${currentMatch.score2} $
{currentMatch.team2}`, 'info', currentMatch.backgroundNotifications);

// Petit regain d'énergie après la mi-temps


currentMatch.fatigue.team1 = Math.min(1.0,
currentMatch.fatigue.team1 + 0.1);
currentMatch.fatigue.team2 = Math.min(1.0,
currentMatch.fatigue.team2 + 0.1);

simulateMatchEvents();
updateMatchDisplay();

// Continuer la simulation
simulationTimer = setTimeout(simulateMatchMinutes,
CURRENT_SIMULATION_SPEED);
}
}, 3000); // Pause de 3 secondes pour la mi-temps

return;
}

// Mise à jour de la fatigue (augmente progressivement)


updateFatigue();

// Mise à jour de l'intensité (peut varier au fil du match)


updateIntensity();

// Simulation des événements de cette minute


simulateMatchEvents();

// Mise à jour de l'affichage


updateMatchDisplay();

// Planifier la prochaine minute


simulationTimer = setTimeout(simulateMatchMinutes,
CURRENT_SIMULATION_SPEED);
}

// Simulation des événements du match


function simulateMatchEvents() {
// Mise à jour de la possession
updatePossession();

// Variables pour suivre les événements générés cette minute


let team1Event = null;
let team2Event = null;
// Calculer les probabilités de but pour cette minute en utilisant les
xG ajustés
// xG = buts attendus sur 90 minutes, donc xG/90 = probabilité par
minute
let minuteProbTeam1 = currentMatch.xgTeam1 / 90;
let minuteProbTeam2 = currentMatch.xgTeam2 / 90;

// Appliquer les facteurs de force issus des catégories


minuteProbTeam1 *= currentMatch.team1StrengthFactor;
minuteProbTeam2 *= currentMatch.team2StrengthFactor;

// Ajuster selon la fatigue et le momentum


minuteProbTeam1 *= currentMatch.fatigue.team1 * (1 +
(currentMatch.momentum.team1 * 0.05));
minuteProbTeam2 *= currentMatch.fatigue.team2 * (1 +
(currentMatch.momentum.team2 * 0.05));

// Ajuster selon l'intensité du match


minuteProbTeam1 *= currentMatch.intensity;
minuteProbTeam2 *= currentMatch.intensity;

// Pénalités pour cartons rouges


if (currentMatch.stats.redCards.team1 > 0) {
minuteProbTeam1 *= Math.pow(0.7,
currentMatch.stats.redCards.team1);
}
if (currentMatch.stats.redCards.team2 > 0) {
minuteProbTeam2 *= Math.pow(0.7,
currentMatch.stats.redCards.team2);
}

// Moments clés (plus de buts en fin de mi-temps)


if ((currentMatch.minute >= 42 && currentMatch.minute <= 45) ||
(currentMatch.minute >= 85 && currentMatch.minute <= 90)) {
minuteProbTeam1 *= 1.5;
minuteProbTeam2 *= 1.5;

// Équipe qui perd pousse encore plus en fin de match


if (currentMatch.score1 < currentMatch.score2 &&
currentMatch.minute >= 85) {
minuteProbTeam1 *= 1.3;
} else if (currentMatch.score2 < currentMatch.score1 &&
currentMatch.minute >= 85) {
minuteProbTeam2 *= 1.3;
}
}

// Augmenter les chances de marquer pour l'équipe qui a la possession


if (currentMatch.possessionTeam === currentMatch.team1) {
minuteProbTeam1 *= 1.2;
} else {
minuteProbTeam2 *= 1.2;
}

// S'assurer que la probabilité totale par minute reste réaliste


const totalProb = minuteProbTeam1 + minuteProbTeam2;
if (totalProb > 0.15) { // Limiter à environ 13-14 buts par match
maximum
const factor = 0.15 / totalProb;
minuteProbTeam1 *= factor;
minuteProbTeam2 *= factor;
}

// S'assurer qu'un tir cadré est enregistré avant un but


if (Math.random() < minuteProbTeam1 * 4) { // 4 fois plus de chances
d'avoir un tir
currentMatch.stats.shots.team1++;

// Sélectionner un commentaire aléatoire pour le tir


const shotComments = [
`🥅 Tir de ${currentMatch.team1}!`,
`🥅 Tentative de ${currentMatch.team1}!`,
`🥅 ${currentMatch.team1} tente sa chance!`,
`🥅 Frappe de ${currentMatch.team1}!`
];

// Tir cadré?
if (Math.random() < 0.6) { // 60% des tirs sont cadrés
currentMatch.stats.shotsOnTarget.team1++;

const shotOnTargetComments = [
`🎯 Tir cadré de ${currentMatch.team1}!`,
`🎯 Belle tentative de ${currentMatch.team1}!`,
`🎯 ${currentMatch.team1} teste le gardien adverse.`,
`🎯 Tir puissant de ${currentMatch.team1}!`
];

if (Math.random() < minuteProbTeam1 / (minuteProbTeam1 * 4 *


0.6)) { // La probabilité de marquer parmi les tirs cadrés
// But pour l'équipe 1
currentMatch.score1++;
team1Event = 'goal';

// Ajouter l'événement à l'historique


const goalComments = [
`⚽ BUT! ${currentMatch.team1} ouvre le score!`,
`⚽ BUUUT! ${currentMatch.team1} marque!`,
`⚽ GOAL! ${currentMatch.team1} trouve le chemin des
filets!`
];

// Sélectionner un commentaire en fonction du contexte


let goalComment = '';

if (currentMatch.score1 === 1 && currentMatch.score2 === 0)


{
goalComment = `⚽ BUT! ${currentMatch.team1} ouvre le
score!`;
} else if (currentMatch.score1 === currentMatch.score2) {
goalComment = `⚽ BUT! ${currentMatch.team1} égalise!`;
} else if (currentMatch.score1 > currentMatch.score2) {
goalComment = `⚽ BUT! ${currentMatch.team1} creuse
l'écart!`;
} else {
goalComment = `⚽ BUT! ${currentMatch.team1} réduit
l'écart!`;
}
addMatchEvent(currentMatch, goalComment);

// Notification de but
const score = `${currentMatch.team1} $
{currentMatch.score1}-${currentMatch.score2} ${currentMatch.team2}`;
showInAppNotification(`⚽ BUT! ${currentMatch.team1} marque!
(${score})`, 'success', true);

// Afficher l'animation de but


showGoalAnimation(currentMatch.team1);

// Changement de possession après un but


currentMatch.possessionTeam = currentMatch.team2;

// Boost de momentum pour l'équipe qui marque


currentMatch.momentum.team1 = Math.min(5,
currentMatch.momentum.team1 + 2);
currentMatch.momentum.team2 = Math.max(-2,
currentMatch.momentum.team2 - 1);
} else {
// Tir cadré sans but
addMatchEvent(currentMatch,
shotOnTargetComments[Math.floor(Math.random() * shotOnTargetComments.length)]);
}
} else {
// Tir non-cadré
addMatchEvent(currentMatch,
shotComments[Math.floor(Math.random() * shotComments.length)]);
}
}

if (Math.random() < minuteProbTeam2 * 4) {


currentMatch.stats.shots.team2++;

// Sélectionner un commentaire aléatoire pour le tir


const shotComments = [
`🥅 Tir de ${currentMatch.team2}!`,
`🥅 Tentative de ${currentMatch.team2}!`,
`🥅 ${currentMatch.team2} tente sa chance!`,
`🥅 Frappe de ${currentMatch.team2}!`
];

// Tir cadré?
if (Math.random() < 0.6) { // 60% des tirs sont cadrés
currentMatch.stats.shotsOnTarget.team2++;

const shotOnTargetComments = [
`🎯 Tir cadré de ${currentMatch.team2}!`,
`🎯 Belle tentative de ${currentMatch.team2}!`,
`🎯 ${currentMatch.team2} teste le gardien adverse.`,
`🎯 Tir puissant de ${currentMatch.team2}!`
];

if (Math.random() < minuteProbTeam2 / (minuteProbTeam2 * 4 *


0.6)) {
// But pour l'équipe 2
currentMatch.score2++;
team2Event = 'goal';
// Sélectionner un commentaire en fonction du contexte
let goalComment = '';

if (currentMatch.score2 === 1 && currentMatch.score1 === 0)


{
goalComment = `⚽ BUT! ${currentMatch.team2} ouvre le
score!`;
} else if (currentMatch.score1 === currentMatch.score2) {
goalComment = `⚽ BUT! ${currentMatch.team2} égalise!`;
} else if (currentMatch.score2 > currentMatch.score1) {
goalComment = `⚽ BUT! ${currentMatch.team2} creuse
l'écart!`;
} else {
goalComment = `⚽ BUT! ${currentMatch.team2} réduit
l'écart!`;
}

addMatchEvent(currentMatch, goalComment);

// Notification de but
const score = `${currentMatch.team1} $
{currentMatch.score1}-${currentMatch.score2} ${currentMatch.team2}`;
showInAppNotification(`⚽ BUT! ${currentMatch.team2} marque!
(${score})`, 'success', true);

// Afficher l'animation de but


showGoalAnimation(currentMatch.team2);

// Changement de possession après un but


currentMatch.possessionTeam = currentMatch.team1;

// Boost de momentum pour l'équipe qui marque


currentMatch.momentum.team2 = Math.min(5,
currentMatch.momentum.team2 + 2);
currentMatch.momentum.team1 = Math.max(-2,
currentMatch.momentum.team1 - 1);
} else {
// Tir cadré sans but
addMatchEvent(currentMatch,
shotOnTargetComments[Math.floor(Math.random() * shotOnTargetComments.length)]);
}
} else {
// Tir non-cadré
addMatchEvent(currentMatch,
shotComments[Math.floor(Math.random() * shotComments.length)]);
}
}

// Simulation des cartons (plus probable si fatigue élevée et


discipline faible)
simulateCards();

// Simulation des actions notables (si pas de but)


if (!team1Event && !team2Event) {
simulateNotableActions();
}

// Mise à jour du momentum basé sur les événements


updateMomentum();
}

// Simulation des cartons


function simulateCards() {
// Moyennes réalistes pour un match de foot complet:
// - Moyenne de cartons jaunes par match: ~3-4
// - Moyenne de cartons rouges par match: ~0.2-0.3 (1 tous les 5 matchs
environ)

// Probabilités de base
const baseYellowProb = 0.01;
const baseRedProb = 0.001;

// Limites de cartons par équipe pour un match réaliste


const maxYellowsPerTeam = 4; // Maximum 4 jaunes par équipe
const maxRedsPerTeam = 1; // Maximum 1 rouge par équipe
généralement

// Vérifier si les équipes ont déjà atteint les limites


if (currentMatch.stats.yellowCards.team1 >= maxYellowsPerTeam &&
currentMatch.stats.yellowCards.team2 >= maxYellowsPerTeam) {
return; // Ne plus donner de cartons jaunes
}

if (currentMatch.stats.redCards.team1 >= maxRedsPerTeam &&


currentMatch.stats.redCards.team2 >= maxRedsPerTeam) {
return; // Ne plus donner de cartons rouges
}

// Convertir la discipline (5 = très discipliné, 1 = indiscipliné) en


facteur
const team1DisciplineFactor = 1 + ((5 - currentMatch.team1Discipline) *
0.2); // 1.0 à 1.8
const team2DisciplineFactor = 1 + ((5 - currentMatch.team2Discipline) *
0.2);

// Facteur de fatigue - plus de chances de cartons avec fatigue


const team1FatigueFactor = 1 + ((1 - currentMatch.fatigue.team1) *
0.5);
const team2FatigueFactor = 1 + ((1 - currentMatch.fatigue.team2) *
0.5);

// L'intensité du match impacte les cartons


const intensityFactor = Math.min(1.5, currentMatch.intensity);

// Facteurs temporels: plus de cartons en fin de match


let temporalFactor = 1.0;
if (currentMatch.minute > 75) temporalFactor = 1.3;
else if (currentMatch.minute > 60) temporalFactor = 1.2;

// Match serré = plus de cartons


const scoreDiffFactor = Math.abs(currentMatch.score1 -
currentMatch.score2) <= 1 ? 1.2 : 1.0;

// Derbys = plus de cartons


const derbyFactor = currentMatch.matchType === "Derby" ? 1.5 : 1.0;

// Probabilités finales
let team1YellowProb = baseYellowProb * team1DisciplineFactor *
team1FatigueFactor *
intensityFactor * temporalFactor * scoreDiffFactor
* derbyFactor;
let team2YellowProb = baseYellowProb * team2DisciplineFactor *
team2FatigueFactor *
intensityFactor * temporalFactor * scoreDiffFactor
* derbyFactor;

// Probabilité de rouge uniquement si déjà jaune


let team1RedProb = currentMatch.stats.yellowCards.team1 > 0 ?
baseRedProb * team1DisciplineFactor * team1FatigueFactor *
intensityFactor * temporalFactor : 0;
let team2RedProb = currentMatch.stats.yellowCards.team2 > 0 ?
baseRedProb * team2DisciplineFactor * team2FatigueFactor *
intensityFactor * temporalFactor : 0;

// Limite pour les équipes à 10


if (currentMatch.stats.redCards.team1 > 0) team1RedProb *= 0.2;
if (currentMatch.stats.redCards.team2 > 0) team2RedProb *= 0.2;

// Simulation
if (Math.random() < team1YellowProb &&
currentMatch.stats.yellowCards.team1 < maxYellowsPerTeam) {
currentMatch.stats.yellowCards.team1++;

const cardComments = [
`🟨 Carton jaune pour ${currentMatch.team1}!`,
`🟨 Le joueur de ${currentMatch.team1} est averti par
l'arbitre.`,
`🟨 Faute dangereuse, carton jaune pour ${currentMatch.team1}.`
];

const comment = cardComments[Math.floor(Math.random() *


cardComments.length)];
addMatchEvent(currentMatch, comment);

// Notification de carton jaune


showInAppNotification(`🟨 Carton jaune pour ${currentMatch.team1}`,
'warning', currentMatch.backgroundNotifications);
}

if (Math.random() < team2YellowProb &&


currentMatch.stats.yellowCards.team2 < maxYellowsPerTeam) {
currentMatch.stats.yellowCards.team2++;

const cardComments = [
`🟨 Carton jaune pour ${currentMatch.team2}!`,
`🟨 Le joueur de ${currentMatch.team2} est averti par
l'arbitre.`,
`🟨 Faute dangereuse, carton jaune pour ${currentMatch.team2}.`
];

const comment = cardComments[Math.floor(Math.random() *


cardComments.length)];
addMatchEvent(currentMatch, comment);

// Notification de carton jaune


showInAppNotification(`🟨 Carton jaune pour ${currentMatch.team2}`,
'warning', currentMatch.backgroundNotifications);
}

if (Math.random() < team1RedProb && currentMatch.stats.redCards.team1 <


maxRedsPerTeam) {
currentMatch.stats.redCards.team1++;

const cardComments = [
`🟥 Carton rouge! ${currentMatch.team1} joue à ${10 -
currentMatch.stats.redCards.team1}!`,
`🟥 Expulsion pour ${currentMatch.team1}! Faute grave.`,
`🟥 ${currentMatch.team1} perd un joueur sur carton rouge!`
];

const comment = cardComments[Math.floor(Math.random() *


cardComments.length)];
addMatchEvent(currentMatch, comment);

// Notification de carton rouge


showInAppNotification(`🟥 Carton rouge pour ${currentMatch.team1}!
Expulsion!`, 'error', true);

// Impact important sur la dynamique du match


currentMatch.momentum.team1 = Math.max(-5,
currentMatch.momentum.team1 - 3);
currentMatch.momentum.team2 = Math.min(5,
currentMatch.momentum.team2 + 2);
}

if (Math.random() < team2RedProb && currentMatch.stats.redCards.team2 <


maxRedsPerTeam) {
currentMatch.stats.redCards.team2++;

const cardComments = [
`🟥 Carton rouge! ${currentMatch.team2} joue à ${10 -
currentMatch.stats.redCards.team2}!`,
`🟥 Expulsion pour ${currentMatch.team2}! Faute grave.`,
`🟥 ${currentMatch.team2} perd un joueur sur carton rouge!`
];

const comment = cardComments[Math.floor(Math.random() *


cardComments.length)];
addMatchEvent(currentMatch, comment);

// Notification de carton rouge


showInAppNotification(`🟥 Carton rouge pour ${currentMatch.team2}!
Expulsion!`, 'error', true);

// Impact important sur la dynamique du match


currentMatch.momentum.team2 = Math.max(-5,
currentMatch.momentum.team2 - 3);
currentMatch.momentum.team1 = Math.min(5,
currentMatch.momentum.team1 + 2);
}
}

// Simulation des actions notables


function simulateNotableActions() {
// Probabilité d'action basée sur qui a la possession
const possessionTeam = currentMatch.possessionTeam;
const otherTeam = possessionTeam === currentMatch.team1 ?
currentMatch.team2 : currentMatch.team1;

// Probabilités de base pour les actions


let attackProb = 0.20; // Attaque
let shotProb = 0.10; // Tir
let cornerProb = 0.08; // Corner

// Facteur de possession pour ajuster les probabilités


const possRate = currentMatch.stats.possession.current / 100;
const possessionFactor = possessionTeam === currentMatch.team1 ?
possRate : (1 - possRate);

// Facteur de forme
const formFactor = possessionTeam === currentMatch.team1 ?
(currentMatch.formTeam1 ? (currentMatch.formTeam1.winRate / 100) *
0.5 + 0.75 : 1) :
(currentMatch.formTeam2 ? (currentMatch.formTeam2.winRate / 100) *
0.5 + 0.75 : 1);

// Facteur de la catégorie de l'équipe


const strengthFactor = possessionTeam === currentMatch.team1 ?
currentMatch.team1StrengthFactor :
currentMatch.team2StrengthFactor;

if (possessionTeam === currentMatch.team1) {


// Ajuster selon le style de l'équipe 1
if (currentMatch.team1Style === "Offensive") {
attackProb = 0.25;
shotProb = 0.15;
cornerProb = 0.10;
} else if (currentMatch.team1Style === "Defensive") {
attackProb = 0.15;
shotProb = 0.08;
cornerProb = 0.05;
}
} else {
// Ajuster selon le style de l'équipe 2
if (currentMatch.team2Style === "Offensive") {
attackProb = 0.25;
shotProb = 0.15;
cornerProb = 0.10;
} else if (currentMatch.team2Style === "Defensive") {
attackProb = 0.15;
shotProb = 0.08;
cornerProb = 0.05;
}
}

// Appliquer les facteurs


attackProb *= possessionFactor * formFactor * strengthFactor;
shotProb *= possessionFactor * formFactor * strengthFactor;
cornerProb *= possessionFactor * formFactor * strengthFactor;

// Ajuster selon la fatigue


const fatigueFactor = possessionTeam === currentMatch.team1 ?
currentMatch.fatigue.team1 : currentMatch.fatigue.team2;
attackProb *= fatigueFactor;
shotProb *= fatigueFactor;
// Ajuster selon les cartons rouges
if ((possessionTeam === currentMatch.team1 &&
currentMatch.stats.redCards.team1 > 0) ||
(possessionTeam === currentMatch.team2 &&
currentMatch.stats.redCards.team2 > 0)) {
attackProb *= 0.6;
shotProb *= 0.5;
}

// Commentaires variés pour les actions


const attackComments = [
`${possessionTeam} lance une attaque!`,
`${possessionTeam} avance sur le terrain.`,
`Belle progression de ${possessionTeam}!`,
`${possessionTeam} met la pression.`
];

const shotComments = [
`Tir non cadré de ${possessionTeam}!`,
`${possessionTeam} tente sa chance, mais ça passe à côté.`,
`Tentative manquée pour ${possessionTeam}.`,
`Le tir de ${possessionTeam} n'inquiète pas le gardien adverse.`
];

const cornerComments = [
`${possessionTeam} obtient un corner!`,
`Corner pour ${possessionTeam}.`,
`${possessionTeam} va tirer un corner.`,
`L'arbitre accorde un corner à ${possessionTeam}.`
];

// Simulation des actions


if (Math.random() < attackProb) {
addMatchEvent(currentMatch, attackComments[Math.floor(Math.random()
* attackComments.length)]);

// Mettre à jour les statistiques


if (possessionTeam === currentMatch.team1) {
currentMatch.stats.dangerousAttacks.team1++;
} else {
currentMatch.stats.dangerousAttacks.team2++;
}

// Possibilité d'un tir à la suite d'une attaque


if (Math.random() < shotProb) {
if (possessionTeam === currentMatch.team1) {
currentMatch.stats.shots.team1++;
} else {
currentMatch.stats.shots.team2++;
}

addMatchEvent(currentMatch,
shotComments[Math.floor(Math.random() * shotComments.length)]);

// Possibilité d'un corner à la suite d'un tir


if (Math.random() < cornerProb) {
addMatchEvent(currentMatch,
cornerComments[Math.floor(Math.random() * cornerComments.length)]);
// Mettre à jour les statistiques
if (possessionTeam === currentMatch.team1) {
currentMatch.stats.corners.team1++;
} else {
currentMatch.stats.corners.team2++;
}
}
}
}
}

// Mise à jour de la fatigue


function updateFatigue() {
// La fatigue augmente plus rapidement après la 60e minute
let fatigueRate = currentMatch.minute < 60 ? 0.003 : 0.006;

// L'intensité élevée accélère la fatigue


fatigueRate *= currentMatch.intensity;

// Ajustement selon le style de jeu


if (currentMatch.team1Style === "Offensive") {
currentMatch.fatigue.team1 = Math.max(0.7,
currentMatch.fatigue.team1 - fatigueRate * 1.2);
} else if (currentMatch.team1Style === "Defensive") {
currentMatch.fatigue.team1 = Math.max(0.7,
currentMatch.fatigue.team1 - fatigueRate * 0.9);
} else {
currentMatch.fatigue.team1 = Math.max(0.7,
currentMatch.fatigue.team1 - fatigueRate);
}

if (currentMatch.team2Style === "Offensive") {


currentMatch.fatigue.team2 = Math.max(0.7,
currentMatch.fatigue.team2 - fatigueRate * 1.2);
} else if (currentMatch.team2Style === "Defensive") {
currentMatch.fatigue.team2 = Math.max(0.7,
currentMatch.fatigue.team2 - fatigueRate * 0.9);
} else {
currentMatch.fatigue.team2 = Math.max(0.7,
currentMatch.fatigue.team2 - fatigueRate);
}

// Les cartons rouges augmentent la fatigue de l'équipe concernée


if (currentMatch.stats.redCards.team1 > 0) {
currentMatch.fatigue.team1 = Math.max(0.6,
currentMatch.fatigue.team1 - 0.002 * currentMatch.stats.redCards.team1);
}

if (currentMatch.stats.redCards.team2 > 0) {
currentMatch.fatigue.team2 = Math.max(0.6,
currentMatch.fatigue.team2 - 0.002 * currentMatch.stats.redCards.team2);
}
}

// Mise à jour de l'intensité


function updateIntensity() {
// Option : facteur d'intensité aléatoire
if (currentMatch.intensityFactor) {
// L'intensité varie naturellement au cours du match
if (Math.random() < 0.1) {
// 10% de chance de changement d'intensité
const change = (Math.random() - 0.5) * 0.3; // Entre -0.15 et
+0.15
currentMatch.intensity = Math.min(Math.max(0.8,
currentMatch.intensity + change), 1.5);
}
}

// Les derbys sont plus intenses


if (currentMatch.matchType === "Derby") {
currentMatch.intensity = Math.min(1.5, currentMatch.intensity +
0.1);
}

// Les fins de match serrées sont plus intenses


if (currentMatch.minute > 75 && Math.abs(currentMatch.score1 -
currentMatch.score2) <= 1) {
currentMatch.intensity = Math.min(1.5, currentMatch.intensity +
0.1);
}

// Un écart de buts important réduit l'intensité


if (Math.abs(currentMatch.score1 - currentMatch.score2) >= 3) {
currentMatch.intensity = Math.max(0.8, currentMatch.intensity -
0.1);
}

// L'équipe qui perd augmente son intensité en fin de match


if (currentMatch.minute > 75) {
if (currentMatch.score1 < currentMatch.score2) {
currentMatch.intensity = Math.min(1.5, currentMatch.intensity +
0.05);
} else if (currentMatch.score1 > currentMatch.score2) {
currentMatch.intensity = Math.min(1.5, currentMatch.intensity +
0.05);
}
}
}

// Mise à jour de la possession


function updatePossession() {
// Base: utiliser les valeurs de possession des équipes
const team1BasePoss = currentMatch.stats.possession.team1;
const team2BasePoss = currentMatch.stats.possession.team2;

// Facteurs qui influencent la possession actuelle

// 1. Équipe qui a actuellement la possession garde une inertie


const possFactor = currentMatch.possessionTeam === currentMatch.team1 ?
0.2 : -0.2;

// 2. Impact des styles de jeu


let styleFactor = 0;
if (currentMatch.team1Style === "Offensive" &&
currentMatch.team2Style !== "Offensive") styleFactor += 0.05;
if (currentMatch.team1Style === "Defensive" &&
currentMatch.team2Style !== "Defensive") styleFactor -= 0.05;
if (currentMatch.team2Style === "Offensive" &&
currentMatch.team1Style !== "Offensive") styleFactor -= 0.05;
if (currentMatch.team2Style === "Defensive" &&
currentMatch.team1Style !== "Defensive") styleFactor += 0.05;

// 3. Impact des cartons rouges


const redCardsFactor = (currentMatch.stats.redCards.team2 -
currentMatch.stats.redCards.team1) * 0.15;

// 4. Impact du score (l'équipe qui mène se replie souvent)


let scoreFactor = 0;
if (currentMatch.score1 > currentMatch.score2) scoreFactor -= 0.05;
if (currentMatch.score1 < currentMatch.score2) scoreFactor += 0.05;

// 5. Impact de la fatigue
const fatigueFactor = (currentMatch.fatigue.team1 -
currentMatch.fatigue.team2) * 0.1;

// 6. Catégorie des équipes


let categoryFactor = 0;
if (currentMatch.team1Category === "Grande" &&
currentMatch.team2Category !== "Grande") categoryFactor += 0.05;
if (currentMatch.team2Category === "Grande" &&
currentMatch.team1Category !== "Grande") categoryFactor -= 0.05;
if (currentMatch.team1Category === "Petite" &&
currentMatch.team2Category !== "Petite") categoryFactor -= 0.05;
if (currentMatch.team2Category === "Petite" &&
currentMatch.team1Category !== "Petite") categoryFactor += 0.05;

// 7. Calcul de la possession actuelle


let currentPoss = team1BasePoss +
(possFactor + styleFactor + redCardsFactor +
scoreFactor + fatigueFactor + categoryFactor) * 100;

// Borner entre 35% et 65% pour rester réaliste


currentPoss = Math.min(Math.max(35, currentPoss), 65);

// Mettre à jour la possession


currentMatch.stats.possession.current = currentPoss;

// Calculer la probabilité de changement de possession


let changeProb = 0.3; // Probabilité de base

// Facteurs qui affectent cette probabilité

// Équipe dominante garde plus facilement la possession


if (currentMatch.possessionTeam === currentMatch.team1 && currentPoss >
50) {
changeProb -= (currentPoss - 50) * 0.004;
} else if (currentMatch.possessionTeam === currentMatch.team2 &&
currentPoss < 50) {
changeProb -= (50 - currentPoss) * 0.004;
}

// Équipe avec carton rouge perd plus facilement la possession


if (currentMatch.possessionTeam === currentMatch.team1 &&
currentMatch.stats.redCards.team1 > 0) {
changeProb += 0.1 * currentMatch.stats.redCards.team1;
} else if (currentMatch.possessionTeam === currentMatch.team2 &&
currentMatch.stats.redCards.team2 > 0) {
changeProb += 0.1 * currentMatch.stats.redCards.team2;
}

// La fatigue augmente les pertes de balle


if (currentMatch.possessionTeam === currentMatch.team1) {
changeProb += (1 - currentMatch.fatigue.team1) * 0.2;
} else {
changeProb += (1 - currentMatch.fatigue.team2) * 0.2;
}

// Borner entre 0.15 et 0.8


changeProb = Math.min(Math.max(0.15, changeProb), 0.8);

// Décision finale de changement de possession


if (Math.random() < changeProb) {
currentMatch.possessionTeam = currentMatch.possessionTeam ===
currentMatch.team1 ?
currentMatch.team2 : currentMatch.team1;
}
}

// Mise à jour du momentum


function updateMomentum() {
// Décroissance naturelle du momentum (retour à l'équilibre)
if (currentMatch.momentum.team1 > 0) currentMatch.momentum.team1 -=
0.05;
if (currentMatch.momentum.team1 < 0) currentMatch.momentum.team1 +=
0.05;
if (currentMatch.momentum.team2 > 0) currentMatch.momentum.team2 -=
0.05;
if (currentMatch.momentum.team2 < 0) currentMatch.momentum.team2 +=
0.05;

// Impact de la possession actuelle


const possessionImpact = (currentMatch.stats.possession.current - 50) *
0.02;
currentMatch.momentum.team1 += possessionImpact;
currentMatch.momentum.team2 -= possessionImpact;

// Moments clés du match - regain de momentum


if (currentMatch.minute === 46 || currentMatch.minute === 60 ||
currentMatch.minute === 75) {
// L'équipe perdante reçoit un boost
if (currentMatch.score1 < currentMatch.score2) {
currentMatch.momentum.team1 = Math.min(5,
currentMatch.momentum.team1 + 1);
} else if (currentMatch.score1 > currentMatch.score2) {
currentMatch.momentum.team2 = Math.min(5,
currentMatch.momentum.team2 + 1);
}
}

// Borner le momentum entre -5 et 5


currentMatch.momentum.team1 = Math.min(Math.max(-5,
currentMatch.momentum.team1), 5);
currentMatch.momentum.team2 = Math.min(Math.max(-5,
currentMatch.momentum.team2), 5);
}
// Ajouter un événement au match
function addMatchEvent(match, description) {
// Créer l'objet événement
const event = {
minute: match.minute,
description: description,
timestamp: new Date().toISOString()
};

// Ajouter à l'historique
match.events.push(event);

// Afficher l'événement si l'option est activée


if (match.commentaryEnabled) {
const commentaryList = document.getElementById('commentaryList');
const eventItem = document.createElement('li');
eventItem.className = 'commentary-item';
eventItem.innerHTML = `<span class="commentary-time">$
{match.minute}'</span> ${description}`;
commentaryList.prepend(eventItem);
}
}

// Terminer le match
function finishMatch() {
if (!currentMatch) return;

// Marquer le match comme terminé


currentMatch.status = "Terminé";
currentMatch.endTime = new Date();

// Ajouter un événement de fin de match


addMatchEvent(currentMatch, `🏁 Match terminé! Score final: $
{currentMatch.team1} ${currentMatch.score1} - ${currentMatch.score2} $
{currentMatch.team2}`);

// Notification de fin de match


showInAppNotification(`🏁 Match terminé! ${currentMatch.team1} $
{currentMatch.score1}-${currentMatch.score2} ${currentMatch.team2}`, 'info', true);

// Mettre à jour l'affichage


updateMatchDisplay();

// Afficher le résumé du match


displayMatchSummary();

// Stopper le timer
clearTimeout(simulationTimer);

showNotification(`Match terminé: ${currentMatch.team1} $


{currentMatch.score1} - ${currentMatch.score2} ${currentMatch.team2}`, 'success');
}

// Afficher ou mettre à jour l'interface du match


function updateMatchDisplay() {
if (!currentMatch) return;

// Mettre à jour le temps


const matchTimeDisplay = currentMatch.minute >= 45 &&
currentMatch.minute < 46 ?
"Mi-temps" : (currentMatch.minute > 90 ? "90+" :
currentMatch.minute + "'");
document.getElementById('matchTime').textContent = matchTimeDisplay;

// Mettre à jour les équipes et le score


document.getElementById('homeTeam').textContent = currentMatch.team1;
document.getElementById('awayTeam').textContent = currentMatch.team2;
document.getElementById('homeScore').textContent = currentMatch.score1;
document.getElementById('awayScore').textContent = currentMatch.score2;

// Mettre à jour les informations du match


document.getElementById('matchType').textContent =
currentMatch.matchType;

// Déterminer l'info contextuelle


let matchInfoText = '';

if (currentMatch.minute < 45) {


matchInfoText = '1ère mi-temps';
} else if (currentMatch.minute === 45) {
matchInfoText = 'Mi-temps';
} else if (currentMatch.minute > 45 && currentMatch.minute <= 90) {
matchInfoText = '2ème mi-temps';
} else if (currentMatch.minute > 90) {
matchInfoText = 'Temps additionnel';
}

document.getElementById('matchInfo').textContent = matchInfoText;

// Mettre à jour les statistiques


updateStats();
}

// Mettre à jour les statistiques affichées


function updateStats() {
// Possession
const homePossession =
Math.round(currentMatch.stats.possession.current);
const awayPossession = 100 - homePossession;
document.getElementById('homePossession').textContent = homePossession
+ "%";
document.getElementById('awayPossession').textContent = awayPossession
+ "%";
document.getElementById('possessionBar').style.width = homePossession +
"%";

// Tirs
document.getElementById('homeShots').textContent =
currentMatch.stats.shots.team1;
document.getElementById('awayShots').textContent =
currentMatch.stats.shots.team2;

const totalShots = currentMatch.stats.shots.team1 +


currentMatch.stats.shots.team2;
const shotsPct = totalShots === 0 ? 50 :
Math.round((currentMatch.stats.shots.team1 / totalShots) * 100);
document.getElementById('shotsBar').style.width = shotsPct + "%";
// Tirs cadrés
document.getElementById('homeShotsOnTarget').textContent =
currentMatch.stats.shotsOnTarget.team1;
document.getElementById('awayShotsOnTarget').textContent =
currentMatch.stats.shotsOnTarget.team2;

const totalShotsOnTarget = currentMatch.stats.shotsOnTarget.team1 +


currentMatch.stats.shotsOnTarget.team2;
const shotsOnTargetPct = totalShotsOnTarget === 0 ? 50 :
Math.round((currentMatch.stats.shotsOnTarget.team1 /
totalShotsOnTarget) * 100);
document.getElementById('shotsOnTargetBar').style.width =
shotsOnTargetPct + "%";

// Corners
document.getElementById('homeCorners').textContent =
currentMatch.stats.corners.team1;
document.getElementById('awayCorners').textContent =
currentMatch.stats.corners.team2;

const totalCorners = currentMatch.stats.corners.team1 +


currentMatch.stats.corners.team2;
const cornersPct = totalCorners === 0 ? 50 :
Math.round((currentMatch.stats.corners.team1 / totalCorners) * 100);
document.getElementById('cornersBar').style.width = cornersPct + "%";

// Cartons
document.getElementById('homeYellowCards').textContent =
currentMatch.stats.yellowCards.team1;
document.getElementById('awayYellowCards').textContent =
currentMatch.stats.yellowCards.team2;
document.getElementById('homeRedCards').textContent =
currentMatch.stats.redCards.team1;
document.getElementById('awayRedCards').textContent =
currentMatch.stats.redCards.team2;

// Statistiques avancées (si activées)


if (currentMatch.advancedStats) {
document.getElementById('advancedStatsContainer').style.display =
'block';

// xG
document.getElementById('homeXG').textContent =
currentMatch.xgTeam1.toFixed(2);
document.getElementById('awayXG').textContent =
currentMatch.xgTeam2.toFixed(2);

const totalXG = currentMatch.xgTeam1 + currentMatch.xgTeam2;


const xgPct = totalXG === 0 ? 50 : Math.round((currentMatch.xgTeam1
/ totalXG) * 100);
document.getElementById('xgBar').style.width = xgPct + "%";

// Attaques dangereuses
document.getElementById('homeDangerousAttacks').textContent =
currentMatch.stats.dangerousAttacks.team1;
document.getElementById('awayDangerousAttacks').textContent =
currentMatch.stats.dangerousAttacks.team2;
const totalDangerousAttacks =
currentMatch.stats.dangerousAttacks.team1 +
currentMatch.stats.dangerousAttacks.team2;
const dangerousAttacksPct = totalDangerousAttacks === 0 ? 50 :
Math.round((currentMatch.stats.dangerousAttacks.team1 /
totalDangerousAttacks) * 100);
document.getElementById('dangerousAttacksBar').style.width =
dangerousAttacksPct + "%";

// Momentum
document.getElementById('homeMomentum').textContent =
currentMatch.momentum.team1.toFixed(1);
document.getElementById('awayMomentum').textContent =
currentMatch.momentum.team2.toFixed(1);

const momentumPct = 50 + Math.round((currentMatch.momentum.team1 -


currentMatch.momentum.team2) * 5);
document.getElementById('momentumBar').style.width = momentumPct +
"%";

// Fatigue
const homeFatigue = Math.round(currentMatch.fatigue.team1 * 100);
const awayFatigue = Math.round(currentMatch.fatigue.team2 * 100);
document.getElementById('homeFatigue').textContent = homeFatigue +
"%";
document.getElementById('awayFatigue').textContent = awayFatigue +
"%";

const fatiguePct = Math.round((homeFatigue / (homeFatigue +


awayFatigue)) * 100);
document.getElementById('fatigueBar').style.width = fatiguePct +
"%";
} else {
document.getElementById('advancedStatsContainer').style.display =
'none';
}
}

// Afficher le résumé du match


function displayMatchSummary() {
const summaryElement = document.getElementById('matchSummary');
const summaryContent = document.getElementById('matchSummaryContent');

if (!currentMatch || !summaryElement || !summaryContent) return;

// Générer le contenu du résumé


let summaryHTML = `
<div class="text-center mb-4">
<div class="text-2xl font-bold mb-2">
${currentMatch.team1} ${currentMatch.score1} - $
{currentMatch.score2} ${currentMatch.team2}
</div>
<div class="text-sm text-muted">
Match ${currentMatch.matchType} joué le ${new
Date(currentMatch.startTime).toLocaleDateString()}
à ${new Date(currentMatch.startTime).toLocaleTimeString()}
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
<div class="card">
<h4 class="font-bold mb-2">Statistiques du match</h4>
<div class="grid grid-cols-3 gap-2">
<div class="text-right">$
{currentMatch.stats.shots.team1}</div>
<div class="text-center">Tirs</div>
<div>${currentMatch.stats.shots.team2}</div>

<div class="text-right">$
{currentMatch.stats.shotsOnTarget.team1}</div>
<div class="text-center">Tirs cadrés</div>
<div>${currentMatch.stats.shotsOnTarget.team2}</div>

<div class="text-right">$
{currentMatch.stats.corners.team1}</div>
<div class="text-center">Corners</div>
<div>${currentMatch.stats.corners.team2}</div>

<div class="text-right">$
{Math.round(currentMatch.stats.possession.current)}%</div>
<div class="text-center">Possession</div>
<div>${100 -
Math.round(currentMatch.stats.possession.current)}%</div>

<div class="text-right">$
{currentMatch.stats.yellowCards.team1}</div>
<div class="text-center">Cartons jaunes</div>
<div>${currentMatch.stats.yellowCards.team2}</div>

<div class="text-right">$
{currentMatch.stats.redCards.team1}</div>
<div class="text-center">Cartons rouges</div>
<div>${currentMatch.stats.redCards.team2}</div>

<div class="text-right">$
{currentMatch.xgTeam1.toFixed(2)}</div>
<div class="text-center">xG</div>
<div>${currentMatch.xgTeam2.toFixed(2)}</div>
</div>
</div>

<div class="card">
<h4 class="font-bold mb-2">Moments clés</h4>
<ul class="text-sm space-y-1" style="max-height: 200px;
overflow-y: auto;">
`;

// Filtrer les moments clés (buts, cartons, etc.)


const keyEvents = currentMatch.events.filter(event =>
event.description.includes('⚽') ||
event.description.includes('🟨') ||
event.description.includes('🟥') ||
event.description.includes('🏁')
);

// Ajouter les moments clés


keyEvents.forEach(event => {
summaryHTML += `<li><span
class="font-bold">${event.minute}':</span> ${event.description}</li>`;
});

summaryHTML += `
</ul>
</div>
</div>

<div class="card">
<h4 class="font-bold mb-2">Analyse du match</h4>
<p>${generateMatchAnalysis()}</p>
</div>
`;

// Mettre à jour le contenu et afficher le résumé


summaryContent.innerHTML = summaryHTML;
summaryElement.style.display = 'block';
}

// Générer une analyse du match


function generateMatchAnalysis() {
if (!currentMatch) return "";

let analysis = "";

// Résultat du match
if (currentMatch.score1 > currentMatch.score2) {
analysis += `${currentMatch.team1} s'impose contre $
{currentMatch.team2} avec un score de ${currentMatch.score1}-$
{currentMatch.score2}. `;
} else if (currentMatch.score1 < currentMatch.score2) {
analysis += `${currentMatch.team2} l'emporte face à $
{currentMatch.team1} avec un score de ${currentMatch.score2}-$
{currentMatch.score1}. `;
} else {
analysis += `Match nul ${currentMatch.score1}-$
{currentMatch.score2} entre ${currentMatch.team1} et ${currentMatch.team2}. `;
}

// Possession
const homePossession =
Math.round(currentMatch.stats.possession.current);
if (homePossession > 55) {
analysis += `${currentMatch.team1} a dominé la possession avec $
{homePossession}% du temps de jeu. `;
} else if (homePossession < 45) {
analysis += `${currentMatch.team2} a dominé la possession avec
${100-homePossession}% du temps de jeu. `;
} else {
analysis += `La possession a été équilibrée (${homePossession}%-
${100-homePossession}%). `;
}

// Occasions
const homeShotsOnTarget = currentMatch.stats.shotsOnTarget.team1;
const awayShotsOnTarget = currentMatch.stats.shotsOnTarget.team2;
const totalShots = currentMatch.stats.shots.team1 +
currentMatch.stats.shots.team2;
if (totalShots > 20) {
analysis += "Match très offensif avec de nombreuses occasions. ";
} else if (totalShots < 10) {
analysis += "Match fermé avec peu d'occasions franches. ";
}

if (Math.abs(homeShotsOnTarget - awayShotsOnTarget) >= 3) {


const dominantTeam = homeShotsOnTarget > awayShotsOnTarget ?
currentMatch.team1 : currentMatch.team2;
const shotsDiff = Math.abs(homeShotsOnTarget - awayShotsOnTarget);
analysis += `${dominantTeam} a été plus dangereux avec ${shotsDiff}
tirs cadrés de plus que son adversaire. `;
}

// Cartons
const totalCards = currentMatch.stats.yellowCards.team1 +
currentMatch.stats.yellowCards.team2 +
currentMatch.stats.redCards.team1 +
currentMatch.stats.redCards.team2;

if (totalCards >= 6) {
analysis += "Match tendu avec de nombreux cartons. ";
}

if (currentMatch.stats.redCards.team1 > 0 ||
currentMatch.stats.redCards.team2 > 0) {
const teamWithRed = currentMatch.stats.redCards.team1 > 0 ?
currentMatch.team1 : currentMatch.team2;
analysis += `${teamWithRed} a terminé en infériorité numérique. `;
}

// Efficacité
const team1Efficiency = currentMatch.stats.shotsOnTarget.team1 > 0 ?
currentMatch.score1 /
currentMatch.stats.shotsOnTarget.team1 : 0;
const team2Efficiency = currentMatch.stats.shotsOnTarget.team2 > 0 ?
currentMatch.score2 /
currentMatch.stats.shotsOnTarget.team2 : 0;

if (team1Efficiency > 0.5 && currentMatch.stats.shotsOnTarget.team1 >=


2) {
analysis += `${currentMatch.team1} a été très efficace devant le
but. `;
}

if (team2Efficiency > 0.5 && currentMatch.stats.shotsOnTarget.team2 >=


2) {
analysis += `${currentMatch.team2} a été très efficace devant le
but. `;
}

if (homeShotsOnTarget > 5 && currentMatch.score1 === 0) {


analysis += `Malgré de nombreuses occasions, ${currentMatch.team1}
n'a pas réussi à marquer. `;
}

if (awayShotsOnTarget > 5 && currentMatch.score2 === 0) {


analysis += `Malgré de nombreuses occasions, ${currentMatch.team2}
n'a pas réussi à marquer. `;
}

// Conclusion
if (currentMatch.score1 + currentMatch.score2 >= 4) {
analysis += "Un match spectaculaire avec beaucoup de buts!";
} else if (currentMatch.score1 + currentMatch.score2 <= 1) {
analysis += "Un match plutôt fermé au niveau du score.";
} else if (Math.abs(currentMatch.score1 - currentMatch.score2) >= 3) {
const dominantTeam = currentMatch.score1 > currentMatch.score2 ?
currentMatch.team1 : currentMatch.team2;
analysis += `Victoire écrasante de ${dominantTeam}.`;
} else if (currentMatch.score1 === currentMatch.score2) {
analysis += "Un match équilibré qui s'est soldé par un nul.";
} else {
analysis += "Un match disputé jusqu'au bout.";
}

return analysis;
}

// Sauvegarder le résultat du match dans l'historique


function saveMatchResult() {
if (!currentMatch) return;

// Créer un objet de résultat plus léger pour l'historique


const matchResult = {
id: currentMatch.id,
team1: currentMatch.team1,
team2: currentMatch.team2,
score1: currentMatch.score1,
score2: currentMatch.score2,
matchType: currentMatch.matchType,
date: currentMatch.startTime,
stats: {
possession: currentMatch.stats.possession.current,
shotsOnTarget: {
team1: currentMatch.stats.shotsOnTarget.team1,
team2: currentMatch.stats.shotsOnTarget.team2
},
corners: {
team1: currentMatch.stats.corners.team1,
team2: currentMatch.stats.corners.team2
},
cards: {
yellow1: currentMatch.stats.yellowCards.team1,
yellow2: currentMatch.stats.yellowCards.team2,
red1: currentMatch.stats.redCards.team1,
red2: currentMatch.stats.redCards.team2
}
},
xg: {
team1: currentMatch.xgTeam1,
team2: currentMatch.xgTeam2
},
events: currentMatch.events.filter(event =>
event.description.includes('⚽') ||
event.description.includes('🟨') ||
event.description.includes('🟥') ||
event.description.includes('🏁')
)
};

// Ajouter au début de l'historique


matchHistory.unshift(matchResult);

// Limiter l'historique à 20 matchs


if (matchHistory.length > 20) {
matchHistory.pop();
}

// Sauvegarder l'historique
saveData();

// Mettre à jour l'affichage de l'historique


updateMatchHistory();

showNotification('Résultat du match ajouté à l\'historique',


'success');
}

// Mettre à jour l'affichage de l'historique des matchs


function updateMatchHistory() {
const historyList = document.getElementById('matchHistoryList');

if (!historyList) return;

if (matchHistory.length === 0) {
historyList.innerHTML = `
<div class="text-center text-muted py-4">
<i class="fas fa-history fa-2x mb-2"></i>
<p>Aucun match dans l'historique</p>
<p class="text-sm">Lancez une simulation pour commencer</p>
</div>
`;
return;
}

let historyHTML = '';

matchHistory.forEach(match => {
// Déterminer le style en fonction du résultat
let resultStyle = '';
let resultText = '';

if (match.score1 > match.score2) {


resultStyle = 'text-success';
resultText = 'Victoire';
} else if (match.score1 < match.score2) {
resultStyle = 'text-danger';
resultText = 'Défaite';
} else {
resultStyle = 'text-warning';
resultText = 'Match nul';
}

historyHTML += `
<div class="match-history-item" data-match-id="${match.id}">
<div class="match-result">
<div class="flex items-center gap-2">
<span class="badge badge-primary">$
{match.matchType}</span>
<span class="font-bold">${match.team1} $
{match.score1} - ${match.score2} ${match.team2}</span>
</div>
<span class="${resultStyle}
font-bold">${resultText}</span>
</div>
<div class="match-details">
<div class="flex justify-between">
<span>Possession: $
{Math.round(match.stats.possession)}% - ${100-Math.round(match.stats.possession)}
%</span>
<span>xG: ${match.xg.team1.toFixed(2)} - $
{match.xg.team2.toFixed(2)}</span>
<span>Date: ${new
Date(match.date).toLocaleDateString()}</span>
</div>
</div>
</div>
`;
});

historyList.innerHTML = historyHTML;

// Ajouter des gestionnaires d'événements pour les éléments de


l'historique
const historyItems = document.querySelectorAll('.match-history-item');
historyItems.forEach(item => {
item.addEventListener('click', function() {
const matchId = this.getAttribute('data-match-id');
viewMatchFromHistory(matchId);
});
});
}

// Afficher les détails d'un match de l'historique


function viewMatchFromHistory(matchId) {
const match = matchHistory.find(m => m.id === matchId);

if (!match) {
showNotification('Match non trouvé dans l\'historique', 'error');
return;
}

// Créer un modal pour afficher les détails du match


const modalHTML = `
<div class="fixed inset-0 flex items-center justify-center z-50 bg-
black bg-opacity-50">
<div class="bg-card p-6 rounded-lg shadow-lg max-w-4xl w-full
mx-4 max-h-90vh overflow-y-auto" style="background-color: var(--bg-card); color:
var(--text-primary);">
<div class="flex justify-between items-center mb-4">
<h3 class="text-xl font-bold">Détails du Match</h3>
<button id="closeMatchModal" class="text-
2xl">&times;</button>
</div>
<div class="text-center mb-4">
<div class="text-2xl font-bold mb-2">
${match.team1} ${match.score1} - ${match.score2} $
{match.team2}
</div>
<div class="text-sm text-muted">
Match ${match.matchType} joué le ${new
Date(match.date).toLocaleDateString()}
à ${new Date(match.date).toLocaleTimeString()}
</div>
</div>

<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">


<div class="card">
<h4 class="font-bold mb-2">Statistiques du
match</h4>
<div class="grid grid-cols-3 gap-2">
<div class="text-right">$
{match.stats.shotsOnTarget.team1}</div>
<div class="text-center">Tirs cadrés</div>
<div>${match.stats.shotsOnTarget.team2}</div>

<div class="text-right">$
{match.stats.corners.team1}</div>
<div class="text-center">Corners</div>
<div>${match.stats.corners.team2}</div>

<div class="text-right">$
{Math.round(match.stats.possession)}%</div>
<div class="text-center">Possession</div>
<div>${100-Math.round(match.stats.possession)}
%</div>

<div class="text-right">$
{match.stats.cards.yellow1}</div>
<div class="text-center">Cartons jaunes</div>
<div>${match.stats.cards.yellow2}</div>

<div class="text-right">$
{match.stats.cards.red1}</div>
<div class="text-center">Cartons rouges</div>
<div>${match.stats.cards.red2}</div>

<div class="text-right">$
{match.xg.team1.toFixed(2)}</div>
<div class="text-center">xG</div>
<div>${match.xg.team2.toFixed(2)}</div>
</div>
</div>

<div class="card">
<h4 class="font-bold mb-2">Moments clés</h4>
<ul class="text-sm space-y-1" style="max-height:
200px; overflow-y: auto;">
${match.events.map(event =>
`<li><span class="font-bold">$
{event.minute}':</span> ${event.description}</li>`
).join('')}
</ul>
</div>
</div>
</div>
</div>
`;

// Ajouter le modal au document


const modalContainer = document.createElement('div');
modalContainer.innerHTML = modalHTML;
document.body.appendChild(modalContainer);

// Ajouter l'événement pour fermer le modal


document.getElementById('closeMatchModal').addEventListener('click',
function() {
modalContainer.remove();
});

// Fermer le modal en cliquant à l'extérieur


modalContainer.firstElementChild.addEventListener('click', function(e)
{
if (e.target === this) {
modalContainer.remove();
}
});
}

// Effacer l'historique des matchs


function clearMatchHistory() {
showConfirmation(
'Êtes-vous sûr de vouloir effacer tout l\'historique des matchs ?',
function() {
matchHistory = [];
saveData();
updateMatchHistory();
showNotification('Historique des matchs effacé', 'success');
}
);
}

// Fonction pour afficher une animation de but


function showGoalAnimation(team) {
const goalAnimation = document.getElementById('goalAnimation');
const goalTeam = document.getElementById('goalTeam');

if (!goalAnimation || !goalTeam) return;

goalTeam.textContent = team;
goalAnimation.style.display = 'flex';

// Cacher l'animation après 2 secondes


setTimeout(() => {
goalAnimation.style.display = 'none';
}, 2000);
}

// Fonctions de contrôle de la simulation


function togglePauseResume() {
if (!currentMatch) return;
simulationPaused = !simulationPaused;

const pauseResumeBtn = document.getElementById('pauseResumeBtn');

if (simulationPaused) {
pauseResumeBtn.innerHTML = '<i class="fas fa-play"></i> Reprendre';
clearTimeout(simulationTimer);
showNotification('Simulation en pause', 'info');
} else {
pauseResumeBtn.innerHTML = '<i class="fas fa-pause"></i> Pause';
simulateMatchMinutes();
showNotification('Simulation reprise', 'info');
}
}

function stopSimulation() {
if (!currentMatch) return;

showConfirmation(
'Êtes-vous sûr de vouloir arrêter la simulation ?',
function() {
clearTimeout(simulationTimer);

// Si le match n'est pas terminé, le terminer


if (currentMatch.status !== "Terminé") {
finishMatch();
}
}
);
}

function newSimulation() {
// Réinitialiser l'interface
document.getElementById('simulationContainer').style.display = 'none';
document.getElementById('matchSummary').style.display = 'none';
document.getElementById('matchSetupCard').style.display = 'block';

// Effacer la liste des commentaires


document.getElementById('commentaryList').innerHTML = '';

// Nettoyer la référence au match actuel


currentMatch = null;

// Effacer les timers


clearTimeout(simulationTimer);

showNotification('Prêt pour une nouvelle simulation', 'info');


}

function changeSimulationSpeed(speed) {
// Valider la vitesse
if (speed === 'real') {
CURRENT_SIMULATION_SPEED = DEFAULT_SIMULATION_SPEED;
showNotification('Simulation en temps quasi-réel (1 min = 60 sec)',
'info');
} else if (speed === 'fast') {
CURRENT_SIMULATION_SPEED = FAST_SIMULATION_SPEED;
showNotification('Simulation rapide (1 min = 5 sec)', 'info');
} else if (speed === 'very-fast') {
CURRENT_SIMULATION_SPEED = VERY_FAST_SIMULATION_SPEED;
showNotification('Simulation très rapide (1 min = 1 sec)', 'info');
}
}

/* =========================================================
GESTION DES MATCHS PLANIFIÉS
========================================================= */

// Mettre à jour l'affichage des matchs planifiés


function updateScheduledMatchesDisplay() {
const container = document.getElementById('scheduledMatchesList');
const emptyIndicator =
document.getElementById('scheduledMatchesEmpty');

if (scheduledMatches.length === 0) {
container.innerHTML = '';
emptyIndicator.style.display = 'block';
return;
}

emptyIndicator.style.display = 'none';

// Trier les matchs par date


const sortedMatches = [...scheduledMatches].sort((a, b) =>
a.scheduledAt - b.scheduledAt);

let html = '';


sortedMatches.forEach(match => {
const scheduledDate = new Date(match.scheduledAt);
const now = new Date();
const timeUntil = scheduledDate > now
? calculateTimeUntil(scheduledDate)
: 'Démarrage imminent';

const status = scheduledDate > now


? `<span class="badge badge-primary">Planifié</span>`
: `<span class="badge badge-warning">En attente</span>`;

html += `
<div class="scheduled-match-item" data-id="${match.id}">
<div>
<div class="scheduled-match-teams">${match.team1} vs $
{match.team2}</div>
<div class="scheduled-match-time">
${status}
${scheduledDate.toLocaleString()} (${timeUntil})
</div>
</div>
<div class="scheduled-match-buttons">
<button class="btn btn-sm btn-primary start-now-btn"
data-id="${match.id}">
<i class="fas fa-play"></i>
</button>
<button class="btn btn-sm btn-danger delete-scheduled-
btn" data-id="${match.id}">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
`;
});

container.innerHTML = html;

// Ajouter les gestionnaires d'événements


document.querySelectorAll('.start-now-btn').forEach(btn => {
btn.addEventListener('click', function() {
const matchId = this.getAttribute('data-id');
startScheduledMatch(matchId);
});
});

document.querySelectorAll('.delete-scheduled-btn').forEach(btn => {
btn.addEventListener('click', function() {
const matchId = this.getAttribute('data-id');
deleteScheduledMatch(matchId);
});
});
}

// Calculer le temps restant jusqu'à un match planifié


function calculateTimeUntil(date) {
const now = new Date();
const diffMs = date - now;

if (diffMs <= 0) return 'Maintenant';

const diffSecs = Math.floor(diffMs / 1000);


const diffMins = Math.floor(diffSecs / 60);
const diffHours = Math.floor(diffMins / 60);
const diffDays = Math.floor(diffHours / 24);

if (diffDays > 0) {
return `Dans ${diffDays} jour${diffDays > 1 ? 's' : ''}`;
} else if (diffHours > 0) {
return `Dans ${diffHours} heure${diffHours > 1 ? 's' : ''}`;
} else if (diffMins > 0) {
return `Dans ${diffMins} minute${diffMins > 1 ? 's' : ''}`;
} else {
return `Dans quelques secondes`;
}
}

// Supprimer un match planifié


function deleteScheduledMatch(matchId) {
const matchIndex = scheduledMatches.findIndex(m => m.id === matchId);

if (matchIndex === -1) {


showNotification('Match planifié non trouvé', 'error');
return;
}

showConfirmation(
'Êtes-vous sûr de vouloir supprimer ce match planifié ?',
function() {
// Supprimer le timer associé
if (scheduledMatchesTimers[matchId]) {
clearTimeout(scheduledMatchesTimers[matchId]);
delete scheduledMatchesTimers[matchId];
}

// Supprimer le match de la liste


scheduledMatches.splice(matchIndex, 1);
saveData();

// Mettre à jour l'affichage


updateScheduledMatchesDisplay();

showNotification('Match planifié supprimé', 'success');


}
);
}

// Démarrer un match planifié immédiatement


function startScheduledMatch(matchId) {
const match = scheduledMatches.find(m => m.id === matchId);

if (!match) {
showNotification('Match planifié non trouvé', 'error');
return;
}

// Supprimer le timer associé


if (scheduledMatchesTimers[matchId]) {
clearTimeout(scheduledMatchesTimers[matchId]);
delete scheduledMatchesTimers[matchId];
}

// Supprimer le match de la liste des planifiés


const matchIndex = scheduledMatches.findIndex(m => m.id === matchId);
scheduledMatches.splice(matchIndex, 1);
saveData();

// Mettre à jour l'affichage


updateScheduledMatchesDisplay();

// Passer à l'onglet de configuration du match


document.querySelector('.tab[data-tab="match-setup"]').click();

// Remplir le formulaire avec les données du match planifié


document.getElementById('team1').value = match.team1;
document.getElementById('team2').value = match.team2;
document.getElementById('matchType').value = match.matchType;
document.getElementById('venue').value = match.venue;
document.getElementById('weather').value = match.weather;
document.getElementById('crowd').value = match.crowd;
document.getElementById('pastMatches').value =
match.pastMatches.join(',');
document.getElementById('last5Team1').value = match.last5Team1;
document.getElementById('last5Team2').value = match.last5Team2;
document.getElementById('commentaryEnabled').checked =
match.commentaryEnabled;
document.getElementById('advancedStats').checked = match.advancedStats;
document.getElementById('realismMode').checked = match.realismMode;
document.getElementById('intensityFactor').checked =
match.intensityFactor;
document.getElementById('backgroundNotifications').checked =
match.backgroundNotifications;

// Mettre à jour la vitesse de simulation


CURRENT_SIMULATION_SPEED = match.simulationSpeed ||
VERY_FAST_SIMULATION_SPEED;
document.querySelectorAll('.speed-option').forEach(btn => {
btn.classList.remove('active');
if ((match.simulationSpeed === DEFAULT_SIMULATION_SPEED &&
btn.getAttribute('data-speed') === 'real') ||
(match.simulationSpeed === FAST_SIMULATION_SPEED &&
btn.getAttribute('data-speed') === 'fast') ||
(match.simulationSpeed === VERY_FAST_SIMULATION_SPEED &&
btn.getAttribute('data-speed') === 'very-fast')) {
btn.classList.add('active');
}
});

// Mettre à jour les infos d'équipe


updateTeamInfo();

// Démarrer le match
startMatch();
}

// Planifier un match
function scheduleMatchNotification(match) {
const now = Date.now();
const scheduledTime = match.scheduledAt;

if (scheduledTime <= now) {


// Le match devrait déjà commencer
return;
}

// Calculer le délai avant le match


const delay = scheduledTime - now;

// Créer un timer pour la notification 5 minutes avant le match


if (delay > 5 * 60 * 1000) {
const preNotificationDelay = delay - 5 * 60 * 1000;

setTimeout(() => {
// Envoyer une notification 5 minutes avant
showInAppNotification(
`⏰ Match ${match.team1} vs ${match.team2} dans 5 minutes`,
'info',
true
);
}, preNotificationDelay);
}

// Créer un timer pour démarrer le match à l'heure prévue


scheduledMatchesTimers[match.id] = setTimeout(() => {
// Notification de démarrage
showInAppNotification(
`⚽ Le match ${match.team1} vs ${match.team2} commence
maintenant !`,
'success',
true
);

// Démarrer le match automatiquement


startScheduledMatch(match.id);
}, delay);
}

// Vérifier les matchs planifiés au démarrage


function checkScheduledMatches() {
// Nettoyer les matchs expirés
const now = Date.now();

// Filtrer les matchs qui sont planifiés pour plus tard


const validMatches = scheduledMatches.filter(match => match.scheduledAt
> now - 10 * 60 * 1000); // 10 minutes de grâce

// S'il y a des matchs expirés, les supprimer


if (validMatches.length < scheduledMatches.length) {
scheduledMatches = validMatches;
saveData();
}

// Planifier les notifications pour les matchs valides


validMatches.forEach(match => {
scheduleMatchNotification(match);
});

// Mettre à jour l'affichage


updateScheduledMatchesDisplay();
}

/* =========================================================
UTILITAIRES ET FONCTIONS DIVERSES
========================================================= */

// Afficher une notification


function showNotification(message, type = 'info', duration = 3000) {
const notificationsContainer =
document.getElementById('notifications');

// Créer la notification
const notification = document.createElement('div');
notification.className = `notification ${type}`;

// Ajouter l'icône selon le type


let icon;
switch (type) {
case 'success':
icon = 'fa-check-circle';
break;
case 'error':
icon = 'fa-times-circle';
break;
case 'warning':
icon = 'fa-exclamation-triangle';
break;
default:
icon = 'fa-info-circle';
}

notification.innerHTML = `
<i class="fas ${icon}"></i>
<span>${message}</span>
`;

// Ajouter la notification au conteneur


notificationsContainer.appendChild(notification);

// Définir un délai pour la suppression automatique


setTimeout(() => {
notification.style.animation = 'fadeout 0.3s ease forwards';
setTimeout(() => {
notification.remove();
}, 300);
}, duration);
}

// Afficher une notification en arrière-plan


function showInAppNotification(message, type = 'info', useBackground =
false) {
// Toujours afficher une notification in-app
showNotification(message, type);

// Si les notifications en arrière-plan sont désactivées, ne pas


continuer
if (!useBackground) return;

// Si l'API Notification n'est pas disponible, ne pas continuer


if (!('Notification' in window)) return;

// Demander la permission si nécessaire


if (Notification.permission === 'default') {
Notification.requestPermission();
return;
}

// Si la permission a été refusée, ne pas continuer


if (Notification.permission === 'denied') return;

// Déterminer l'icône en fonction du type


let icon;
switch (type) {
case 'success':
icon =
'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZp
ZXdCb3g9IjAgMCA1MTIgNTEyIj48cGF0aCBmaWxsPSIjMkVDQzcxIiBkPSJNMjU2IDhDMTE5IDggOCAxMTk
gOCAyNTZzMTExIDI0OCAyNDggMjQ4IDI0OC0xMTEgMjQ4LTI0OFMzOTMgOCAyNTYgOHptMCA0NDhjLTExMC
41IDAtMjAwLTg5LjUtMjAwLTIwMFMxNDUuNSA1NiAyNTYgNTZzMjAwIDg5LjUgMjAwIDIwMC04OS41IDIwM
C0yMDAgMjAwem0xMjkuOS0zMDMuMmM0LjcgNC43IDQuNyAxMi4zIDAgMTdMMjYzLjkgMzkxLjFjLTQuNyA0
LjctMTIuMyA0LjctMTcgMGwtMTI5LTEyOWMtNC43LTQuNy00LjctMTIuMyAwLTE3bDguNC04LjRjNC43LTQ
uNyAxMi4zLTQuNyAxNyAwbDEwMS42IDEwMS42IDE3Mi4xLTE3Mi4xYzQuNy00LjcgMTIuMy00LjcgMTcgMG
w4LjQgOC40eiI+PC9wYXRoPjwvc3ZnPg==';
break;
case 'error':
icon =
'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZp
ZXdCb3g9IjAgMCA1MTIgNTEyIj48cGF0aCBmaWxsPSIjRTc0QzNDIiBkPSJNMjU2IDhDMTE5IDggOCAxMTk
gOCAyNTZzMTExIDI0OCAyNDggMjQ4IDI0OC0xMTEgMjQ4LTI0OFMzOTMgOCAyNTYgOHptMCA0NDhjLTExMC
41IDAtMjAwLTg5LjUtMjAwLTIwMFMxNDUuNSA1NiAyNTYgNTZzMjAwIDg5LjUgMjAwIDIwMC04OS41IDIwM
C0yMDAgMjAwem0xMDEuOC0yNjIuMkwzMDEuNiAyNTZsNTYuMiA1Ni4yYzQuNyA0LjcgNC43IDEyLjMgMCAx
N2wtMTcgMTdjLTQuNyA0LjctMTIuMyA0LjctMTcgMEwyNjggMjkwbC01Ni4yIDU2LjJjLTQuNyA0LjctMTI
uMyA0LjctMTcgMGwtMTctMTdjLTQuNy00LjctNC43LTEyLjMgMC0xN0wyMzQuNCAyNTZsLTU2LjItNTYuMm
MtNC43LTQuNy00LjctMTIuMyAwLTE3bDE3LTE3YzQuNy00LjcgMTIuMy00LjcgMTcgMEwyNjggMjIyLjRsN
TYuMi01Ni4yYzQuNy00LjcgMTIuMy00LjcgMTcgMGwxNyAxN2M0LjcgNC43IDQuNyAxMi4zIDAgMTd6Ij48
L3BhdGg+PC9zdmc+';
break;
case 'warning':
icon =
'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZp
ZXdCb3g9IjAgMCA1NzYgNTEyIj48cGF0aCBmaWxsPSIjRjM5QzEyIiBkPSJNNTY5LjUxNyA0NDIuODgzYy0
yMC45MzctMzMuNzg3LTE0MC41NzMtMjI3LjQ4LTE3OS43Mi0yOTMuNzg3Qzg3MC45MiA2MS4zNDcgNzYuOS
A0Ny44NyA1MS4zNTcgNDcuODdjLTIxLjk0NyAwLTMxLjY4IDE1LjgyLTE5LjQxIDQzLjMwM2wxNzkuMzU3I
DI4OS40MjdhMjIuOCAyMi44IDAgMDAyMS4xOCAxMy45MmgyODcuODRjMjkuMDkzIDAgNDguNTktMzIuNDgg
MzMuMDEtNTAuNjY3ek0yODggMzkzLjcyM2MtMjEuMDM3IDAtMzguMTI1LTE3LjA4OC0zOC4xMjUtMzguMTI
1UzI2Ni45NjMgMzE3LjQ3MyAyODggMzE3LjQ3M3MzOC4xMjQgMTcuMDg4IDM4LjEyNCAzOC4xMjUtMTcuMD
g3IDM4LjEyNS0zOC4xMjQgMzguMTI1em0yMS41NjMtMTg0LjAzM2gtNDMuMTI3bC0xMy42MDMgMTAyLjU3Y
y0yLjQxIDE4LjA5NSAxMS45IDM0LjAzMyAzMC4xNjQgMzQuMDMzIDE3LjI5NCAwIDMxLjcxNC0xNC45MzYg
MjkuNzMzLTMyLjk5NGwtMy4xNjctMTAzLjYxeiI+PC9wYXRoPjwvc3ZnPg==';
break;
default:
icon =
'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZp
ZXdCb3g9IjAgMCA1MTIgNTEyIj48cGF0aCBmaWxsPSIjM0I4MURCIiBkPSJNMjU2IDhDMTE5LjA0MyA4IDg
gMTE5LjA4MyA4IDI1NmMwIDEzNi45OTcgMTExLjA0MyAyNDggMjQ4IDI0OHMyNDgtMTExLjAwMyAyNDgtMj
Q4QzUwNCAxMTkuMDgzIDM5Mi45NTcgOCAyNTYgOHptMCA0NDhjLTExMC41MzIgMC0yMDAtODkuNDMxLTIwM
C0yMDBTMTQ1LjQ4NSA1NiAyNTYgNTZzMjAwIDg5LjQzMSAyMDAgMjAwLTg5LjQ1MiAyMDAtMjAwIDIwMHpt
MC0zMzhjLTIzLjE5NiAwLTQyIDEwLjk5OS00MiAyNCAwIDEzLjAwMSAxOC44MDQgMjQgNDIgMjRzNDItMTA
uOTk5IDQyLTI0YzAtMTMuMDAxLTE4LjgwNC0yNC00Mi0yNHptNTYgMjU0YzAgNi42MjctNS4zNzMgMTItMT
IgMTJoLTg4Yy02LjYyNyAwLTEyLTUuMzczLTEyLTEydi0yNGMwLTYuNjI3IDUuMzczLTEyIDEyLTEyaDEyd
i02NGgtMTJjLTYuNjI3IDAtMTItNS4zNzMtMTItMTJ2LTI0YzAtNi42MjcgNS4zNzMtMTIgMTItMTJoNjRj
Ni42MjcgMCAxMiA1LjM3MyAxMiAxMnYxMDBoMTJjNi42MjcgMCAxMiA1LjM3MyAxMiAxMnYyNHoiPjwvcGF
0aD48L3N2Zz4=';
}

// Si l'application est visible, utiliser le service worker pour les


notifications système
if (document.visibilityState === 'visible' && 'serviceWorker' in
navigator && navigator.serviceWorker.controller) {
// Utiliser le service worker pour afficher la notification
navigator.serviceWorker.controller.postMessage({
type: 'SHOW_NOTIFICATION',
payload: {
id: Date.now().toString(),
title: 'Simulateur de Football',
body: message,
icon: icon
}
});
} else {
// Sinon, utiliser directement l'API Notification
try {
new Notification('Simulateur de Football', {
body: message,
icon: icon
});
} catch (error) {
console.error('Erreur lors de l\'affichage de la
notification:', error);
}
}
}

// Afficher une boîte de dialogue de confirmation


function showConfirmation(message, onConfirm, onCancel) {
// Créer les éléments de la boîte de dialogue
const confirmDialog = document.createElement('div');
confirmDialog.className = 'fixed inset-0 flex items-center justify-
center z-50 bg-black bg-opacity-50';
confirmDialog.style.transition = 'opacity 0.3s ease';

confirmDialog.innerHTML = `
<div class="bg-card p-6 rounded-lg shadow-lg max-w-sm w-full mx-4"
style="background-color: var(--bg-card); color: var(--text-primary);">
<h3 class="text-lg font-bold mb-4">Confirmation</h3>
<p class="mb-6">${message}</p>
<div class="flex justify-end gap-3">
<button id="confirmCancel" class="btn btn-
secondary">Annuler</button>
<button id="confirmOk" class="btn
btn-danger">Confirmer</button>
</div>
</div>
`;

// Ajouter la boîte de dialogue au document


document.body.appendChild(confirmDialog);

// Ajouter les gestionnaires d'événements


document.getElementById('confirmCancel').addEventListener('click',
function() {
if (typeof onCancel === 'function') {
onCancel();
}
confirmDialog.remove();
});

document.getElementById('confirmOk').addEventListener('click',
function() {
if (typeof onConfirm === 'function') {
onConfirm();
}
confirmDialog.remove();
});

// Permettre de fermer en cliquant à l'extérieur


confirmDialog.addEventListener('click', function(e) {
if (e.target === confirmDialog) {
if (typeof onCancel === 'function') {
onCancel();
}
confirmDialog.remove();
}
});
}
// Gérer le thème sombre/clair
function checkTheme() {
// Vérifier la préférence stockée
const savedTheme = localStorage.getItem('simulatorTheme');

if (savedTheme === 'dark') {


document.body.classList.add('dark');
document.getElementById('themeToggle').innerHTML = '<i class="fas
fa-sun"></i>';
} else if (savedTheme === 'light') {
document.body.classList.remove('dark');
document.getElementById('themeToggle').innerHTML = '<i class="fas
fa-moon"></i>';
} else {
// Vérifier la préférence du système
if (window.matchMedia && window.matchMedia('(prefers-color-scheme:
dark)').matches) {
document.body.classList.add('dark');
document.getElementById('themeToggle').innerHTML = '<i
class="fas fa-sun"></i>';
}
}
}

function toggleTheme() {
if (document.body.classList.contains('dark')) {
// Passer au thème clair
document.body.classList.remove('dark');
localStorage.setItem('simulatorTheme', 'light');
document.getElementById('themeToggle').innerHTML = '<i class="fas
fa-moon"></i>';
} else {
// Passer au thème sombre
document.body.classList.add('dark');
localStorage.setItem('simulatorTheme', 'dark');
document.getElementById('themeToggle').innerHTML = '<i class="fas
fa-sun"></i>';
}
}

// Afficher/cacher le loader
function showLoader() {
document.getElementById('loaderContainer').style.display = 'flex';
}

function hideLoader() {
document.getElementById('loaderContainer').style.display = 'none';
}
</script>
</body>
</html>
Pour organiser tous les événements possibles d’un match de football de manière
structurée et exhaustive, on peut les classer en 10 grandes catégories :

---

1 Événements liés aux attaques


1️⃣
🔹 Construction d’attaque (jeu court, long, sur les ailes, dans l’axe)
🔹 Passes en profondeur, combinaisons rapides
🔹 Centres (tendu, en retrait, second poteau)
🔹 Tirs (frappe de loin, enroulé, volée, reprise de la tête)
🔹 Face-à-face avec le gardien
🔹 Buts marqués (tir placé, frappe puissante, but contre son camp)
🔹 Contres rapides après récupération

---

2️⃣ Événements défensifs

🔹 Pressing haut, récupération rapide


🔹 Bloc bas et compact pour défendre un score
🔹 Interception d’une passe ou d’un centre
🔹 Duel aérien gagné en défense
🔹 Tacle propre ou tacle dangereux
🔹 Dégagement en catastrophe sur la ligne
🔹 Faute tactique pour stopper un contre

---

3️⃣ Événements liés aux fautes et sanctions

🔹 Faute légère (obstruction, accrochage, charge)


🔹 Faute sévère (tacle en retard, coup volontaire)
🔹 Carton jaune pour jeu dangereux ou protestation
🔹 Deuxième jaune = expulsion
🔹 Carton rouge direct (tacle violent, insulte, main volontaire)
🔹 Coup franc obtenu après une faute dangereuse
🔹 Pénalty sifflé après une faute dans la surface

---

4️⃣ Événements liés aux duels et aux contacts

🔹 Duel aérien après un long ballon


🔹 Duel physique au milieu du terrain
🔹 Duel 1v1 entre un attaquant et un défenseur
🔹 Contact litigieux dans la surface (VAR possible)
🔹 Duel remporté grâce à la vitesse et au dribble
🔹 Duel gagné par l’expérience (lecture du jeu, placement)

---

5️⃣ Événements liés aux gardiens

🔹 Parade spectaculaire sur une frappe puissante


🔹 Arrêt réflexe à bout portant
🔹 Mauvaise sortie aérienne, le ballon reste dangereux
🔹 Dégagement manqué, balle récupérée par l’adversaire
🔹 Gardien battu, mais sauvetage d’un défenseur sur la ligne
🔹 Arrêt du gardien sur un penalty
🔹 Relance rapide pour lancer une contre-attaque
---

6️⃣ Événements liés aux coups de pied arrêtés

🔹 Coup franc direct tenté (frappe enroulée, puissante)


🔹 Coup franc joué à deux pour une combinaison
🔹 Corner tiré au premier poteau, déviation dangereuse
🔹 Corner au second poteau, tête puissante mais au-dessus
🔹 Penalty marqué ou stoppé
🔹 Engagement après un but (calme ou agressif)
🔹 Dernier coup franc du match, tension maximale

---

7️⃣ Événements liés à la dynamique du match

🔹 Équipe qui domine en possession et pousse


🔹 Équipe qui subit et attend le contre
🔹 Changement tactique pour renforcer l’attaque ou la défense
🔹 Fatigue des joueurs qui se ressent après la 75ᵉ minute
🔹 Coaching décisif avec un changement gagnant
🔹 Joueur qui commence à perdre patience et force ses tirs
🔹 Équipe qui joue la montre pour garder son avantage

---

8️⃣ Événements liés aux décisions arbitrales

🔹 Arbitrage strict (beaucoup de cartons, jeu haché)


🔹 Arbitrage laxiste (contact autorisé, match physique)
🔹 Vérification VAR pour un but ou un penalty
🔹 Hors-jeu signalé sur un but potentiel
🔹 Faute oubliée qui fait polémique
🔹 Pénalty annulé après VAR, protestations des joueurs
🔹 Temps additionnel généreux ou limité

---

9️⃣ Événements liés aux conditions extérieures

🔹 Pluie rendant le terrain glissant (plus de fautes, contrôles ratés)


🔹 Vent fort influençant les passes longues et les tirs
🔹 Chaleur impactant la fatigue des joueurs
🔹 Stade en fusion, l’ambiance influence les décisions
🔹 Supporters qui mettent la pression sur l’arbitre
🔹 Terrain en mauvais état, rebonds imprévisibles

---

11️⃣00️⃣Événements exceptionnels et imprévus

🔹 Blessure d’un joueur après un duel


🔹 Changement forcé après une blessure
🔹 Erreur monumentale d’un joueur (passe en retrait interceptée)
🔹 But contre son camp malchanceux
🔹 Enorme raté devant un but vide
🔹 Équipe qui revient de 2 buts en fin de match
🔹 Fumigènes qui interrompent le match temporairement

---

➡️ Résumé des catégories

1️⃣ Attaques
2️⃣ Défenses
3️⃣ Fautes et sanctions
4️⃣ Duels
5️⃣ Gardiens
6️⃣ Coups de pied arrêtés
7️⃣ Dynamique du match
8️⃣ Arbitrage
9️⃣ Conditions extérieures
🔟 Événements exceptionnels

premièrement intègre l'intégralité des 10 catégories d'événements c'est-à-dire


attaque défense faute et sanction duel gardien coup de pied arrêté dynamique du
match arbitrage conditions extérieur événement exceptionnel qui ont été mentionnés
ci-dessous et bien expliqué intégrer eux tous

Oui, il existe plusieurs catégories d’équipes qui influencent la dynamique du match


et les événements possibles. On peut classer ces équipes en profils stratégiques et
qualitatifs, et voir comment cela affecte les événements en simulation.

---

1 1 Types de Catégories d’Équipes


1️⃣

1️⃣ Top Teams (Équipes d’élite / Favoris / Grandes équipes)


🏆 Clubs ou nations dominants (ex: Manchester City, Real Madrid, Bayern Munich)
🔹 Contrôle du jeu, possession élevée
🔹 Capables de marquer à tout moment
🔹 Pressing haut, asphyxient les adversaires
🔹 Défense solide, rarement surprises
🔹 Peuvent tuer un match rapidement après avoir marqué

2️⃣ Challengers (Équipes solides mais pas favorites)


⚡ Clubs compétitifs mais un cran en dessous des top teams (ex: Dortmund, Napoli,
Arsenal)
🔹 Bonnes phases de domination mais vulnérables sous pression
🔹 Peuvent rivaliser mais dépendent de leur efficacité offensive
🔹 Défense parfois instable sur les transitions rapides
🔹 Performances variables en fonction du contexte

3️⃣ Mid-Table Teams (Équipes moyennes de milieu de tableau)


⚖️ Équipes équilibrées, ni trop faibles ni trop fortes (ex: West Ham, Lyon,
Fiorentina)
🔹 Matchs souvent ouverts, alternance d’attaques et de défenses
🔹 Parfois imprévisibles : capables de battre un favori mais aussi de perdre contre
un plus faible
🔹 Stratégies variées : certains jouent défensif, d’autres sont plus offensifs
4️⃣ Underdogs (Équipes outsiders / Petites équipes)
Clubs jouant le maintien ou nations considérées comme inférieures (ex: Brest,
Burnley, Bologne)
🔹 Défendent très bas, subissent mais restent dangereuses en contre
🔹 Peu d’occasions créées mais très efficaces sur CPA (coups de pied arrêtés)
🔹 Peuvent résister longtemps avant de craquer
🔹 Matchs souvent fermés avec peu de buts

5️⃣ Ultra-Défensives (Équipes "bus" / Antijeu / Catenaccio moderne)


🏰 Clubs jouant ultra-bas, cherchant le 0-0 (ex: Atlético Madrid en mode défensif,
certaines équipes en Coupe du Monde)
🔹 Bloc très compact, refus du jeu
🔹 Simulent beaucoup pour casser le rythme
🔹 Stratégie d’usure, misent sur un but opportuniste
🔹 Peu de tirs mais gros danger sur corners ou contres

6️⃣ Ultra-Offensives (Équipes spectaculaires / Attaque à tout prix)


🎇 Clubs qui attaquent sans cesse mais défendent mal (ex: Atalanta, Bayer
Leverkusen, certaines équipes de Ligue 1)
🔹 Beaucoup de buts marqués mais aussi encaissés
🔹 Défense souvent exposée aux contres
🔹 Style spectaculaire, actions incessantes
🔹 Matchs avec score élevé (4-3, 5-2…)

7️⃣ Équipes Physiques (Duel, intensité, agressivité)


💪 Clubs jouant sur le contact et l’impact physique (ex: Stoke City des années 2010,
Uruguay)
🔹 Beaucoup de duels et fautes
🔹 Matchs hachés avec des cartons
🔹 Jeu direct avec beaucoup de longs ballons
🔹 Peu de créativité mais dangereux sur phases arrêtées

8️⃣ Équipes Techniques (Possession, jeu au sol, flair technique)


⚽ Clubs misant sur la maîtrise du ballon et les combinaisons (ex: Barcelone,
Espagne 2010, PSG)
🔹 Beaucoup de passes courtes et jeu collectif
🔹 Peu de ballons longs, construction patiente
🔹 Peu de fautes mais vulnérables aux équipes physiques
🔹 Dominent souvent la possession

9️⃣ Équipes Opportunistes (Attentes + Exploitation des erreurs)


🎯 Équipes qui laissent jouer l’adversaire et frappent au bon moment (ex: Inter
Milan 2021, certaines équipes africaines)
🔹 Peu d’occasions mais ultra-réalistes devant le but
🔹 Stratégie minimaliste, souvent défensifs avant d’accélérer
🔹 Attendent une erreur pour marquer un but décisif
🔹 Peuvent se montrer ennuyeuses mais efficaces

🔟 Équipes Chaotiques (Inconstantes, parfois géniales, parfois nulles)


🎢 Équipes ultra-imprévisibles, capables de battre un favori et de perdre contre une
équipe faible (ex: Marseille, certaines sélections africaines)
🔹 Matchs avec des rebondissements, capables du meilleur comme du pire
🔹 Alternent entre domination et moments d’absence totale
🔹 Défensivement instables mais peuvent enflammer un match

nous pouvons aussi ajouter le type de catégorie d'équipe quelle mentionné ci-
dessous
Parfait ! On va maintenant attribuer des probabilités aux événements du match en
fonction des types d’équipes.

---

1️⃣ Probabilités des événements selon les types d’équipes

On définit des pourcentages d’apparition des événements selon chaque catégorie


d’équipe.

🔹 Événements Offensifs

Possession (%) : Pourcentage moyen de possession de balle.

Attaques placées (%) : % d’actions construites.

Contres rapides (%) : % d’attaques en contre.

Longs ballons (%) : % d’attaques via des dégagements longs.

CPA dangereux (%) : % d’actions sur coups de pied arrêtés (corners, coups francs).

xG par attaque : Danger moyen d’une attaque (Expected Goals).

---

🔹 Événements Défensifs et duels

Pressing Haut (%) : Équipe qui récupère le ballon haut sur le terrain.

Bloc Médian (%) : Défense en milieu de terrain.

Défense Basse (%) : Équipe recroquevillée en défense.

Fautes / Match : Nombre moyen de fautes.

Cartons Jaunes (%) : Probabilité d’un carton jaune.

Cartons Rouges (%) : Probabilité d’un carton rouge.

---

2️⃣ Gestion Dynamique du Score et Style de Jeu

Selon le score du match, une équipe peut adapter son comportement.

🔹 Si une équipe mène au score :

🔹 Si une équipe est menée au score :

---
3️⃣ Intégration dans la Simulation

On peut maintenant implémenter ces dynamiques dans le simulateur en créant des


algorithmes qui :
✅ Adaptent le style de jeu en fonction du score.
✅ Changent la probabilité des événements selon la situation du match.
✅ Simulent des renversements de situation réalistes.

D'accord, voici tout le contenu sans tableau pour que tu puisses le recopier
facilement.

---

1️⃣ Probabilités des événements selon les types d’équipes

🔹 Événements Offensifs

Top Team : Possession entre 65 et 75 %, attaques placées 55 %, contres rapides 10


%, longs ballons 5 %, CPA dangereux 30 %, xG par attaque entre 0.15 et 0.25.

Challenger : Possession entre 50 et 60 %, attaques placées 45 %, contres rapides 15


%, longs ballons 10 %, CPA dangereux 30 %, xG par attaque entre 0.12 et 0.22.

Mid-Table : Possession entre 45 et 55 %, attaques placées 35 %, contres rapides 20


%, longs ballons 15 %, CPA dangereux 30 %, xG par attaque entre 0.10 et 0.20.

Underdog : Possession entre 30 et 40 %, attaques placées 20 %, contres rapides 30


%, longs ballons 30 %, CPA dangereux 20 %, xG par attaque entre 0.08 et 0.15.

Ultra-Défensive : Possession entre 20 et 30 %, attaques placées 10 %, contres


rapides 20 %, longs ballons 50 %, CPA dangereux 20 %, xG par attaque entre 0.05 et
0.12.

Ultra-Offensive : Possession entre 55 et 65 %, attaques placées 40 %, contres


rapides 25 %, longs ballons 10 %, CPA dangereux 25 %, xG par attaque entre 0.18 et
0.28.

Physique : Possession entre 40 et 50 %, attaques placées 30 %, contres rapides 10


%, longs ballons 30 %, CPA dangereux 30 %, xG par attaque entre 0.10 et 0.18.

Technique : Possession entre 60 et 70 %, attaques placées 50 %, contres rapides 10


%, longs ballons 5 %, CPA dangereux 35 %, xG par attaque entre 0.14 et 0.22.

Opportuniste : Possession entre 35 et 45 %, attaques placées 25 %, contres rapides


30 %, longs ballons 20 %, CPA dangereux 25 %, xG par attaque entre 0.12 et 0.20.

Chaotique : Possession entre 40 et 60 %, attaques placées 30 %, contres rapides 30


%, longs ballons 20 %, CPA dangereux 20 %, xG par attaque entre 0.15 et 0.25.

---

🔹 Événements Défensifs et duels

Top Team : Pressing haut 70 %, bloc médian 20 %, défense basse 10 %, fautes par
match entre 8 et 12, cartons jaunes 15 %, cartons rouges 2 %.
Challenger : Pressing haut 60 %, bloc médian 30 %, défense basse 10 %, fautes par
match entre 10 et 14, cartons jaunes 20 %, cartons rouges 3 %.

Mid-Table : Pressing haut 50 %, bloc médian 35 %, défense basse 15 %, fautes par


match entre 12 et 16, cartons jaunes 25 %, cartons rouges 4 %.

Underdog : Pressing haut 30 %, bloc médian 40 %, défense basse 30 %, fautes par


match entre 14 et 20, cartons jaunes 30 %, cartons rouges 5 %.

Ultra-Défensive : Pressing haut 10 %, bloc médian 30 %, défense basse 60 %, fautes


par match entre 15 et 22, cartons jaunes 40 %, cartons rouges 7 %.

Ultra-Offensive : Pressing haut 60 %, bloc médian 30 %, défense basse 10 %, fautes


par match entre 10 et 14, cartons jaunes 20 %, cartons rouges 3 %.

Physique : Pressing haut 40 %, bloc médian 40 %, défense basse 20 %, fautes par


match entre 18 et 26, cartons jaunes 45 %, cartons rouges 10 %.

Technique : Pressing haut 75 %, bloc médian 20 %, défense basse 5 %, fautes par


match entre 6 et 10, cartons jaunes 10 %, cartons rouges 1 %.

Opportuniste : Pressing haut 30 %, bloc médian 40 %, défense basse 30 %, fautes par


match entre 12 et 18, cartons jaunes 25 %, cartons rouges 4 %.

Chaotique : Pressing haut 40 %, bloc médian 35 %, défense basse 25 %, fautes par


match entre 15 et 22, cartons jaunes 35 %, cartons rouges 6 %.

---

2️⃣ Gestion Dynamique du Score et Style de Jeu

Si une équipe mène au score :

Une Top Team garde la possession et contrôle le match.

Une Challenger défend un peu plus et joue en contre.

Une Mid-Table baisse son bloc et bétonne après 2 buts d’avance.

Une Underdog se replie complètement et joue la défense à fond.

Une Ultra-Défensive reste en bloc bas quoi qu’il arrive.

Une Ultra-Offensive continue d’attaquer même avec un gros avantage.

Une Physique ralentit le jeu en multipliant les fautes.

Une Technique garde la possession et épuise l’adversaire.

Une Opportuniste défend en contre et ferme les espaces.

Une Chaotique peut continuer à attaquer ou totalement perdre le fil.

Si une équipe est menée au score :


Une Top Team augmente son pressing et attaque à fond.

Une Challenger prend des risques et pousse offensivement.

Une Mid-Table tente de revenir mais peut se désorganiser.

Une Underdog continue de défendre et peut même abandonner.

Une Ultra-Défensive ne change quasiment rien à son jeu.

Une Ultra-Offensive tente de marquer rapidement mais laisse des espaces.

Une Physique devient plus agressive et multiplie les fautes.

Une Technique accélère les transmissions et le pressing.

Une Opportuniste joue plus direct et tente des tirs à distance.

Une Chaotique peut complètement exploser ou réussir un retournement spectaculaire.

---

3️⃣ Intégration dans la Simulation

On peut maintenant intégrer ces paramètres dans le moteur de simulation, ce qui


permettra de :
✅ Adapter le style de jeu des équipes selon le score.
✅ Changer la probabilité des événements en fonction des situations du match.
✅ Simuler des renversements de situation réalistes avec des surprises.

👉 D'accord, voici tout le contenu sans tableau pour que tu puisses le recopier
facilement.

---

1️⃣ Probabilités des événements selon les types d’équipes

🔹 Événements Offensifs

Top Team : Possession entre 65 et 75 %, attaques placées 55 %, contres rapides 10


%, longs ballons 5 %, CPA dangereux 30 %, xG par attaque entre 0.15 et 0.25.

Challenger : Possession entre 50 et 60 %, attaques placées 45 %, contres rapides 15


%, longs ballons 10 %, CPA dangereux 30 %, xG par attaque entre 0.12 et 0.22.

Mid-Table : Possession entre 45 et 55 %, attaques placées 35 %, contres rapides 20


%, longs ballons 15 %, CPA dangereux 30 %, xG par attaque entre 0.10 et 0.20.

Underdog : Possession entre 30 et 40 %, attaques placées 20 %, contres rapides 30


%, longs ballons 30 %, CPA dangereux 20 %, xG par attaque entre 0.08 et 0.15.

Ultra-Défensive : Possession entre 20 et 30 %, attaques placées 10 %, contres


rapides 20 %, longs ballons 50 %, CPA dangereux 20 %, xG par attaque entre 0.05 et
0.12.

Ultra-Offensive : Possession entre 55 et 65 %, attaques placées 40 %, contres


rapides 25 %, longs ballons 10 %, CPA dangereux 25 %, xG par attaque entre 0.18 et
0.28.

Physique : Possession entre 40 et 50 %, attaques placées 30 %, contres rapides 10


%, longs ballons 30 %, CPA dangereux 30 %, xG par attaque entre 0.10 et 0.18.

Technique : Possession entre 60 et 70 %, attaques placées 50 %, contres rapides 10


%, longs ballons 5 %, CPA dangereux 35 %, xG par attaque entre 0.14 et 0.22.

Opportuniste : Possession entre 35 et 45 %, attaques placées 25 %, contres rapides


30 %, longs ballons 20 %, CPA dangereux 25 %, xG par attaque entre 0.12 et 0.20.

Chaotique : Possession entre 40 et 60 %, attaques placées 30 %, contres rapides 30


%, longs ballons 20 %, CPA dangereux 20 %, xG par attaque entre 0.15 et 0.25.

---

🔹 Événements Défensifs et duels

Top Team : Pressing haut 70 %, bloc médian 20 %, défense basse 10 %, fautes par
match entre 8 et 12, cartons jaunes 15 %, cartons rouges 2 %.

Challenger : Pressing haut 60 %, bloc médian 30 %, défense basse 10 %, fautes par


match entre 10 et 14, cartons jaunes 20 %, cartons rouges 3 %.

Mid-Table : Pressing haut 50 %, bloc médian 35 %, défense basse 15 %, fautes par


match entre 12 et 16, cartons jaunes 25 %, cartons rouges 4 %.

Underdog : Pressing haut 30 %, bloc médian 40 %, défense basse 30 %, fautes par


match entre 14 et 20, cartons jaunes 30 %, cartons rouges 5 %.

Ultra-Défensive : Pressing haut 10 %, bloc médian 30 %, défense basse 60 %, fautes


par match entre 15 et 22, cartons jaunes 40 %, cartons rouges 7 %.

Ultra-Offensive : Pressing haut 60 %, bloc médian 30 %, défense basse 10 %, fautes


par match entre 10 et 14, cartons jaunes 20 %, cartons rouges 3 %.

Physique : Pressing haut 40 %, bloc médian 40 %, défense basse 20 %, fautes par


match entre 18 et 26, cartons jaunes 45 %, cartons rouges 10 %.

Technique : Pressing haut 75 %, bloc médian 20 %, défense basse 5 %, fautes par


match entre 6 et 10, cartons jaunes 10 %, cartons rouges 1 %.

Opportuniste : Pressing haut 30 %, bloc médian 40 %, défense basse 30 %, fautes par


match entre 12 et 18, cartons jaunes 25 %, cartons rouges 4 %.

Chaotique : Pressing haut 40 %, bloc médian 35 %, défense basse 25 %, fautes par


match entre 15 et 22, cartons jaunes 35 %, cartons rouges 6 %.

---

2️⃣ Gestion Dynamique du Score et Style de Jeu

Si une équipe mène au score :


Une Top Team garde la possession et contrôle le match.

Une Challenger défend un peu plus et joue en contre.

Une Mid-Table baisse son bloc et bétonne après 2 buts d’avance.

Une Underdog se replie complètement et joue la défense à fond.

Une Ultra-Défensive reste en bloc bas quoi qu’il arrive.

Une Ultra-Offensive continue d’attaquer même avec un gros avantage.

Une Physique ralentit le jeu en multipliant les fautes.

Une Technique garde la possession et épuise l’adversaire.

Une Opportuniste défend en contre et ferme les espaces.

Une Chaotique peut continuer à attaquer ou totalement perdre le fil.

Si une équipe est menée au score :

Une Top Team augmente son pressing et attaque à fond.

Une Challenger prend des risques et pousse offensivement.

Une Mid-Table tente de revenir mais peut se désorganiser.

Une Underdog continue de défendre et peut même abandonner.

Une Ultra-Défensive ne change quasiment rien à son jeu.

Une Ultra-Offensive tente de marquer rapidement mais laisse des espaces.

Une Physique devient plus agressive et multiplie les fautes.

Une Technique accélère les transmissions et le pressing.

Une Opportuniste joue plus direct et tente des tirs à distance.

Une Chaotique peut complètement exploser ou réussir un retournement spectaculaire.

---

3️⃣ Intégration dans la Simulation

On peut maintenant intégrer ces paramètres dans le moteur de simulation, ce qui


permettra de :
✅ Adapter le style de jeu des équipes selon le score.
✅ Changer la probabilité des événements en fonction des situations du match.
✅ Simuler des renversements de situation réalistes avec des surprises.

donc ajoute aussi tout ça ci-dessous tous les éléments ci-dessous au complet tout
ce que je te demande doit être intégré au complet ne saute rien car ça pourrait
aider pour un encore plus réaliste et vivant
D'accord, voici une description ultra détaillée des événements minute par minute,
prenant en compte toutes les catégories d’équipes et leurs comportements.

---

1️⃣ Types d'Événements Minute par Minute

Chaque minute du match, un ou plusieurs événements peuvent se produire en


fonction :
✅ Du type d’équipe (Top Team, Challenger, Mid-Table, etc.)
✅ Du score (menée, égalité, en tête)
✅ Du contexte du match (fatigue, météo, cartons, intensité, pressing, etc.)
✅ Du moment dans le match (début, mi-temps, fin, temps additionnel)

---

2️⃣ Liste Complète des Événements Simulables

🔹 Événements de Possession et de Construction de Jeu

Circulation du ballon en défense : Une équipe fait tourner pour contrôler le


rythme.

Relance rapide du gardien : Une équipe essaie de repartir vite après un arrêt.

Relance longue du gardien : Jeu direct vers l’attaquant cible.

Pressing adverse efficace : L’adversaire gêne la relance, forçant un dégagement.

Bloc défensif compact : Une équipe laisse l’adversaire faire le jeu sans presser.

Construction progressive : Passes courtes pour progresser sur le terrain.

Perte de balle sur passe mal ajustée : Mauvaise transmission interceptée.

🔹 Événements Offensifs

Attaque placée : L’équipe construit une action en attaque en multipliant les


passes.

Percée individuelle : Un joueur élimine un adversaire et entre dans les 30 derniers


mètres.

Centre dangereux : Un joueur envoie un ballon tendu dans la surface.

Tir lointain : Un joueur tente une frappe hors de la surface.

Tir cadré : Un tir oblige le gardien à intervenir.

Tir non cadré : Une frappe passe à côté ou au-dessus.

Tir repoussé par le gardien : Une frappe est arrêtée sans être captée.

Action avortée : L’attaque est stoppée par une interception ou une erreur
technique.
🔹 Événements Défensifs

Pressing haut réussi : L’équipe récupère le ballon dans le camp adverse.

Pressing haut raté : L’adversaire passe le pressing et progresse.

Bloc défensif en place : L’équipe défend en bloc et ne laisse pas d’espace.

Fautes tactiques : Une faute est commise pour casser une action.

Carton jaune pour faute grossière : Un joueur reçoit un avertissement.

Carton rouge : Un joueur est expulsé après une faute dangereuse ou un second jaune.

Erreur défensive : Un joueur perd le ballon dans une zone dangereuse.

🔹 Événements de Coup de Pied Arrêté

Corner obtenu : Une défense repousse un ballon en corner.

Corner joué à deux : Un petit jeu combiné près du poteau de corner.

Corner direct dans la surface : Le ballon arrive directement pour une tête ou une
reprise.

Coup franc dangereux : Un joueur tire depuis une bonne position.

Coup franc repoussé par le mur : Le ballon est dévié avant d’atteindre le but.

Penalty sifflé : Une faute dans la surface entraîne un penalty.

Penalty marqué : Le tireur trompe le gardien.

Penalty arrêté : Le gardien repousse la tentative.

🔹 Événements Tactiques et Stratégiques

Changement tactique : Une équipe modifie son approche (plus offensif, plus
défensif).

Remplacement stratégique : Un entraîneur fait entrer un joueur pour modifier


l’équilibre.

Bloc équipe plus haut : L’équipe monte d’un cran et joue plus agressivement.

Bloc équipe plus bas : L’équipe recule pour protéger son avantage.

🔹 Événements liés au Score et à la Physionomie du Match

Baisse d’intensité : L’équipe qui mène ralentit le jeu.

Rush final : L’équipe qui perd pousse dans les dernières minutes.

Temps additionnel indiqué : L’arbitre annonce le nombre de minutes restantes.


---

3️⃣ Événements Spécifiques à Chaque Type d’Équipe

🔹 Top Team

Dominera la possession (60-75 %) et enchaînera les attaques placées.

Pressing haut efficace, récupérant le ballon dans la moitié adverse.

Peu de fautes et peu de longs ballons, mais un jeu très organisé.

À 1-0, gère le match en gardant la balle.

Si menée, intensifie le pressing et les attaques.

🔹 Challenger

Bonne construction de jeu, alternant entre attaques placées et contres.

Relativement équilibré dans ses prises de risques.

À 1-0, défend un peu plus mais reste dangereux en contre.

Si menée, accélère le jeu et tente plus de frappes lointaines.

🔹 Mid-Table

Beaucoup de duels et un jeu direct (mi-long ballons, mi-attacks placées).

Peut se replier en défense après avoir marqué.

Si menée, peut se précipiter et rater des passes.

🔹 Underdog

Défend bas et joue beaucoup sur les longs ballons et les CPA.

Faible pressing, laissant l’adversaire faire le jeu.

Si mène au score, bétonne complètement sa défense.

Si menée, peut avoir du mal à revenir et se contenter de défendre.

🔹 Ultra-Défensive

Bloc très bas, très peu de pressing haut.

Cherche les CPA et les contres.

Peut frustrer une équipe offensive par sa solidité défensive.

Si menée, a du mal à se créer des occasions.


🔹 Ultra-Offensive

Attaques très rapides, jeu très direct.

Beaucoup de tirs et de tentatives de percussion.

Peut laisser des trous en défense, notamment si elle mène.

Si menée, met encore plus de pression mais peut se faire contrer.

🔹 Physique

Beaucoup de duels, de fautes et de CPA.

Peut fatiguer l’adversaire avec son impact physique.

À 1-0, ralentit le jeu et multiplie les fautes tactiques.

Si menée, devient encore plus agressive et tente de pousser.

🔹 Technique

Passe beaucoup, garde la possession.

Peu de fautes, mise sur des actions bien construites.

Si mène, contrôle le tempo et épuise l’adversaire.

Si menée, peut manquer d’efficacité face à un bloc bas.

🔹 Opportuniste

Ne force pas le jeu, attend les erreurs adverses.

Peu d’attaques placées, beaucoup de contre-attaques et de tirs soudains.

Si mène, défend et joue encore plus en contre.

Si menée, prend plus de risques et accélère le jeu.

🔹 Chaotique

Attaque et défense désorganisées, jeu imprévisible.

Peut alterner phases de domination et périodes catastrophiques.

Si menée, peut exploser mentalement et prendre plusieurs buts rapidement.

Si mène, continue d’attaquer et peut se découvrir.

---
4️⃣ Vers une Simulation Ultra Réaliste

Avec ce modèle, on peut :


✅ Générer des matchs minute par minute, avec une vraie intensité évolutive.
✅ Adapter les réactions des équipes selon leur style et le score.
✅ Simuler des scénarios réalistes et variés, avec du suspense.

Tu veux qu’on commence à coder une première version dynamique, ou tu veux d’abord
peaufiner certains détails ?

maintenant ce que tu vois c'est la description complète ultra réaliste de la


simulation minutes par minute intègre tout aussi en fonction si tu regardes très
bien tu vois comment ça a été configuré

et aussi voici les améliorations que je veux apporter au code

function poissonProbability(lambda, k) {
return (Math.pow(lambda, k) * Math.exp(-lambda)) / factorial(k);
}

function factorial(n) {
return n === 0 ? 1 : n * factorial(n - 1);
}

// 🔥 Fonction pour catégoriser une équipe


function determineTeamCategory(possession, goalsFor, goalsAgainst, xG, xGA, prgP) {
let score = 0;

if (possession > 55) score += 2;


else if (possession > 50) score += 1;
else score -= 1;

if (goalsFor > 2.0) score += 3;


else if (goalsFor > 1.5) score += 2;
else if (goalsFor > 1.0) score += 1;
else score -= 1;

if (goalsAgainst < 1.0) score += 2;


else if (goalsAgainst < 1.5) score += 1;
else score -= 1;

let xG_diff = xG - xGA;


if (xG_diff > 1.5) score += 3;
else if (xG_diff > 1.0) score += 2;
else if (xG_diff > 0.5) score += 1;
else score -= 1;

if (prgP > 1000) score += 2;


else if (prgP > 800) score += 1;
else score -= 1;

if (score >= 9) return "Grande";


if (score >= 5) return "Moyenne";
return "Petite";
}

// 🔥 Fonction pour calculer la forme actuelle


function calculateForm(last5Matches) {
let score = 0;
for (let result of last5Matches) {
if (result === "V") score += 2;
else if (result === "N") score += 1;
else if (result === "D") score -= 2;
}
return score;
}

// 🔥 Calcul dynamique des xG et xGA avec impact de la forme


function calculateXG(shots, shotQuality, keyPasses, counterAttacks,
opponentDefStrength, odds, form) {
let xG = shots * (shotQuality / 100) + keyPasses * 0.05 + counterAttacks * 0.1;
xG *= (1 - opponentDefStrength / 100);
xG *= (1 + (2.5 - odds) * 0.1);
xG += (form * 0.1);
return parseFloat(xG.toFixed(2));
}

class UltimateMatchSimulation {
constructor(team1, team2, category1, category2, form1, form2, shots1, shots2,
shotQuality1, shotQuality2, keyPasses1, keyPasses2, counterAttacks1,
counterAttacks2, defStrength1, defStrength2, h2h, odds1, odds2, matchType, weather,
attendance, age1, age2, possession1, possession2, yellowCards1, yellowCards2,
redCards1, redCards2) {
this.team1 = team1;
this.team2 = team2;
this.category1 = category1;
this.category2 = category2;
this.form1 = form1;
this.form2 = form2;
this.h2h = h2h;
this.odds1 = odds1;
this.odds2 = odds2;
this.matchType = matchType;
this.weather = weather;
this.attendance = attendance;
this.age1 = age1;
this.age2 = age2;
this.possession1 = possession1;
this.possession2 = possession2;
this.yellowCards1 = yellowCards1;
this.yellowCards2 = yellowCards2;
this.redCards1 = redCards1;
this.redCards2 = redCards2;

this.xG1 = calculateXG(shots1, shotQuality1, keyPasses1, counterAttacks1,


defStrength2, odds1, form1);
this.xG2 = calculateXG(shots2, shotQuality2, keyPasses2, counterAttacks2,
defStrength1, odds2, form2);

this.score1 = 0;
this.score2 = 0;
}

adjustForContext() {
if (this.form1 > 3) this.xG1 += 0.2;
if (this.form2 > 3) this.xG2 += 0.2;
if (this.h2h > 3) this.xG1 += 0.2;
if (this.h2h < -3) this.xG2 += 0.2;

switch (this.matchType) {
case "Championnat":
this.xG1 += 0.1;
this.xG2 += 0.1;
break;
case "Derby":
this.xG1 += 0.3, this.xG2 += 0.3;
break;
case "Finale de coupe":
this.xG1 -= 0.2, this.xG2 -= 0.2;
break;
case "Qualification Europe":
this.xG1 += 0.2, this.xG2 += 0.2;
break;
case "Relégation":
this.xG1 += 0.3, this.xG2 += 0.3;
break;
}

if (this.weather === "Pluie" || this.weather === "Neige") {


this.xG1 -= 0.2, this.xG2 -= 0.2;
}

if (this.attendance === "Complet") {


this.xG1 += 0.1;
}
}

simulateMatchMonteCarlo(iterations = 10000) {
this.adjustForContext();
let totalGoals1 = 0;
let totalGoals2 = 0;

for (let i = 0; i < iterations; i++) {


totalGoals1 += this.simulatePoisson(this.xG1);
totalGoals2 += this.simulatePoisson(this.xG2);
}

this.score1 = Math.round(totalGoals1 / iterations);


this.score2 = Math.round(totalGoals2 / iterations);

return {
team1: this.team1,
team2: this.team2,
score1: this.score1,
score2: this.score2
};
}

simulatePoisson(lambda) {
let L = Math.exp(-lambda);
let k = 0;
let p = 1;
do {
k++;
p *= Math.random();
} while (p > L);
return k - 1;
}
}

donc s'il y a des formulaires à ajouter pour prendre comme sa nouvelle amélioration
intègre

Et si nécessaire tu vas me donner le javascript soit en quatre parties ou trois


parties ou bien cinq parties pour être sûr que ça intéresse l'intégralité de toutes
mes demandes je dis bien l'intégralité
Voici le type de données que je possède pour être sûr que tu saches Rk Squad
Comp # Pl Age Poss MP Starts Min 90s Gls Ast G+A G-PK
PK PKatt CrdY CrdR xG npxG xAG npxG+xAG PrgC PrgP Gls Ast
G+A G-PK G+A-PK xG xAG xG+xAG npxG npxG+xAG
1 Alavés es La Liga 28 27.2 46.0 26 286 2,340 26.0 29 15
44 23 6 7 69 2 29.7 24.2 17.5 41.7 321 806 1.12
0.58 1.69 0.88 1.46 1.14 0.67 1.82 0.93 1.60
2 Angers fr Ligue 1 25 28.3 42.0 24 264 2,160 24.0 25 14
39 20 5 6 33 2 29.1 24.4 19.5 43.9 360 687 1.04
0.58 1.62 0.83 1.42 1.21 0.81 2.03 1.02 1.83
3 Arsenal eng Premier League 24 26.5 55.9 27 297 2,430 27.0
49 38 87 47 2 2 56 5 42.2 40.7 32.4 73.1 579
1297 1.81 1.41 3.22 1.74 3.15 1.56 1.20 2.76 1.51 2.71
4 Aston Villa eng Premier League 28 27.6 51.8 28 308 2,520
28.0 39 30 69 37 2 3 56 2 39.9 37.5 29.4 66.9
540 988 1.39 1.07 2.46 1.32 2.39 1.42 1.05 2.47 1.34 2.39
5 Atalanta it Serie A 32 27.6 56.7 27 297 2,430 27.0 57 41
98 54 3 5 49 0 46.4 42.5 33.9 76.3 637 1316 2.11
1.52 3.63 2.00 3.52 1.72 1.25 2.97 1.57 2.83
6 Athletic Club es La Liga 30 27.6 49.1 26 286 2,340 26.0 44
34 78 42 2 4 47 3 40.6 37.4 28.2 65.6 498 1136
1.69 1.31 3.00 1.62 2.92 1.56 1.09 2.65 1.44 2.52
7 Atlético Madrid es La Liga 24 29.1 51.6 26 286 2,340 26.0 43
35 78 41 2 3 61 2 43.0 40.6 32.7 73.4 504 1057
1.65 1.35 3.00 1.58 2.92 1.65 1.26 2.91 1.56 2.82
8 Augsburg de Bundesliga 28 27.4 44.0 24 264 2,160 24.0 27
19 46 26 1 2 62 2 24.8 23.2 17.4 40.6 317 816
1.13 0.79 1.92 1.08 1.87 1.03 0.72 1.76 0.97 1.69
9 Auxerre fr Ligue 1 28 26.6 41.7 24 264 2,160 24.0 34 24
58 30 4 5 45 4 30.3 26.7 22.7 49.4 332 786 1.42
1.00 2.42 1.25 2.25 1.26 0.95 2.21 1.11 2.06
10 Barcelona es La Liga 27 25.2 66.9 26 286 2,340 26.0 70 50
120 66 4 5 45 3 63.6 59.7 48.9 108.6 584 1452 2.69
1.92 4.62 2.54 4.46 2.45 1.88 4.33 2.30 4.18
11 Bayern Munich de Bundesliga 26 28.5 68.4 24 264 2,160 24.0
70 43 113 61 9 9 38 0 59.7 52.8 41.4 94.2 589
1494 2.92 1.79 4.71 2.54 4.33 2.49 1.73 4.21 2.20 3.92
12 Betis es La Liga 35 27.9 51.2 26 286 2,340 26.0 32 19
51 27 5 7 57 4 38.7 33.3 26.0 59.3 414 989 1.23
0.73 1.96 1.04 1.77 1.49 1.00 2.49 1.28 2.28
13 Bochum de Bundesliga 26 28.8 44.5 24 264 2,160 24.0 22
17 39 21 1 2 46 1 29.7 28.1 21.9 50.0 270 743
0.92 0.71 1.62 0.87 1.58 1.24 0.91 2.15 1.17 2.08
14 Bologna it Serie A 29 26.9 57.9 27 297 2,430 27.0 41 29
70 35 6 7 42 3 36.7 31.2 24.1 55.2 474 1073 1.52
1.07 2.59 1.30 2.37 1.36 0.89 2.25 1.15 2.05
15 Bournemouth eng Premier League 28 25.8 46.9 27 297 2,430
27.0 45 34 79 39 6 7 68 1 49.2 43.6 33.8 77.4
522 1067 1.67 1.26 2.93 1.44 2.70 1.82 1.25 3.07 1.62 2.87
16 Brentford eng Premier League 27 26.5 48.0 27 297 2,430 27.0
48 31 79 44 4 4 38 1 43.1 39.9 31.4 71.4 440
996 1.78 1.15 2.93 1.63 2.78 1.60 1.16 2.76 1.48 2.64
17 Brest fr Ligue 1 28 28.7 47.7 24 264 2,160 24.0 36 21
57 29 7 9 44 1 32.4 25.4 20.1 45.4 316 768 1.50
0.87 2.37 1.21 2.08 1.35 0.84 2.19 1.06 1.89
18 Brighton eng Premier League 30 25.7 52.2 27 297 2,430 27.0
43 29 72 41 2 2 57 1 40.1 38.5 28.4 66.9 581
1100 1.59 1.07 2.67 1.52 2.59 1.48 1.05 2.54 1.43 2.48
19 Cagliari it Serie A 26 27.3 45.6 27 297 2,430 27.0 25 20
45 22 3 3 48 3 32.3 30.0 23.1 53.1 377 843 0.93
0.74 1.67 0.81 1.56 1.20 0.85 2.05 1.11 1.97
20 Celta Vigo es La Liga 30 27.6 53.4 26 286 2,340 26.0 37 25
62 32 5 6 61 5 36.5 31.8 26.6 58.4 461 1107 1.42
0.96 2.38 1.23 2.19 1.40 1.02 2.43 1.22 2.24
21 Chelsea eng Premier League 29 24.2 58.2 27 297 2,430 27.0
51 39 90 48 3 3 77 1 53.8 51.5 41.8 93.3 627
1118 1.89 1.44 3.33 1.78 3.22 1.99 1.55 3.54 1.91 3.46
22 Como it Serie A 37 27.1 53.2 27 297 2,430 27.0 28 25 53
28 0 2 65 5 30.3 28.9 24.6 53.4 456 908 1.04 0.93
1.96 1.04 1.96 1.12 0.91 2.03 1.07 1.98
23 Crystal Palace eng Premier League 27 26.8 43.5 27 297 2,430
27.0 33 26 59 31 2 2 54 1 40.9 39.3 31.2 70.6
354 862 1.22 0.96 2.19 1.15 2.11 1.52 1.16 2.67 1.46 2.61
24 Dortmund de Bundesliga 28 27.2 60.5 24 264 2,160 24.0 43
32 75 39 4 4 58 6 39.2 36.1 29.8 66.0 510 1093
1.79 1.33 3.12 1.62 2.96 1.63 1.24 2.88 1.50 2.75
25 Eint Frankfurt de Bundesliga 26 25.7 49.0 24 264 2,160 24.0
50 32 82 47 3 4 38 1 44.7 41.6 30.6 72.2 427
943 2.08 1.33 3.42 1.96 3.29 1.86 1.28 3.14 1.73 3.01
26 Empoli it Serie A 33 26.1 41.0 27 297 2,430 27.0 22 13
35 20 2 3 48 3 23.4 21.1 16.1 37.1 307 698 0.81
0.48 1.30 0.74 1.22 0.87 0.59 1.46 0.78 1.37
27 Espanyol es La Liga 26 26.0 39.0 25 275 2,340 26.0 23 17
40 21 2 2 64 0 20.3 18.7 14.3 33.0 320 646 0.88
0.65 1.54 0.81 1.46 0.78 0.55 1.33 0.72 1.27
28 Everton eng Premier League 25 28.9 40.5 27 297 2,430 27.0
27 17 44 26 1 1 58 2 29.4 28.6 22.2 50.8 347
736 1.00 0.63 1.63 0.96 1.59 1.09 0.82 1.91 1.06 1.88
29 Fiorentina it Serie A 34 26.7 49.9 27 297 2,430 27.0 41 27
68 35 6 8 58 2 34.9 28.7 23.6 52.3 448 912 1.52
1.00 2.52 1.30 2.30 1.29 0.87 2.17 1.06 1.94
30 Freiburg de Bundesliga 26 27.9 47.3 24 264 2,160 24.0 32
23 55 32 0 4 40 2 29.8 26.7 18.9 45.6 294 764
1.33 0.96 2.29 1.33 2.29 1.24 0.79 2.03 1.11 1.90
31 Fulham eng Premier League 26 28.6 52.7 27 297 2,430 27.0
39 35 74 36 3 4 59 2 37.9 34.8 29.6 64.4 579
1135 1.44 1.30 2.74 1.33 2.63 1.41 1.10 2.50 1.29 2.38
32 Genoa it Serie A 34 27.1 45.1 27 297 2,430 27.0 24 18
42 24 0 2 52 0 28.2 26.6 20.4 46.9 339 735 0.89
0.67 1.56 0.89 1.56 1.05 0.75 1.80 0.98 1.74
33 Getafe es La Liga 30 28.2 42.7 26 286 2,340 26.0 21 10
31 16 5 5 80 2 24.1 20.2 13.8 34.0 253 679 0.81
0.38 1.19 0.62 1.00 0.93 0.53 1.46 0.78 1.31
34 Girona es La Liga 29 27.9 56.9 26 286 2,340 26.0 33 25
58 30 3 4 58 2 30.2 27.2 21.9 49.1 508 1186 1.27
0.96 2.23 1.15 2.12 1.16 0.84 2.01 1.05 1.89
35 Gladbach de Bundesliga 25 27.2 50.8 24 264 2,160 24.0 37
30 67 35 2 3 43 2 36.7 34.2 28.7 62.9 377 808
1.54 1.25 2.79 1.46 2.71 1.53 1.20 2.72 1.42 2.62
36 Heidenheim de Bundesliga 26 27.5 43.3 24 264 2,160 24.0 27
17 44 23 4 4 46 1 29.3 26.2 20.0 46.2 366 685
1.13 0.71 1.83 0.96 1.67 1.22 0.83 2.06 1.09 1.92
37 Hellas Verona it Serie A 32 25.8 38.0 27 297 2,430 27.0 24
17 41 22 2 2 72 7 26.2 24.6 19.8 44.3 330 724
0.89 0.63 1.52 0.81 1.44 0.97 0.73 1.70 0.91 1.64
38 Hoffenheim de Bundesliga 34 27.0 49.4 24 264 2,160 24.0 31
18 49 29 2 2 46 1 30.9 29.3 24.0 53.3 340 893
1.29 0.75 2.04 1.21 1.96 1.29 1.00 2.29 1.22 2.22
39 Holstein Kiel de Bundesliga 27 26.1 42.9 24 264 2,160 24.0
35 21 56 32 3 4 54 3 27.3 24.1 18.2 42.3 250
596 1.46 0.87 2.33 1.33 2.21 1.14 0.76 1.89 1.00 1.76
40 Inter it Serie A 25 30.0 59.3 27 297 2,430 27.0 58 43
101 52 6 7 36 0 48.7 43.5 33.4 76.9 464 1236 2.15
1.59 3.74 1.93 3.52 1.80 1.24 3.04 1.61 2.85
41 Ipswich Town eng Premier League 32 26.4 40.8 27 297 2,430
27.0 25 17 42 23 2 2 71 3 25.3 23.7 17.0 40.7
373 679 0.93 0.63 1.56 0.85 1.48 0.94 0.63 1.57 0.88 1.51
42 Juventus it Serie A 29 25.3 58.4 27 297 2,430 27.0 43 29
72 38 5 5 45 1 38.5 34.7 26.4 61.1 599 1091 1.59
1.07 2.67 1.41 2.48 1.43 0.98 2.40 1.28 2.26
43 Las Palmas es La Liga 30 27.8 50.7 26 286 2,340 26.0 28 21
49 27 1 2 76 3 24.8 23.2 18.6 41.8 337 840 1.08
0.81 1.88 1.04 1.85 0.95 0.71 1.67 0.89 1.61
44 Lazio it Serie A 25 27.8 54.9 27 297 2,430 27.0 48 34
82 43 5 6 62 4 42.8 38.3 28.3 66.6 576 1133 1.78
1.26 3.04 1.59 2.85 1.58 1.05 2.63 1.42 2.47
45 Le Havre fr Ligue 1 31 27.4 42.4 24 264 2,160 24.0 21 14
35 17 4 4 43 4 25.8 22.6 16.2 38.8 366 751 0.87
0.58 1.46 0.71 1.29 1.07 0.67 1.75 0.94 1.62
46 Lecce it Serie A 30 26.7 44.9 27 297 2,430 27.0 18 14
32 17 1 2 43 5 24.8 23.2 18.0 41.2 374 774 0.67
0.52 1.19 0.63 1.15 0.92 0.67 1.58 0.86 1.53
47 Leganés es La Liga 25 28.5 42.4 26 286 2,340 26.0 23 18
41 19 4 6 54 5 20.9 16.2 12.3 28.5 258 660 0.88
0.69 1.58 0.73 1.42 0.81 0.47 1.28 0.62 1.10
48 Leicester City eng Premier League 28 27.2 45.7 27 297 2,430
27.0 25 20 45 23 2 2 62 0 24.5 23.1 19.5 42.6
442 789 0.93 0.74 1.67 0.85 1.59 0.91 0.72 1.63 0.86 1.58
49 Lens fr Ligue 1 32 27.7 55.1 24 264 2,160 24.0 27 15 42
23 4 4 64 7 37.9 34.9 25.5 60.4 404 1080 1.13 0.62
1.75 0.96 1.58 1.58 1.06 2.64 1.45 2.52
50 Leverkusen de Bundesliga 23 27.6 58.4 24 264 2,160 24.0 54
40 94 52 2 5 42 1 45.1 41.1 32.1 73.2 470 1212
2.25 1.67 3.92 2.17 3.83 1.88 1.34 3.22 1.71 3.05
51 Lille fr Ligue 1 27 26.4 57.1 24 264 2,160 24.0 39 21
60 33 6 9 58 1 40.5 34.2 25.4 59.6 477 1150 1.62
0.87 2.50 1.37 2.25 1.69 1.06 2.75 1.42 2.48
52 Liverpool eng Premier League 24 27.8 56.6 28 308 2,520 28.0
66 50 116 59 7 7 54 2 61.1 55.6 45.1 100.7 596
1361 2.36 1.79 4.14 2.11 3.89 2.18 1.61 3.79 1.99 3.60
53 Lyon fr Ligue 1 27 28.1 55.2 24 264 2,160 24.0 41 32 73
38 3 5 39 0 40.7 36.8 30.1 66.9 460 1163 1.71 1.33
3.04 1.58 2.92 1.70 1.25 2.95 1.53 2.79
54 Mainz 05 de Bundesliga 24 28.2 49.4 24 264 2,160 24.0 37
26 63 34 3 3 57 3 34.2 31.9 25.9 57.8 314 895
1.54 1.08 2.62 1.42 2.50 1.43 1.08 2.50 1.33 2.41
55 Mallorca es La Liga 27 29.3 46.2 26 286 2,340 26.0 24 16
40 21 3 3 56 6 29.6 27.3 20.6 47.8 350 835 0.92
0.62 1.54 0.81 1.42 1.14 0.79 1.93 1.05 1.84
56 Manchester City eng Premier League 27 27.6 60.4 27 297 2,430
27.0 52 39 91 51 1 2 46 1 48.8 47.2 40.8 87.9
827 1396 1.93 1.44 3.37 1.89 3.33 1.81 1.51 3.32 1.75 3.26
57 Manchester Utd eng Premier League 27 26.3 53.6 27 297 2,430
27.0 31 20 51 28 3 3 64 3 36.4 34.1 27.7 61.8
461 1018 1.15 0.74 1.89 1.04 1.78 1.35 1.03 2.38 1.26 2.29
58 Marseille fr Ligue 1 32 27.2 64.1 24 264 2,160 24.0 51 34
85 45 6 7 48 6 46.0 40.4 32.1 72.5 458 1177 2.12
1.42 3.54 1.87 3.29 1.92 1.34 3.26 1.68 3.02
59 Milan it Serie A 32 26.2 54.9 27 297 2,430 27.0 39 26
65 36 3 6 51 5 43.9 39.3 31.1 70.4 693 1205 1.44
0.96 2.41 1.33 2.30 1.63 1.15 2.78 1.45 2.61
60 Monaco fr Ligue 1 28 24.9 56.3 24 264 2,160 24.0 48 38
86 45 3 3 43 3 54.4 52.2 42.0 94.2 481 1205 2.00
1.58 3.58 1.87 3.46 2.27 1.75 4.02 2.18 3.92
61 Montpellier fr Ligue 1 34 27.2 45.7 24 264 2,160 24.0 21
12 33 18 3 4 70 6 28.4 25.3 19.5 44.9 391 770
0.87 0.50 1.37 0.75 1.25 1.18 0.81 2.00 1.06 1.87
62 Monza it Serie A 35 27.3 48.3 27 297 2,430 27.0 20 12
32 17 3 3 70 3 21.5 19.4 14.4 33.8 360 772 0.74
0.44 1.19 0.63 1.07 0.80 0.53 1.33 0.72 1.25
63 Nantes fr Ligue 1 29 27.3 40.3 24 264 2,160 24.0 28 20
48 24 4 4 50 2 30.0 27.0 21.2 48.2 358 790 1.17
0.83 2.00 1.00 1.83 1.25 0.88 2.14 1.12 2.01
64 Napoli it Serie A 26 29.2 53.6 27 297 2,430 27.0 40 29
69 36 4 5 33 0 36.6 32.8 26.8 59.6 526 1130 1.48
1.07 2.56 1.33 2.41 1.36 0.99 2.35 1.22 2.21
65 Newcastle Utd eng Premier League 24 27.7 50.4 27 297 2,430
27.0 45 34 79 42 3 4 51 1 44.9 41.7 33.5 75.2
515 1087 1.67 1.26 2.93 1.56 2.81 1.66 1.24 2.90 1.55 2.79
66 Nice fr Ligue 1 29 26.9 47.1 24 264 2,160 24.0 45 30 75
40 5 5 45 3 42.8 38.9 29.1 68.0 491 884 1.87 1.25
3.12 1.67 2.92 1.78 1.21 3.00 1.62 2.83
67 Nott'ham Forest eng Premier League 23 26.9 40.1 27 297 2,430
27.0 43 30 73 40 3 3 60 2 34.3 32.0 24.6 56.5
453 778 1.59 1.11 2.70 1.48 2.59 1.27 0.91 2.18 1.18 2.09
68 Osasuna es La Liga 23 28.1 46.5 26 286 2,340 26.0 30 16
46 24 6 6 65 0 28.1 24.1 18.5 42.6 405 863 1.15
0.62 1.77 0.92 1.54 1.08 0.71 1.79 0.93 1.64
69 Paris S-G fr Ligue 1 24 24.5 68.0 24 264 2,160 24.0 64 48
112 60 4 4 26 0 63.0 59.9 50.7 110.5 670 1466 2.67
2.00 4.67 2.50 4.50 2.63 2.11 4.74 2.50 4.61
70 Parma it Serie A 32 24.5 44.4 27 297 2,430 27.0 31 21
52 25 6 7 52 5 31.8 26.3 20.4 46.8 456 797 1.15
0.78 1.93 0.93 1.70 1.18 0.76 1.94 0.97 1.73
71 Rayo Vallecano es La Liga 24 29.6 50.6 26 286 2,340 26.0 26
20 46 26 0 0 65 3 32.8 32.8 26.9 59.7 395 914
1.00 0.77 1.77 1.00 1.77 1.26 1.03 2.30 1.26 2.30
72 RB Leipzig de Bundesliga 28 26.3 52.9 24 264 2,160 24.0 37
25 62 35 2 3 47 4 32.9 30.5 25.5 56.0 364 953
1.54 1.04 2.58 1.46 2.50 1.37 1.06 2.43 1.27 2.33
73 Real Madrid es La Liga 26 27.6 61.2 26 286 2,340 26.0 55
40 95 47 8 10 40 3 50.4 42.9 36.5 79.4 663 1371
2.12 1.54 3.65 1.81 3.35 1.94 1.41 3.34 1.65 3.06
74 Real Sociedad es La Liga 29 25.9 54.4 26 286 2,340 26.0 22
17 39 20 2 2 62 4 28.9 27.3 22.6 49.9 459 945
0.85 0.65 1.50 0.77 1.42 1.11 0.87 1.98 1.05 1.92
75 Reims fr Ligue 1 32 25.4 45.8 24 264 2,160 24.0 25 12
37 24 1 2 52 5 26.4 24.8 17.1 42.0 436 888 1.04
0.50 1.54 1.00 1.50 1.10 0.71 1.81 1.04 1.75
76 Rennes fr Ligue 1 35 26.2 50.8 24 264 2,160 24.0 34 18
52 29 5 7 52 2 33.0 27.9 18.7 46.6 470 1017 1.42
0.75 2.17 1.21 1.96 1.37 0.78 2.15 1.16 1.94
77 Roma it Serie A 28 27.2 55.4 27 297 2,430 27.0 41 25 66
34 7 7 53 1 38.5 33.0 25.1 58.1 503 1083 1.52 0.93
2.44 1.26 2.19 1.43 0.93 2.36 1.22 2.15
78 Saint-Étienne fr Ligue 1 32 26.4 45.8 24 264 2,160 24.0 24
17 41 21 3 3 47 2 25.4 23.1 17.9 41.0 360 810
1.00 0.71 1.71 0.87 1.58 1.06 0.75 1.81 0.96 1.71
79 Sevilla es La Liga 34 26.8 52.1 26 286 2,340 26.0 28 24
52 27 1 2 70 5 28.3 26.7 19.9 46.5 418 795 1.08
0.92 2.00 1.04 1.96 1.09 0.76 1.85 1.03 1.79
80 Southampton eng Premier League 34 26.0 50.9 27 297 2,430
27.0 18 12 30 18 0 2 74 3 25.3 23.5 19.4 42.9
479 849 0.67 0.44 1.11 0.67 1.11 0.94 0.72 1.65 0.87 1.59
81 St. Pauli de Bundesliga 27 27.7 44.2 24 264 2,160 24.0 17
15 32 16 1 2 37 1 24.0 22.4 18.5 40.9 301 761
0.71 0.62 1.33 0.67 1.29 1.00 0.77 1.77 0.93 1.70
82 Strasbourg fr Ligue 1 28 22.1 49.5 24 264 2,160 24.0 37 28
65 35 2 2 55 1 35.8 34.2 27.3 61.6 384 829 1.54
1.17 2.71 1.46 2.62 1.49 1.14 2.63 1.43 2.57
83 Stuttgart de Bundesliga 27 25.5 57.0 24 264 2,160 24.0 40
27 67 38 2 5 54 2 41.6 37.7 28.7 66.4 380 1056
1.67 1.13 2.79 1.58 2.71 1.73 1.20 2.93 1.57 2.77
84 Torino it Serie A 28 27.4 47.3 27 297 2,430 27.0 28 18
46 27 1 2 60 2 24.3 22.8 15.9 38.7 380 806 1.04
0.67 1.70 1.00 1.67 0.90 0.59 1.49 0.84 1.43
85 Tottenham eng Premier League 31 25.8 57.1 27 297 2,430 27.0
50 40 90 50 0 1 51 1 44.8 44.0 35.2 79.2 630
1227 1.85 1.48 3.33 1.85 3.33 1.66 1.30 2.96 1.63 2.94
86 Toulouse fr Ligue 1 27 25.7 45.7 24 264 2,160 24.0 29 22
51 26 3 4 38 1 36.0 32.8 25.6 58.4 394 873 1.21
0.92 2.12 1.08 2.00 1.50 1.06 2.56 1.37 2.43
87 Udinese it Serie A 29 27.2 46.7 27 297 2,430 27.0 34 23
57 32 2 4 61 4 27.5 24.3 18.8 43.2 402 828 1.26
0.85 2.11 1.19 2.04 1.02 0.70 1.72 0.90 1.60
88 Union Berlin de Bundesliga 27 27.7 42.6 24 264 2,160 24.0
21 14 35 18 3 3 60 1 28.4 26.0 19.1 45.1 298
808 0.87 0.58 1.46 0.75 1.33 1.18 0.79 1.98 1.09 1.88
89 Valencia es La Liga 32 25.2 47.0 26 286 2,340 26.0 28 18
46 25 3 3 57 1 28.6 26.2 20.6 46.8 392 888 1.08
0.69 1.77 0.96 1.65 1.10 0.79 1.89 1.01 1.80
90 Valladolid es La Liga 32 26.2 43.3 26 286 2,340 26.0 17 11
28 14 3 3 73 4 22.3 20.0 16.8 36.8 326 695 0.65
0.42 1.08 0.54 0.96 0.86 0.64 1.50 0.77 1.42
91 Venezia it Serie A 36 26.1 44.6 27 297 2,430 27.0 22 12
34 19 3 4 52 1 24.2 21.2 14.8 35.9 369 765 0.81
0.44 1.26 0.70 1.15 0.90 0.55 1.45 0.78 1.33
92 Villarreal es La Liga 28 27.6 48.4 25 275 2,340 26.0 46 30
76 41 5 7 68 2 43.4 37.7 31.2 69.0 450 1017 1.77
1.15 2.92 1.58 2.73 1.67 1.20 2.87 1.45 2.65
93 Werder Bremen de Bundesliga 23 28.2 50.9 24 264 2,160 24.0
35 27 62 34 1 3 53 4 32.6 30.6 25.0 55.6 348
982 1.46 1.13 2.58 1.42 2.54 1.36 1.04 2.40 1.27 2.32
94 West Ham eng Premier League 27 28.9 46.9 27 297 2,430 27.0
30 17 47 27 3 3 61 3 35.1 32.8 24.8 57.6 439
871 1.11 0.63 1.74 1.00 1.63 1.30 0.92 2.22 1.21 2.13
95 Wolfsburg de Bundesliga 25 25.8 44.0 24 264 2,160 24.0 46
32 78 43 3 4 60 2 34.7 31.5 23.9 55.3 314 789
1.92 1.33 3.25 1.79 3.12 1.45 0.99 2.44 1.31 2.30
96 Wolves eng Premier League 28 27.5 47.4 27 297 2,430 27.0
37 31 68 37 0 0 61 2 30.5 30.5 25.0 55.4 425
848 1.37 1.15 2.52 1.37 2.52 1.13 0.92 2.05 1.13 2.05

You might also like