4 from datetime import datetime
5 import sqlalchemy as sa
6 import sqlalchemy.sql.functions as func
7 from colorsys import rgb_to_hls, hls_to_rgb
9 from pyramid.paster import bootstrap
10 from xonstat.models import *
11 from xonstat.util import qfont_decode
14 # similar to html_colors() from util.py
15 _contrast_threshold = 0.5
17 _dec_colors = [ (0.5,0.5,0.5),
30 # parameters to affect the output, could be changed via URL
34 'bg': 1, # 0 - black, 1 - dark_wall
35 'font': 0, # 0 - xolonium, 1 - dejavu sans
39 # maximal number of query results (for testing, set to 0 to get all)
44 """Return player data as dict.
46 This function is similar to the function in player.py but more optimized
53 # duel/dm/tdm/ctf elo + rank
55 player_id = player.player_id
59 games_played = DBSession.query(
60 Game.game_type_cd, func.count(), func.sum(PlayerGameStat.alivetime)).\
61 filter(Game.game_id == PlayerGameStat.game_id).\
62 filter(PlayerGameStat.player_id == player_id).\
63 group_by(Game.game_type_cd).\
64 order_by(func.count().desc()).\
65 limit(3).all() # limit to 3 gametypes!
67 total_stats['games'] = 0
68 total_stats['games_breakdown'] = {} # this is a dictionary inside a dictionary .. dictception?
69 total_stats['games_alivetime'] = {}
70 total_stats['gametypes'] = []
71 for (game_type_cd, games, alivetime) in games_played:
72 total_stats['games'] += games
73 total_stats['gametypes'].append(game_type_cd)
74 total_stats['games_breakdown'][game_type_cd] = games
75 total_stats['games_alivetime'][game_type_cd] = alivetime
77 (total_stats['kills'], total_stats['deaths'], total_stats['suicides'],
78 total_stats['alivetime'],) = DBSession.query(
79 func.sum(PlayerGameStat.kills),
80 func.sum(PlayerGameStat.deaths),
81 func.sum(PlayerGameStat.suicides),
82 func.sum(PlayerGameStat.alivetime)).\
83 filter(PlayerGameStat.player_id == player_id).\
86 (total_stats['wins'],) = DBSession.query(
88 filter(Game.game_id == PlayerGameStat.game_id).\
89 filter(PlayerGameStat.player_id == player_id).\
90 filter(Game.winner == PlayerGameStat.team or PlayerGameStat.rank == 1).\
93 ranks = DBSession.query("game_type_cd", "rank", "max_rank").\
95 "select pr.game_type_cd, pr.rank, overall.max_rank "
96 "from player_ranks pr, "
97 "(select game_type_cd, max(rank) max_rank "
99 "group by game_type_cd) overall "
100 "where pr.game_type_cd = overall.game_type_cd "
101 "and player_id = :player_id "
103 params(player_id=player_id).all()
106 for gtc,rank,max_rank in ranks:
107 ranks_dict[gtc] = (rank, max_rank)
109 elos = DBSession.query(PlayerElo).\
110 filter_by(player_id=player_id).\
111 order_by(PlayerElo.elo.desc()).\
117 elos_dict[elo.game_type_cd] = elo.elo
121 'total_stats':total_stats,
130 def render_image(data):
131 """Render an image from the given data fields."""
133 width, height = params['width'], params['height']
134 output = "output/%s.png" % data['player'].player_id
137 if params['font'] == 1:
140 total_stats = data['total_stats']
141 total_games = total_stats['games']
143 ranks = data["ranks"]
148 surf = C.ImageSurface(C.FORMAT_RGB24, width, height)
149 ctx = C.Context(surf)
150 ctx.set_antialias(C.ANTIALIAS_GRAY)
152 # draw background (just plain fillcolor)
153 if params['bg'] == 0:
154 ctx.rectangle(0, 0, width, height)
155 ctx.set_source_rgba(0.2, 0.2, 0.2, 1.0)
158 # draw background image (try to get correct tiling, too)
161 if params['bg'] == 1:
162 bg = C.ImageSurface.create_from_png("img/dark_wall.png")
165 bg_w, bg_h = bg.get_width(), bg.get_height()
167 while bg_xoff < width:
169 while bg_yoff < height:
170 ctx.set_source_surface(bg, bg_xoff, bg_yoff)
176 ## draw player's nickname with fancy colors
178 # fontsize is reduced if width gets too large
180 ctx.select_font_face(font, C.FONT_SLANT_NORMAL, C.FONT_WEIGHT_NORMAL)
181 ctx.set_font_size(20)
182 xoff, yoff, tw, th = ctx.text_extents(player.stripped_nick)[:4]
184 ctx.set_font_size(18)
185 xoff, yoff, tw, th = ctx.text_extents(player.stripped_nick)[:4]
187 ctx.set_font_size(16)
188 xoff, yoff, tw, th = ctx.text_extents(player.stripped_nick)[:4]
190 ctx.set_font_size(14)
191 xoff, yoff, tw, th = ctx.text_extents(player.stripped_nick)[:4]
193 ctx.set_font_size(12)
195 # split up nick into colored segments and draw each of them
196 qstr = qfont_decode(player.nick).replace('^^', '^').replace('\x00', ' ')
198 txt_xpos, txt_ypos = 5,18
200 # split nick into colored segments
204 npos = qstr.find('^', pos)
206 parts.append(qstr[pos-1:])
208 parts.append(qstr[pos-1:npos])
212 r,g,b = _dec_colors[7]
214 if txt.startswith('^'):
216 if txt.startswith('x'):
217 r = int(txt[1] * 2, 16) / 255.0
218 g = int(txt[2] * 2, 16) / 255.0
219 b = int(txt[3] * 2, 16) / 255.0
220 hue, light, satur = rgb_to_hls(r, g, b)
221 if light < _contrast_threshold:
222 light = _contrast_threshold
223 r, g, b = hls_to_rgb(hue, light, satur)
226 r,g,b = _dec_colors[int(txt[0])]
229 r,g,b = _dec_colors[7]
232 # only colorcode and no real text, skip this
235 ctx.set_source_rgb(r, g, b)
236 ctx.move_to(txt_xpos + txt_xoff, txt_ypos)
239 xoff, yoff, tw, th = ctx.text_extents(txt)[:4]
241 # only whitespaces, use some extra space
247 ## print elos and ranks
249 games_x, games_y = 60,35
250 games_w = 110 # width of each gametype field
252 # show up to three gametypes the player has participated in
253 for gt in total_stats['gametypes'][:3]:
254 ctx.select_font_face(font, C.FONT_SLANT_NORMAL, C.FONT_WEIGHT_BOLD)
255 ctx.set_font_size(10)
256 ctx.set_source_rgb(1.0, 1.0, 1.0)
257 txt = "[ %s ]" % gt.upper()
258 xoff, yoff, tw, th = ctx.text_extents(txt)[:4]
259 ctx.move_to(games_x-xoff-tw/2,games_y-yoff-4)
262 old_aa = ctx.get_antialias()
263 ctx.set_antialias(C.ANTIALIAS_NONE)
264 ctx.set_source_rgb(0.8, 0.8, 0.8)
265 ctx.set_line_width(1)
266 ctx.move_to(games_x-games_w/2+5, games_y+8)
267 ctx.line_to(games_x+games_w/2-5, games_y+8)
269 ctx.move_to(games_x-games_w/2+5, games_y+32)
270 ctx.line_to(games_x+games_w/2-5, games_y+32)
272 ctx.set_antialias(old_aa)
274 if not elos.has_key(gt) or not ranks.has_key(gt):
275 ctx.select_font_face(font, C.FONT_SLANT_NORMAL, C.FONT_WEIGHT_BOLD)
276 ctx.set_font_size(12)
277 ctx.set_source_rgb(0.8, 0.2, 0.2)
278 txt = "no stats yet!"
279 xoff, yoff, tw, th = ctx.text_extents(txt)[:4]
280 ctx.move_to(games_x-xoff-tw/2,games_y+28-yoff-4)
282 ctx.rotate(math.radians(-10))
286 ctx.select_font_face(font, C.FONT_SLANT_NORMAL, C.FONT_WEIGHT_NORMAL)
287 ctx.set_font_size(10)
288 ctx.set_source_rgb(1.0, 1.0, 0.5)
289 txt = "Elo: %.0f" % round(elos[gt], 0)
290 xoff, yoff, tw, th = ctx.text_extents(txt)[:4]
291 ctx.move_to(games_x-xoff-tw/2,games_y+15-yoff-4)
294 ctx.select_font_face(font, C.FONT_SLANT_NORMAL, C.FONT_WEIGHT_NORMAL)
296 ctx.set_source_rgb(0.8, 0.8, 0.8)
297 txt = "Rank %d of %d" % ranks[gt]
298 xoff, yoff, tw, th = ctx.text_extents(txt)[:4]
299 ctx.move_to(games_x-xoff-tw/2,games_y+25-yoff-3)
305 # print win percentage
306 win_x, win_y = 505,11
307 win_w, win_h = 100,14
309 ctx.rectangle(win_x-win_w/2,win_y-win_h/2,win_w,win_h)
310 ctx.set_source_rgba(0.8, 0.8, 0.8, 0.1)
313 ctx.select_font_face(font, C.FONT_SLANT_NORMAL, C.FONT_WEIGHT_NORMAL)
314 ctx.set_font_size(10)
315 ctx.set_source_rgb(0.8, 0.8, 0.8)
316 txt = "Win Percentage"
317 xoff, yoff, tw, th = ctx.text_extents(txt)[:4]
318 ctx.move_to(win_x-xoff-tw/2,win_y-yoff-3)
322 if total_games > 0 and total_stats['wins'] is not None:
323 ratio = float(total_stats['wins'])/total_games
324 txt = "%.2f%%" % round(ratio * 100, 2)
325 ctx.select_font_face(font, C.FONT_SLANT_NORMAL, C.FONT_WEIGHT_BOLD)
326 ctx.set_font_size(12)
328 ctx.set_source_rgb(0.2, 1.0, 1.0)
330 ctx.set_source_rgb(0.5, 1.0, 1.0)
332 ctx.set_source_rgb(0.5, 1.0, 0.8)
334 ctx.set_source_rgb(0.8, 1.0, 0.5)
336 ctx.set_source_rgb(1.0, 1.0, 0.5)
337 xoff, yoff, tw, th = ctx.text_extents(txt)[:4]
338 ctx.move_to(win_x-xoff-tw/2,win_y+16-yoff-4)
341 ctx.select_font_face(font, C.FONT_SLANT_NORMAL, C.FONT_WEIGHT_NORMAL)
343 ctx.set_source_rgb(0.6, 0.8, 0.6)
344 txt = "%d wins" % total_stats["wins"]
345 xoff, yoff, tw, th = ctx.text_extents(txt)[:4]
346 ctx.move_to(win_x-xoff-tw/2,win_y+28-yoff-3)
348 ctx.set_source_rgb(0.8, 0.6, 0.6)
349 txt = "%d losses" % (total_games-total_stats['wins'])
350 xoff, yoff, tw, th = ctx.text_extents(txt)[:4]
351 ctx.move_to(win_x-xoff-tw/2,win_y+38-yoff-3)
355 # print kill/death ratio
356 kill_x, kill_y = 395,11
357 kill_w, kill_h = 100,14
359 ctx.rectangle(kill_x-kill_w/2,kill_y-kill_h/2,kill_w,kill_h)
360 ctx.set_source_rgba(0.8, 0.8, 0.8, 0.1)
363 ctx.select_font_face(font, C.FONT_SLANT_NORMAL, C.FONT_WEIGHT_NORMAL)
364 ctx.set_font_size(10)
365 ctx.set_source_rgb(0.8, 0.8, 0.8)
367 xoff, yoff, tw, th = ctx.text_extents(txt)[:4]
368 ctx.move_to(kill_x-xoff-tw/2,kill_y-yoff-3)
372 if total_stats['deaths'] > 0 and total_stats['kills'] is not None:
373 ratio = float(total_stats['kills'])/total_stats['deaths']
374 txt = "%.3f" % round(ratio, 3)
375 ctx.select_font_face(font, C.FONT_SLANT_NORMAL, C.FONT_WEIGHT_BOLD)
376 ctx.set_font_size(12)
378 ctx.set_source_rgb(0.0, 1.0, 0.0)
380 ctx.set_source_rgb(0.2, 1.0, 0.2)
382 ctx.set_source_rgb(0.5, 1.0, 0.5)
384 ctx.set_source_rgb(1.0, 0.5, 0.5)
386 ctx.set_source_rgb(1.0, 0.2, 0.2)
387 xoff, yoff, tw, th = ctx.text_extents(txt)[:4]
388 ctx.move_to(kill_x-xoff-tw/2,kill_y+16-yoff-4)
391 ctx.select_font_face(font, C.FONT_SLANT_NORMAL, C.FONT_WEIGHT_NORMAL)
393 ctx.set_source_rgb(0.6, 0.8, 0.6)
394 txt = "%d kills" % total_stats["kills"]
395 xoff, yoff, tw, th = ctx.text_extents(txt)[:4]
396 ctx.move_to(kill_x-xoff-tw/2,kill_y+28-yoff-3)
398 ctx.set_source_rgb(0.8, 0.6, 0.6)
399 txt = "%d deaths" % total_stats['deaths']
400 xoff, yoff, tw, th = ctx.text_extents(txt)[:4]
401 ctx.move_to(kill_x-xoff-tw/2,kill_y+38-yoff-3)
406 time_x, time_y = 450,64
407 time_w, time_h = 210,10
409 ctx.rectangle(time_x-time_w/2,time_y-time_h/2-1,time_w,time_y+time_h/2-1)
410 ctx.set_source_rgba(0.8, 0.8, 0.8, 0.6)
413 ctx.select_font_face(font, C.FONT_SLANT_NORMAL, C.FONT_WEIGHT_NORMAL)
414 ctx.set_font_size(10)
415 ctx.set_source_rgb(0.1, 0.1, 0.1)
416 txt = "Playing time: %s" % str(total_stats['alivetime'])
417 xoff, yoff, tw, th = ctx.text_extents(txt)[:4]
418 ctx.move_to(time_x-xoff-tw/2,time_y-yoff-4)
423 surf.write_to_png(output)
427 env = bootstrap('../../../development.ini')
429 req.matchdict = {'id':3}
431 print "Requesting player data from db ..."
432 start = datetime.now()
433 players = DBSession.query(Player).\
434 filter(Player.player_id == PlayerElo.player_id).\
435 filter(Player.nick != None).\
436 filter(Player.player_id > 2).\
437 filter(Player.active_ind == True).\
438 limit(NUM_PLAYERS).all()
439 stop = datetime.now()
440 print "Query took %.2f seconds" % (stop-start).total_seconds()
442 print "Creating badges for %d players ..." % len(players)
443 start = datetime.now()
444 data_time, render_time = 0,0
445 for player in players:
446 req.matchdict['id'] = player.player_id
448 sstart = datetime.now()
449 data = get_data(player)
450 sstop = datetime.now()
451 data_time += (sstop-sstart).total_seconds()
453 sstart = datetime.now()
455 sstop = datetime.now()
456 render_time += (sstop-sstart).total_seconds()
458 stop = datetime.now()
459 print "Creating the badges took %.2f seconds (%.2f s per player)" % ((stop-start).total_seconds(), (stop-start).total_seconds()/float(len(players)))
460 print "Total time for redering images: %.2f s" % render_time
461 print "Total time for getting data: %.2f s" % data_time