Created
July 26, 2012 11:47
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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