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 sqlalchemy import text
12 from webhelpers.paginate import Page
13 from xonstat.models import DBSession, Server, Map, Game, PlayerWeaponStat, Player, Hashkey
14 from xonstat.models import PlayerElo, PlayerCaptime, PlayerMedal, GameType
15 from xonstat.models.player import PlayerCapTime
16 from xonstat.util import is_cake_day, verify_request
17 from xonstat.util import page_url, to_json, pretty_date, datetime_seconds
18 from xonstat.views.helpers import RecentGame, recent_games_q
20 log = logging.getLogger(__name__)
23 def player_index_data(request):
24 if request.params.has_key('page'):
25 current_page = request.params['page']
30 player_q = DBSession.query(Player).\
31 filter(Player.player_id > 2).\
32 filter(Player.active_ind == True).\
33 filter(sa.not_(Player.nick.like('Anonymous Player%'))).\
34 order_by(Player.player_id.desc())
36 players = Page(player_q, current_page, items_per_page=25, url=page_url)
38 except Exception as e:
42 return {'players':players
46 def player_index(request):
48 Provides a list of all the current players.
50 return player_index_data(request)
53 def player_index_json(request):
55 Provides a list of all the current players. JSON.
57 return [{'status':'not implemented'}]
60 def get_games_played(player_id):
62 Provides a breakdown by gametype of the games played by player_id.
64 Returns a list of namedtuples with the following members:
71 The list itself is ordered by the number of games played
73 GamesPlayed = namedtuple('GamesPlayed', ['game_type_cd', 'games', 'wins',
76 raw_games_played = DBSession.query('game_type_cd', 'wins', 'losses').\
78 "SELECT game_type_cd, "
81 "FROM (SELECT g.game_id, "
84 "WHEN g.winner = pgs.team THEN 1 "
85 "WHEN pgs.scoreboardpos = 1 THEN 1 "
89 "WHEN g.winner = pgs.team THEN 0 "
90 "WHEN pgs.scoreboardpos = 1 THEN 0 "
94 "player_game_stats pgs "
95 "WHERE g.game_id = pgs.game_id "
96 "AND pgs.player_id = :player_id "
97 "AND g.players @> ARRAY[:player_id]) win_loss "
98 "GROUP BY game_type_cd "
99 )).params(player_id=player_id).all()
105 for row in raw_games_played:
106 games = row.wins + row.losses
107 overall_games += games
109 # DM, CTS, and KA don't really have "winners"
110 if row.game_type_cd in ["dm", "cts", "ka"]:
113 overall_wins += row.wins
114 overall_losses += row.losses
115 win_pct = float(row.wins)/games * 100
117 games_played.append(GamesPlayed(row.game_type_cd, games, row.wins,
118 row.losses, win_pct))
121 overall_win_pct = float(overall_wins)/(overall_wins + overall_losses) * 100
123 overall_win_pct = 0.0
125 games_played.append(GamesPlayed('overall', overall_games, overall_wins,
126 overall_losses, overall_win_pct))
128 # sort the resulting list by # of games played
129 games_played = sorted(games_played, key=lambda x:x.games)
130 games_played.reverse()
135 def get_overall_stats(player_id):
137 Provides a breakdown of stats by gametype played by player_id.
139 Returns a dictionary of namedtuples with the following members:
143 - last_played (last time the player played the game type)
144 - last_played_epoch (same as above, but in seconds since epoch)
145 - last_played_fuzzy (same as above, but in relative date)
146 - total_playing_time (total amount of time played the game type)
147 - total_playing_time_secs (same as the above, but in seconds)
148 - total_pickups (ctf only)
149 - total_captures (ctf only)
150 - cap_ratio (ctf only)
151 - total_carrier_frags (ctf only)
155 The key to the dictionary is the game type code. There is also an
156 "overall" game_type_cd which sums the totals and computes the total ratios.
158 OverallStats = namedtuple('OverallStats', ['total_kills', 'total_deaths',
159 'k_d_ratio', 'last_played', 'last_played_epoch', 'last_played_fuzzy',
160 'total_playing_time', 'total_playing_time_secs', 'total_pickups', 'total_captures', 'cap_ratio',
161 'total_carrier_frags', 'game_type_cd', 'game_type_descr'])
163 raw_stats = DBSession.query('game_type_cd', 'game_type_descr',
164 'total_kills', 'total_deaths', 'last_played', 'total_playing_time',
165 'total_pickups', 'total_captures', 'total_carrier_frags').\
167 "SELECT g.game_type_cd, "
168 "gt.descr game_type_descr, "
169 "Sum(pgs.kills) total_kills, "
170 "Sum(pgs.deaths) total_deaths, "
171 "Max(pgs.create_dt) last_played, "
172 "Sum(pgs.alivetime) total_playing_time, "
173 "Sum(pgs.pickups) total_pickups, "
174 "Sum(pgs.captures) total_captures, "
175 "Sum(pgs.carrier_frags) total_carrier_frags "
178 "player_game_stats pgs "
179 "WHERE g.game_id = pgs.game_id "
180 "AND g.game_type_cd = gt.game_type_cd "
181 "AND g.players @> ARRAY[:player_id] "
182 "AND pgs.player_id = :player_id "
183 "GROUP BY g.game_type_cd, game_type_descr "
185 "SELECT 'overall' game_type_cd, "
186 "'Overall' game_type_descr, "
187 "Sum(pgs.kills) total_kills, "
188 "Sum(pgs.deaths) total_deaths, "
189 "Max(pgs.create_dt) last_played, "
190 "Sum(pgs.alivetime) total_playing_time, "
191 "Sum(pgs.pickups) total_pickups, "
192 "Sum(pgs.captures) total_captures, "
193 "Sum(pgs.carrier_frags) total_carrier_frags "
194 "FROM player_game_stats pgs "
195 "WHERE pgs.player_id = :player_id "
196 )).params(player_id=player_id).all()
198 # to be indexed by game_type_cd
201 for row in raw_stats:
202 # individual gametype ratio calculations
204 k_d_ratio = float(row.total_kills)/row.total_deaths
209 cap_ratio = float(row.total_captures)/row.total_pickups
213 # everything else is untouched or "raw"
214 os = OverallStats(total_kills=row.total_kills,
215 total_deaths=row.total_deaths,
217 last_played=row.last_played,
218 last_played_epoch=timegm(row.last_played.timetuple()),
219 last_played_fuzzy=pretty_date(row.last_played),
220 total_playing_time=row.total_playing_time,
221 total_playing_time_secs=int(datetime_seconds(row.total_playing_time)),
222 total_pickups=row.total_pickups,
223 total_captures=row.total_captures,
225 total_carrier_frags=row.total_carrier_frags,
226 game_type_cd=row.game_type_cd,
227 game_type_descr=row.game_type_descr)
229 overall_stats[row.game_type_cd] = os
231 # We have to edit "overall" stats to exclude deaths in CTS.
232 # Although we still want to record deaths, they shouldn't
233 # count towards the overall K:D ratio.
234 if 'cts' in overall_stats:
235 os = overall_stats['overall']
238 k_d_ratio = float(os.total_kills)/(os.total_deaths - overall_stats['cts'].total_deaths)
242 non_cts_deaths = os.total_deaths - overall_stats['cts'].total_deaths
245 overall_stats['overall'] = OverallStats(
246 total_kills = os.total_kills,
247 total_deaths = non_cts_deaths,
248 k_d_ratio = k_d_ratio,
249 last_played = os.last_played,
250 last_played_epoch = os.last_played_epoch,
251 last_played_fuzzy = os.last_played_fuzzy,
252 total_playing_time = os.total_playing_time,
253 total_playing_time_secs = os.total_playing_time_secs,
254 total_pickups = os.total_pickups,
255 total_captures = os.total_captures,
256 cap_ratio = os.cap_ratio,
257 total_carrier_frags = os.total_carrier_frags,
258 game_type_cd = os.game_type_cd,
259 game_type_descr = os.game_type_descr)
264 def get_fav_maps(player_id, game_type_cd=None):
266 Provides a breakdown of favorite maps by gametype.
268 Returns a dictionary of namedtuples with the following members:
270 - map_name (map name)
274 The favorite map is defined as the map you've played the most
275 for the given game_type_cd.
277 The key to the dictionary is the game type code. There is also an
278 "overall" game_type_cd which is the overall favorite map. This is
279 defined as the favorite map of the game type you've played the
280 most. The input parameter game_type_cd is for this.
282 FavMap = namedtuple('FavMap', ['map_name', 'map_id', 'times_played', 'game_type_cd'])
284 raw_favs = DBSession.query('game_type_cd', 'map_name',
285 'map_id', 'times_played').\
287 "SELECT game_type_cd, "
291 "FROM (SELECT g.game_type_cd, "
294 "Count(*) times_played, "
297 "partition BY g.game_type_cd "
298 "ORDER BY Count(*) DESC, m.map_id ASC) rank "
300 "player_game_stats pgs, "
302 "WHERE g.game_id = pgs.game_id "
303 "AND g.map_id = m.map_id "
304 "AND g.players @> ARRAY[:player_id]"
305 "AND pgs.player_id = :player_id "
306 "GROUP BY g.game_type_cd, "
308 "m.name) most_played "
310 "ORDER BY times_played desc "
311 )).params(player_id=player_id).all()
316 fv = FavMap(map_name=row.map_name,
318 times_played=row.times_played,
319 game_type_cd=row.game_type_cd)
321 # if we aren't given a favorite game_type_cd
322 # then the overall favorite is the one we've
324 if overall_fav is None:
325 fav_maps['overall'] = fv
326 overall_fav = fv.game_type_cd
328 # otherwise it is the favorite map from the
329 # favorite game_type_cd (provided as a param)
330 # and we'll overwrite the first dict entry
331 if game_type_cd == fv.game_type_cd:
332 fav_maps['overall'] = fv
334 fav_maps[row.game_type_cd] = fv
339 def get_ranks(player_id):
341 Provides a breakdown of the player's ranks by game type.
343 Returns a dictionary of namedtuples with the following members:
348 The key to the dictionary is the game type code. There is also an
349 "overall" game_type_cd which is the overall best rank.
351 Rank = namedtuple('Rank', ['rank', 'max_rank', 'percentile', 'game_type_cd'])
353 raw_ranks = DBSession.query("game_type_cd", "rank", "max_rank").\
355 "select pr.game_type_cd, pr.rank, overall.max_rank "
356 "from player_ranks pr, "
357 "(select game_type_cd, max(rank) max_rank "
359 "group by game_type_cd) overall "
360 "where pr.game_type_cd = overall.game_type_cd "
362 "and player_id = :player_id "
364 params(player_id=player_id).all()
367 found_top_rank = False
368 for row in raw_ranks:
369 rank = Rank(rank=row.rank,
370 max_rank=row.max_rank,
371 percentile=100 - 100*float(row.rank-1)/(row.max_rank-1),
372 game_type_cd=row.game_type_cd)
375 if not found_top_rank:
376 ranks['overall'] = rank
377 found_top_rank = True
378 elif rank.percentile > ranks['overall'].percentile:
379 ranks['overall'] = rank
381 ranks[row.game_type_cd] = rank
386 def get_elos(player_id):
388 Provides a breakdown of the player's elos by game type.
390 Returns a dictionary of namedtuples with the following members:
396 The key to the dictionary is the game type code. There is also an
397 "overall" game_type_cd which is the overall best rank.
399 raw_elos = DBSession.query(PlayerElo).filter_by(player_id=player_id).\
400 order_by(PlayerElo.elo.desc()).all()
403 found_max_elo = False
405 if not found_max_elo:
406 elos['overall'] = row
409 elos[row.game_type_cd] = row
414 def get_recent_games(player_id, limit=10):
416 Provides a list of recent games for a player. Uses the recent_games_q helper.
418 # recent games played in descending order
419 rgs = recent_games_q(player_id=player_id, force_player_id=True).limit(limit).all()
420 recent_games = [RecentGame(row) for row in rgs]
425 def get_accuracy_stats(player_id, weapon_cd, games):
427 Provides accuracy for weapon_cd by player_id for the past N games.
429 # Reaching back 90 days should give us an accurate enough average
430 # We then multiply this out for the number of data points (games) to
431 # create parameters for a flot graph
433 raw_avg = DBSession.query(func.sum(PlayerWeaponStat.hit),
434 func.sum(PlayerWeaponStat.fired)).\
435 filter(PlayerWeaponStat.player_id == player_id).\
436 filter(PlayerWeaponStat.weapon_cd == weapon_cd).\
439 avg = round(float(raw_avg[0])/raw_avg[1]*100, 2)
441 # Determine the raw accuracy (hit, fired) numbers for $games games
442 # This is then enumerated to create parameters for a flot graph
443 raw_accs = DBSession.query(PlayerWeaponStat.game_id,
444 PlayerWeaponStat.hit, PlayerWeaponStat.fired).\
445 filter(PlayerWeaponStat.player_id == player_id).\
446 filter(PlayerWeaponStat.weapon_cd == weapon_cd).\
447 order_by(PlayerWeaponStat.game_id.desc()).\
451 # they come out in opposite order, so flip them in the right direction
455 for i in range(len(raw_accs)):
456 accs.append((raw_accs[i][0], round(float(raw_accs[i][1])/raw_accs[i][2]*100, 2)))
464 def get_damage_stats(player_id, weapon_cd, games):
466 Provides damage info for weapon_cd by player_id for the past N games.
469 raw_avg = DBSession.query(func.sum(PlayerWeaponStat.actual),
470 func.sum(PlayerWeaponStat.hit)).\
471 filter(PlayerWeaponStat.player_id == player_id).\
472 filter(PlayerWeaponStat.weapon_cd == weapon_cd).\
475 avg = round(float(raw_avg[0])/raw_avg[1], 2)
477 # Determine the damage efficiency (hit, fired) numbers for $games games
478 # This is then enumerated to create parameters for a flot graph
479 raw_dmgs = DBSession.query(PlayerWeaponStat.game_id,
480 PlayerWeaponStat.actual, PlayerWeaponStat.hit).\
481 filter(PlayerWeaponStat.player_id == player_id).\
482 filter(PlayerWeaponStat.weapon_cd == weapon_cd).\
483 order_by(PlayerWeaponStat.game_id.desc()).\
487 # they come out in opposite order, so flip them in the right direction
491 for i in range(len(raw_dmgs)):
492 # try to derive, unless we've hit nothing then set to 0!
494 dmg = round(float(raw_dmgs[i][1])/raw_dmgs[i][2], 2)
498 dmgs.append((raw_dmgs[i][0], dmg))
499 except Exception as e:
506 def get_player_medals(player_id):
507 """Retrieves the list of medals the player has received from tournaments or
510 medals = DBSession.query(PlayerMedal)\
511 .filter(PlayerMedal.player_id==player_id)\
512 .order_by(PlayerMedal.place)\
513 .order_by(PlayerMedal.create_dt)\
518 except Exception as e:
523 def player_info_data(request):
524 player_id = int(request.matchdict['id'])
529 player = DBSession.query(Player).filter_by(player_id=player_id).\
530 filter(Player.active_ind == True).one()
532 games_played = get_games_played(player_id)
533 overall_stats = get_overall_stats(player_id)
534 fav_maps = get_fav_maps(player_id)
535 elos = get_elos(player_id)
537 medals = get_player_medals(player_id)
538 recent_games = get_recent_games(player_id)
539 cake_day = is_cake_day(player.create_dt)
541 except Exception as e:
543 raise pyramid.httpexceptions.HTTPNotFound
545 ## do not raise application exceptions here (only for debugging)
548 return {'player':player,
549 'games_played':games_played,
550 'overall_stats':overall_stats,
555 'recent_games':recent_games,
560 def player_info(request):
562 Provides detailed information on a specific player
564 return player_info_data(request)
567 def player_info_json(request):
569 Provides detailed information on a specific player. JSON.
572 # All player_info fields are converted into JSON-formattable dictionaries
573 player_info = player_info_data(request)
575 player = player_info['player'].to_dict()
578 for game in player_info['games_played']:
579 games_played[game.game_type_cd] = to_json(game)
582 for gt,stats in player_info['overall_stats'].items():
583 overall_stats[gt] = to_json(stats)
586 for gt,elo in player_info['elos'].items():
587 elos[gt] = to_json(elo.to_dict())
590 for gt,rank in player_info['ranks'].items():
591 ranks[gt] = to_json(rank)
594 for gt,mapinfo in player_info['fav_maps'].items():
595 fav_maps[gt] = to_json(mapinfo)
597 recent_games = [g.to_dict() for g in player_info['recent_games']]
601 'games_played': games_played,
602 'overall_stats': overall_stats,
603 'fav_maps': fav_maps,
606 'recent_games': recent_games,
610 def player_game_index_data(request):
612 player_id = int(request.matchdict['player_id'])
617 game_type_descr = None
619 if request.params.has_key('type'):
620 game_type_cd = request.params['type']
622 game_type_descr = DBSession.query(GameType.descr).\
623 filter(GameType.game_type_cd == game_type_cd).\
625 except Exception as e:
630 game_type_descr = None
632 if request.params.has_key('page'):
633 current_page = request.params['page']
638 player = DBSession.query(Player).\
639 filter_by(player_id=player_id).\
640 filter(Player.active_ind == True).\
643 rgs_q = recent_games_q(player_id=player.player_id,
644 force_player_id=True, game_type_cd=game_type_cd)
646 games = Page(rgs_q, current_page, items_per_page=20, url=page_url)
648 # replace the items in the canned pagination class with more rich ones
649 games.items = [RecentGame(row) for row in games.items]
651 games_played = get_games_played(player_id)
653 except Exception as e:
658 game_type_descr = None
662 'player_id':player.player_id,
665 'game_type_cd':game_type_cd,
666 'game_type_descr':game_type_descr,
667 'games_played':games_played,
671 def player_game_index(request):
673 Provides an index of the games in which a particular
674 player was involved. This is ordered by game_id, with
675 the most recent game_ids first. Paginated.
677 return player_game_index_data(request)
680 def player_game_index_json(request):
682 Provides an index of the games in which a particular
683 player was involved. This is ordered by game_id, with
684 the most recent game_ids first. Paginated. JSON.
686 return [{'status':'not implemented'}]
689 def player_accuracy_data(request):
690 player_id = request.matchdict['id']
691 allowed_weapons = ['nex', 'rifle', 'shotgun', 'uzi', 'minstanex']
695 if request.params.has_key('weapon'):
696 if request.params['weapon'] in allowed_weapons:
697 weapon_cd = request.params['weapon']
699 if request.params.has_key('games'):
701 games = request.params['games']
710 (avg, accs) = get_accuracy_stats(player_id, weapon_cd, games)
712 # if we don't have enough data for the given weapon
713 if len(accs) < games:
717 'player_id':player_id,
718 'player_url':request.route_url('player_info', id=player_id),
726 def player_accuracy(request):
728 Provides the accuracy for the given weapon. (JSON only)
730 return player_accuracy_data(request)
733 def player_accuracy_json(request):
735 Provides a JSON response representing the accuracy for the given weapon.
738 weapon = which weapon to display accuracy for. Valid values are 'nex',
739 'shotgun', 'uzi', and 'minstanex'.
740 games = over how many games to display accuracy. Can be up to 50.
742 return player_accuracy_data(request)
745 def player_damage_data(request):
746 player_id = request.matchdict['id']
747 allowed_weapons = ['grenadelauncher', 'electro', 'crylink', 'hagar',
748 'rocketlauncher', 'laser']
749 weapon_cd = 'rocketlauncher'
752 if request.params.has_key('weapon'):
753 if request.params['weapon'] in allowed_weapons:
754 weapon_cd = request.params['weapon']
756 if request.params.has_key('games'):
758 games = request.params['games']
767 (avg, dmgs) = get_damage_stats(player_id, weapon_cd, games)
769 # if we don't have enough data for the given weapon
770 if len(dmgs) < games:
774 'player_id':player_id,
775 'player_url':request.route_url('player_info', id=player_id),
783 def player_damage_json(request):
785 Provides a JSON response representing the damage for the given weapon.
788 weapon = which weapon to display damage for. Valid values are
789 'grenadelauncher', 'electro', 'crylink', 'hagar', 'rocketlauncher',
791 games = over how many games to display damage. Can be up to 50.
793 return player_damage_data(request)
796 def player_hashkey_info_data(request):
797 # hashkey = request.matchdict['hashkey']
799 # the incoming hashkey is double quoted, and WSGI unquotes once...
800 # hashkey = unquote(hashkey)
802 # if using request verification to obtain the hashkey
803 (idfp, status) = verify_request(request)
804 log.debug("d0_blind_id verification: idfp={0} status={1}\n".format(idfp, status))
806 log.debug("\n----- BEGIN REQUEST BODY -----\n" + request.body +
807 "----- END REQUEST BODY -----\n\n")
809 # if config is to *not* verify requests and we get nothing back, this
810 # query will return nothing and we'll 404.
812 player = DBSession.query(Player).\
813 filter(Player.player_id == Hashkey.player_id).\
814 filter(Player.active_ind == True).\
815 filter(Hashkey.hashkey == idfp).one()
817 games_played = get_games_played(player.player_id)
818 overall_stats = get_overall_stats(player.player_id)
819 fav_maps = get_fav_maps(player.player_id)
820 elos = get_elos(player.player_id)
822 most_recent_game = get_recent_games(player.player_id, 1)[0]
824 except Exception as e:
825 raise pyramid.httpexceptions.HTTPNotFound
827 return {'player':player,
829 'games_played':games_played,
830 'overall_stats':overall_stats,
834 'most_recent_game':most_recent_game,
838 def player_hashkey_info_json(request):
840 Provides detailed information on a specific player. JSON.
843 # All player_info fields are converted into JSON-formattable dictionaries
844 player_info = player_hashkey_info_data(request)
846 player = player_info['player'].to_dict()
849 for game in player_info['games_played']:
850 games_played[game.game_type_cd] = to_json(game)
853 for gt,stats in player_info['overall_stats'].items():
854 overall_stats[gt] = to_json(stats)
857 for gt,elo in player_info['elos'].items():
858 elos[gt] = to_json(elo.to_dict())
861 for gt,rank in player_info['ranks'].items():
862 ranks[gt] = to_json(rank)
865 for gt,mapinfo in player_info['fav_maps'].items():
866 fav_maps[gt] = to_json(mapinfo)
868 most_recent_game = to_json(player_info['most_recent_game'])
873 'games_played': games_played,
874 'overall_stats': overall_stats,
875 'fav_maps': fav_maps,
878 'most_recent_game': most_recent_game,
882 def player_hashkey_info_text(request):
884 Provides detailed information on a specific player. Plain text.
887 now = timegm(datetime.datetime.utcnow().timetuple())
889 # All player_info fields are converted into JSON-formattable dictionaries
890 player_info = player_hashkey_info_data(request)
892 # gather all of the data up into aggregate structures
893 player = player_info['player']
894 games_played = player_info['games_played']
895 overall_stats = player_info['overall_stats']
896 elos = player_info['elos']
897 ranks = player_info['ranks']
898 fav_maps = player_info['fav_maps']
899 most_recent_game = player_info['most_recent_game']
901 # one-offs for things needing conversion for text/plain
902 player_joined = timegm(player.create_dt.timetuple())
903 player_joined_dt = player.create_dt
904 alivetime = int(datetime_seconds(overall_stats['overall'].total_playing_time))
906 # this is a plain text response, if we don't do this here then
907 # Pyramid will assume html
908 request.response.content_type = 'text/plain'
914 'hashkey': player_info['hashkey'],
915 'player_joined': player_joined,
916 'player_joined_dt': player_joined_dt,
917 'games_played': games_played,
918 'overall_stats': overall_stats,
919 'alivetime': alivetime,
920 'fav_maps': fav_maps,
923 'most_recent_game': most_recent_game,
927 def player_elo_info_data(request):
929 Provides elo information on a specific player. Raw data is returned.
931 (idfp, status) = verify_request(request)
932 log.debug("d0_blind_id verification: idfp={0} status={1}\n".format(idfp, status))
934 log.debug("\n----- BEGIN REQUEST BODY -----\n" + request.body +
935 "----- END REQUEST BODY -----\n\n")
937 hashkey = request.matchdict['hashkey']
939 # the incoming hashkey is double quoted, and WSGI unquotes once...
940 hashkey = unquote(hashkey)
943 player = DBSession.query(Player).\
944 filter(Player.player_id == Hashkey.player_id).\
945 filter(Player.active_ind == True).\
946 filter(Hashkey.hashkey == hashkey).one()
948 elos = get_elos(player.player_id)
950 except Exception as e:
952 raise pyramid.httpexceptions.HTTPNotFound
961 def player_elo_info_json(request):
963 Provides elo information on a specific player. JSON.
965 elo_info = player_elo_info_data(request)
967 player = player_info['player'].to_dict()
970 for gt, elo in elo_info['elos'].items():
971 elos[gt] = to_json(elo.to_dict())
980 def player_elo_info_text(request):
982 Provides elo information on a specific player. Plain text.
985 now = timegm(datetime.datetime.utcnow().timetuple())
987 # All player_info fields are converted into JSON-formattable dictionaries
988 elo_info = player_elo_info_data(request)
990 # this is a plain text response, if we don't do this here then
991 # Pyramid will assume html
992 request.response.content_type = 'text/plain'
997 'hashkey': elo_info['hashkey'],
998 'player': elo_info['player'],
999 'elos': elo_info['elos'],
1003 def player_captimes_data(request):
1004 player_id = int(request.matchdict['player_id'])
1008 page = request.params.get("page", 1)
1010 sort = request.params.get("sort", "create_dt")
1013 player = DBSession.query(Player).filter_by(player_id=player_id).one()
1015 pct_q = DBSession.query(PlayerCaptime.fastest_cap, PlayerCaptime.create_dt,
1016 PlayerCaptime.player_id, PlayerCaptime.game_id, PlayerCaptime.map_id,
1017 Map.name.label('map_name'), Game.server_id, Server.name.label('server_name')).\
1018 filter(PlayerCaptime.player_id==player_id).\
1019 filter(PlayerCaptime.game_id==Game.game_id).\
1020 filter(PlayerCaptime.map_id==Map.map_id).\
1021 filter(Game.server_id==Server.server_id)
1023 if sort == "fastest":
1024 pct_q = pct_q.order_by(PlayerCaptime.fastest_cap)
1027 pct_q = pct_q.order_by(expr.desc(PlayerCaptime.create_dt))
1029 except Exception as e:
1030 raise pyramid.httpexceptions.HTTPNotFound
1032 captimes = Page(pct_q, page, items_per_page=20, url=page_url)
1034 # replace the items in the canned pagination class with more rich ones
1035 captimes.items = [PlayerCapTime(row) for row in captimes.items]
1038 "player_id" : player_id,
1040 "captimes" : captimes,
1046 def player_captimes(request):
1047 return player_captimes_data(request)
1050 def player_captimes_json(request):
1051 data = player_captimes_data(request)
1052 page = request.params.get("page", 1)
1054 # perform any necessary JSON conversions
1055 player_id = data["player_id"]
1056 player = data["player"].to_dict()
1057 captimes = [ct.to_dict() for ct in data["captimes"].items]
1061 "captimes" : captimes,
1066 def player_weaponstats_data_json(request):
1067 player_id = int(request.matchdict["id"])
1071 game_type_cd = request.params.get("game_type", None)
1072 if game_type_cd == "overall":
1076 if request.params.has_key("limit"):
1077 limit = int(request.params["limit"])
1085 # the game_ids of the most recently played ones
1086 # of the given game type is used for a subquery
1087 games_list = DBSession.query(Game.game_id).\
1088 filter(Game.players.contains([player_id]))
1090 if game_type_cd is not None:
1091 games_list = games_list.filter(Game.game_type_cd == game_type_cd)
1093 games_list = games_list.order_by(Game.game_id.desc()).limit(limit)
1095 weapon_stats_raw = DBSession.query(PlayerWeaponStat).\
1096 filter(PlayerWeaponStat.player_id == player_id).\
1097 filter(PlayerWeaponStat.game_id.in_(games_list)).\
1100 games_to_weapons = {}
1103 for ws in weapon_stats_raw:
1104 if ws.game_id not in games_to_weapons:
1105 games_to_weapons[ws.game_id] = [ws.weapon_cd]
1107 games_to_weapons[ws.game_id].append(ws.weapon_cd)
1109 weapons_used[ws.weapon_cd] = weapons_used.get(ws.weapon_cd, 0) + 1
1110 sum_avgs[ws.weapon_cd] = sum_avgs.get(ws.weapon_cd, 0) + float(ws.hit)/float(ws.fired)
1112 # Creating zero-valued weapon stat entries for games where a weapon was not
1113 # used in that game, but was used in another game for the set. This makes
1114 # the charts look smoother
1115 for game_id in games_to_weapons.keys():
1116 for weapon_cd in set(weapons_used.keys()) - set(games_to_weapons[game_id]):
1117 weapon_stats_raw.append(PlayerWeaponStat(player_id=player_id,
1118 game_id=game_id, weapon_cd=weapon_cd))
1120 # averages for the weapons used in the range
1122 for w in weapons_used.keys():
1123 avgs[w] = round(sum_avgs[w]/float(weapons_used[w])*100, 2)
1125 weapon_stats_raw = sorted(weapon_stats_raw, key = lambda x: x.game_id)
1126 games = sorted(games_to_weapons.keys())
1127 weapon_stats = [ws.to_dict() for ws in weapon_stats_raw]
1130 "weapon_stats": weapon_stats,
1131 "weapons_used": weapons_used.keys(),
1137 def player_versus_data(request):
1139 p1_id = int(request.params.get("p1", None))
1140 p2_id = int(request.params.get("p2", None))
1145 players = DBSession.query(Player).filter(sa.or_(Player.player_id ==
1146 p1_id, Player.player_id == p2_id)).order_by(Player.player_id).all()
1149 if len(players) < 2:
1150 raise Exception("Not enough players found.")
1152 # assign the players from the array retrieved above
1153 if players[0].player_id == p1_id:
1160 # note that wins and losses are from p1's perspective
1161 win_loss_sql = text("""select win_loss, count(1)
1164 when pgsp1.score >= pgsp2.score then 'win'
1167 from games g join player_game_stats pgsp1
1168 on g.game_id = pgsp1.game_id and pgsp1.player_id = :p1
1169 join player_game_stats pgsp2
1170 on g.game_id = pgsp2.game_id and pgsp2.player_id = :p2
1171 where g.players @> ARRAY[:p1,:p2]
1172 and g.game_type_cd = 'duel'
1173 and pgsp1.create_dt between g.create_dt - interval '1 hour'
1174 and g.create_dt + interval '1 hour'
1175 and pgsp2.create_dt between g.create_dt - interval '1 hour'
1176 and g.create_dt + interval '1 hour'
1181 wins_losses = DBSession.query("win_loss", "count").\
1182 from_statement(win_loss_sql).\
1183 params(p1=p1_id, p2=p2_id).all()
1185 for row in wins_losses:
1186 if row.win_loss == "win":
1188 elif row.win_loss == "loss":
1191 # grab the 20 most recent games between the two
1192 rgs_raw = recent_games_q(player_id=p1_id, player_id_2=p2_id,
1193 game_type_cd="duel").limit(20).all()
1195 rgs = [RecentGame(row) for row in rgs_raw]
1197 except Exception as e:
1213 "p1_wins" : p1_wins,
1214 "p2_wins" : p2_wins,
1215 "recent_games" : rgs,
1219 def player_versus(request):
1220 return player_versus_data(request)