Add player tab featuring table of players
This commit is contained in:
parent
d3f0f56244
commit
8e1b84467b
4 changed files with 138 additions and 3 deletions
|
@ -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,
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
Loading…
Reference in a new issue