create simple stats page

This commit is contained in:
Ilion Beyst 2022-10-24 07:19:52 +02:00
parent 03cdbbc003
commit e3164246e1
2 changed files with 175 additions and 3 deletions

View file

@ -5,8 +5,10 @@
const apiClient = new ApiClient(fetch); const apiClient = new ApiClient(fetch);
try { try {
const [botData, matchesPage] = await Promise.all([ const bot_name = params["bot_name"];
apiClient.get(`/api/bots/${params["bot_name"]}`), const [botData, botStats, matchesPage] = await Promise.all([
apiClient.get(`/api/bots/${bot_name}`),
apiClient.get(`/api/bots/${bot_name}/stats`),
apiClient.get("/api/matches", { bot: params["bot_name"], count: "20" }), apiClient.get("/api/matches", { bot: params["bot_name"], count: "20" }),
]); ]);
@ -19,6 +21,7 @@
bot, bot,
owner, owner,
versions, versions,
botStats,
matches: matchesPage["matches"], matches: matchesPage["matches"],
}, },
}; };
@ -41,7 +44,7 @@
export let owner: object; export let owner: object;
export let versions: object[]; export let versions: object[];
export let matches: object[]; export let matches: object[];
export let botStats: object;
// function last_updated() { // function last_updated() {
// versions.sort() // versions.sort()
// } // }

View file

@ -0,0 +1,169 @@
<script lang="ts" context="module">
import { ApiClient } from "$lib/api_client";
export async function load({ params, fetch }) {
const apiClient = new ApiClient(fetch);
try {
const bot_name = params["bot_name"];
const [botData, botStats, leaderboard] = await Promise.all([
apiClient.get(`/api/bots/${bot_name}`),
apiClient.get(`/api/bots/${bot_name}/stats`),
apiClient.get("/api/leaderboard"),
]);
const { bot, owner } = botData;
return {
props: {
bot,
owner,
botStats,
leaderboard,
},
};
} catch (error) {
return {
status: error.status,
error: error,
};
}
}
function mergedStats(rawStats: object) {
return Object.fromEntries(
Object.entries(rawStats).map(([opponent, ms]) => {
const mapStats = ms as { k: MatchupStats };
return [opponent, Object.values(mapStats).reduce(mergeStats)];
})
);
}
type MatchupStats = {
win: number;
tie: number;
loss: number;
};
function winRate(stats: MatchupStats) {
return (stats.win + 0.5 * stats.tie) / (stats.win + stats.tie + stats.loss);
}
function mergeStats(a: MatchupStats, b: MatchupStats): MatchupStats {
return {
win: a.win + b.win,
tie: a.tie + b.tie,
loss: a.loss + b.loss,
};
}
</script>
<script lang="ts">
export let bot: object;
export let owner: object;
export let botStats: object;
export let leaderboard: object[];
$: mergedStats = mergedStats(botStats);
</script>
<div class="container">
<div class="header">
<h1 class="bot-name">{bot["name"]}</h1>
{#if owner}
<a class="owner-name" href="/users/{owner['username']}">
{owner["username"]}
</a>
{/if}
</div>
<h2>Stats</h2>
<table class="leaderboard">
<tr class="leaderboard-row leaderboard-header">
<th class="leaderboard-rank">Rank</th>
<th class="leaderboard-rating">Rating</th>
<th class="leaderboard-bot">Bot</th>
<th class="leaderboard-author">Author</th>
<th>Winrate</th>
<th>Matches</th>
</tr>
{#each leaderboard as entry, index}
<tr class="leaderboard-row">
<td class="leaderboard-rank">{index + 1}</td>
<td class="leaderboard-rating">
{entry["rating"].toFixed(0)}
</td>
<td class="leaderboard-bot">
<a class="leaderboard-href" href="/bots/{entry['bot']['name']}"
>{entry["bot"]["name"]}
</a></td
>
<td class="leaderboard-author">
{#if entry["author"]}
<!-- TODO: remove duplication -->
<a class="leaderboard-href" href="/users/{entry['author']['username']}"
>{entry["author"]["username"]}</a
>
{/if}
</td>
{#if mergedStats[entry["bot"]["name"]]}
<td>
{winRate(mergedStats[entry["bot"]["name"]]).toFixed(2)}
</td>
<td>
<a href={`/matches?bot=${bot["name"]}&opponent=${entry["bot"]["name"]}`}>view matches</a
>
</td>
{:else}
<td />
<td>no matches yet </td>{/if}
</tr>
{/each}
</table>
</div>
<style lang="scss">
.container {
width: 800px;
max-width: 80%;
margin: 50px auto;
}
.header {
display: flex;
justify-content: space-between;
align-items: flex-end;
margin-bottom: 60px;
border-bottom: 1px solid black;
}
$header-space-above-line: 12px;
.bot-name {
font-size: 24pt;
margin-bottom: $header-space-above-line;
}
.owner-name {
font-size: 14pt;
text-decoration: none;
color: #333;
margin-bottom: $header-space-above-line;
}
.leaderboard {
margin: 18px 10px;
text-align: center;
}
.leaderboard th,
.leaderboard td {
padding: 8px 16px;
}
.leaderboard-rank {
color: #333;
}
.leaderboard-href {
text-decoration: none;
color: black;
}
</style>