Skip to content

Instantly share code, notes, and snippets.

@cmrfrd
Created December 9, 2024 20:54
Show Gist options
  • Select an option

  • Save cmrfrd/241bab6b105444d1812a5a2010b5d514 to your computer and use it in GitHub Desktop.

Select an option

Save cmrfrd/241bab6b105444d1812a5a2010b5d514 to your computer and use it in GitHub Desktop.
terry_bradley
import random
from functools import reduce
import numpy as np
from matplotlib import pyplot as plt
player_names = ["Alice", "Bob", "Charlie", "David"]
match_matrix = np.zeros((len(player_names), len(player_names)))
# player in the first position is the prob of winning
# this is for sampling
pair_winning_prob = {
("Alice", "Bob"): 0.5,
("Alice", "Charlie"): 0.4,
("Alice", "David"): 0.6,
("Bob", "Charlie"): 0.5,
("Bob", "David"): 0.5,
("Charlie", "David"): 0.4,
}
## add inverses
for (p1, p2), prob in dict(pair_winning_prob).items():
pair_winning_prob[(p2, p1)] = 1 - prob
def add_match() -> None:
# create a random matchup
p1, p2 = random.sample(list(player_names), 2)
# sample the winner
prob = pair_winning_prob[(p1, p2)]
winner = p1 if random.random() < prob else p2
# update the match matrix
p1_index = player_names.index(p1)
p2_index = player_names.index(p2)
if winner == p1:
match_matrix[p1_index, p2_index] += 1
else:
match_matrix[p2_index, p1_index] += 1
def terry_bradley_calc(iters: int = 10) -> dict[str, float]:
"""Calculate the Terry-Bradley values for each player."""
init_tb = {player: 1.0 for player in player_names}
for _ in range(iters):
for player in player_names:
wins_by_player = match_matrix[player_names.index(player)]
losses_from_opponents = match_matrix[:, player_names.index(player)]
# wins normalized by the sum of wins and losses
numerator = 0
for opponent in filter(lambda x: x != player, player_names):
numerator += wins_by_player[player_names.index(opponent)] * (
init_tb[opponent] / (init_tb[player] + init_tb[opponent])
)
# losses normalized by the sum of wins and losses
denominator = 0
for opponent in filter(lambda x: x != player, player_names):
denominator += losses_from_opponents[player_names.index(opponent)] * (
init_tb[opponent] / (init_tb[player] + init_tb[opponent])
)
init_tb[player] = numerator / denominator
geometric_mean = reduce(lambda x, y: x * y, init_tb.values()) ** (1 / len(init_tb))
init_tb = {player: init_tb[player] / geometric_mean for player in player_names}
return init_tb
# Step 3: Simulate matches
def simulate(num_rounds: int = 100, matches_per_round: int = 1000) -> list[dict[str, float]]:
results = []
for _ in range(num_rounds):
for _ in range(matches_per_round):
add_match()
results.append(terry_bradley_calc())
return results
# Run the simulation
results = simulate(num_rounds=1_000)
## plot the measured values
player_measures_over_time: dict[str, list[float]] = {player: [] for player in player_names}
for measure in results:
for player, stats in measure.items():
player_measures_over_time[player].append(stats)
fig, ax = plt.subplots()
for player, measures in player_measures_over_time.items():
ax.plot(measures, label=player, linestyle="-")
ax.legend()
plt.savefig("terry_bradley.png")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment