From 42b2e1341918b7e4db247816eb2fd3f455f24e2d Mon Sep 17 00:00:00 2001 From: "Jan D. Behrens" Date: Wed, 29 Aug 2012 12:58:31 +0200 Subject: [PATCH] Added multiple skin support and better commandline parsing - Skins other than "classic" will be put into a subdir in the output directory (e.g. output/minimal/###.png) - By default, "classic" and "minimal" badges are created for all players that were active in the last 6 hours - A skin list can be passed to the program for testing (e.g. "gen_badges.py classic archer") - The "-force" parameter enforces an update of all badges by setting delta to 2^24 hours - The "-test" parameter limits the number of players to be considered to 200 (used for testing) --- xonstat/batch/badges/gen_badges.py | 71 +++++-- xonstat/batch/badges/output/_dummy_ | 0 xonstat/batch/badges/output/minimal/_dummy_ | 0 xonstat/batch/badges/render.py | 216 +++++++++++--------- 4 files changed, 167 insertions(+), 120 deletions(-) create mode 100644 xonstat/batch/badges/output/_dummy_ create mode 100644 xonstat/batch/badges/output/minimal/_dummy_ diff --git a/xonstat/batch/badges/gen_badges.py b/xonstat/batch/badges/gen_badges.py index f556ab3..dcb6a1e 100644 --- a/xonstat/batch/badges/gen_badges.py +++ b/xonstat/batch/badges/gen_badges.py @@ -8,26 +8,29 @@ from sqlalchemy import distinct from pyramid.paster import bootstrap from xonstat.models import * -from render import Skin +from render import PlayerData, Skin # maximal number of query results (for testing, set to 0 to get all) -#NUM_PLAYERS = 200 +NUM_PLAYERS = None # we look for players who have activity within the past DELTA hours DELTA = 6 -skin_classic = Skin( +# classic skin WITHOUT NAME - writes PNGs into "output//###.png" +skin_classic = Skin( "", bg = "asfalt", ) -skin_archer = Skin( +# more fancy skin [** WIP **]- writes PNGs into "output/archer/###.png" +skin_archer = Skin( "archer", bg = "background_archer-v1", overlay = "", ) -skin_minimal = Skin( +# minimal skin - writes PNGs into "output/minimal/###.png" +skin_minimal = Skin( "minimal", bg = None, bgcolor = (0.04, 0.04, 0.04, 1.0), overlay = "overlay_minimal", @@ -57,15 +60,38 @@ skin_minimal = Skin( ) # parse cmdline parameters (for testing) -skin = skin_classic -if len(sys.argv) > 1: - arg = sys.argv[1].lower() - if arg == "classic": - skin = skin_classic - elif arg == "minimal": - skin = skin_minimal - elif arg == "archer": - skin = skin_archer + +skins = [] +for arg in sys.argv[1:]: + if arg.startswith("-"): + arg = arg[1:] + if arg == "force": + DELTA = 2**24 # large enough to enforce update, and doesn't result in errors + elif arg == "test": + NUM_PLAYERS = 200 + else: + print """Usage: gen_badges.py [options] [skin list] + Options: + -force Force updating all badges (delta = 2^24) + -testing Limit number of players to 200 (for testing) + -help Show this help text + Skin list: + Space-separated list of skins to use when creating badges. + Available skins: classic, minimal, archer + If no skins are given, classic and minmal will be used by default. + NOTE: Output directories must exists before running the program! +""" + sys.exit(-1) + else: + if arg == "classic": + skins.append(skin_classic) + elif arg == "minimal": + skins.append(skin_minimal) + elif arg == "archer": + skins.append(skin_archer) + +if len(skins) == 0: + skins = [ skin_classic, skin_minimal ] # environment setup @@ -77,7 +103,7 @@ print "Requesting player data from db ..." cutoff_dt = datetime.utcnow() - timedelta(hours=DELTA) start = datetime.now() players = [] -if locals().has_key('NUM_PLAYERS'): +if NUM_PLAYERS: players = DBSession.query(distinct(Player.player_id)).\ filter(Player.player_id == PlayerElo.player_id).\ filter(Player.player_id == PlayerGameStat.player_id).\ @@ -96,6 +122,8 @@ else: filter(Player.active_ind == True).\ all() +playerdata = PlayerData() + if len(players) > 0: stop = datetime.now() td = stop-start @@ -109,24 +137,25 @@ if len(players) > 0: req.matchdict['id'] = player_id sstart = datetime.now() - skin.get_data(player_id) + playerdata.get_data(player_id) sstop = datetime.now() td = sstop-sstart - total_seconds = (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6 + total_seconds = float(td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6 data_time += total_seconds sstart = datetime.now() - skin.render_image("output/%d.png" % player_id) + for sk in skins: + sk.render_image(playerdata, "output/%s/%d.png" % (str(sk), player_id[0])) sstop = datetime.now() td = sstop-sstart - total_seconds = (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6 + total_seconds = float(td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6 render_time += total_seconds stop = datetime.now() td = stop-start - total_seconds = (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6 + total_seconds = float(td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6 print "Creating the badges took %.1f seconds (%.3f s per player)" % (total_seconds, total_seconds/float(len(players))) - print "Total time for redering images: %.3f s" % render_time + print "Total time for rendering images: %.3f s" % render_time print "Total time for getting data: %.3f s" % data_time else: diff --git a/xonstat/batch/badges/output/_dummy_ b/xonstat/batch/badges/output/_dummy_ new file mode 100644 index 0000000..e69de29 diff --git a/xonstat/batch/badges/output/minimal/_dummy_ b/xonstat/batch/badges/output/minimal/_dummy_ new file mode 100644 index 0000000..e69de29 diff --git a/xonstat/batch/badges/render.py b/xonstat/batch/badges/render.py index d0fa997..685e649 100644 --- a/xonstat/batch/badges/render.py +++ b/xonstat/batch/badges/render.py @@ -47,16 +47,121 @@ def writepng(filename, buf, width, height): f.close() -class Skin: +class PlayerData: # player data, will be filled by get_data() data = {} + def __init__(self): + self.data = {} + + def __getattr__(self, key): + if self.data.has_key(key): + return self.data[key] + return None + + def get_data(self, player_id): + """Return player data as dict. + + This function is similar to the function in player.py but more optimized + for this purpose. + """ + # total games + # wins/losses + # kills/deaths + # duel/dm/tdm/ctf elo + rank + + player = DBSession.query(Player).filter(Player.player_id == player_id).one() + + games_played = DBSession.query( + Game.game_type_cd, func.count(), func.sum(PlayerGameStat.alivetime)).\ + filter(Game.game_id == PlayerGameStat.game_id).\ + filter(PlayerGameStat.player_id == player_id).\ + group_by(Game.game_type_cd).\ + order_by(func.count().desc()).\ + limit(3).all() # limit to 3 gametypes! + + total_stats = {} + total_stats['games'] = 0 + total_stats['games_breakdown'] = {} # this is a dictionary inside a dictionary .. dictception? + total_stats['games_alivetime'] = {} + total_stats['gametypes'] = [] + for (game_type_cd, games, alivetime) in games_played: + total_stats['games'] += games + total_stats['gametypes'].append(game_type_cd) + total_stats['games_breakdown'][game_type_cd] = games + total_stats['games_alivetime'][game_type_cd] = alivetime + + (total_stats['kills'], total_stats['deaths'], total_stats['alivetime'],) = DBSession.query( + func.sum(PlayerGameStat.kills), + func.sum(PlayerGameStat.deaths), + func.sum(PlayerGameStat.alivetime)).\ + filter(PlayerGameStat.player_id == player_id).\ + one() + + # (total_stats['wins'],) = DBSession.query( + # func.count("*")).\ + # filter(Game.game_id == PlayerGameStat.game_id).\ + # filter(PlayerGameStat.player_id == player_id).\ + # filter(Game.winner == PlayerGameStat.team or PlayerGameStat.rank == 1).\ + # one() + + (total_stats['wins'],) = DBSession.\ + query("total_wins").\ + from_statement( + "select count(*) total_wins " + "from games g, player_game_stats pgs " + "where g.game_id = pgs.game_id " + "and player_id=:player_id " + "and (g.winner = pgs.team or pgs.rank = 1)" + ).params(player_id=player_id).one() + + ranks = DBSession.query("game_type_cd", "rank", "max_rank").\ + from_statement( + "select pr.game_type_cd, pr.rank, overall.max_rank " + "from player_ranks pr, " + "(select game_type_cd, max(rank) max_rank " + "from player_ranks " + "group by game_type_cd) overall " + "where pr.game_type_cd = overall.game_type_cd " + "and player_id = :player_id " + "order by rank").\ + params(player_id=player_id).all() + + ranks_dict = {} + for gtc,rank,max_rank in ranks: + ranks_dict[gtc] = (rank, max_rank) + + elos = DBSession.query(PlayerElo).\ + filter_by(player_id=player_id).\ + order_by(PlayerElo.elo.desc()).\ + all() + + elos_dict = {} + for elo in elos: + if elo.games > 32: + elos_dict[elo.game_type_cd] = elo.elo + + self.data = { + 'player':player, + 'total_stats':total_stats, + 'ranks':ranks_dict, + 'elos':elos_dict, + } + + + +class Skin: + # skin parameters, can be overriden by init params = {} - def __init__(self, **params): + # skin name + name = "" + + def __init__(self, name, **params): # default parameters + self.name = name self.params = { 'bg': "dark_wall", # None - plain; otherwise use given texture 'bgcolor': None, # transparent bg when bgcolor==None @@ -131,21 +236,23 @@ class Skin: if self.params.has_key(k): self.params[k] = v + def __str__(self): + return self.name + def __getattr__(self, key): if self.params.has_key(key): return self.params[key] return None - def render_image(self, output_filename): + def render_image(self, data, output_filename): """Render an image for the given player id.""" # setup variables - player = self.data['player'] - total_stats = self.data['total_stats'] - total_games = total_stats['games'] - elos = self.data["elos"] - ranks = self.data["ranks"] + player = data.player + total_stats = data.total_stats + elos = data.elos + ranks = data.ranks font = "Xolonium" if self.font == 1: @@ -347,7 +454,8 @@ class Skin: ctx.move_to(self.wintext_pos[0]-xoff-tw/2, self.wintext_pos[1]-yoff) ctx.show_text(txt) - wins, losses = total_stats["wins"], total_games-total_stats["wins"] + total_games = total_stats['games'] + wins, losses = total_stats['wins'], total_games-total_stats['wins'] txt = "???" try: ratio = float(wins)/total_games @@ -477,93 +585,3 @@ class Skin: imgdata = surf.get_data() writepng(output_filename, imgdata, self.width, self.height) - - def get_data(self, player_id): - """Return player data as dict. - - This function is similar to the function in player.py but more optimized - for this purpose. - """ - - # total games - # wins/losses - # kills/deaths - # duel/dm/tdm/ctf elo + rank - player = DBSession.query(Player).filter(Player.player_id == player_id).one() - - games_played = DBSession.query( - Game.game_type_cd, func.count(), func.sum(PlayerGameStat.alivetime)).\ - filter(Game.game_id == PlayerGameStat.game_id).\ - filter(PlayerGameStat.player_id == player_id).\ - group_by(Game.game_type_cd).\ - order_by(func.count().desc()).\ - limit(3).all() # limit to 3 gametypes! - - total_stats = {} - total_stats['games'] = 0 - total_stats['games_breakdown'] = {} # this is a dictionary inside a dictionary .. dictception? - total_stats['games_alivetime'] = {} - total_stats['gametypes'] = [] - for (game_type_cd, games, alivetime) in games_played: - total_stats['games'] += games - total_stats['gametypes'].append(game_type_cd) - total_stats['games_breakdown'][game_type_cd] = games - total_stats['games_alivetime'][game_type_cd] = alivetime - - (total_stats['kills'], total_stats['deaths'], total_stats['alivetime'],) = DBSession.query( - func.sum(PlayerGameStat.kills), - func.sum(PlayerGameStat.deaths), - func.sum(PlayerGameStat.alivetime)).\ - filter(PlayerGameStat.player_id == player_id).\ - one() - - # (total_stats['wins'],) = DBSession.query( - # func.count("*")).\ - # filter(Game.game_id == PlayerGameStat.game_id).\ - # filter(PlayerGameStat.player_id == player_id).\ - # filter(Game.winner == PlayerGameStat.team or PlayerGameStat.rank == 1).\ - # one() - - (total_stats['wins'],) = DBSession.\ - query("total_wins").\ - from_statement( - "select count(*) total_wins " - "from games g, player_game_stats pgs " - "where g.game_id = pgs.game_id " - "and player_id=:player_id " - "and (g.winner = pgs.team or pgs.rank = 1)" - ).params(player_id=player_id).one() - - ranks = DBSession.query("game_type_cd", "rank", "max_rank").\ - from_statement( - "select pr.game_type_cd, pr.rank, overall.max_rank " - "from player_ranks pr, " - "(select game_type_cd, max(rank) max_rank " - "from player_ranks " - "group by game_type_cd) overall " - "where pr.game_type_cd = overall.game_type_cd " - "and player_id = :player_id " - "order by rank").\ - params(player_id=player_id).all() - - ranks_dict = {} - for gtc,rank,max_rank in ranks: - ranks_dict[gtc] = (rank, max_rank) - - elos = DBSession.query(PlayerElo).\ - filter_by(player_id=player_id).\ - order_by(PlayerElo.elo.desc()).\ - all() - - elos_dict = {} - for elo in elos: - if elo.games > 32: - elos_dict[elo.game_type_cd] = elo.elo - - self.data = { - 'player':player, - 'total_stats':total_stats, - 'ranks':ranks_dict, - 'elos':elos_dict, - } - -- 2.39.2