]> de.git.xonotic.org Git - xonotic/xonstat.git/blobdiff - xonstat/views/player.py
Add a hashkey view similar to the player_info JSON one.
[xonotic/xonstat.git] / xonstat / views / player.py
index 7d51a1ddb32e5db786e1eaf293ee04d8aa4dd9b7..66e74eb3e78b14871dbc773bd43582ff4405dfd7 100644 (file)
@@ -5,13 +5,14 @@ import re
 import sqlalchemy as sa
 import sqlalchemy.sql.functions as func
 import time
+from calendar import timegm
 from collections import namedtuple
 from pyramid.response import Response
 from pyramid.url import current_route_url
 from sqlalchemy import desc, distinct
 from webhelpers.paginate import Page, PageURL
 from xonstat.models import *
-from xonstat.util import page_url
+from xonstat.util import page_url, to_json, pretty_date
 
 log = logging.getLogger(__name__)
 
@@ -130,6 +131,8 @@ def get_overall_stats(player_id):
         - total_deaths
         - k_d_ratio
         - last_played (last time the player played the game type)
+        - last_played_epoch (same as above, but in seconds since epoch)
+        - last_played_fuzzy (same as above, but in relative date)
         - total_playing_time (total amount of time played the game type)
         - total_pickups (ctf only)
         - total_captures (ctf only)
@@ -141,8 +144,9 @@ def get_overall_stats(player_id):
     "overall" game_type_cd which sums the totals and computes the total ratios.
     """
     OverallStats = namedtuple('OverallStats', ['total_kills', 'total_deaths',
-        'k_d_ratio', 'last_played', 'total_playing_time', 'total_pickups',
-        'total_captures', 'cap_ratio', 'total_carrier_frags', 'game_type_cd'])
+        'k_d_ratio', 'last_played', 'last_played_epoch', 'last_played_fuzzy',
+        'total_playing_time', 'total_pickups', 'total_captures', 'cap_ratio',
+        'total_carrier_frags', 'game_type_cd'])
 
     raw_stats = DBSession.query('game_type_cd', 'total_kills',
             'total_deaths', 'last_played', 'total_playing_time',
@@ -190,7 +194,7 @@ def get_overall_stats(player_id):
             k_d_ratio = None
 
         try:
-            cap_ratio = float(row.total_pickups)/row.total_captures
+            cap_ratio = float(row.total_captures)/row.total_pickups
         except:
             cap_ratio = None
 
@@ -201,6 +205,8 @@ def get_overall_stats(player_id):
                 total_deaths=row.total_deaths,
                 k_d_ratio=k_d_ratio,
                 last_played=row.last_played,
+                last_played_epoch=timegm(row.last_played.timetuple()),
+                last_played_fuzzy=pretty_date(row.last_played),
                 total_playing_time=row.total_playing_time,
                 total_pickups=row.total_pickups,
                 total_captures=row.total_captures,
@@ -220,6 +226,8 @@ def get_overall_stats(player_id):
             total_deaths=overall_deaths,
             k_d_ratio=overall_k_d_ratio,
             last_played=overall_last_played,
+            last_played_epoch=timegm(overall_last_played.timetuple()),
+            last_played_fuzzy=pretty_date(overall_last_played),
             total_playing_time=overall_playing_time,
             total_pickups=None,
             total_captures=None,
@@ -250,6 +258,8 @@ def get_fav_maps(player_id, game_type_cd=None):
     defined as the favorite map of the game type you've played the
     most. The input parameter game_type_cd is for this.
     """
+    FavMap = namedtuple('FavMap', ['map_name', 'map_id', 'times_played', 'game_type_cd'])
+
     raw_favs = DBSession.query('game_type_cd', 'map_name',
             'map_id', 'times_played').\
             from_statement(
@@ -281,20 +291,25 @@ def get_fav_maps(player_id, game_type_cd=None):
     fav_maps = {}
     overall_fav = None
     for row in raw_favs:
+        fv = FavMap(map_name=row.map_name,
+            map_id=row.map_id,
+            times_played=row.times_played,
+            game_type_cd=row.game_type_cd)
+    
         # if we aren't given a favorite game_type_cd
         # then the overall favorite is the one we've
         # played the most
         if overall_fav is None:
-            fav_maps['overall'] = row
-            overall_fav = row.game_type_cd
+            fav_maps['overall'] = fv
+            overall_fav = fv.game_type_cd
 
         # otherwise it is the favorite map from the
         # favorite game_type_cd (provided as a param)
         # and we'll overwrite the first dict entry
-        if game_type_cd == row.game_type_cd:
-            fav_maps['overall'] = row
+        if game_type_cd == fv.game_type_cd:
+            fav_maps['overall'] = fv
 
-        fav_maps[row.game_type_cd] = row
+        fav_maps[row.game_type_cd] = fv
 
     return fav_maps
 
@@ -310,7 +325,9 @@ def get_ranks(player_id):
 
     The key to the dictionary is the game type code. There is also an
     "overall" game_type_cd which is the overall best rank.
-    """
+    """    
+    Rank = namedtuple('Rank', ['rank', 'max_rank', 'percentile', 'game_type_cd'])
+
     raw_ranks = DBSession.query("game_type_cd", "rank", "max_rank").\
             from_statement(
                 "select pr.game_type_cd, pr.rank, overall.max_rank "
@@ -326,11 +343,19 @@ def get_ranks(player_id):
     ranks = {}
     found_top_rank = False
     for row in raw_ranks:
+        rank = Rank(rank=row.rank,
+            max_rank=row.max_rank,
+            percentile=100 - 100*float(row.rank)/row.max_rank,
+            game_type_cd=row.game_type_cd)
+
+
         if not found_top_rank:
-            ranks['overall'] = row
+            ranks['overall'] = rank
             found_top_rank = True
+        elif rank.percentile > ranks['overall'].percentile:
+            ranks['overall'] = rank
 
-        ranks[row.game_type_cd] = row
+        ranks[row.game_type_cd] = rank
 
     return ranks;
 
@@ -370,6 +395,8 @@ def get_recent_games(player_id):
     Returns the full PlayerGameStat, Game, Server, Map
     objects for all recent games.
     """
+    RecentGame = namedtuple('RecentGame', ['player_stats', 'game', 'server', 'map'])
+
     # recent games table, all data
     recent_games = DBSession.query(PlayerGameStat, Game, Server, Map).\
             filter(PlayerGameStat.player_id == player_id).\
@@ -378,7 +405,12 @@ def get_recent_games(player_id):
             filter(Game.map_id == Map.map_id).\
             order_by(Game.game_id.desc())[0:10]
 
-    return recent_games
+    return [
+        RecentGame(player_stats=row.PlayerGameStat,
+            game=row.Game,
+            server=row.Server,
+            map=row.Map)
+        for row in recent_games ]
 
 
 def get_recent_weapons(player_id):
@@ -399,153 +431,6 @@ def get_recent_weapons(player_id):
     return recent_weapons
 
 
-def _get_games_played(player_id):
-    """
-    Provides a breakdown by gametype of the games played by player_id.
-
-    Returns a tuple containing (total_games, games_breakdown), where
-    total_games is the absolute number of games played by player_id
-    and games_breakdown is an array containing (game_type_cd, # games)
-    """
-    games_played = DBSession.query(Game.game_type_cd, func.count()).\
-            filter(Game.game_id == PlayerGameStat.game_id).\
-            filter(PlayerGameStat.player_id == player_id).\
-            group_by(Game.game_type_cd).\
-            order_by(func.count().desc()).all()
-
-    total = 0
-    for (game_type_cd, games) in games_played:
-        total += games
-
-    return (total, games_played)
-
-
-# TODO: should probably factor the above function into this one such that
-# total_stats['ctf_games'] is the count of CTF games and so on...
-def _get_total_stats(player_id):
-    """
-    Provides aggregated stats by player_id.
-
-    Returns a dict with the keys 'kills', 'deaths', 'alivetime'.
-
-    kills = how many kills a player has over all games
-    deaths = how many deaths a player has over all games
-    alivetime = how long a player has played over all games
-
-    If any of the above are None, they are set to 0.
-    """
-    total_stats = {}
-    (total_stats['kills'], total_stats['deaths'], total_stats['alivetime']) = DBSession.\
-            query("total_kills", "total_deaths", "total_alivetime").\
-            from_statement(
-                "select sum(kills) total_kills, "
-                "sum(deaths) total_deaths, "
-                "sum(alivetime) total_alivetime "
-                "from player_game_stats "
-                "where player_id=:player_id"
-            ).params(player_id=player_id).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()
-
-    for (key,value) in total_stats.items():
-        if value == None:
-            total_stats[key] = 0
-
-    return total_stats
-
-
-def _get_fav_maps(player_id):
-    """
-    Get the player's favorite map. The favorite map is defined
-    as the map that he or she has played the most with game
-    types considered separate. This is to say that if a person
-    plays dm and duel on stormkeep with 25 games in each mode, 
-    final_rage could still be the favorite map overall if it has
-    26 dm games.
-
-    Returns a dictionary with entries for each played game type.
-    Each game type ditionary value contained a nested dictionary
-    with the following keys:
-        id = the favorite map id
-        name = the favorite map's name
-        times_played = the number of times the map was played in that mode
-
-    Note also that there's a superficial "overall" game type that is
-    meant to hold the top map overall. It'll be a dupe of one of the
-    other game types' nested dictionary.
-    """
-    fav_maps = {}
-    for (game_type_cd, name, map_id, times_played) in DBSession.\
-            query("game_type_cd", "name", "map_id", "times_played").\
-            from_statement(
-                "SELECT game_type_cd, "
-                       "name, "
-                       "map_id, "
-                       "times_played "
-                "FROM   (SELECT g.game_type_cd, "
-                               "m.name, "
-                               "m.map_id, "
-                               "count(*) times_played, "
-                               "row_number() "
-                                 "over ( "
-                                   "PARTITION BY g.game_type_cd "
-                                   "ORDER BY Count(*) DESC, m.map_id ASC) rank "
-                        "FROM   games g, "
-                               "player_game_stats pgs, "
-                               "maps m "
-                        "WHERE  g.game_id = pgs.game_id "
-                               "AND g.map_id = m.map_id "
-                               "AND pgs.player_id = :player_id "
-                        "GROUP  BY g.game_type_cd, "
-                                  "m.map_id, "
-                                  "m.name) most_played "
-                "WHERE  rank = 1"
-            ).params(player_id=player_id).all():
-                fav_map_detail = {}
-                fav_map_detail['name'] = name
-                fav_map_detail['map_id'] = map_id
-                fav_map_detail['times_played'] = times_played
-                fav_maps[game_type_cd] = fav_map_detail
-
-    max_played = 0
-    overall = {}
-    for fav_map_detail in fav_maps.values():
-        if fav_map_detail['times_played'] > max_played:
-            max_played = fav_map_detail['times_played']
-            overall = fav_map_detail
-
-    fav_maps['overall'] = overall
-
-    return fav_maps
-
-
-def _get_rank(player_id):
-    """
-    Get the player's rank as well as the total number of ranks.
-    """
-    rank = 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()
-
-    return rank;
-
-
 def get_accuracy_stats(player_id, weapon_cd, games):
     """
     Provides accuracy for weapon_cd by player_id for the past N games.
@@ -676,10 +561,57 @@ def player_info_json(request):
     """
     Provides detailed information on a specific player. JSON.
     """
-    return [{'status':'not implemented'}]
+
+    # All player_info fields are converted into JSON-formattable dictionaries
+    player_info = player_info_data(request)    
+
+    player = player_info['player'].to_dict()
+
+    games_played = {}
+    for game in player_info['games_played']:
+        games_played[game.game_type_cd] = to_json(game)
+
+    overall_stats = {}
+    for gt,stats in player_info['overall_stats'].items():
+        overall_stats[gt] = to_json(stats)
+
+    elos = {}
+    for gt,elo in player_info['elos'].items():
+        elos[gt] = to_json(elo.to_dict())
+
+    ranks = {}
+    for gt,rank in player_info['ranks'].items():
+        ranks[gt] = to_json(rank)
+
+    fav_maps = {}
+    for gt,mapinfo in player_info['fav_maps'].items():
+        fav_maps[gt] = to_json(mapinfo)
+
+    recent_games = []
+    for game in player_info['recent_games']:
+        recent_games.append(to_json(game))
+
+    #recent_weapons = player_info['recent_weapons']
+
+    return [{
+        'player':           player,
+        'games_played':     games_played,
+        'overall_stats':    overall_stats,
+        'fav_maps':         fav_maps,
+        'elos':             elos,
+        'ranks':            ranks,
+        'recent_games':     recent_games,
+    #    'recent_weapons':   recent_weapons,
+        'recent_weapons':   ['not implemented'],
+    }]
+    #return [{'status':'not implemented'}]
 
 
 def player_game_index_data(request):
+    RecentGame = namedtuple('RecentGame', ['game_id', 'game_type_cd', 'winner',
+        'game_create_dt', 'game_epoch', 'game_fuzzy', 'server_id',
+        'server_name', 'map_id', 'map_name', 'team', 'rank', 'elo_delta'])
+
     player_id = request.matchdict['player_id']
 
     if request.params.has_key('page'):
@@ -688,7 +620,14 @@ def player_game_index_data(request):
         current_page = 1
 
     try:
-        games_q = DBSession.query(Game, Server, Map).\
+        player = DBSession.query(Player).filter_by(player_id=player_id).\
+                filter(Player.active_ind == True).one()
+
+        games_q = DBSession.query(Game.game_id, Game.game_type_cd, Game.winner,
+                Game.create_dt, Server.server_id,
+                Server.name.label('server_name'), Map.map_id,
+                Map.name.label('map_name'), PlayerGameStat.team,
+                PlayerGameStat.rank, PlayerGameStat.elo_delta).\
             filter(PlayerGameStat.game_id == Game.game_id).\
             filter(PlayerGameStat.player_id == player_id).\
             filter(Game.server_id == Server.server_id).\
@@ -697,20 +636,32 @@ def player_game_index_data(request):
 
         games = Page(games_q, current_page, items_per_page=10, url=page_url)
 
-        pgstats = {}
-        for (game, server, map) in games:
-            pgstats[game.game_id] = DBSession.query(PlayerGameStat).\
-                    filter(PlayerGameStat.game_id == game.game_id).\
-                    order_by(PlayerGameStat.rank).\
-                    order_by(PlayerGameStat.score).all()
+        # replace the items in the canned pagination class with more rich ones
+        games.items = [RecentGame(
+            game_id        = row.game_id,
+            game_type_cd   = row.game_type_cd,
+            winner         = row.winner,
+            game_create_dt = row.create_dt,
+            game_epoch     = timegm(row.create_dt.timetuple()),
+            game_fuzzy     = pretty_date(row.create_dt),
+            server_id      = row.server_id,
+            server_name    = row.server_name,
+            map_id         = row.map_id,
+            map_name       = row.map_name,
+            team           = row.team,
+            rank           = row.rank,
+            elo_delta      = row.elo_delta
+        ) for row in games.items]
 
     except Exception as e:
         player = None
         games = None
 
-    return {'player_id':player_id,
+    return {
+            'player_id':player.player_id,
+            'player':player,
             'games':games,
-            'pgstats':pgstats}
+           }
 
 
 def player_game_index(request):
@@ -836,3 +787,76 @@ def player_damage_json(request):
        games = over how many games to display damage. Can be up to 50.
     """
     return player_damage_data(request)
+
+
+def player_hashkey_info_data(request):
+    hashkey = request.matchdict['hashkey']
+    try:
+        player = DBSession.query(Player).\
+                filter(Player.player_id == Hashkey.player_id).\
+                filter(Player.active_ind == True).\
+                filter(Hashkey.hashkey == hashkey).one()
+
+        games_played   = get_games_played(player.player_id)
+        overall_stats  = get_overall_stats(player.player_id)
+        fav_maps       = get_fav_maps(player.player_id)
+        elos           = get_elos(player.player_id)
+        ranks          = get_ranks(player.player_id)
+
+    except Exception as e:
+        raise e
+        player         = None
+        games_played   = None
+        overall_stats  = None
+        fav_maps       = None
+        elos           = None
+        ranks          = None
+
+    return {'player':player,
+            'games_played':games_played,
+            'overall_stats':overall_stats,
+            'fav_maps':fav_maps,
+            'elos':elos,
+            'ranks':ranks,
+            }
+
+
+def player_hashkey_info_json(request):
+    """
+    Provides detailed information on a specific player. JSON.
+    """
+
+    # All player_info fields are converted into JSON-formattable dictionaries
+    player_info = player_hashkey_info_data(request)
+
+    player = player_info['player'].to_dict()
+
+    games_played = {}
+    for game in player_info['games_played']:
+        games_played[game.game_type_cd] = to_json(game)
+
+    overall_stats = {}
+    for gt,stats in player_info['overall_stats'].items():
+        overall_stats[gt] = to_json(stats)
+
+    elos = {}
+    for gt,elo in player_info['elos'].items():
+        elos[gt] = to_json(elo.to_dict())
+
+    ranks = {}
+    for gt,rank in player_info['ranks'].items():
+        ranks[gt] = to_json(rank)
+
+    fav_maps = {}
+    for gt,mapinfo in player_info['fav_maps'].items():
+        fav_maps[gt] = to_json(mapinfo)
+
+    return [{
+        'version':          1,
+        'player':           player,
+        'games_played':     games_played,
+        'overall_stats':    overall_stats,
+        'fav_maps':         fav_maps,
+        'elos':             elos,
+        'ranks':            ranks,
+    }]