Skip to content

Instantly share code, notes, and snippets.

@antzucaro
Created July 26, 2012 11:47
Show Gist options
  • Save antzucaro/3181618 to your computer and use it in GitHub Desktop.
Save antzucaro/3181618 to your computer and use it in GitHub Desktop.
Blended Elo - for non-duel games, use the real scorefactor for S. For duels, use 1, 0, and 0.5.
from pyramid.paster import bootstrap
from xonstat.elo import *
from xonstat.models import *
def process_elos(game, session, game_type_cd=None):
if game_type_cd is None:
game_type_cd = game.game_type_cd
# we do not have the actual duration of the game, so use the
# maximum alivetime of the players instead
duration = 0
for d in session.query(sfunc.max(PlayerGameStat.alivetime)).\
filter(PlayerGameStat.game_id==game.game_id).\
one():
duration = d.seconds
scores = {}
alivetimes = {}
for (p,s,a) in session.query(PlayerGameStat.player_id,
PlayerGameStat.score, PlayerGameStat.alivetime).\
filter(PlayerGameStat.game_id==game.game_id).\
filter(PlayerGameStat.alivetime > timedelta(seconds=0)).\
filter(PlayerGameStat.player_id > 2).\
all():
# scores are per second
scores[p] = s/float(a.seconds)
alivetimes[p] = a.seconds
player_ids = scores.keys()
elos = {}
for e in session.query(PlayerElo).\
filter(PlayerElo.player_id.in_(player_ids)).\
filter(PlayerElo.game_type_cd==game_type_cd).all():
elos[e.player_id] = e
# ensure that all player_ids have an elo record
for pid in player_ids:
if pid not in elos.keys():
elos[pid] = PlayerElo(pid, game_type_cd)
for pid in player_ids:
elos[pid].k = KREDUCTION.eval(elos[pid].games, alivetimes[pid],
duration)
if elos[pid].k == 0:
del(elos[pid])
del(scores[pid])
del(alivetimes[pid])
elos = update_elos(game, session, elos, scores, ELOPARMS)
# add the elos to the session for committing
for e in elos:
session.add(elos[e])
def update_elos(game, session, elos, scores, ep):
if len(elos) < 2:
return elos
pids = elos.keys()
eloadjust = {}
for pid in pids:
eloadjust[pid] = 0.0
for i in xrange(0, len(pids)):
ei = elos[pids[i]]
for j in xrange(i+1, len(pids)):
ej = elos[pids[j]]
si = scores[ei.player_id]
sj = scores[ej.player_id]
# normalize scores
ofs = min(0, si, sj)
si -= ofs
sj -= ofs
if si + sj == 0:
si, sj = 1, 1 # a draw
# real score factor
scorefactor_real = si / float(si + sj)
# duels are done traditionally - a win nets
# full points, not the score factor
if game.game_type_cd == 'duel':
# player i won
if scorefactor_real > 0.5:
scorefactor_real = 1.0
# player j won
elif scorefactor_real < 0.5:
scorefactor_real = 0.0
# nothing to do here for draws
# expected score factor by elo
elodiff = min(ep.maxlogdistance, max(-ep.maxlogdistance,
(float(ei.elo) - float(ej.elo)) * ep.logdistancefactor))
scorefactor_elo = 1 / (1 + math.exp(-elodiff))
# initial adjustment values, which we may modify with additional rules
adjustmenti = scorefactor_real - scorefactor_elo
adjustmentj = scorefactor_elo - scorefactor_real
if scorefactor_elo > 0.5:
# player i is expected to win
if scorefactor_real > 0.5:
# he DID win, so he should never lose points.
adjustmenti = max(0, adjustmenti)
else:
# he lost, but let's make it continuous (making him lose less points in the result)
adjustmenti = (2 * scorefactor_real - 1) * scorefactor_elo
else:
# player j is expected to win
if scorefactor_real > 0.5:
# he lost, but let's make it continuous (making him lose less points in the result)
adjustmentj = (1 - 2 * scorefactor_real) * (1 - scorefactor_elo)
else:
# he DID win, so he should never lose points.
adjustmentj = max(0, adjustmentj)
eloadjust[ei.player_id] += adjustmenti
eloadjust[ej.player_id] += adjustmentj
elo_deltas = {}
for pid in pids:
old_elo = float(elos[pid].elo)
new_elo = max(float(elos[pid].elo) + eloadjust[pid] * elos[pid].k * ep.global_K / float(len(elos) - 1), ep.floor)
elo_deltas[pid] = new_elo - old_elo
elos[pid].elo = new_elo
elos[pid].games += 1
print "Setting Player {0}'s Elo delta to {1}. Elo is now {2} (was {3}).".format(pid, elo_deltas[pid], new_elo, old_elo)
save_elo_deltas(game, session, elo_deltas)
return elos
def save_elo_deltas(game, session, elo_deltas):
"""
Saves the amount by which each player's Elo goes up or down
in a given game in the PlayerGameStat row, allowing for scoreboard display.
elo_deltas is a dictionary such that elo_deltas[player_id] is the elo_delta
for that player_id.
"""
pgstats = {}
for pgstat in session.query(PlayerGameStat).\
filter(PlayerGameStat.game_id == game.game_id).\
all():
pgstats[pgstat.player_id] = pgstat
for pid in elo_deltas.keys():
try:
pgstats[pid].elo_delta = elo_deltas[pid]
session.add(pgstats[pid])
except:
log.debug("Unable to save Elo delta value for player_id {0}".format(pid))
###############################################################################
# MAIN
###############################################################################
# environment setup
env = bootstrap('../../../development.ini.home')
# grab the list of games
games = DBSession.query(Game).\
order_by(Game.game_id).\
all()
for game in games:
session = DBSession()
print "Processing game %s" % game.game_id
try:
process_elos(game, session, game.game_type_cd)
session.commit()
except Exception as e:
print "Failed processing game {0}: {1}".format(game.game_id, e)
raise e
session.rollback()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment