Skip to content

Commit

Permalink
1.0.1
Browse files Browse the repository at this point in the history
- Adjusted model to change a team's rating if they win or lose based on an ELO system that randomly scales per opponent per match to simulate match results (blowouts, close games)
- Moved duplicated code into Bracket interface
- Updated the match odds to better reflect best-of-one and best-of-three matches
- Removed debug code
  • Loading branch information
Foulest committed Dec 6, 2024
1 parent eb4ffa3 commit 7f7db7a
Show file tree
Hide file tree
Showing 7 changed files with 220 additions and 206 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ plugins {
}

group = 'net.foulest'
version = '1.0.0'
version = '1.0.1'
description = 'Swiss'

// Set the project's language level
Expand Down
32 changes: 14 additions & 18 deletions src/main/java/net/foulest/swiss/Swiss.java
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ public static void main(String[] args) {
System.out.println("by Foulest | github.com/Foulest");
System.out.println();
System.out.println("Choose the bracket to simulate:");
System.out.println("0. Manual Matches");
System.out.println("1. Standard Bracket");
System.out.println("2. Champions Bracket");
System.out.println();
Expand All @@ -94,8 +95,17 @@ public static void main(String[] args) {
// Get whether to simulate Standard or Champions bracket
int bracketNumber = scanner.nextInt();

if (bracketNumber != 1 && bracketNumber != 2) {
System.out.println("Invalid input. Please enter 1 or 2.");
// Validate the input
if (bracketNumber != 0 && bracketNumber != 1 && bracketNumber != 2) {
System.out.println("Invalid input. Please enter 0, 1 or 2.");
return;
}

// You can also display the winner of a match based on win probability
// instead of simulating the entire bracket (these are just examples).
if (bracketNumber == 0) {
Match.displayWinnerFromProbability(theMongolZ, heroic, false);
Match.displayWinnerFromProbability(theMongolZ, mibr, true);
return;
}

Expand All @@ -107,7 +117,7 @@ public static void main(String[] args) {
System.out.println("On average, for Standard brackets, every 1,000,000 simulations takes 7.5 seconds.");
System.out.println("On average, for Champions brackets, every 1,000,000 simulations takes 1.5 seconds.");
System.out.println("You can do the math to figure out how long it would take to simulate your desired amount of brackets.");
System.out.println("You can also enter 0 to just print the win probability of the matches below.");

System.out.println();
System.out.print("Enter the amount of brackets to simulate: ");

Expand Down Expand Up @@ -135,9 +145,8 @@ public static void main(String[] args) {
}

// Validate the input
if (bracketsToSimulate < 0 || bracketsToSimulate > 50000000) {
if (bracketsToSimulate <= 0 || bracketsToSimulate > 50000000) {
System.out.println("Invalid input. Please enter a number between 1 and 50,000,000.");
return;
} else {
System.out.println("Simulating " + bracketsToSimulate + " brackets...");

Expand All @@ -150,18 +159,5 @@ public static void main(String[] args) {
bracket.simulateMultipleBrackets(bracketsToSimulate);
}
}

// You can also display the winner of a match based on win probability
// instead of simulating the entire bracket.
if (bracketsToSimulate == 0) {
Match.displayWinnerFromProbability(natusVincere, mibr);
Match.displayWinnerFromProbability(vitality, furia);
Match.displayWinnerFromProbability(mouz, theMongolZ);
Match.displayWinnerFromProbability(faze, heroic);
Match.displayWinnerFromProbability(g2, big);
Match.displayWinnerFromProbability(spirit, wildcard);
Match.displayWinnerFromProbability(_3DMAX, paiN);
Match.displayWinnerFromProbability(liquid, gamerLegion);
}
}
}
104 changes: 104 additions & 0 deletions src/main/java/net/foulest/swiss/brackets/Bracket.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package net.foulest.swiss.brackets;

import net.foulest.swiss.match.Match;
import net.foulest.swiss.team.Team;
import org.jetbrains.annotations.NotNull;

import java.util.List;
import java.util.Map;

public interface Bracket {

List<Team> getTeams();

long getStartingTime();

/**
* Update the ratings for both teams based on the match outcome.
*
* @param team1 The first team.
* @param team2 The second team.
* @param winner The winning team.
*/
default void updateTeamRatings(Team team1, Team team2, Team winner, boolean bestOfThree) {
// Calculate win probability for dynamic rating adjustment
double winProbability = Match.calculateWinProbability(team1, team2, bestOfThree);
double actualOutcome = (winner == team1) ? 1 : 0; // 1 if team1 wins, 0 if team2 wins

// Randomly generate the K-factor between 0.003 and 0.007
double K = 0.003 + Math.random() * 0.004;

// Adjust ratings for both teams
double team1Expected = winProbability; // Expected score for team1
double team2Expected = 1 - winProbability; // Expected score for team2

// Update ratings using Elo-like formula
team1.setAvgPlayerRating(team1.getAvgPlayerRating() + K * (actualOutcome - team1Expected));
team2.setAvgPlayerRating(team2.getAvgPlayerRating() + K * ((1 - actualOutcome) - team2Expected));
}

/**
* Print the match result.
*
* @param winner The winning team.
* @param records The records of each team.
* @param loser The losing team.
*/
default void printMatchResult(@NotNull Team winner,
@NotNull Map<Team, int[]> records,
@NotNull Team loser) {
String winnerName = winner.getName();
String loserName = loser.getName();

int[] winnerRecords = records.get(winner);
int[] loserRecords = records.get(loser);

System.out.println(winnerName + " (" + winnerRecords[0] + "-" + winnerRecords[1] + ")"
+ " beat " + loserName + " (" + loserRecords[0] + "-" + loserRecords[1] + ")");
}

/**
* Append a probability to the result string if it exists.
*
* @param resultString The result string to append to.
* @param record The record to check for.
* @param recordCounts The record counts for the team.
* @param numSimulations The number of simulations.
*/
static void appendProbability(StringBuilder resultString, String record,
@NotNull Map<String, Integer> recordCounts,
int numSimulations) {
if (recordCounts.containsKey(record)) {
double probability = recordCounts.get(record) * 100.0 / numSimulations;
resultString.append(String.format("%s (%.2f%%) ", record, probability));
}
}

/**
* Print the header for the results.
*
* @param numSimulations The number of simulations.
* @param startingTime The starting time of the simulations.
*/
static void printHeader(int numSimulations, long startingTime) {
long duration = System.currentTimeMillis() - startingTime;
double seconds = duration / 1000.0;

System.out.println();
System.out.println("Results after " + numSimulations + " simulations (took " + seconds + " seconds):");
System.out.println();
System.out.println("Individual Team Results:");
}

/**
* Update the records for each team.
*
* @param records The records of each team.
* @param winner The winning team.
* @param loser The losing team.
*/
static void updateRecords(@NotNull Map<Team, int[]> records, Team winner, Team loser) {
records.get(winner)[0] += 1;
records.get(loser)[1] += 1;
}
}
105 changes: 36 additions & 69 deletions src/main/java/net/foulest/swiss/brackets/ChampionsBracket.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;

/**
* Represents a bracket for the Challengers & Legends stages of the CS2 Major.
Expand All @@ -38,7 +39,7 @@
* @author Foulest
*/
@Data
public class ChampionsBracket {
public class ChampionsBracket implements Bracket {

private List<Team> teams;
private long startingTime;
Expand Down Expand Up @@ -105,7 +106,9 @@ private void simulateBracket(Map<Team, Map<String, Integer>> results) {
List<Team> seededTeams = new ArrayList<>(teams);
seededTeams.sort(Comparator.comparingInt(Team::getSeeding));

List<Team> activeTeams = new ArrayList<>(seededTeams); // All teams start active
List<Team> activeTeams = seededTeams.stream()
.map(Team::clone) // Clone or copy each team
.collect(Collectors.toList());

// Simulate each round, eliminating teams that lose
while (activeTeams.size() > 1) {
Expand All @@ -115,14 +118,19 @@ private void simulateBracket(Map<Team, Map<String, Integer>> results) {
for (int i = 0; i < activeTeams.size() / 2; i++) {
Team team1 = activeTeams.get(i); // Lower seed
Team team2 = activeTeams.get(activeTeams.size() - 1 - i); // Higher seed

// Create a match between the two teams
Match match = new Match(team1, team2, 1); // Best of 1 match

// Simulate match
// Simulate the match
Team winner = match.simulate(false);
Team loser = (winner == team1) ? team2 : team1;

// Update team ratings
updateTeamRatings(team1, team2, winner, false);

// Update records
updateRecords(records, winner, loser);
Bracket.updateRecords(records, winner, loser);

// // Print the match result
// printMatchResult(winner, records, loser);
Expand Down Expand Up @@ -150,50 +158,31 @@ private void simulateBracket(Map<Team, Map<String, Integer>> results) {
// Record every team's final record
results.get(team).merge(result, 1, Integer::sum);
}
}

/**
* Print the match result.
*
* @param winner The winning team.
* @param records The records of each team.
* @param loser The losing team.
*/
private static void printMatchResult(@NotNull Team winner,
@NotNull Map<Team, int[]> records,
@NotNull Team loser) {
String winnerName = winner.getName();
String loserName = loser.getName();

int[] winnerRecords = records.get(winner);
int[] loserRecords = records.get(loser);

System.out.println(winnerName + " (" + winnerRecords[0] + "-" + winnerRecords[1] + ")"
+ " beat " + loserName + " (" + loserRecords[0] + "-" + loserRecords[1] + ")");
// // Print the rating change for each team
// printRatingChange(initialRatings, records);
}

/**
* Update the records for each team.
* Print the rating change for each team.
*
* @param records The records of each team.
* @param winner The winning team.
* @param loser The losing team.
* @param initialRatings The initial ratings of each team.
* @param records The records of each team.
*/
private static void updateRecords(@NotNull Map<Team, int[]> records, Team winner, Team loser) {
records.get(winner)[0] += 1;
records.get(loser)[1] += 1;
}
private static void printRatingChange(@NotNull Map<Team, Double> initialRatings, Map<Team, int[]> records) {
System.out.println();

/**
* Update the past opponents for each team.
*
* @param pastOpponents The past opponents for each team.
* @param winner The winning team.
* @param loser The losing team.
*/
private static void updatePastOpponents(@NotNull Map<Team, List<Team>> pastOpponents, Team winner, Team loser) {
pastOpponents.get(winner).add(loser);
pastOpponents.get(loser).add(winner);
for (Map.Entry<Team, Double> entry : initialRatings.entrySet()) {
Team team = entry.getKey();
double initialRating = initialRatings.get(team);
double finalRating = team.getAvgPlayerRating();
double ratingChange = finalRating - initialRating;
String teamName = team.getName();

System.out.println(teamName + " (" + records.get(team)[0] + "-" + records.get(team)[1] + ")"
+ " started with a rating of " + initialRating
+ " and ended with a rating of " + finalRating + " (" + ratingChange + ")");
}
}

/**
Expand All @@ -204,13 +193,8 @@ private static void updatePastOpponents(@NotNull Map<Team, List<Team>> pastOppon
*/
private static void printResults(@NotNull Map<Team, Map<String, Integer>> results,
int numSimulations, long startingTime) {
long duration = System.currentTimeMillis() - startingTime;
double seconds = duration / 1000.0;

System.out.println();
System.out.println("Results after " + numSimulations + " simulations (took " + seconds + " seconds):");
System.out.println();
System.out.println("Individual Team Results:");
// Print the header
Bracket.printHeader(numSimulations, startingTime);

for (Map.Entry<Team, Map<String, Integer>> entry : results.entrySet()) {
Team team = entry.getKey();
Expand All @@ -221,30 +205,13 @@ private static void printResults(@NotNull Map<Team, Map<String, Integer>> result
StringBuilder resultString = new StringBuilder(String.format("Team: %s | ", teamName));

// Add individual probabilities
appendProbability(resultString, "3-0", recordCounts, numSimulations);
appendProbability(resultString, "2-1", recordCounts, numSimulations);
appendProbability(resultString, "1-1", recordCounts, numSimulations);
appendProbability(resultString, "0-1", recordCounts, numSimulations);
Bracket.appendProbability(resultString, "3-0", recordCounts, numSimulations);
Bracket.appendProbability(resultString, "2-1", recordCounts, numSimulations);
Bracket.appendProbability(resultString, "1-1", recordCounts, numSimulations);
Bracket.appendProbability(resultString, "0-1", recordCounts, numSimulations);

// Print the team's result
System.out.println(resultString.toString().trim());
}
}

/**
* Append a probability to the result string if it exists.
*
* @param resultString The result string to append to.
* @param record The record to check for.
* @param recordCounts The record counts for the team.
* @param numSimulations The number of simulations.
*/
private static void appendProbability(StringBuilder resultString, String record,
@NotNull Map<String, Integer> recordCounts,
int numSimulations) {
if (recordCounts.containsKey(record)) {
double probability = recordCounts.get(record) * 100.0 / numSimulations;
resultString.append(String.format("%s (%.2f%%) ", record, probability));
}
}
}
Loading

0 comments on commit 7f7db7a

Please sign in to comment.