feat: beggening of player stats

This commit is contained in:
TrèsAbhi committed 2024-06-07 14:31:43 -05:00
1 parent 521b0e8906
commit dc5e4fc2de
7 files changed
+249 -5

No files matched your search

+3
View File
@@ -19,6 +19,7 @@
"betakrieg",
"BINORMAL",
"Binormals",
"bkni",
"BKRL",
"BKRTS",
"BKRTSMANIFEST",
@@ -87,6 +88,7 @@
"loddist",
"lods",
"lossrate",
"malum",
"mantlets",
"metallicness",
"minitelrose",
@@ -136,6 +138,7 @@
"Specularity",
"SSAO",
"startcase",
"statkit",
"statsfull",
"steamapps",
"Stringifying",
+2 -2
View File
@@ -5,9 +5,9 @@
> [!CAUTION]
> This is a working draft and is incomplete. Do not implement.
## Why BkPM over WN8?
## Why BkNI over WN8?
BkPM addresses a lot of issues with WN8:
BkNI addresses a lot of issues with WN8:
- WN8 incorporates win rate into the metric; a metric like this must be a correlate of win rate, not a dependent
- The weights and biases are not dynamic and their formulation is not transparent
+71
View File
@@ -0,0 +1,71 @@
import { Flex, Heading, Text } from '@radix-ui/themes';
import PageWrapper from '../../../../components/PageWrapper';
import { getAccountInfo } from '../../../../core/blitz/getAccountInfo';
import { getClanAccountInfo } from '../../../../core/blitz/getClanAccountInfo';
import { idToRegion } from '../../../../core/blitz/idToRegion';
import getBkniPercentile from '../../../../core/statistics/getBkniPercentile';
import strings from '../../../../lang/en-US.json';
export default async function Page({ params }: { params: { id: string } }) {
const id = parseInt(params.id);
const region = idToRegion(id);
const accountInfo = await getAccountInfo(region, id);
const clanAccountInfo = await getClanAccountInfo(region, id, ['clan']);
const bkniRaw = Math.random() * 2 - 1;
const bkniFraction = Math.random();
const bkniMetric = Math.round(bkniFraction * 200);
const bkniPercentile = getBkniPercentile(bkniMetric);
return (
<PageWrapper>
<Flex gap="2">
<Heading>{accountInfo.nickname}</Heading>
{clanAccountInfo?.clan && (
<Heading color="gray">[{clanAccountInfo.clan.tag}]</Heading>
)}
</Flex>
<Flex direction="column" align="center">
<svg
width={180}
height={100}
style={{
transform: 'scaleX(-1)',
}}
>
<circle
cx={90}
cy={90}
r={80}
strokeLinecap="round"
fill="none"
stroke="var(--color-panel-solid)"
strokeWidth={20}
strokeDasharray={80 * Math.PI}
strokeDashoffset={80 * Math.PI}
/>
<circle
cx={90}
cy={90}
r={80}
strokeLinecap="round"
fill="none"
stroke="purple"
strokeWidth={20}
strokeDasharray={(160 - 80 * bkniFraction) * Math.PI}
strokeDashoffset={(160 - 80 * bkniFraction) * Math.PI}
/>
</svg>
<Flex direction="column" align="center" mt="-7">
<Text weight="bold" size="8">
{bkniMetric}
</Text>
<Text color="gray" size="4">
{strings.common.bkni_percentile[bkniPercentile]}
</Text>
</Flex>
</Flex>
</PageWrapper>
);
}
+110
View File
@@ -0,0 +1,110 @@
'use client';
import { ArrowDownIcon, MagnifyingGlassIcon } from '@radix-ui/react-icons';
import { Button, Card, Flex, Text, TextField } from '@radix-ui/themes';
import { useRef, useState } from 'react';
import { Link } from '../../../components/Link';
import PageWrapper from '../../../components/PageWrapper';
import { UNLOCALIZED_REGION_NAMES_SHORT } from '../../../constants/regions';
import searchPlayersAcrossRegions, {
AccountListWithServer,
} from '../../../core/blitz/searchPlayersAcrossRegions';
export default function Page() {
const input = useRef<HTMLInputElement>(null);
const [showSearch, setShowSearch] = useState(false);
const [searching, setSearching] = useState(false);
const [searchResults, setSearchResults] = useState<AccountListWithServer>([]);
async function search() {
if (!input.current) return;
setSearchResults(await searchPlayersAcrossRegions(input.current.value, 9));
setSearching(false);
}
return (
<PageWrapper>
<Flex
style={{ flex: 1 }}
align="center"
justify="center"
direction="column"
gap="3"
>
<Flex gap="2" align="center">
<Text color="gray">Look up a player to get started</Text>
<ArrowDownIcon />
</Flex>
<TextField.Root
size="3"
style={{
position: 'relative',
width: '75vw',
maxWidth: 480,
}}
ref={input}
placeholder="Search players..."
onChange={() => {
if (!input.current) return;
const sanitized = input.current.value.trim();
if (sanitized.length > 0) {
setShowSearch(true);
setSearching(true);
search();
} else {
setShowSearch(false);
}
}}
>
<TextField.Slot>
<MagnifyingGlassIcon />
</TextField.Slot>
{showSearch && (
<Card
mt="2"
style={{
position: 'absolute',
top: '100%',
left: 0,
width: '100%',
}}
>
{searching && (
<Flex justify="center">
<Text color="gray">Searching...</Text>
</Flex>
)}
{!searching && searchResults.length === 0 && (
<Flex justify="center">
<Text color="gray">No players found</Text>
</Flex>
)}
{!searching && searchResults.length > 0 && (
<Flex direction="column" gap="2">
{searchResults.map((player) => (
<Link
key={player.account_id}
href={`/tools/player-stats/${player.account_id}`}
>
<Button key={player.account_id} variant="ghost">
{player.nickname} (
{UNLOCALIZED_REGION_NAMES_SHORT[player.region]})
</Button>
</Link>
))}
</Flex>
)}
</Card>
)}
</TextField.Root>
</Flex>
</PageWrapper>
);
}
+3 -3
View File
@@ -31,9 +31,9 @@ export const TOOLS: Tool[] = [
significant: true,
},
{
id: 'statistics',
title: 'Statistics',
description: 'Player statistics',
id: 'player-stats',
title: 'Player stats',
description: 'Periodical player statistics',
pageDescription:
'Periodical player statistics and tanks played in World of Tanks Blitz',
image: '8gJGkKP',
+46
View File
@@ -0,0 +1,46 @@
/**
* (10) 0 - 10: super malum
* (10) 10 - 20: malum
* (20) 20 - 40: very bad
* (20) 40 - 60: bad
* (20) 60 - 80: below average
* (40) 80 - 120: average
* (20) 120 - 140: above average
* (20) 140 - 160: good
* (20) 160 - 180: very good
* (10) 180 - 190: unicum
* (10) 190 - 200: super unicum
*/
export enum BkniPercentile {
SuperMalum,
Malum,
VeryBad,
Bad,
BelowAverage,
Average,
AboveAverage,
Good,
VeryGood,
Unicum,
SuperUnicum,
}
const BKNI_PERCENTILES = [
{ min: 0, percentile: BkniPercentile.SuperMalum },
{ min: 10, percentile: BkniPercentile.Malum },
{ min: 20, percentile: BkniPercentile.VeryBad },
{ min: 40, percentile: BkniPercentile.Bad },
{ min: 60, percentile: BkniPercentile.BelowAverage },
{ min: 80, percentile: BkniPercentile.Average },
{ min: 120, percentile: BkniPercentile.AboveAverage },
{ min: 140, percentile: BkniPercentile.Good },
{ min: 160, percentile: BkniPercentile.VeryGood },
{ min: 180, percentile: BkniPercentile.Unicum },
{ min: 190, percentile: BkniPercentile.SuperUnicum },
];
export default function getBkniPercentile(wn8: number) {
const lastIndex = BKNI_PERCENTILES.findLastIndex(({ min }) => min <= wn8);
return BKNI_PERCENTILES[lastIndex === -1 ? 0 : lastIndex].percentile;
}
+14
View File
@@ -13,6 +13,20 @@
"9": "Super unicum"
},
"bkni_percentile": {
"0": "Super malum",
"1": "Malum",
"2": "Very bad",
"3": "Bad",
"4": "Below average",
"5": "Average",
"6": "Above average",
"7": "Good",
"8": "Very good",
"9": "Unicum",
"10": "Super unicum"
},
"nations": {
"ussr": "U.S.S.R.",
"germany": "Germany",