2 import sqlalchemy as sa
3 import sqlalchemy.sql.functions as func
4 import sqlalchemy.sql.expression as expr
5 from beaker.cache import cache_regions, cache_region
6 from collections import namedtuple
7 from datetime import datetime, timedelta
8 from pyramid.response import Response
9 from xonstat.models import *
10 from xonstat.util import *
11 from xonstat.views.helpers import RecentGame, recent_games_q
12 from webhelpers.paginate import Page
15 log = logging.getLogger(__name__)
18 @cache_region('hourly_term')
19 def get_summary_stats(scope="all"):
21 Gets the following aggregate statistics according to the provided scope:
23 - the number of active players
24 - the number of games per game type
26 Scope can be "all" or "day".
28 The fetched information is summarized into a string which is passed
29 directly to the template.
31 if scope not in ["all", "day"]:
35 ss = DBSession.query("num_players", "game_type_cd", "num_games").\
37 "SELECT num_players, game_type_cd, num_games "
38 "FROM summary_stats_mv "
39 "WHERE scope = :scope "
40 "ORDER BY sort_order "
41 ).params(scope=scope).all()
47 # the number of players is constant in each row
48 total_players = row.num_players
50 total_games += row.num_games
52 # we can't show all game types on the single summary line, so any
53 # past the fifth one will get bundled in to an "other" count
55 other_games += row.num_games
59 # don't send anything if we don't have any activity
63 # This is ugly because we're doing template-like stuff within the
64 # view code. The alternative isn't any better, though: we would
65 # have to assemble the string inside the template by using a Python
66 # code block. For now I'll leave it like this since it is the lesser
68 # Also we need to hard-code the URL structure in here to allow caching,
70 in_paren = "; ".join(["{:2,d} {}".format(
72 "<a href='/games?type={0}'>{0}</a>".format(g.game_type_cd)
76 in_paren += "; {:2,d} other".format(other_games)
78 stat_line = "{:2,d} players and {:2,d} games ({})".format(
84 except Exception as e:
91 @cache_region('hourly_term')
92 def get_ranks(game_type_cd):
94 Gets a set number of the top-ranked people for the specified game_type_cd.
96 The game_type_cd parameter is the type to fetch. Currently limited to
97 duel, dm, ctf, and tdm.
99 # how many ranks we want to fetch
100 leaderboard_count = 10
102 # only a few game modes are actually ranked
103 if game_type_cd not in 'duel' 'dm' 'ctf' 'tdm':
106 ranks = DBSession.query(PlayerRank).\
107 filter(PlayerRank.game_type_cd==game_type_cd).\
108 order_by(PlayerRank.rank).\
109 limit(leaderboard_count).all()
114 def top_players_by_time_q(cutoff_days):
116 Query for the top players by the amount of time played during a date range.
118 Games older than cutoff_days days old are ignored.
121 # only games played during this range are considered
122 right_now = datetime.utcnow()
123 cutoff_dt = right_now - timedelta(days=cutoff_days)
125 top_players_q = DBSession.query(Player.player_id, Player.nick,
126 func.sum(PlayerGameStat.alivetime)).\
127 filter(Player.player_id == PlayerGameStat.player_id).\
128 filter(Player.player_id > 2).\
129 filter(expr.between(PlayerGameStat.create_dt, cutoff_dt, right_now)).\
130 order_by(expr.desc(func.sum(PlayerGameStat.alivetime))).\
131 group_by(Player.nick).\
132 group_by(Player.player_id)
137 @cache_region('hourly_term')
138 def get_top_players_by_time(cutoff_days):
140 The top players by the amount of time played during a date range.
142 Games older than cutoff_days days old are ignored.
144 # how many to retrieve
147 # only games played during this range are considered
148 right_now = datetime.utcnow()
149 cutoff_dt = right_now - timedelta(days=cutoff_days)
151 top_players_q = top_players_by_time_q(cutoff_days)
153 top_players = top_players_q.limit(count).all()
155 top_players = [(player_id, html_colors(nick), score) \
156 for (player_id, nick, score) in top_players]
161 def top_servers_by_players_q(cutoff_days):
163 Query to get the top servers by the amount of players active
166 Games older than cutoff_days days old are ignored.
168 # only games played during this range are considered
169 right_now = datetime.utcnow()
170 cutoff_dt = right_now - timedelta(days=cutoff_days)
172 top_servers_q = DBSession.query(Server.server_id, Server.name,
174 filter(Game.server_id==Server.server_id).\
175 filter(expr.between(Game.create_dt, cutoff_dt, right_now)).\
176 order_by(expr.desc(func.count(Game.game_id))).\
177 group_by(Server.server_id).\
178 group_by(Server.name)
183 @cache_region('hourly_term')
184 def get_top_servers_by_players(cutoff_days):
186 The top servers by the amount of players active during a date range.
188 Games older than cutoff_days days old are ignored.
190 # how many to retrieve
193 top_servers = top_servers_by_players_q(cutoff_days).limit(count).all()
198 def top_maps_by_times_played_q(cutoff_days):
200 Query to retrieve the top maps by the amount of times it was played
203 Games older than cutoff_days days old are ignored.
205 # only games played during this range are considered
206 right_now = datetime.utcnow()
207 cutoff_dt = right_now - timedelta(days=cutoff_days)
209 top_maps_q = DBSession.query(Game.map_id, Map.name,
211 filter(Map.map_id==Game.map_id).\
212 filter(expr.between(Game.create_dt, cutoff_dt, right_now)).\
213 order_by(expr.desc(func.count())).\
214 group_by(Game.map_id).\
220 @cache_region('hourly_term')
221 def get_top_maps_by_times_played(cutoff_days):
223 The top maps by the amount of times it was played during a date range.
225 Games older than cutoff_days days old are ignored.
227 # how many to retrieve
230 top_maps = top_maps_by_times_played_q(cutoff_days).limit(count).all()
235 def _main_index_data(request):
237 leaderboard_lifetime = int(
238 request.registry.settings['xonstat.leaderboard_lifetime'])
240 leaderboard_lifetime = 30
242 leaderboard_count = 10
243 recent_games_count = 20
245 # summary statistics for the tagline
246 stat_line = get_summary_stats("all")
247 day_stat_line = get_summary_stats("day")
250 # the three top ranks tables
252 for gtc in ['duel', 'ctf', 'dm', 'tdm']:
253 rank = get_ranks(gtc)
257 right_now = datetime.utcnow()
258 back_then = datetime.utcnow() - timedelta(days=leaderboard_lifetime)
260 # top players by playing time
261 top_players = get_top_players_by_time(leaderboard_lifetime)
263 # top servers by number of total players played
264 top_servers = get_top_servers_by_players(leaderboard_lifetime)
266 # top maps by total times played
267 top_maps = get_top_maps_by_times_played(leaderboard_lifetime)
269 # recent games played in descending order
270 rgs = recent_games_q(cutoff=back_then).limit(recent_games_count).all()
271 recent_games = [RecentGame(row) for row in rgs]
273 return {'top_players':top_players,
274 'top_servers':top_servers,
276 'recent_games':recent_games,
278 'stat_line':stat_line,
279 'day_stat_line':day_stat_line,
283 def main_index(request):
285 Display the main page information.
287 mainindex_data = _main_index_data(request)
289 # FIXME: code clone, should get these from _main_index_data
290 leaderboard_count = 10
291 recent_games_count = 20
293 for i in range(leaderboard_count-len(mainindex_data['top_players'])):
294 mainindex_data['top_players'].append(('-', '-', '-'))
296 for i in range(leaderboard_count-len(mainindex_data['top_servers'])):
297 mainindex_data['top_servers'].append(('-', '-', '-'))
299 for i in range(leaderboard_count-len(mainindex_data['top_maps'])):
300 mainindex_data['top_maps'].append(('-', '-', '-'))
302 return mainindex_data
305 def main_index_json(request):
307 JSON output of the main page information.
309 return [{'status':'not implemented'}]
312 def top_players_by_time(request):
313 current_page = request.params.get('page', 1)
315 cutoff_days = int(request.registry.settings.\
316 get('xonstat.leaderboard_lifetime', 30))
318 top_players_q = top_players_by_time_q(cutoff_days)
320 top_players = Page(top_players_q, current_page, items_per_page=25, url=page_url)
322 top_players.items = [(player_id, html_colors(nick), score) \
323 for (player_id, nick, score) in top_players.items]
325 return {'top_players':top_players}
328 def top_servers_by_players(request):
329 current_page = request.params.get('page', 1)
331 cutoff_days = int(request.registry.settings.\
332 get('xonstat.leaderboard_lifetime', 30))
334 top_servers_q = top_servers_by_players_q(cutoff_days)
336 top_servers = Page(top_servers_q, current_page, items_per_page=25, url=page_url)
338 return {'top_servers':top_servers}
341 def top_maps_by_times_played(request):
342 current_page = request.params.get('page', 1)
344 cutoff_days = int(request.registry.settings.\
345 get('xonstat.leaderboard_lifetime', 30))
347 top_maps_q = top_maps_by_times_played_q(cutoff_days)
349 top_maps = Page(top_maps_q, current_page, items_per_page=25, url=page_url)
351 return {'top_maps':top_maps}