Skip to content

Instantly share code, notes, and snippets.

@hising
Created October 5, 2024 19:44
Show Gist options
  • Save hising/742ef56f38e17fec87eda46cfae00f3b to your computer and use it in GitHub Desktop.
Save hising/742ef56f38e17fec87eda46cfae00f3b to your computer and use it in GitHub Desktop.
IK Sirius - Last 5 2024
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Football Points Simulation</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</head>
<body>
<canvas id="resultsChart" width="400" height="200"></canvas>
<h3>Top 3 Most Probable Points</h3>
<ul id="topThreePoints"></ul>
<h3>Additional Insights</h3>
<ul id="additionalStats"></ul>
<script>
// Number of simulations and games
const simulations = 10000;
const games = 5;
// Elfsborg, Halmstad, Hammarby, Mjällbu, GAIS
// Array of opponent strengths (adjust based on opponent difficulty, 0 to 1 scale)
const opponents_strength = [1, 0.3, 0.7, 0.6, 0.5]; // Strength of each opponent
const team_strength = 0.5;
// Home/Away modifier
const homeAway = [true, false, true, true, false]; // True = home, False = away
const homeAdvantage = 0.1; // Boost for home games
// Injuries and fatigue modifiers
const injuryImpact = [0, 0, 0, 0, 0];
const fatigueFactor = [-0.3, 0, 0, 0, 0];
// New factors based on team form and motivation
const motivationFactor = [1.2, 1.2, 1.2, 1.2, 0.8]; // 1.2 = high motivation (survival or Europe), 0.8 = less motivation (mid-table)
// Form-based multipliers (e.g., 1.1 for winning streak, 0.9 for losing streak)
const formMultiplier = [1, .9, 0.95, .95, .9]; // Reflects how recent form affects team performance
// Function to calculate win probability based on team, opponent strength, and motivation
function calculateWinProbability(teamStrength, opponentStrength, gameIndex) {
const randomness = Math.random() * 0.1 - 0.05; // +/- 0.05 random variation
const motivationBoost = motivationFactor[gameIndex];
const formBoost = formMultiplier[gameIndex];
const adjustedStrength = (teamStrength + injuryImpact[gameIndex] + fatigueFactor[gameIndex]) * motivationBoost * formBoost;
const baseWinProb = (adjustedStrength + randomness) / (adjustedStrength + opponentStrength);
const drawProb = 0.2; // Draw probability remains the same
return {
win: baseWinProb,
draw: drawProb,
loss: 1 - baseWinProb - drawProb
};
}
// Function to simulate points for one match
function simulateMatch(teamStrength, opponentStrength, gameIndex) {
const probs = calculateWinProbability(teamStrength, opponentStrength, gameIndex);
const randomOutcome = Math.random();
if (randomOutcome < probs.win) {
return 3; // Win
} else if (randomOutcome < probs.win + probs.draw) {
return 1; // Draw
} else {
return 0; // Loss
}
}
// Simulate points for the last 5 games with all factors
function simulatePoints() {
let totalPoints = 0;
for (let i = 0; i < games; i++) {
let adjustedTeamStrength = team_strength + (homeAway[i] ? homeAdvantage : 0);
totalPoints += simulateMatch(adjustedTeamStrength, opponents_strength[i], i);
}
return totalPoints;
}
// Running 10,000 simulations
let pointsDistribution = new Array(16).fill(0); // Possible points range from 0 to 15
let allPoints = [];
for (let i = 0; i < simulations; i++) {
const points = simulatePoints();
pointsDistribution[points]++;
allPoints.push(points); // Store all point outcomes for further analysis
}
// Normalize the data for the line chart
let normalizedData = pointsDistribution.map(p => p / simulations);
// Find the top 3 most probable point outcomes
function findTopThreePoints(pointsDistribution) {
const pointProbabilities = pointsDistribution.map((count, points) => ({ points, count }));
pointProbabilities.sort((a, b) => b.count - a.count); // Sort by count in descending order
return pointProbabilities.slice(0, 3); // Get top 3
}
// Display the top 3 most probable points
const topThree = findTopThreePoints(pointsDistribution);
const topThreeList = document.getElementById('topThreePoints');
topThree.forEach(({ points, count }) => {
const li = document.createElement('li');
li.textContent = `${points} points - ${((count / simulations) * 100).toFixed(2)}% probability`;
topThreeList.appendChild(li);
});
// Additional fun stats
function calculateAveragePoints(pointsArray) {
const totalPoints = pointsArray.reduce((a, b) => a + b, 0);
return totalPoints / pointsArray.length;
}
function calculateMedianPoints(pointsArray) {
pointsArray.sort((a, b) => a - b);
const middle = Math.floor(pointsArray.length / 2);
if (pointsArray.length % 2 === 0) {
return (pointsArray[middle - 1] + pointsArray[middle]) / 2;
} else {
return pointsArray[middle];
}
}
function calculateVariance(pointsArray) {
const mean = calculateAveragePoints(pointsArray);
const squaredDiffs = pointsArray.map(p => Math.pow(p - mean, 2));
const variance = squaredDiffs.reduce((a, b) => a + b, 0) / pointsArray.length;
return variance;
}
function calculateStandardDeviation(pointsArray) {
return Math.sqrt(calculateVariance(pointsArray));
}
// Calculate the probability of getting more than 10 points
function probabilityMoreThan(pointsArray, threshold) {
const count = pointsArray.filter(p => p > threshold).length;
return (count / pointsArray.length) * 100;
}
// Display additional stats
const additionalStatsList = document.getElementById('additionalStats');
const stats = [
`Average points: ${calculateAveragePoints(allPoints).toFixed(2)}`,
`Median points: ${calculateMedianPoints(allPoints)}`,
`Variance: ${calculateVariance(allPoints).toFixed(2)}`,
`Standard Deviation: ${calculateStandardDeviation(allPoints).toFixed(2)}`,
`Probability of more than 5 points: ${probabilityMoreThan(allPoints, 5).toFixed(2)}%`,
`Probability of more than 10 points: ${probabilityMoreThan(allPoints, 10).toFixed(2)}%`,
];
stats.forEach(stat => {
const li = document.createElement('li');
li.textContent = stat;
additionalStatsList.appendChild(li);
});
// Create the histogram chart
const ctx = document.getElementById('resultsChart').getContext('2d');
const chart = new Chart(ctx, {
type: 'bar',
data: {
labels: [...Array(16).keys()],
datasets: [
{
label: 'Frequency of Points (Bar)',
data: pointsDistribution,
backgroundColor: 'rgba(75, 192, 192, 0.2)',
borderColor: 'rgba(75, 192, 192, 1)',
borderWidth: 1
}
]
},
options: {
scales: {
y: {
beginAtZero: true
}
}
}
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment