Implement rating leaderboard

This commit is contained in:
Maximilian Keßler 2023-12-04 00:40:29 +01:00
parent 3d720b5599
commit 65f174c95f
Signed by: max
GPG Key ID: BCC5A619923C0BA5
3 changed files with 75 additions and 118 deletions

View File

@ -1,14 +1,33 @@
from pathlib import Path
from typing import Dict
import jinja2 import jinja2
import psycopg2.extras import psycopg2.extras
import constants import constants
import utils import utils
import ratings import ratings
from dataclasses import dataclass
from database import conn_manager from database import conn_manager
def get_rating_leaderboards(): @dataclass
class Leader:
title: str
player_name: str
user_accounts: str
score: int
@dataclass
class PlayerEntry:
player_name: str
user_accounts: str
score: int
def get_rating_lists():
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(
"SELECT * FROM (" "SELECT * FROM ("
@ -28,15 +47,26 @@ def get_rating_leaderboards():
"ORDER BY type ASC, current_rating DESC", "ORDER BY type ASC, current_rating DESC",
(", ",) (", ",)
) )
novar_leaderboard = [] leaderboard = {
clue_starved_leaderboard = [] utils.get_rating_type(x): []
for x in [True, False]
}
for row in cur.fetchall(): for row in cur.fetchall():
if row['type'] == utils.get_rating_type(False): rating_type = row['type']
novar_leaderboard.append(row) leaderboard[rating_type].append(row)
else:
clue_starved_leaderboard.append(row)
return novar_leaderboard, clue_starved_leaderboard return leaderboard
def get_leaders(rating_lists: Dict):
leaders = {}
for rating_type, rating_list in rating_lists.items():
if len(rating_list) != 0:
leader = rating_list[0]
leaders[rating_type] = {
'Player Rating': Leader('Highest Rating', leader['player_name'], leader['user_accounts'], round(leader['current_rating']))
}
return leaders
def get_streak_leaderboards(): def get_streak_leaderboards():
@ -58,27 +88,35 @@ def get_streak_leaderboards():
def get_total_games(): def get_total_games():
cur = conn_manager.get_new_cursor() cur = conn_manager.get_new_cursor()
cur.execute("SELECT MAX(league_id) FROM games") cur.execute("SELECT MAX(league_id) FROM games")
(num_games, ) = cur.fetchone() (num_games,) = cur.fetchone()
return num_games return num_games
def render_leaderboard(): def render_leaderboard():
novar_leaderboard, clue_starved_leaderboard = get_rating_leaderboards() rating_lists = get_rating_lists()
leaders = get_leaders(rating_lists)
leaderboards = {
'Player Rating': rating_lists
}
env = jinja2.Environment(loader=jinja2.FileSystemLoader('templates')) env = jinja2.Environment(loader=jinja2.FileSystemLoader('templates'))
template = env.get_template('content.html') template = env.get_template('content.html')
# rendered_html = template.render(leaders=leaders, leaderboards=leaderboards, variants=variants) # rendered_html = template.render(leaders=leaders, leaderboards=leaderboards, variants=variants)
rendered_html = template.render( rendered_html = template.render(
# leaders=leaders, leaders=leaders,
# leaderboards=leaderboards, leaderboards=leaderboards
# variants=variants, # leaders=leaders,
# total_games_played=constants['total_games_played'], # leaderboards=leaderboards,
# latest_run=latest_run_utc_formatted, # variants=variants,
# total_players=total_players # total_games_played=constants['total_games_played'],
# latest_run=latest_run_utc_formatted,
# total_players=total_players
) )
with open(constants.WEBSITE_OUTPUT_DIRECTORY / 'index.html') as f: output_file = Path(constants.WEBSITE_OUTPUT_DIRECTORY) / 'index.html'
output_file.parent.mkdir(exist_ok=True, parents=True)
with open(output_file, 'w') as f:
f.write(rendered_html) f.write(rendered_html)
get_leaderboards() render_leaderboard()
#render_leaderboard()

View File

@ -1,45 +1,42 @@
{% extends "layout.html" %} {% extends "layout.html" %}
{% block content %} {% block content %}
<div class="tab-content" id="myTabContent"> <div class="tab-content" id="myTabContent">
<div class="tab-pane fade show active" id="leaderboards"> {% for rating_type, leaders in leaders.items() %}
<div class="tab-pane fade{{' active show' if rating_type == 0 else ''}}" id="leaderboards-{{rating_type}}">
<div class="container my-5"> <div class="container my-5">
<!-- Header -->
<!-- <h1 class="text-center mb-5">Hanabi Leaderboards</h1> -->
<!-- Leader Cards --> <!-- Leader Cards -->
<div class="card-deck mb-5"> <div class="card-deck mb-5">
{% for category, data in leaders.items() %} {% for category, data in leaders.items() %}
<div class="card text-center"> <div class="card text-center">
<div class="card-body"> <div class="card-body">
<h5 class="card-title mb-4">{{ data.title }}</h5> <h5 class="card-title mb-4">{{ data.title }}</h5>
<p class="mb-0 player-name">{{ data.leader.player_name_og }}</p> <p class="mb-0 player-name">{{ data.player_name }}</p>
<p class="mt-1 alt-name">{{ data.leader.player_name }}</p> <p class="mt-1 alt-name">{{ data.user_accounts }}</p>
<p class="score-large">{{ data.leader.score }}</p> <p class="score-large">{{ data.score }}</p>
</div> </div>
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
<!-- Leaderboards --> <!-- Leaderboards -->
<div id="leaderboards" class="accordion"> <div id="leaderboards-{{rating_type}}-data" class="accordion">
{% for category, leaderboard in leaderboards.items() %} {% for category, leaderboard in leaderboards.items() %}
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header">
<button class="btn btn-link btn-block text-left" data-toggle="collapse" data-target="#{{ category|lower|replace(' ', '') }}"> <button class="btn btn-link btn-block text-left" data-toggle="collapse" data-target="#{{ category|lower|replace(' ', '-') }}">
{{ category }} {{ category }}
</button> </button>
</div> </div>
<div id="{{ category|lower|replace(' ', '') }}" class="collapse" data-parent="#leaderboards"> <div id="{{ category|lower|replace(' ', '-') }}" class="collapse" data-parent="#leaderboards-{{rating_type}}-data">
<div class="card-body"> <div class="card-body">
<table class="table table-striped"> <table class="table table-striped">
{% for player in leaderboard %} {% for row in leaderboard[rating_type] %}
<tr> <tr>
<td> <td>
<div class="player-name">{{ player.player_name_og }}</div> <div class="player-name">{{ row['player_name'] }}</div>
<div class="alt-name">{{ player.player_name }}</div> <div class="alt-name">{{ row['user_accounts'] }}</div>
</td> </td>
<td class="score">{{ player.score }}</td> <td class="score">{{ row['current_rating'] }}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</table> </table>
@ -48,90 +45,9 @@
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
</div> </div>
{% endfor %}
<div class="tab-pane fade" id="variants">
<div class="container my-5">
<table class="table table-hover table-striped">
<thead>
<tr>
<th scope="col" class="text-center">Variant</th>
<th scope="col" class="text-center">Rating</th>
<th scope="col" class="text-center">Games Played</th>
<th scope="col" class="text-center">Max Scores</th>
</tr>
</thead>
<tbody>
{% for variant in variants %}
<tr>
<td class="text-center"><strong>{{ variant.variant_name }}</strong></td>
<td class="text-center variant-rating">{{ variant.variant_rating|int }}</td>
<td class="text-center">{{ variant.number_of_games_variant }}</td>
<td class="text-center">{{ variant.number_of_max_scores_variant }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="tab-pane fade" id="info">
<div class="container my-5">
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="alert alert-info" role="alert">
<h4 class="alert-heading">Welcome to the league!</h4>
<p>The Test Season will be live from <strong>July 15th - August 31st!</strong></p>
<hr>
<p class="mb-0">Sign ups are now open <a href="https://docs.google.com/forms/d/e/1FAIpQLSfDDW0Iz4pNHaQjqBpCuRsARJ1PE1kO85ndrvKte3irjCJhvw/viewform" class="alert-link">HERE</a>!</p>
</div>
</div>
</div>
</div>
<h3>About</h3>
<p>The Hanabi Pro Hunting League creates a new way for folks to play more "serious" Hanabi, but without the stress, formality, and scheduling headaches of normal tournaments. For folks of all skill levels, the most important goal is simply to compete against yourself and to constantly learn and improve!</p>
<h3>How to Play</h3>
<p>Everyone is welcome to participate! To sign up, just click the link above to fill out the short Google Form. Just remember the following important info:</p>
<ul>
<li>Everyone joins <strong>individually</strong>, and can play with any mix of other participants.</li>
<li>League games must be played with a <strong>registered alt account</strong>.</li>
<li>ALL games played from this new account will be tracked each Season, so dont forget to switch back when playing non-League games!</li>
</ul>
<p>All game data will be automatically tracked & recorded, so all you need to do is play! Make sure to check out this page for important game & League info.</p>
<h3>Game Rules</h3>
<ul>
<li><strong>3-5 player games</strong> only</li>
<li><strong>5-6 suit games</strong> only, and only of the following variants (110 total!):
<ul>
<li>No Variant, the 9 non-dark special suits, and all combinations</li>
<li>All Clue Starved combinations</li>
</ul>
</li>
<li>Time or Untimed, but no alternate game modes or special rules (Bottom-Deck Blindplays, Empty Clues, Detrimental Chars, etc.)</li>
</ul>
<p>Only games with which follow these game rules and which include exclusively registered players will be tracked!</p>
<h3>Introducing the Hanabi League Rating system!</h3>
<p>Were excited to announce the new, experimental <strong>Hanabi League Rating</strong> (HLR) system! Adapted from Elo systems, the HLR basically works by estimating the expected outcome of each game. Each player will then gain or lose rating points based on how (un)likely the result was calculated to be.</p>
<p>These estimates are based on the teammates ratings, versus the chosen variants estimated difficulty (variants have ratings too!).</p>
<ul>
<li>The system counts <strong>Max Scores</strong> as <em>Wins</em>; any other result is simply a <em>Loss</em>.</li>
<li>For the veteran glory-seekers, there will also be <strong>Leaderboards</strong> tracking a few popular stats. For now, these include <em><strong>Rating</strong></em>, <em><strong>Longest Win Streak</strong></em>, and <em><strong>Scores Hunted</strong></em>.</li>
<li>A few other fun stats will be tracked as well, just for curiositys sake.</li>
<li>All the player ratings, leaderboards, and variant stats will be tracked & updated automatically on this website.</li>
</ul>
</div>
</div>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -17,7 +17,10 @@
<div class="collapse navbar-collapse" id="navbarNav"> <div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ml-auto"> <ul class="navbar-nav ml-auto">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link active" id="leaderboards-tab" data-toggle="tab" href="#leaderboards">Leaderboards</a> <a class="nav-link active" id="leaderboards-0-tab" data-toggle="tab" href="#leaderboards-0">No Variant</a>
</li>
<li class="nav-item">
<a class="nav-link" id="leaderboards-1-tab" data-toggle="tab" href="#leaderboards-1">Clue Starved</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" id="variant-tab" data-toggle="tab" href="#variants">Variants</a> <a class="nav-link" id="variant-tab" data-toggle="tab" href="#variants">Variants</a>