3 from calendar import timegm
4 from collections import namedtuple
5 from urllib import unquote
7 import pyramid.httpexceptions
8 import sqlalchemy as sa
9 import sqlalchemy.sql.expression as expr
10 import sqlalchemy.sql.functions as func
11 from webhelpers.paginate import Page
12 from xonstat.models import DBSession, Server, Map, Game, PlayerWeaponStat, Player, Hashkey
13 from xonstat.models import PlayerElo, PlayerCaptime, PlayerMedal, GameType
14 from xonstat.models.player import PlayerCapTime
15 from xonstat.util import is_cake_day, verify_request
16 from xonstat.util import page_url, to_json, pretty_date, datetime_seconds
17 from xonstat.views.helpers import RecentGame, recent_games_q
19 log = logging.getLogger(__name__)
22 def player_index_data(request):
23 if request.params.has_key('page'):
24 current_page = request.params['page']
29 player_q = DBSession.query(Player).\
30 filter(Player.player_id > 2).\
31 filter(Player.active_ind == True).\
32 filter(sa.not_(Player.nick.like('Anonymous Player%'))).\
33 order_by(Player.player_id.desc())
35 players = Page(player_q, current_page, items_per_page=25, url=page_url)
37 except Exception as e:
41 return {'players':players
45 def player_index(request):
47 Provides a list of all the current players.
49 return player_index_data(request)
52 def player_index_json(request):
54 Provides a list of all the current players. JSON.
56 return [{'status':'not implemented'}]
59 def get_games_played(player_id):
61 Provides a breakdown by gametype of the games played by player_id.
63 Returns a list of namedtuples with the following members:
70 The list itself is ordered by the number of games played
72 GamesPlayed = namedtuple('GamesPlayed', ['game_type_cd', 'games', 'wins',
75 raw_games_played = DBSession.query('game_type_cd', 'wins', 'losses').\
77 "SELECT game_type_cd, "
80 "FROM (SELECT g.game_id, "
83 "WHEN g.winner = pgs.team THEN 1 "
84 "WHEN pgs.scoreboardpos = 1 THEN 1 "
88 "WHEN g.winner = pgs.team THEN 0 "
89 "WHEN pgs.scoreboardpos = 1 THEN 0 "
93 "player_game_stats pgs "
94 "WHERE g.game_id = pgs.game_id "
95 "AND pgs.player_id = :player_id "
96 "AND g.players @> ARRAY[:player_id]) win_loss "
97 "GROUP BY game_type_cd "
98 ).params(player_id=player_id).all()
104 for row in raw_games_played:
105 games = row.wins + row.losses
106 overall_games += games
108 # DM and CTS don't really have "winners"
109 if row.game_type_cd in ["dm", "cts"]:
112 overall_wins += row.wins
113 overall_losses += row.losses
114 win_pct = float(row.wins)/games * 100
116 games_played.append(GamesPlayed(row.game_type_cd, games, row.wins,
117 row.losses, win_pct))
120 overall_win_pct = float(overall_wins)/(overall_wins + overall_losses) * 100
122 overall_win_pct = 0.0
124 games_played.append(GamesPlayed('overall', overall_games, overall_wins,
125 overall_losses, overall_win_pct))
127 # sort the resulting list by # of games played
128 games_played = sorted(games_played, key=lambda x:x.games)
129 games_played.reverse()
134 def get_overall_stats(player_id):
136 Provides a breakdown of stats by gametype played by player_id.
138 Returns a dictionary of namedtuples with the following members:
142 - last_played (last time the player played the game type)
143 - last_played_epoch (same as above, but in seconds since epoch)
144 - last_played_fuzzy (same as above, but in relative date)
145 - total_playing_time (total amount of time played the game type)
146 - total_playing_time_secs (same as the above, but in seconds)
147 - total_pickups (ctf only)
148 - total_captures (ctf only)
149 - cap_ratio (ctf only)
150 - total_carrier_frags (ctf only)
154 The key to the dictionary is the game type code. There is also an
155 "overall" game_type_cd which sums the totals and computes the total ratios.
157 OverallStats = namedtuple('OverallStats', ['total_kills', 'total_deaths',
158 'k_d_ratio', 'last_played', 'last_played_epoch', 'last_played_fuzzy',
159 'total_playing_time', 'total_playing_time_secs', 'total_pickups', 'total_captures', 'cap_ratio',
160 'total_carrier_frags', 'game_type_cd', 'game_type_descr'])
162 raw_stats = DBSession.query('game_type_cd', 'game_type_descr',
163 'total_kills', 'total_deaths', 'last_played', 'total_playing_time',
164 'total_pickups', 'total_captures', 'total_carrier_frags').\
166 "SELECT g.game_type_cd, "
167 "gt.descr game_type_descr, "
168 "Sum(pgs.kills) total_kills, "
169 "Sum(pgs.deaths) total_deaths, "
170 "Max(pgs.create_dt) last_played, "
171 "Sum(pgs.alivetime) total_playing_time, "
172 "Sum(pgs.pickups) total_pickups, "
173 "Sum(pgs.captures) total_captures, "
174 "Sum(pgs.carrier_frags) total_carrier_frags "
177 "player_game_stats pgs "
178 "WHERE g.game_id = pgs.game_id "
179 "AND g.game_type_cd = gt.game_type_cd "
180 "AND g.players @> ARRAY[:player_id] "
181 "AND pgs.player_id = :player_id "
182 "GROUP BY g.game_type_cd, game_type_descr "
184 "SELECT 'overall' game_type_cd, "
185 "'Overall' game_type_descr, "
186 "Sum(pgs.kills) total_kills, "
187 "Sum(pgs.deaths) total_deaths, "
188 "Max(pgs.create_dt) last_played, "
189 "Sum(pgs.alivetime) total_playing_time, "
190 "Sum(pgs.pickups) total_pickups, "
191 "Sum(pgs.captures) total_captures, "
192 "Sum(pgs.carrier_frags) total_carrier_frags "
193 "FROM player_game_stats pgs "
194 "WHERE pgs.player_id = :player_id "
195 ).params(player_id=player_id).all()
197 # to be indexed by game_type_cd
200 for row in raw_stats:
201 # individual gametype ratio calculations
203 k_d_ratio = float(row.total_kills)/row.total_deaths
208 cap_ratio = float(row.total_captures)/row.total_pickups
212 # everything else is untouched or "raw"
213 os = OverallStats(total_kills=row.total_kills,
214 total_deaths=row.total_deaths,
216 last_played=row.last_played,
217 last_played_epoch=timegm(row.last_played.timetuple()),
218 last_played_fuzzy=pretty_date(row.last_played),
219 total_playing_time=row.total_playing_time,
220 total_playing_time_secs=int(datetime_seconds(row.total_playing_time)),
221 total_pickups=row.total_pickups,
222 total_captures=row.total_captures,
224 total_carrier_frags=row.total_carrier_frags,
225 game_type_cd=row.game_type_cd,
226 game_type_descr=row.game_type_descr)
228 overall_stats[row.game_type_cd] = os
230 # We have to edit "overall" stats to exclude deaths in CTS.
231 # Although we still want to record deaths, they shouldn't
232 # count towards the overall K:D ratio.
233 if 'cts' in overall_stats:
234 os = overall_stats['overall']
237 k_d_ratio = float(os.total_kills)/(os.total_deaths - overall_stats['cts'].total_deaths)
241 non_cts_deaths = os.total_deaths - overall_stats['cts'].total_deaths
244 overall_stats['overall'] = OverallStats(
245 total_kills = os.total_kills,
246 total_deaths = non_cts_deaths,
247 k_d_ratio = k_d_ratio,
248 last_played = os.last_played,
249 last_played_epoch = os.last_played_epoch,
250 last_played_fuzzy = os.last_played_fuzzy,
251 total_playing_time = os.total_playing_time,
252 total_playing_time_secs = os.total_playing_time_secs,
253 total_pickups = os.total_pickups,
254 total_captures = os.total_captures,
255 cap_ratio = os.cap_ratio,
256 total_carrier_frags = os.total_carrier_frags,
257 game_type_cd = os.game_type_cd,
258 game_type_descr = os.game_type_descr)
263 def get_fav_maps(player_id, game_type_cd=None):
265 Provides a breakdown of favorite maps by gametype.
267 Returns a dictionary of namedtuples with the following members:
269 - map_name (map name)
273 The favorite map is defined as the map you've played the most
274 for the given game_type_cd.
276 The key to the dictionary is the game type code. There is also an
277 "overall" game_type_cd which is the overall favorite map. This is
278 defined as the favorite map of the game type you've played the
279 most. The input parameter game_type_cd is for this.
281 FavMap = namedtuple('FavMap', ['map_name', 'map_id', 'times_played', 'game_type_cd'])
283 raw_favs = DBSession.query('game_type_cd', 'map_name',
284 'map_id', 'times_played').\
286 "SELECT game_type_cd, "
290 "FROM (SELECT g.game_type_cd, "
293 "Count(*) times_played, "
296 "partition BY g.game_type_cd "
297 "ORDER BY Count(*) DESC, m.map_id ASC) rank "
299 "player_game_stats pgs, "
301 "WHERE g.game_id = pgs.game_id "
302 "AND g.map_id = m.map_id "
303 "AND g.players @> ARRAY[:player_id]"
304 "AND pgs.player_id = :player_id "
305 "GROUP BY g.game_type_cd, "
307 "m.name) most_played "
309 "ORDER BY times_played desc "
310 ).params(player_id=player_id).all()
315 fv = FavMap(map_name=row.map_name,
317 times_played=row.times_played,
318 game_type_cd=row.game_type_cd)
320 # if we aren't given a favorite game_type_cd
321 # then the overall favorite is the one we've
323 if overall_fav is None:
324 fav_maps['overall'] = fv
325 overall_fav = fv.game_type_cd
327 # otherwise it is the favorite map from the
328 # favorite game_type_cd (provided as a param)
329 # and we'll overwrite the first dict entry
330 if game_type_cd == fv.game_type_cd:
331 fav_maps['overall'] = fv
333 fav_maps[row.game_type_cd] = fv
338 def get_ranks(player_id):
340 Provides a breakdown of the player's ranks by game type.
342 Returns a dictionary of namedtuples with the following members:
347 The key to the dictionary is the game type code. There is also an
348 "overall" game_type_cd which is the overall best rank.
350 Rank = namedtuple('Rank', ['rank', 'max_rank', 'percentile', 'game_type_cd'])
352 raw_ranks = DBSession.query("game_type_cd", "rank", "max_rank").\
354 "select pr.game_type_cd, pr.rank, overall.max_rank "
355 "from player_ranks pr, "
356 "(select game_type_cd, max(rank) max_rank "
358 "group by game_type_cd) overall "
359 "where pr.game_type_cd = overall.game_type_cd "
361 "and player_id = :player_id "
363 params(player_id=player_id).all()
366 found_top_rank = False
367 for row in raw_ranks:
368 rank = Rank(rank=row.rank,
369 max_rank=row.max_rank,
370 percentile=100 - 100*float(row.rank-1)/(row.max_rank-1),
371 game_type_cd=row.game_type_cd)
374 if not found_top_rank:
375 ranks['overall'] = rank
376 found_top_rank = True
377 elif rank.percentile > ranks['overall'].percentile:
378 ranks['overall'] = rank
380 ranks[row.game_type_cd] = rank
385 def get_elos(player_id):
387 Provides a breakdown of the player's elos by game type.
389 Returns a dictionary of namedtuples with the following members:
395 The key to the dictionary is the game type code. There is also an
396 "overall" game_type_cd which is the overall best rank.
398 raw_elos = DBSession.query(PlayerElo).filter_by(player_id=player_id).\
399 order_by(PlayerElo.elo.desc()).all()
402 found_max_elo = False
404 if not found_max_elo:
405 elos['overall'] = row
408 elos[row.game_type_cd] = row
413 def get_recent_games(player_id, limit=10):
415 Provides a list of recent games for a player. Uses the recent_games_q helper.
417 # recent games played in descending order
418 rgs = recent_games_q(player_id=player_id, force_player_id=True).limit(limit).all()
419 recent_games = [RecentGame(row) for row in rgs]
424 def get_accuracy_stats(player_id, weapon_cd, games):
426 Provides accuracy for weapon_cd by player_id for the past N games.
428 # Reaching back 90 days should give us an accurate enough average
429 # We then multiply this out for the number of data points (games) to
430 # create parameters for a flot graph
432 raw_avg = DBSession.query(func.sum(PlayerWeaponStat.hit),
433 func.sum(PlayerWeaponStat.fired)).\
434 filter(PlayerWeaponStat.player_id == player_id).\
435 filter(PlayerWeaponStat.weapon_cd == weapon_cd).\
438 avg = round(float(raw_avg[0])/raw_avg[1]*100, 2)
440 # Determine the raw accuracy (hit, fired) numbers for $games games
441 # This is then enumerated to create parameters for a flot graph
442 raw_accs = DBSession.query(PlayerWeaponStat.game_id,
443 PlayerWeaponStat.hit, PlayerWeaponStat.fired).\
444 filter(PlayerWeaponStat.player_id == player_id).\
445 filter(PlayerWeaponStat.weapon_cd == weapon_cd).\
446 order_by(PlayerWeaponStat.game_id.desc()).\
450 # they come out in opposite order, so flip them in the right direction
454 for i in range(len(raw_accs)):
455 accs.append((raw_accs[i][0], round(float(raw_accs[i][1])/raw_accs[i][2]*100, 2)))
463 def get_damage_stats(player_id, weapon_cd, games):
465 Provides damage info for weapon_cd by player_id for the past N games.
468 raw_avg = DBSession.query(func.sum(PlayerWeaponStat.actual),
469 func.sum(PlayerWeaponStat.hit)).\
470 filter(PlayerWeaponStat.player_id == player_id).\
471 filter(PlayerWeaponStat.weapon_cd == weapon_cd).\
474 avg = round(float(raw_avg[0])/raw_avg[1], 2)
476 # Determine the damage efficiency (hit, fired) numbers for $games games
477 # This is then enumerated to create parameters for a flot graph
478 raw_dmgs = DBSession.query(PlayerWeaponStat.game_id,
479 PlayerWeaponStat.actual, PlayerWeaponStat.hit).\
480 filter(PlayerWeaponStat.player_id == player_id).\
481 filter(PlayerWeaponStat.weapon_cd == weapon_cd).\
482 order_by(PlayerWeaponStat.game_id.desc()).\
486 # they come out in opposite order, so flip them in the right direction
490 for i in range(len(raw_dmgs)):
491 # try to derive, unless we've hit nothing then set to 0!
493 dmg = round(float(raw_dmgs[i][1])/raw_dmgs[i][2], 2)
497 dmgs.append((raw_dmgs[i][0], dmg))
498 except Exception as e:
505 def get_player_medals(player_id):
506 """Retrieves the list of medals the player has received from tournaments or
509 medals = DBSession.query(PlayerMedal)\
510 .filter(PlayerMedal.player_id==player_id)\
511 .order_by(PlayerMedal.place)\
512 .order_by(PlayerMedal.create_dt)\
517 except Exception as e:
522 def player_info_data(request):
523 player_id = int(request.matchdict['id'])
528 player = DBSession.query(Player).filter_by(player_id=player_id).\
529 filter(Player.active_ind == True).one()
531 games_played = get_games_played(player_id)
532 overall_stats = get_overall_stats(player_id)
533 fav_maps = get_fav_maps(player_id)
534 elos = get_elos(player_id)
536 medals = get_player_medals(player_id)
537 recent_games = get_recent_games(player_id)
538 cake_day = is_cake_day(player.create_dt)
540 except Exception as e:
541 raise pyramid.httpexceptions.HTTPNotFound
543 ## do not raise application exceptions here (only for debugging)
546 return {'player':player,
547 'games_played':games_played,
548 'overall_stats':overall_stats,
553 'recent_games':recent_games,
558 def player_info(request):
560 Provides detailed information on a specific player
562 return player_info_data(request)
565 def player_info_json(request):
567 Provides detailed information on a specific player. JSON.
570 # All player_info fields are converted into JSON-formattable dictionaries
571 player_info = player_info_data(request)
573 player = player_info['player'].to_dict()
576 for game in player_info['games_played']:
577 games_played[game.game_type_cd] = to_json(game)
580 for gt,stats in player_info['overall_stats'].items():
581 overall_stats[gt] = to_json(stats)
584 for gt,elo in player_info['elos'].items():
585 elos[gt] = to_json(elo.to_dict())
588 for gt,rank in player_info['ranks'].items():
589 ranks[gt] = to_json(rank)
592 for gt,mapinfo in player_info['fav_maps'].items():
593 fav_maps[gt] = to_json(mapinfo)
595 recent_games = [g.to_dict() for g in player_info['recent_games']]
599 'games_played': games_played,
600 'overall_stats': overall_stats,
601 'fav_maps': fav_maps,
604 'recent_games': recent_games,
608 def player_game_index_data(request):
610 player_id = int(request.matchdict['player_id'])
615 game_type_descr = None
617 if request.params.has_key('type'):
618 game_type_cd = request.params['type']
620 game_type_descr = DBSession.query(GameType.descr).\
621 filter(GameType.game_type_cd == game_type_cd).\
623 except Exception as e:
628 game_type_descr = None
630 if request.params.has_key('page'):
631 current_page = request.params['page']
636 player = DBSession.query(Player).\
637 filter_by(player_id=player_id).\
638 filter(Player.active_ind == True).\
641 rgs_q = recent_games_q(player_id=player.player_id,
642 force_player_id=True, game_type_cd=game_type_cd)
644 games = Page(rgs_q, current_page, items_per_page=20, url=page_url)
646 # replace the items in the canned pagination class with more rich ones
647 games.items = [RecentGame(row) for row in games.items]
649 games_played = get_games_played(player_id)
651 except Exception as e:
656 game_type_descr = None
660 'player_id':player.player_id,
663 'game_type_cd':game_type_cd,
664 'game_type_descr':game_type_descr,
665 'games_played':games_played,
669 def player_game_index(request):
671 Provides an index of the games in which a particular
672 player was involved. This is ordered by game_id, with
673 the most recent game_ids first. Paginated.
675 return player_game_index_data(request)
678 def player_game_index_json(request):
680 Provides an index of the games in which a particular
681 player was involved. This is ordered by game_id, with
682 the most recent game_ids first. Paginated. JSON.
684 return [{'status':'not implemented'}]
687 def player_accuracy_data(request):
688 player_id = request.matchdict['id']
689 allowed_weapons = ['nex', 'rifle', 'shotgun', 'uzi', 'minstanex']
693 if request.params.has_key('weapon'):
694 if request.params['weapon'] in allowed_weapons:
695 weapon_cd = request.params['weapon']
697 if request.params.has_key('games'):
699 games = request.params['games']
708 (avg, accs) = get_accuracy_stats(player_id, weapon_cd, games)
710 # if we don't have enough data for the given weapon
711 if len(accs) < games:
715 'player_id':player_id,
716 'player_url':request.route_url('player_info', id=player_id),
724 def player_accuracy(request):
726 Provides the accuracy for the given weapon. (JSON only)
728 return player_accuracy_data(request)
731 def player_accuracy_json(request):
733 Provides a JSON response representing the accuracy for the given weapon.
736 weapon = which weapon to display accuracy for. Valid values are 'nex',
737 'shotgun', 'uzi', and 'minstanex'.
738 games = over how many games to display accuracy. Can be up to 50.
740 return player_accuracy_data(request)
743 def player_damage_data(request):
744 player_id = request.matchdict['id']
745 allowed_weapons = ['grenadelauncher', 'electro', 'crylink', 'hagar',
746 'rocketlauncher', 'laser']
747 weapon_cd = 'rocketlauncher'
750 if request.params.has_key('weapon'):
751 if request.params['weapon'] in allowed_weapons:
752 weapon_cd = request.params['weapon']
754 if request.params.has_key('games'):
756 games = request.params['games']
765 (avg, dmgs) = get_damage_stats(player_id, weapon_cd, games)
767 # if we don't have enough data for the given weapon
768 if len(dmgs) < games:
772 'player_id':player_id,
773 'player_url':request.route_url('player_info', id=player_id),
781 def player_damage_json(request):
783 Provides a JSON response representing the damage for the given weapon.
786 weapon = which weapon to display damage for. Valid values are
787 'grenadelauncher', 'electro', 'crylink', 'hagar', 'rocketlauncher',
789 games = over how many games to display damage. Can be up to 50.
791 return player_damage_data(request)
794 def player_hashkey_info_data(request):
795 # hashkey = request.matchdict['hashkey']
797 # the incoming hashkey is double quoted, and WSGI unquotes once...
798 # hashkey = unquote(hashkey)
800 # if using request verification to obtain the hashkey
801 (idfp, status) = verify_request(request)
802 log.debug("d0_blind_id verification: idfp={0} status={1}\n".format(idfp, status))
804 log.debug("\n----- BEGIN REQUEST BODY -----\n" + request.body +
805 "----- END REQUEST BODY -----\n\n")
807 # if config is to *not* verify requests and we get nothing back, this
808 # query will return nothing and we'll 404.
810 player = DBSession.query(Player).\
811 filter(Player.player_id == Hashkey.player_id).\
812 filter(Player.active_ind == True).\
813 filter(Hashkey.hashkey == idfp).one()
815 games_played = get_games_played(player.player_id)
816 overall_stats = get_overall_stats(player.player_id)
817 fav_maps = get_fav_maps(player.player_id)
818 elos = get_elos(player.player_id)
820 most_recent_game = get_recent_games(player.player_id, 1)[0]
822 except Exception as e:
823 raise pyramid.httpexceptions.HTTPNotFound
825 return {'player':player,
827 'games_played':games_played,
828 'overall_stats':overall_stats,
832 'most_recent_game':most_recent_game,
836 def player_hashkey_info_json(request):
838 Provides detailed information on a specific player. JSON.
841 # All player_info fields are converted into JSON-formattable dictionaries
842 player_info = player_hashkey_info_data(request)
844 player = player_info['player'].to_dict()
847 for game in player_info['games_played']:
848 games_played[game.game_type_cd] = to_json(game)
851 for gt,stats in player_info['overall_stats'].items():
852 overall_stats[gt] = to_json(stats)
855 for gt,elo in player_info['elos'].items():
856 elos[gt] = to_json(elo.to_dict())
859 for gt,rank in player_info['ranks'].items():
860 ranks[gt] = to_json(rank)
863 for gt,mapinfo in player_info['fav_maps'].items():
864 fav_maps[gt] = to_json(mapinfo)
866 most_recent_game = to_json(player_info['most_recent_game'])
871 'games_played': games_played,
872 'overall_stats': overall_stats,
873 'fav_maps': fav_maps,
876 'most_recent_game': most_recent_game,
880 def player_hashkey_info_text(request):
882 Provides detailed information on a specific player. Plain text.
885 now = timegm(datetime.datetime.utcnow().timetuple())
887 # All player_info fields are converted into JSON-formattable dictionaries
888 player_info = player_hashkey_info_data(request)
890 # gather all of the data up into aggregate structures
891 player = player_info['player']
892 games_played = player_info['games_played']
893 overall_stats = player_info['overall_stats']
894 elos = player_info['elos']
895 ranks = player_info['ranks']
896 fav_maps = player_info['fav_maps']
897 most_recent_game = player_info['most_recent_game']
899 # one-offs for things needing conversion for text/plain
900 player_joined = timegm(player.create_dt.timetuple())
901 player_joined_dt = player.create_dt
902 alivetime = int(datetime_seconds(overall_stats['overall'].total_playing_time))
904 # this is a plain text response, if we don't do this here then
905 # Pyramid will assume html
906 request.response.content_type = 'text/plain'
912 'hashkey': player_info['hashkey'],
913 'player_joined': player_joined,
914 'player_joined_dt': player_joined_dt,
915 'games_played': games_played,
916 'overall_stats': overall_stats,
917 'alivetime': alivetime,
918 'fav_maps': fav_maps,
921 'most_recent_game': most_recent_game,
925 def player_elo_info_data(request):
927 Provides elo information on a specific player. Raw data is returned.
929 (idfp, status) = verify_request(request)
930 log.debug("d0_blind_id verification: idfp={0} status={1}\n".format(idfp, status))
932 log.debug("\n----- BEGIN REQUEST BODY -----\n" + request.body +
933 "----- END REQUEST BODY -----\n\n")
935 hashkey = request.matchdict['hashkey']
937 # the incoming hashkey is double quoted, and WSGI unquotes once...
938 hashkey = unquote(hashkey)
941 player = DBSession.query(Player).\
942 filter(Player.player_id == Hashkey.player_id).\
943 filter(Player.active_ind == True).\
944 filter(Hashkey.hashkey == hashkey).one()
946 elos = get_elos(player.player_id)
948 except Exception as e:
950 raise pyramid.httpexceptions.HTTPNotFound
959 def player_elo_info_json(request):
961 Provides elo information on a specific player. JSON.
963 elo_info = player_elo_info_data(request)
965 player = player_info['player'].to_dict()
968 for gt, elo in elo_info['elos'].items():
969 elos[gt] = to_json(elo.to_dict())
978 def player_elo_info_text(request):
980 Provides elo information on a specific player. Plain text.
983 now = timegm(datetime.datetime.utcnow().timetuple())
985 # All player_info fields are converted into JSON-formattable dictionaries
986 elo_info = player_elo_info_data(request)
988 # this is a plain text response, if we don't do this here then
989 # Pyramid will assume html
990 request.response.content_type = 'text/plain'
995 'hashkey': elo_info['hashkey'],
996 'player': elo_info['player'],
997 'elos': elo_info['elos'],
1001 def player_captimes_data(request):
1002 player_id = int(request.matchdict['player_id'])
1006 page = request.params.get("page", 1)
1008 sort = request.params.get("sort", "create_dt")
1011 player = DBSession.query(Player).filter_by(player_id=player_id).one()
1013 pct_q = DBSession.query(PlayerCaptime.fastest_cap, PlayerCaptime.create_dt,
1014 PlayerCaptime.player_id, PlayerCaptime.game_id, PlayerCaptime.map_id,
1015 Map.name.label('map_name'), Game.server_id, Server.name.label('server_name')).\
1016 filter(PlayerCaptime.player_id==player_id).\
1017 filter(PlayerCaptime.game_id==Game.game_id).\
1018 filter(PlayerCaptime.map_id==Map.map_id).\
1019 filter(Game.server_id==Server.server_id)
1021 if sort == "fastest":
1022 pct_q = pct_q.order_by(PlayerCaptime.fastest_cap)
1025 pct_q = pct_q.order_by(expr.desc(PlayerCaptime.create_dt))
1027 except Exception as e:
1028 raise pyramid.httpexceptions.HTTPNotFound
1030 captimes = Page(pct_q, page, items_per_page=20, url=page_url)
1032 # replace the items in the canned pagination class with more rich ones
1033 captimes.items = [PlayerCapTime(row) for row in captimes.items]
1036 "player_id" : player_id,
1038 "captimes" : captimes,
1044 def player_captimes(request):
1045 return player_captimes_data(request)
1048 def player_captimes_json(request):
1049 data = player_captimes_data(request)
1050 page = request.params.get("page", 1)
1052 # perform any necessary JSON conversions
1053 player_id = data["player_id"]
1054 player = data["player"].to_dict()
1055 captimes = [ct.to_dict() for ct in data["captimes"].items]
1059 "captimes" : captimes,
1064 def player_weaponstats_data_json(request):
1065 player_id = int(request.matchdict["id"])
1069 game_type_cd = request.params.get("game_type", None)
1070 if game_type_cd == "overall":
1074 if request.params.has_key("limit"):
1075 limit = int(request.params["limit"])
1083 # the game_ids of the most recently played ones
1084 # of the given game type is used for a subquery
1085 games_list = DBSession.query(Game.game_id).\
1086 filter(Game.players.contains([player_id]))
1088 if game_type_cd is not None:
1089 games_list = games_list.filter(Game.game_type_cd == game_type_cd)
1091 games_list = games_list.order_by(Game.game_id.desc()).limit(limit)
1093 weapon_stats_raw = DBSession.query(PlayerWeaponStat).\
1094 filter(PlayerWeaponStat.player_id == player_id).\
1095 filter(PlayerWeaponStat.game_id.in_(games_list)).\
1098 games_to_weapons = {}
1101 for ws in weapon_stats_raw:
1102 if ws.game_id not in games_to_weapons:
1103 games_to_weapons[ws.game_id] = [ws.weapon_cd]
1105 games_to_weapons[ws.game_id].append(ws.weapon_cd)
1107 weapons_used[ws.weapon_cd] = weapons_used.get(ws.weapon_cd, 0) + 1
1108 sum_avgs[ws.weapon_cd] = sum_avgs.get(ws.weapon_cd, 0) + float(ws.hit)/float(ws.fired)
1110 # Creating zero-valued weapon stat entries for games where a weapon was not
1111 # used in that game, but was used in another game for the set. This makes
1112 # the charts look smoother
1113 for game_id in games_to_weapons.keys():
1114 for weapon_cd in set(weapons_used.keys()) - set(games_to_weapons[game_id]):
1115 weapon_stats_raw.append(PlayerWeaponStat(player_id=player_id,
1116 game_id=game_id, weapon_cd=weapon_cd))
1118 # averages for the weapons used in the range
1120 for w in weapons_used.keys():
1121 avgs[w] = round(sum_avgs[w]/float(weapons_used[w])*100, 2)
1123 weapon_stats_raw = sorted(weapon_stats_raw, key = lambda x: x.game_id)
1124 games = sorted(games_to_weapons.keys())
1125 weapon_stats = [ws.to_dict() for ws in weapon_stats_raw]
1128 "weapon_stats": weapon_stats,
1129 "weapons_used": weapons_used.keys(),
1135 def player_versus_data(request):
1137 p1_id = int(request.params.get("p1", None))
1138 p2_id = int(request.params.get("p2", None))
1143 players = DBSession.query(Player).filter(sa.or_(Player.player_id ==
1144 p1_id, Player.player_id == p2_id)).order_by(Player.player_id).all()
1147 if len(players) < 2:
1148 raise Exception("Not enough players found.")
1150 # assign the players from the array retrieved above
1151 if players[0].player_id == p1_id:
1158 # note that wins and losses are from p1's perspective
1159 win_loss_sql = """select win_loss, count(1)
1162 when pgsp1.score >= pgsp2.score then 'win'
1165 from games g join player_game_stats pgsp1
1166 on g.game_id = pgsp1.game_id and pgsp1.player_id = :p1
1167 join player_game_stats pgsp2
1168 on g.game_id = pgsp2.game_id and pgsp2.player_id = :p2
1169 where g.players @> ARRAY[:p1,:p2]
1170 and g.game_type_cd = 'duel'
1171 and pgsp1.create_dt between g.create_dt - interval '1 hour'
1172 and g.create_dt + interval '1 hour'
1173 and pgsp2.create_dt between g.create_dt - interval '1 hour'
1174 and g.create_dt + interval '1 hour'
1179 wins_losses = DBSession.query("win_loss", "count").\
1180 from_statement(win_loss_sql).\
1181 params(p1=p1_id, p2=p2_id).all()
1183 for row in wins_losses:
1184 if row.win_loss == "win":
1186 elif row.win_loss == "loss":
1189 # grab the 20 most recent games between the two
1190 rgs_raw = recent_games_q(player_id=p1_id, player_id_2=p2_id,
1191 game_type_cd="duel").limit(20).all()
1193 rgs = [RecentGame(row) for row in rgs_raw]
1195 except Exception as e:
1211 "p1_wins" : p1_wins,
1212 "p2_wins" : p2_wins,
1213 "recent_games" : rgs,
1217 def player_versus(request):
1218 return player_versus_data(request)