Add player tab featuring table of players

This commit is contained in:
Maximilian Keßler 2023-12-24 23:33:36 +01:00
parent d3f0f56244
commit 8e1b84467b
Signed by: max
GPG Key ID: BCC5A619923C0BA5
4 changed files with 138 additions and 3 deletions

View File

@ -346,7 +346,7 @@ CREATE TABLE user_statistics (
games_played INTEGER, games_played INTEGER,
games_won INTEGER, games_won INTEGER,
games_lost INTEGER GENERATED ALWAYS AS (games_played - games_won) STORED, games_lost INTEGER GENERATED ALWAYS AS (games_played - games_won) STORED,
winrate REAL GENERATED ALWAYS AS (CASE WHEN games_played != 0 THEN CAST(games_won AS REAL)/ games_played ELSE NULL END) STORED, winrate REAL GENERATED ALWAYS AS (CASE WHEN games_played != 0 THEN 100 * CAST(games_won AS REAL)/ games_played ELSE NULL END) STORED,
total_bdr INTEGER, total_bdr INTEGER,
/** Number of critical cards that were either discarded or misplayed */ /** Number of critical cards that were either discarded or misplayed */
total_crits_lots INTEGER, total_crits_lots INTEGER,

View File

@ -135,6 +135,23 @@ class PlayerRow:
stats: PlayerStats stats: PlayerStats
@dataclass
class PlayerOverview:
name: str
novar_rating: float
novar_rank: int
novar_games_played: int
novar_winrate: float
novar_cur_streak: int
novar_max_streak: int
cs_rating: float
cs_rank: int
cs_games_played: float
cs_winrate: float
cs_cur_streak: int
cs_max_streak: int
def get_games() -> List[GameRow]: def get_games() -> List[GameRow]:
cur = conn_manager.get_connection().cursor(cursor_factory=psycopg2.extras.DictCursor) cur = conn_manager.get_connection().cursor(cursor_factory=psycopg2.extras.DictCursor)
cur.execute( cur.execute(
@ -424,6 +441,69 @@ def get_player_stats() -> Dict[str, Dict[int, PlayerStats]]:
return ret return ret
def get_player_list() -> List[Dict]:
cur = conn_manager.get_connection().cursor(cursor_factory=psycopg2.extras.DictCursor)
cur.execute(
"WITH users_rated AS ("
" SELECT "
" users.*,"
" COALESCE(novar_rating, novar_base_ratings.rating) AS novar_rating,"
" COALESCE(cs_rating, novar_base_ratings.rating) AS cs_rating"
" FROM users "
" INNER JOIN user_base_ratings AS novar_base_ratings"
" ON users.id = novar_base_ratings.user_id AND novar_base_ratings.rating_type = 0"
" INNER JOIN user_base_ratings AS cs_base_ratings"
" ON users.id = cs_base_ratings.user_id AND cs_base_ratings.rating_type = 1"
" LEFT JOIN LATERAL ("
" SELECT value_after AS novar_rating"
" FROM user_ratings"
" WHERE user_ratings.user_id = users.id AND user_ratings.rating_type = 0"
" ORDER BY league_id DESC"
" LIMIT 1"
" ) ON TRUE "
" LEFT JOIN LATERAL ("
" SELECT value_after AS cs_rating"
" FROM user_ratings"
" WHERE user_ratings.user_id = users.id AND user_ratings.rating_type = 1"
" ORDER BY league_id DESC"
" LIMIT 1"
" ) ON TRUE "
")"
"SELECT"
" player_name AS name,"
" novar_rating,"
" rank() over (ORDER BY novar_rating DESC) AS novar_rank,"
" novar_statistics.games_played AS novar_games_played,"
" COALESCE(novar_statistics.winrate, 0) AS novar_winrate,"
" novar_statistics.current_streak AS novar_cur_streak,"
" novar_statistics.maximum_streak AS novar_max_streak, "
" cs_rating,"
" rank() over (ORDER BY cs_rating DESC) AS cs_rank,"
" cs_statistics.games_played AS cs_games_played,"
" COALESCE(cs_statistics.winrate, 0) AS cs_winrate,"
" cs_statistics.current_streak AS cs_cur_streak,"
" cs_statistics.maximum_streak AS cs_max_streak "
"FROM"
" users_rated "
"LEFT OUTER JOIN user_statistics AS novar_statistics"
" ON novar_statistics.user_id = users_rated.id AND novar_statistics.variant_type = 0 "
"LEFT OUTER JOIN user_statistics AS cs_statistics"
" ON cs_statistics.user_id = users_rated.id AND cs_statistics.variant_type = 1"
)
res = []
for row in cur.fetchall():
# Note: We convert to the dataclass here and then back to the dictionary
# This ensures that all attributes set in the dataclass are in fact specified.
overview = PlayerOverview(**row)
overview.novar_rating = round(overview.novar_rating, 1)
overview.cs_rating = round(overview.cs_rating, 1)
overview.novar_winrate = round(overview.novar_winrate, 1)
overview.cs_winrate = round(overview.cs_winrate, 1)
res.append(dataclasses.asdict(overview))
print(res)
return res
def get_total_games(): def get_total_games():
cur = conn_manager.get_new_cursor() cur = conn_manager.get_new_cursor()
cur.execute("SELECT COUNT(league_id) FROM games") cur.execute("SELECT COUNT(league_id) FROM games")
@ -463,6 +543,7 @@ def render_main_site(env: jinja2.Environment, out_dir: Path):
rating_lists = get_rating_lists() rating_lists = get_rating_lists()
streak_lists = get_streak_list() streak_lists = get_streak_list()
leaders = get_leaders(rating_lists, streak_lists) leaders = get_leaders(rating_lists, streak_lists)
players = get_player_list()
variant_rows: List[VariantRow] = get_variant_rows() variant_rows: List[VariantRow] = get_variant_rows()
@ -479,7 +560,8 @@ def render_main_site(env: jinja2.Environment, out_dir: Path):
total_players=get_num_players(), total_players=get_num_players(),
latest_run=datetime.datetime.now().isoformat(), latest_run=datetime.datetime.now().isoformat(),
variants_with_player_nums=variant_rows, variants_with_player_nums=variant_rows,
unique_variants=build_unique_variants(variant_rows) unique_variants=build_unique_variants(variant_rows),
players=players
# variants=variants, # variants=variants,
) )
@ -559,5 +641,6 @@ def render_all():
if __name__ == "__main__": if __name__ == "__main__":
get_player_list()
render_all() render_all()

View File

@ -1,4 +1,5 @@
{% extends "layout.html" %} {% extends "layout.html" %}
{% from "stats_table.html" import player_table_js %}
{% block navbar %} {% block navbar %}
<nav class="navbar navbar-expand-lg navbar-light bg-light"> <nav class="navbar navbar-expand-lg navbar-light bg-light">
@ -18,6 +19,9 @@
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" id="leaderboards-1-tab" data-toggle="tab" href="#leaderboards-1">Clue Starved</a> <a class="nav-link" id="leaderboards-1-tab" data-toggle="tab" href="#leaderboards-1">Clue Starved</a>
</li> </li>
<li class="nav-item">
<a class="nav-link" id="players-tab" data-toggle="tab" href="#players">Players</a>
</li>
<li class="nav-item dropdown"> <li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="variants-dropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> <a class="nav-link dropdown-toggle" href="#" id="variants-dropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Variants Variants
@ -105,6 +109,22 @@
</div> </div>
{% endfor %} {% endfor %}
<div class="tab-pane fade" id="players">
<div class="container my-5">
<div id="table-players"></div>
</div>
</div>
<script>
let player_tabledata = [
{% for player_row in players %}
{{- player_row -}},
{% endfor %}
];
{{ player_table_js("player_tabledata", "players") }}
</script>
<!-- Variants --> <!-- Variants -->
<div class="tab-pane fade" id="variants"> <div class="tab-pane fade" id="variants">

View File

@ -100,4 +100,36 @@ var table_{{div_id}} = new Tabulator("#table-{{div_id}}", {
{title: "Result", field: "game_outcomes"} {title: "Result", field: "game_outcomes"}
], ],
}); });
{% endmacro %} {% endmacro %}
{% macro player_table_js(data, div_id) %}
var table_{{div_id}} = new Tabulator("#table-{{div_id}}", {
data: {{data}},
layout: "fitDataStretch",
initialSort: [
{column: "name", dir: "asc"},
],
columns: [
{title: "Player", field: "name", formatter: "link", formatterParams:{
urlPrefix: "/player/",
}},
{title: "No Variant", columns: [
{title: "ELO", field: "novar_rating"},
{title: "Rank", field: "novar_rank"},
{title: "Games", field: "novar_games_played"},
{title: "Win%", field: "novar_winrate"},
{title: "Streak", field: "novar_cur_streak"},
{title: "Max Streak", field: "novar_max_streak"},
]},
{title: "Clue Starved", columns: [
{title: "ELO", field: "cs_rating"},
{title: "Rank", field: "cs_rank"},
{title: "Games", field: "cs_games_played"},
{title: "Win%", field: "cs_winrate"},
{title: "Streak", field: "cs_cur_streak"},
{title: "Max Streak", field: "cs_max_streak"}
]},
]
});
{% endmacro %}