Examples

Sample Bout

from elote import EloCompetitor

good = EloCompetitor()
better = EloCompetitor()
best = EloCompetitor()

print('Starting ratings:')
print('%7.2f, %7.2f, %7.2f' % (good.rating, better.rating, best.rating, ))

print('\nAfter matches')

for _ in range(10):
    better.beat(good)
    best.beat(better)
    print('%7.2f, %7.2f, %7.2f' % (good.rating, better.rating, best.rating, ))
Starting ratings:
 400.00,  400.00,  400.00

After matches
 384.00,  399.26,  416.74
 368.70,  398.66,  432.64
 354.08,  398.18,  447.75
 340.10,  397.79,  462.11
 326.73,  397.49,  475.78
 313.95,  397.25,  488.80
 301.71,  397.08,  501.21
 289.99,  396.95,  513.05
 278.77,  396.87,  524.36
 268.01,  396.81,  535.18

Bout with Initialization

from elote import EloCompetitor

good = EloCompetitor(initial_rating=500)
better = EloCompetitor(initial_rating=450)
best = EloCompetitor(initial_rating=400)

print('Starting ratings:')
print('%7.2f, %7.2f, %7.2f' % (good.rating, better.rating, best.rating, ))

print('\nAfter matches')

for _ in range(20):
    better.beat(good)
    best.beat(better)
    print('%7.2f, %7.2f, %7.2f' % (good.rating, better.rating, best.rating, ))
Starting ratings:
 500.00,  450.00,  400.00

After matches
 481.71,  449.18,  419.10
 464.22,  448.50,  437.28
 447.50,  447.94,  454.57
 431.52,  447.49,  471.00
 416.25,  447.13,  486.62
 401.67,  446.86,  501.47
 387.74,  446.65,  515.61
 374.43,  446.51,  529.07
 361.70,  446.41,  541.89
 349.52,  446.35,  554.13
 337.87,  446.32,  565.81
 326.71,  446.31,  576.98
 316.01,  446.33,  587.66
 305.74,  446.36,  597.90
 295.89,  446.40,  607.71
 286.42,  446.45,  617.13
 277.31,  446.51,  626.19
 268.53,  446.57,  634.89
 260.08,  446.64,  643.28
 251.93,  446.70,  651.36

Bout with Ties

from elote import EloCompetitor

good = EloCompetitor(initial_rating=500)
better = EloCompetitor(initial_rating=450)
best = EloCompetitor(initial_rating=400)
also_best = EloCompetitor(initial_rating=400)

print('Starting ratings:')
print('%7.2f, %7.2f, %7.2f, %7.2f' % (good.rating, better.rating, best.rating, also_best.rating, ))

print('\nAfter matches')

for _ in range(20):
    better.beat(good)
    better.lost_to(best)
    best.tied(also_best)
    print('%7.2f, %7.2f, %7.2f, %7.2f' % (good.rating, better.rating, best.rating, also_best.rating, ))
Starting ratings:
 500.00,  450.00,  400.00,  400.00

After matches
 481.71,  449.18,  418.23,  400.88
 464.22,  448.46,  434.81,  402.51
 447.49,  447.79,  449.93,  404.78
 431.51,  447.14,  463.75,  407.60
 416.23,  446.48,  476.42,  410.87
 401.62,  445.80,  488.06,  414.53
 387.64,  445.07,  498.78,  418.51
 374.26,  444.30,  508.69,  422.75
 361.44,  443.48,  517.86,  427.22
 349.15,  442.60,  526.39,  431.86
 337.36,  441.66,  534.33,  436.65
 326.02,  440.68,  541.75,  441.55
 315.12,  439.64,  548.70,  446.54
 304.62,  438.56,  555.22,  451.60
 294.50,  437.44,  561.36,  456.71
 284.73,  436.28,  567.15,  461.84
 275.30,  435.09,  572.62,  466.99
 266.18,  433.87,  577.81,  472.14
 257.35,  432.62,  582.74,  477.28
 248.80,  431.35,  587.44,  482.40

Prediction

from elote import EloCompetitor

good = EloCompetitor(initial_rating=400)
better = EloCompetitor(initial_rating=500)

print('probability of better beating good: %5.2f%%' % (better.expected_score(good) * 100, ))
print('probability of good beating better: %5.2f%%' % (good.expected_score(better) * 100, ))

good.beat(better)

print('probability of better beating good: %5.2f%%' % (better.expected_score(good) * 100, ))
print('probability of good beating better: %5.2f%%' % (good.expected_score(better) * 100, ))
probability of better beating good: 64.01%
probability of good beating better: 35.99%
probability of better beating good: 58.42%
probability of good beating better: 41.58%

Sample Arena

from elote import LambdaArena
import json
import random


# sample bout function which just compares the two inputs
def func(a, b):
    if a == b:
        return None
    else:
        return a > b

# Configure the EloCompetitor class with a moderate k_factor
# Note: Using a more moderate k_factor (20) to prevent ratings from changing too drastically
matchups = [(random.randint(1, 10), random.randint(1, 10)) for _ in range(1000)]

arena = LambdaArena(func)
arena.set_competitor_class_var('_k_factor', 20)
# Using 1200 as initial rating (standard chess starting rating) to prevent negative ratings
arena.tournament(matchups, base_competitor_kwargs={"initial_rating": 1200})

print("Arena results:")
print(json.dumps(arena.leaderboard(), indent=4))
Arena results:
[
    {
        "competitor": 1,
        "rating": 1013.2827659706424
    },
    {
        "competitor": 2,
        "rating": 1058.4698600862191
    },
    ...
    {
        "competitor": 9,
        "rating": 1328.7158907591229
    },
    {
        "competitor": 10,
        "rating": 1435.6511383857194
    }
]

DWZ Arena

from elote import LambdaArena, DWZCompetitor
import json
import random


# sample bout function which just compares the two inputs
def func(a, b):
    if a == b:
        return None
    else:
        return a > b


matchups = [(random.randint(1, 10), random.randint(1, 10)) for _ in range(1000)]

# Create arena with DWZCompetitor and set higher initial rating
arena = LambdaArena(
    func,
    base_competitor=DWZCompetitor,
    base_competitor_kwargs={"initial_rating": 1200}
)
arena.tournament(matchups)

print("Arena results:")
print(json.dumps(arena.leaderboard(), indent=4))
Arena results:
[
    {
        "competitor": 1,
        "rating": 1012.72183429478434
    },
    {
        "competitor": 2,
        "rating": 1044.28657694745118
    },
    ...
    {
        "competitor": 9,
        "rating": 1358.7172315502228
    },
    {
        "competitor": 10,
        "rating": 1411.6297448123669
    }
]

ECF Arena

from elote import LambdaArena, ECFCompetitor
import json
import random


# sample bout function which just compares the two inputs
def func(a, b):
    if a == b:
        return None
    else:
        return a > b


matchups = [(random.randint(1, 10), random.randint(1, 10)) for _ in range(1000)]

# Create arena with ECFCompetitor
# Note: ECFCompetitor now has a default initial rating of 100 (minimum rating)
arena = LambdaArena(func, base_competitor=ECFCompetitor)
arena.tournament(matchups)

print("Arena results:")
print(json.dumps(arena.leaderboard(), indent=4))
Arena results:
[
    {
        "competitor": 1,
        "rating": 100.0
    },
    {
        "competitor": 2,
        "rating": 106.99453852992116
    },
    ...
    {
        "competitor": 9,
        "rating": 134.9562091605315
    },
    {
        "competitor": 10,
        "rating": 145.9090955086219
    }
]

Glicko Arena

from elote import LambdaArena, GlickoCompetitor
import json
import random


# sample bout function which just compares the two inputs
def func(a, b):
    if a == b:
        return None
    else:
        return a > b

matchups = [(random.randint(1, 10), random.randint(1, 10)) for _ in range(1000)]

# Create arena with GlickoCompetitor and set higher initial rating in constructor
arena = LambdaArena(
    func,
    base_competitor=GlickoCompetitor,
    base_competitor_kwargs={"initial_rating": 1500}
)
arena.tournament(matchups)

print("Arena results:")
print(json.dumps(arena.leaderboard(), indent=4))
Arena results:
[
    {
        "competitor": 1,
        "rating": 1126.83348029853865
    },
    {
        "competitor": 2,
        "rating": 1395.7055271953318
    },
    ...
    {
        "competitor": 9,
        "rating": 2421.232857002417
    },
    {
        "competitor": 10,
        "rating": 2984.275564273142
    }
]

Persisting State from an Arena

from elote import LambdaArena, GlickoCompetitor
import json
import random
import copy


# sample bout function which just compares the two inputs
def func(a, b):
    if a == b:
        return None
    else:
        return a > b


# start scoring, stop and save state
matchups = [(random.randint(1, 10), random.randint(1, 10)) for _ in range(10)]
# Create arena with GlickoCompetitor and set higher initial rating
arena = LambdaArena(
    func,
    base_competitor=GlickoCompetitor,
    base_competitor_kwargs={"initial_rating": 1500}
)
arena.tournament(matchups)
print("Arena results:")
print(json.dumps(arena.leaderboard(), indent=4))

# Export state and create a deep copy to avoid modifying the original
saved_state = copy.deepcopy(arena.export_state())

# Create a new arena with the saved state
matchups = [(random.randint(1, 10), random.randint(1, 10)) for _ in range(100)]
new_arena = LambdaArena(func, base_competitor=GlickoCompetitor)

# Use from_state to recreate competitors
for k, v in saved_state.items():
    new_arena.competitors[k] = GlickoCompetitor.from_state(v)

# Run more matches
new_arena.tournament(matchups)
print("Arena results:")
print(json.dumps(new_arena.leaderboard(), indent=4))
Arena results:
[
    {
        "competitor": 2,
        "rating": 1455.4305146831251
    },
    {
        "competitor": 6,
        "rating": 1515.7289656415517
    },
    ...
    {
        "competitor": 4,
        "rating": 1673.3452669422734
    },
    {
        "competitor": 7,
        "rating": 1967.7914038679244
    }
]
Arena results:
[
    {
        "competitor": 2,
        "rating": 1304.09061767108824
    },
    {
        "competitor": 1,
        "rating": 1339.4265505050406
    },
    ...
    {
        "competitor": 9,
        "rating": 2253.7482792356805
    },
    {
        "competitor": 10,
        "rating": 2403.5118562872744
    }
]

Serializing and Deserializing Competitors

Elote provides a standardized serialization format for all competitor types. This example shows how to serialize and deserialize competitors:

from elote import EloCompetitor
import json

# Create a competitor
competitor = EloCompetitor(initial_rating=1500)

# Simulate some matches
competitor.beat(EloCompetitor(initial_rating=1400))
competitor.beat(EloCompetitor(initial_rating=1450))
competitor.lost_to(EloCompetitor(initial_rating=1600))

# Serialize to JSON
json_str = competitor.to_json()

# Print the serialized JSON (formatted for readability)
print("Serialized competitor:")
print(json.dumps(json.loads(json_str), indent=4))

# Deserialize from JSON
new_competitor = EloCompetitor.from_json(json_str)

# Verify the ratings match
print(f"Original rating: {competitor.rating}")
print(f"Deserialized rating: {new_competitor.rating}")

# Continue using the deserialized competitor
new_competitor.beat(EloCompetitor(initial_rating=1450))
print(f"Updated rating after another match: {new_competitor.rating}")
Serialized competitor:
{
    "type": "EloCompetitor",
    "version": 1,
    "created_at": 1625097600,
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "parameters": {
        "initial_rating": 1500
    },
    "state": {
        "rating": 1524.32
    },
    "class_vars": {
        "k_factor": 32
    },
    "initial_rating": 1500,
    "current_rating": 1524.32
}
Original rating: 1524.32
Deserialized rating: 1524.32
Updated rating after another match: 1536.64