Source: Todd Greene on Unsplash
dataclass
The code starts by defining a Team
class using Python's @dataclass
decorator. This is a convenient way to create classes primarily used to store data, automatically generating methods like __init__
, __repr__
, and __eq__
.
import random
from dataclasses import dataclass
@dataclass
class Team:
name: str
seed: int
odds: int
name
: The name of the team (e.g., "Auburn", "Duke").seed
: The team's seeding in the tournament (lower numbers are better, e.g., 1 for a top seed).odds
: A numerical representation of the team's chances of winning the entire tournament, typically from betting markets (lower numbers indicate better chances).The team_odds
dictionary stores the initial odds for various teams. Teams not explicitly listed default to a very high odds value (1,000,000), effectively making them longshots.
The first_round
list sets up the initial matchups. Each element in this list is a tuple containing two Team
objects. Notice how it handles teams not in team_odds
or play-in games (like "Alabama ST/Saint Francis U" or "San Diego State/North Carolina") by providing a default high odds value.
team_odds = {
"Auburn": 340,
"Duke": 360,
# ... (truncated for brevity) ...
"UC Irvine": 100000,
"Yale": 100000,
}
first_round = [
# SOUTH
(Team("Auburn", seed=1, odds=team_odds.get("Auburn", 1000000)), Team("Alabama ST/Saint Francis U", seed=16, odds=1000000)),
# ... (truncated for brevity) ...
]
simulate_game
)This is the heart of the simulation, determining the winner of a single matchup. It combines both seed and odds to calculate a winning probability for each team.
def simulate_game(team1, team2):
# Calculate seed weights (lower seed is better)
team1_seed_weight = 1 / (team1.seed)
team2_seed_weight = 1 / (team2.seed)
# Calculate odds weights (lower odds value is better)
max_odds = max(team1.odds, team2.odds)
team1_odds_weight = max_odds / team1.odds
team2_odds_weight = max_odds / team2.odds
# Combine weights. odds_priority_multiplier gives odds a stronger influence.
odds_priority_multiplier = 10
team1_combined_weight = team1_seed_weight + (team1_odds_weight * odds_priority_multiplier)
team2_combined_weight = team2_seed_weight + (team2_odds_weight * odds_priority_multiplier)
# Convert to probabilities
total_combined_weight = team1_combined_weight + team2_combined_weight
team1_prob = 100 * (team1_combined_weight / total_combined_weight)
team2_prob = 100 * (team2_combined_weight / total_combined_weight)
# Get winner based on probabilities
winner = random.choices([team1, team2], weights=[team1_prob, team2_prob], k=1)[0]
print(
f"{team1.name}-{team1.seed} (Odds: {team1.odds}, Prob: {team1_prob:.1f}%) vs "
f"{team2.name}-{team2.seed} (Odds: {team2.odds}, Prob: {team2_prob:.1f}%), "
f"Winner: {winner.name}"
)
return winner
odds_priority_multiplier
: This crucial variable allows you to adjust how much influence the betting odds have compared to the seed. A value of '10' means odds are considered 10 times more important than seeding in this simulation. You can experiment with this value!random.choices
: This function from Python's 'random' module is used to pick a winner based on the calculated probabilities, making the simulation probabilistic.simulate_tournament
)This function takes the initial round's matchups and simulates the tournament round by round until only one team remains.
def simulate_tournament(first_round):
current_games = first_round
while len(current_games) > 0:
print("
===== NEW ROUND =====")
winners = []
for team1, team2 in current_games:
winner = simulate_game(team1, team2)
winners.append(winner)
if len(winners) == 1:
return winners[0] # Tournament over, we have a champion!
next_round = []
for i in range(0, len(winners), 2):
# Pair up winners for the next round
next_round.append((winners[i], winners[i + 1]))
current_games = next_round # Move to the next round with new matchups
return None # Should not be reached in a complete bracket
Finally, the script calls 'simulate_tournament' with the 'first_round' matchups and prints the ultimate champion.
if __name__ == "__main__":
winner = simulate_tournament(first_round)
print(f"
{winner.name} wins the tournament!")
bracket_sim.py
).python bracket_sim.py
Each time you run it, you'll get a different simulated outcome due to the random nature of 'random.choices'!
This simulation is a great starting point. Here are some ideas for enhancement:
This code provides a fun and insightful way to model probabilistic outcomes in competitive scenarios!
import random
from dataclasses import dataclass
@dataclass
class Team:
name: str
seed: int
odds: int
team_odds = {
"Auburn": 340,
"Duke": 360,
"Florida": 450,
"Houston": 600,
"Alabama": 1100,
"Tennessee": 1300,
"Michigan State": 2200,
"St. John's": 2200,
"Iowa State": 2500,
"Kentucky": 3500,
"Texas Tech": 4000,
"Kansas": 5000,
"Connecticut": 6000,
"Arizona": 5000,
"Gonzaga": 5000,
"Wisconsin": 5000,
"Maryland": 5000,
"Clemson": 6000,
"BYU": 6600,
"Texas A&M": 6600,
"Purdue": 8000,
"Marquette": 8000,
"Michigan": 8000,
"Missouri": 8000,
"Creighton": 10000,
"Illinois": 10000,
"St. Mary's": 10000,
"Mississippi State": 10000,
"UCLA": 10000,
"Ole Miss": 10000,
"Louisville": 10000,
"Oregon": 15000,
"Memphis": 15000,
"Baylor": 20000,
"New Mexico": 20000,
"Vanderbilt": 20000,
"Arkansas": 25000,
"VCU": 25000,
"Indiana": 30000,
"Georgia": 30000,
"Texas": 35000,
"Utah State": 35000,
"North Carolina": 50000,
"San Diego State": 50000,
"Xavier": 50000,
"Boise State": 50000,
"Oklahoma": 50000,
"West Virginia": 50000,
"Drake": 50000,
"UC San Diego": 50000,
"Ohio State": 100000,
"Colorado State": 100000,
"UAB": 100000,
"Grand Canyon": 100000,
"McNeese": 100000,
"UC Irvine": 100000,
"Yale": 100000,
}
first_round = [
# SOUTH
(Team("Auburn", seed=1, odds=team_odds.get("Auburn", 1000000)), Team("Alabama ST/Saint Francis U", seed=16, odds=1000000)),
(Team("Louisville", seed=8, odds=team_odds.get("Louisville", 1000000)), Team("Creighton", seed=9, odds=team_odds.get("Creighton", 1000000))),
(Team("Michigan", seed=5, odds=team_odds.get("Michigan", 1000000)), Team("UC San Diego", seed=12, odds=team_odds.get("UC San Diego", 1000000))),
(Team("Texas A&M", seed=4, odds=team_odds.get("Texas A&M", 1000000)), Team("Yale", seed=13, odds=team_odds.get("Yale", 1000000))),
(Team("Ole Miss", seed=6, odds=team_odds.get("Ole Miss", 1000000)), Team("San Diego State/North Carolina", seed=11, odds=team_odds.get("San Diego State", 1000000) or team_odds.get("North Carolina", 1000000))),
(Team("Iowa State", seed=3, odds=team_odds.get("Iowa State", 1000000)), Team("Lipscomb", seed=14, odds=1000000)),
(Team("Marquette", seed=7, odds=team_odds.get("Marquette", 1000000)), Team("New Mexico", seed=10, odds=team_odds.get("New Mexico", 1000000))),
(Team("Michigan State", seed=2, odds=team_odds.get("Michigan State", 1000000)), Team("Bryant", seed=15, odds=1000000)),
# WEST
(Team("Florida", seed=1, odds=team_odds.get("Florida", 1000000)), Team("Norfolk St.", seed=16, odds=1000000)),
(Team("Connecticut", seed=8, odds=team_odds.get("Connecticut", 1000000)), Team("Oklahoma", seed=9, odds=team_odds.get("Oklahoma", 1000000))),
(Team("Memphis", seed=5, odds=team_odds.get("Memphis", 1000000)), Team("Colorado State", seed=12, odds=team_odds.get("Colorado State", 1000000))),
(Team("Maryland", seed=4, odds=team_odds.get("Maryland", 1000000)), Team("Grand Canyon", seed=13, odds=team_odds.get("Grand Canyon", 1000000))),
(Team("Missouri", seed=6, odds=team_odds.get("Missouri", 1000000)), Team("Drake", seed=11, odds=team_odds.get("Drake", 1000000))),
(Team("Texas Tech", seed=3, odds=team_odds.get("Texas Tech", 1000000)), Team("UNC Wilmington", seed=14, odds=1000000)),
(Team("Kansas", seed=7, odds=team_odds.get("Kansas", 1000000)), Team("Arkansas", seed=10, odds=team_odds.get("Arkansas", 1000000))),
(Team("St. John's", seed=2, odds=team_odds.get("St. John's", 1000000)), Team("Omaha", seed=15, odds=1000000)),
# EAST
(Team("Duke", seed=1, odds=team_odds.get("Duke", 1000000)), Team("America/Mount St Mary's", seed=16, odds=1000000)),
(Team("Mississippi State", seed=8, odds=team_odds.get("Mississippi State", 1000000)), Team("Baylor", seed=9, odds=team_odds.get("Baylor", 1000000))),
(Team("Oregon", seed=5, odds=team_odds.get("Oregon", 1000000)), Team("Liberty", seed=12, odds=1000000)),
(Team("Arizona", seed=4, odds=team_odds.get("Arizona", 1000000)), Team("Akron", seed=13, odds=1000000)),
(Team("BYU", seed=6, odds=team_odds.get("BYU", 1000000)), Team("VCU", seed=11, odds=team_odds.get("VCU", 1000000))),
(Team("Wisconsin", seed=3, odds=team_odds.get("Wisconsin", 1000000)), Team("Montana", seed=14, odds=1000000)),
(Team("St. Mary's", seed=7, odds=team_odds.get("St. Mary's", 1000000)), Team("Vanderbilt", seed=10, odds=team_odds.get("Vanderbilt", 1000000))),
(Team("Alabama", seed=2, odds=team_odds.get("Alabama", 1000000)), Team("Robert Morris", seed=15, odds=1000000)),
# MIDWEST
(Team("Houston", seed=1, odds=team_odds.get("Houston", 1000000)), Team("SIU Edwardsville", seed=16, odds=1000000)),
(Team("Gonzaga", seed=8, odds=team_odds.get("Gonzaga", 1000000)), Team("Georgia", seed=9, odds=team_odds.get("Georgia", 1000000))),
(Team("Clemson", seed=5, odds=team_odds.get("Clemson", 1000000)), Team("McNeese", seed=12, odds=team_odds.get("McNeese", 1000000))),
(Team("Purdue", seed=4, odds=team_odds.get("Purdue", 1000000)), Team("High Point", seed=13, odds=1000000)),
(Team("Illinois", seed=6, odds=team_odds.get("Illinois", 1000000)), Team("Texas/Xavier", seed=11, odds=team_odds.get("Texas", 1000000) or team_odds.get("Xavier", 1000000))),
(Team("Kentucky", seed=3, odds=team_odds.get("Kentucky", 1000000)), Team("Troy", seed=14, odds=1000000)),
(Team("UCLA", seed=7, odds=team_odds.get("UCLA", 1000000)), Team("Utah State", seed=10, odds=team_odds.get("Utah State", 1000000))),
(Team("Tennessee", seed=2, odds=team_odds.get("Tennessee", 1000000)), Team("Wofford", seed=15, odds=1000000)),
]
def simulate_game(team1, team2):
# Calculate seed weights (lower seed is better)
team1_seed_weight = 1 / (team1.seed)
team2_seed_weight = 1 / (team2.seed)
# Calculate odds weights (lower odds value is better)
# We use a large number divided by odds to give higher weight to lower odds
max_odds = max(team1.odds, team2.odds)
team1_odds_weight = max_odds / team1.odds
team2_odds_weight = max_odds / team2.odds
# Combine weights. You can adjust the multiplier for odds to prioritize it more.
# A higher multiplier for odds_weight means odds will have a stronger influence.
# Here, we're giving odds a significantly higher influence than seeding.
odds_priority_multiplier = 10
team1_combined_weight = team1_seed_weight + (team1_odds_weight * odds_priority_multiplier)
team2_combined_weight = team2_seed_weight + (team2_odds_weight * odds_priority_multiplier)
# Convert to probabilities
total_combined_weight = team1_combined_weight + team2_combined_weight
team1_prob = 100 * (team1_combined_weight / total_combined_weight)
team2_prob = 100 * (team2_combined_weight / total_combined_weight)
# Get winner based on probabilities
winner = random.choices([team1, team2], weights=[team1_prob, team2_prob], k=1)[0]
print(
f"{team1.name}-{team1.seed} (Odds: {team1.odds}, Prob: {team1_prob:.1f}%) vs "
f"{team2.name}-{team2.seed} (Odds: {team2.odds}, Prob: {team2_prob:.1f}%), "
f"Winner: {winner.name}"
)
return winner
def simulate_tournament(first_round):
current_games = first_round
while len(current_games) > 0:
print("
===== NEW ROUND =====")
winners = []
for team1, team2 in current_games:
winner = simulate_game(team1, team2)
winners.append(winner)
if len(winners) == 1:
return winners[0]
next_round = []
for i in range(0, len(winners), 2):
next_round.append((winners[i], winners[i + 1]))
current_games = next_round
return None
winner = simulate_tournament(first_round)
print(f"
{winner.name} wins the tournament!")
===== NEW ROUND =====
Auburn-1 (Odds: 340, Prob: 100.0%) vs Alabama ST/Saint Francis U-16 (Odds: 1000000, Prob: 0.0%), Winner: Auburn
Louisville-8 (Odds: 10000, Prob: 50.0%) vs Creighton-9 (Odds: 10000, Prob: 50.0%), Winner: Louisville
Michigan-5 (Odds: 8000, Prob: 86.1%) vs UC San Diego-12 (Odds: 50000, Prob: 13.9%), Winner: Michigan
Texas A&M-4 (Odds: 6600, Prob: 93.8%) vs Yale-13 (Odds: 100000, Prob: 6.2%), Winner: Texas A&M
Ole Miss-6 (Odds: 10000, Prob: 83.3%) vs San Diego State/North Carolina-11 (Odds: 50000, Prob: 16.7%), Winner: Ole Miss
Iowa State-3 (Odds: 2500, Prob: 99.7%) vs Lipscomb-14 (Odds: 1000000, Prob: 0.3%), Winner: Iowa State
Marquette-7 (Odds: 8000, Prob: 71.3%) vs New Mexico-10 (Odds: 20000, Prob: 28.7%), Winner: Marquette
Michigan State-2 (Odds: 2200, Prob: 99.8%) vs Bryant-15 (Odds: 1000000, Prob: 0.2%), Winner: Michigan State
Florida-1 (Odds: 450, Prob: 100.0%) vs Norfolk St.-16 (Odds: 1000000, Prob: 0.0%), Winner: Florida
Connecticut-8 (Odds: 6000, Prob: 89.2%) vs Oklahoma-9 (Odds: 50000, Prob: 10.8%), Winner: Connecticut
Memphis-5 (Odds: 15000, Prob: 86.9%) vs Colorado State-12 (Odds: 100000, Prob: 13.1%), Winner: Memphis
Maryland-4 (Odds: 5000, Prob: 95.2%) vs Grand Canyon-13 (Odds: 100000, Prob: 4.8%), Winner: Grand Canyon
Missouri-6 (Odds: 8000, Prob: 86.1%) vs Drake-11 (Odds: 50000, Prob: 13.9%), Winner: Missouri
Texas Tech-3 (Odds: 4000, Prob: 99.6%) vs UNC Wilmington-14 (Odds: 1000000, Prob: 0.4%), Winner: Texas Tech
Kansas-7 (Odds: 5000, Prob: 83.2%) vs Arkansas-10 (Odds: 25000, Prob: 16.8%), Winner: Kansas
St. John's-2 (Odds: 2200, Prob: 99.8%) vs Omaha-15 (Odds: 1000000, Prob: 0.2%), Winner: St. John's
Duke-1 (Odds: 360, Prob: 100.0%) vs America/Mount St Mary's-16 (Odds: 1000000, Prob: 0.0%), Winner: Duke
Mississippi State-8 (Odds: 10000, Prob: 66.6%) vs Baylor-9 (Odds: 20000, Prob: 33.4%), Winner: Baylor
Oregon-5 (Odds: 15000, Prob: 98.5%) vs Liberty-12 (Odds: 1000000, Prob: 1.5%), Winner: Oregon
Arizona-4 (Odds: 5000, Prob: 99.5%) vs Akron-13 (Odds: 1000000, Prob: 0.5%), Winner: Arizona
BYU-6 (Odds: 6600, Prob: 79.0%) vs VCU-11 (Odds: 25000, Prob: 21.0%), Winner: BYU
Wisconsin-3 (Odds: 5000, Prob: 99.5%) vs Montana-14 (Odds: 1000000, Prob: 0.5%), Winner: Wisconsin
St. Mary's-7 (Odds: 10000, Prob: 66.6%) vs Vanderbilt-10 (Odds: 20000, Prob: 33.4%), Winner: St. Mary's
Alabama-2 (Odds: 1100, Prob: 99.9%) vs Robert Morris-15 (Odds: 1000000, Prob: 0.1%), Winner: Alabama
Houston-1 (Odds: 600, Prob: 99.9%) vs SIU Edwardsville-16 (Odds: 1000000, Prob: 0.1%), Winner: Houston
Gonzaga-8 (Odds: 5000, Prob: 85.6%) vs Georgia-9 (Odds: 30000, Prob: 14.4%), Winner: Gonzaga
Clemson-5 (Odds: 6000, Prob: 94.3%) vs McNeese-12 (Odds: 100000, Prob: 5.7%), Winner: Clemson
Purdue-4 (Odds: 8000, Prob: 99.2%) vs High Point-13 (Odds: 1000000, Prob: 0.8%), Winner: Purdue
Illinois-6 (Odds: 10000, Prob: 77.7%) vs Texas/Xavier-11 (Odds: 35000, Prob: 22.3%), Winner: Illinois
Kentucky-3 (Odds: 3500, Prob: 99.6%) vs Troy-14 (Odds: 1000000, Prob: 0.4%), Winner: Kentucky
UCLA-7 (Odds: 10000, Prob: 77.7%) vs Utah State-10 (Odds: 35000, Prob: 22.3%), Winner: UCLA
Tennessee-2 (Odds: 1300, Prob: 99.9%) vs Wofford-15 (Odds: 1000000, Prob: 0.1%), Winner: Tennessee
===== NEW ROUND =====
Auburn-1 (Odds: 340, Prob: 96.7%) vs Louisville-8 (Odds: 10000, Prob: 3.3%), Winner: Auburn
Michigan-5 (Odds: 8000, Prob: 45.2%) vs Texas A&M-4 (Odds: 6600, Prob: 54.8%), Winner: Michigan
Ole Miss-6 (Odds: 10000, Prob: 20.1%) vs Iowa State-3 (Odds: 2500, Prob: 79.9%), Winner: Iowa State
Marquette-7 (Odds: 8000, Prob: 21.6%) vs Michigan State-2 (Odds: 2200, Prob: 78.4%), Winner: Marquette
Florida-1 (Odds: 450, Prob: 93.0%) vs Connecticut-8 (Odds: 6000, Prob: 7.0%), Winner: Florida
Memphis-5 (Odds: 15000, Prob: 86.9%) vs Grand Canyon-13 (Odds: 100000, Prob: 13.1%), Winner: Memphis
Missouri-6 (Odds: 8000, Prob: 33.3%) vs Texas Tech-3 (Odds: 4000, Prob: 66.7%), Winner: Missouri
Kansas-7 (Odds: 5000, Prob: 30.4%) vs St. John's-2 (Odds: 2200, Prob: 69.6%), Winner: St. John's
Duke-1 (Odds: 360, Prob: 98.2%) vs Baylor-9 (Odds: 20000, Prob: 1.8%), Winner: Duke
Oregon-5 (Odds: 15000, Prob: 25.2%) vs Arizona-4 (Odds: 5000, Prob: 74.8%), Winner: Arizona
BYU-6 (Odds: 6600, Prob: 42.9%) vs Wisconsin-3 (Odds: 5000, Prob: 57.1%), Winner: BYU
St. Mary's-7 (Odds: 10000, Prob: 10.0%) vs Alabama-2 (Odds: 1100, Prob: 90.0%), Winner: Alabama
Houston-1 (Odds: 600, Prob: 89.3%) vs Gonzaga-8 (Odds: 5000, Prob: 10.7%), Winner: Houston
Clemson-5 (Odds: 6000, Prob: 56.9%) vs Purdue-4 (Odds: 8000, Prob: 43.1%), Winner: Purdue
Illinois-6 (Odds: 10000, Prob: 26.0%) vs Kentucky-3 (Odds: 3500, Prob: 74.0%), Winner: Illinois
UCLA-7 (Odds: 10000, Prob: 11.6%) vs Tennessee-2 (Odds: 1300, Prob: 88.4%), Winner: Tennessee
===== NEW ROUND =====
Auburn-1 (Odds: 340, Prob: 95.9%) vs Michigan-5 (Odds: 8000, Prob: 4.1%), Winner: Auburn
Iowa State-3 (Odds: 2500, Prob: 76.1%) vs Marquette-7 (Odds: 8000, Prob: 23.9%), Winner: Iowa State
Florida-1 (Odds: 450, Prob: 97.0%) vs Memphis-5 (Odds: 15000, Prob: 3.0%), Winner: Florida
Missouri-6 (Odds: 8000, Prob: 21.6%) vs St. John's-2 (Odds: 2200, Prob: 78.4%), Winner: St. John's
Duke-1 (Odds: 360, Prob: 93.2%) vs Arizona-4 (Odds: 5000, Prob: 6.8%), Winner: Duke
BYU-6 (Odds: 6600, Prob: 14.4%) vs Alabama-2 (Odds: 1100, Prob: 85.6%), Winner: Alabama
Houston-1 (Odds: 600, Prob: 92.9%) vs Purdue-4 (Odds: 8000, Prob: 7.1%), Winner: Houston
Illinois-6 (Odds: 10000, Prob: 11.6%) vs Tennessee-2 (Odds: 1300, Prob: 88.4%), Winner: Tennessee
===== NEW ROUND =====
Auburn-1 (Odds: 340, Prob: 87.8%) vs Iowa State-3 (Odds: 2500, Prob: 12.2%), Winner: Auburn
Florida-1 (Odds: 450, Prob: 82.6%) vs St. John's-2 (Odds: 2200, Prob: 17.4%), Winner: Florida
Duke-1 (Odds: 360, Prob: 75.0%) vs Alabama-2 (Odds: 1100, Prob: 25.0%), Winner: Duke
Houston-1 (Odds: 600, Prob: 68.3%) vs Tennessee-2 (Odds: 1300, Prob: 31.7%), Winner: Houston
===== NEW ROUND =====
Auburn-1 (Odds: 340, Prob: 56.4%) vs Florida-1 (Odds: 450, Prob: 43.6%), Winner: Florida
Duke-1 (Odds: 360, Prob: 61.6%) vs Houston-1 (Odds: 600, Prob: 38.4%), Winner: Houston
===== NEW ROUND =====
Florida-1 (Odds: 450, Prob: 56.6%) vs Houston-1 (Odds: 600, Prob: 43.4%), Winner: Florida
Florida wins the tournament!
25/32
10/16
6/8
4/4
2/2
1/1
Overall: 48/63 = 76% Correct!