feat: career stats

This commit is contained in:
TrèsAbhi committed 2023-09-24 17:02:09 -05:00
1 parent 8c041d4eef
commit d801447e5d
6 files changed
+252 -121

No files matched your search

+246 -115
View File
@@ -8,7 +8,7 @@ import { Button } from '../../../components/Button';
import PageWrapper from '../../../components/PageWrapper';
import { SearchBar } from '../../../components/SearchBar';
import { TreeTypeEnum } from '../../../components/Tanks';
import { REGION_NAMES } from '../../../constants/regions';
import { REGION_NAMES, Region } from '../../../constants/regions';
import { WARGAMING_APPLICATION_ID } from '../../../constants/wargamingApplicationID';
import calculateWN8 from '../../../core/blitz/calculateWN8';
import { diffNormalizedTankStats } from '../../../core/blitz/diffNormalizedTankStats';
@@ -28,6 +28,9 @@ import {
} from '../../../types/tanksStats';
import * as styles from './page.css';
// 1 once per 5 seconds
const REFRESH_RATE = 1 / 5;
export default function Page() {
const input = useRef<HTMLInputElement>(null);
const [searchResults, setSearchResults] = useState<
@@ -45,63 +48,90 @@ export default function Page() {
500,
);
const session = useSession();
const tankStatsArray = session.isTracking
? (Object.values(session.tankStats) as IndividualTankStats[])
: [];
const since = session.isTracking ? new Date(session.time) : undefined;
const [diff, setDiff] = useState<
| {
stats: IndividualTankStats;
tankopedia?: TankopediaEntry;
career: IndividualTankStats;
currentWN8?: number;
careerWN8?: number;
}[]
careerWN8: number;
list: {
stats: IndividualTankStats;
tankopedia?: TankopediaEntry;
career: IndividualTankStats;
currentWN8?: number;
careerWN8?: number;
}[];
}
| undefined
>(undefined);
useEffect(() => {
(async () => {
if (session.isTracking) {
const { id, region } = session;
const careerRaw = await getWargamingResponse<TanksStats>(
`https://api.wotblitz.${region}/wotb/tanks/stats/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}`,
async function recalculateDiff() {
if (session.isTracking) {
const { id, region } = session;
const careerRaw = await getWargamingResponse<TanksStats>(
`https://api.wotblitz.${region}/wotb/tanks/stats/?application_id=${WARGAMING_APPLICATION_ID}&account_id=${id}`,
);
const career = careerRaw[id].reduce<Record<number, IndividualTankStats>>(
(accumulator, curr) => ({
...accumulator,
[curr.tank_id]: curr,
}),
{},
);
const awaitedTankopedia = await tankopedia;
const awaitedTankAverages = await tankAverages;
const careerWN8 =
careerRaw[id].reduce(
(accumulator, { tank_id, all }) =>
accumulator +
(awaitedTankAverages[tank_id] && all.battles > 0
? all.battles *
calculateWN8(awaitedTankAverages[tank_id].all, all)
: 0),
0,
) /
careerRaw[id].reduce(
(accumulator, { tank_id, all }) =>
accumulator +
(awaitedTankAverages[tank_id] && all.battles > 0 ? all.battles : 0),
0,
);
const career = careerRaw[id].reduce<
Record<number, IndividualTankStats>
>(
(accumulator, curr) => ({
...accumulator,
[curr.tank_id]: curr,
}),
{},
);
const awaitedTankopedia = await tankopedia;
const awaitedTankAverages = await tankAverages;
setDiff(
(
Object.values(
diffNormalizedTankStats(session.tankStats, career),
) as IndividualTankStats[]
).map((stats) => {
return {
stats,
tankopedia: awaitedTankopedia[stats.tank_id],
career: career[stats.tank_id],
currentWN8: awaitedTankAverages[stats.tank_id]
? calculateWN8(
awaitedTankAverages[stats.tank_id].all,
stats.all,
)
: undefined,
careerWN8: awaitedTankAverages[stats.tank_id]
? calculateWN8(
awaitedTankAverages[stats.tank_id].all,
career[stats.tank_id].all,
)
: undefined,
};
}),
);
}
})();
setDiff({
careerWN8,
list: (
Object.values(
diffNormalizedTankStats(session.tankStats, career),
) as IndividualTankStats[]
).map((stats) => {
return {
stats,
tankopedia: awaitedTankopedia[stats.tank_id],
career: career[stats.tank_id],
currentWN8: awaitedTankAverages[stats.tank_id]
? calculateWN8(awaitedTankAverages[stats.tank_id].all, stats.all)
: undefined,
careerWN8: awaitedTankAverages[stats.tank_id]
? calculateWN8(
awaitedTankAverages[stats.tank_id].all,
career[stats.tank_id].all,
)
: undefined,
};
}),
});
}
}
async function setSession(region: Region, id: number, nickname: string) {}
useEffect(() => {
recalculateDiff();
const interval = setInterval(recalculateDiff, 1000 / REFRESH_RATE);
return () => clearInterval(interval);
}, []);
return (
@@ -135,7 +165,6 @@ export default function Page() {
marginTop: 8,
flexDirection: 'column',
backgroundColor: theme.colors.componentNonInteractive,
border: theme.borderStyles.nonInteractive,
borderRadius: 4,
padding: 8,
boxSizing: 'border-box',
@@ -244,69 +273,171 @@ export default function Page() {
</div>
<Breakdown.Root>
{diff &&
diff
.sort((a, b) => b.stats.last_battle_time - a.stats.last_battle_time)
.map(({ stats, tankopedia, career, careerWN8, currentWN8 }) => (
<Breakdown.Row
key={stats.tank_id}
title={tankopedia?.name ?? `Unknown tank ${stats.tank_id}`}
type="tank"
tankType={tankopedia?.type}
treeType={(() => {
if (tankopedia?.is_collectible) return TreeTypeEnum.Collector;
if (tankopedia?.is_premium) return TreeTypeEnum.Premium;
})()}
rows={[
{
title: 'Battles',
current: stats.all.battles.toLocaleString(),
career: career.all.battles.toLocaleString(),
},
{
title: 'Winrate',
current: `${(
100 *
(stats.all.wins / stats.all.battles)
).toFixed(2)}%`,
career: `${(
100 *
(career.all.wins / career.all.battles)
).toFixed(2)}%`,
delta:
stats.all.wins / stats.all.battles -
career.all.wins / career.all.battles,
},
{
title: 'WN8',
current:
currentWN8 === undefined
? undefined
: Math.round(currentWN8).toLocaleString(),
career:
careerWN8 === undefined
? undefined
: Math.round(careerWN8).toLocaleString(),
percentile:
currentWN8 === undefined
? undefined
: getWN8Percentile(currentWN8),
},
{
title: 'Damage',
current: Math.round(
stats.all.damage_dealt / stats.all.battles,
).toLocaleString(),
career: Math.round(
career.all.damage_dealt / career.all.battles,
).toLocaleString(),
delta:
stats.all.damage_dealt / stats.all.battles -
career.all.damage_dealt / career.all.battles,
},
]}
/>
))}
{session.isTracking && since !== undefined && diff !== undefined && (
<Breakdown.Row
title={`Since ${since.toLocaleString(undefined, {
hour: 'numeric',
minute: 'numeric',
hour12: true,
month: 'short',
year: 'numeric',
day: 'numeric',
})}`}
stats={[
{
title: 'Battles',
current: diff.list
.reduce(
(accumulator, { stats }) => accumulator + stats.all.battles,
0,
)
.toLocaleString(),
career: tankStatsArray
.reduce(
(accumulator, stats) => accumulator + stats.all.battles,
0,
)
.toLocaleString(),
},
{
title: 'Winrate',
current: `${(
100 *
(diff.list.reduce(
(accumulator, { stats }) => accumulator + stats.all.wins,
0,
) /
diff.list.reduce(
(accumulator, { stats }) =>
accumulator + stats.all.battles,
0,
))
).toFixed(2)}%`,
career: `${(
100 *
(tankStatsArray.reduce(
(accumulator, stats) => accumulator + stats.all.wins,
0,
) /
tankStatsArray.reduce(
(accumulator, stats) => accumulator + stats.all.battles,
0,
))
).toFixed(2)}%`,
},
(() => {
const currentWN8 =
diff.list.reduce(
(accumulator, { currentWN8, stats }) =>
stats.all.battles * (currentWN8 ?? 0) + accumulator,
0,
) /
diff.list.reduce(
(accumulator, { currentWN8, stats }) =>
(currentWN8 === undefined ? 0 : stats.all.battles) +
accumulator,
0,
);
return {
title: 'WN8',
current: Math.round(currentWN8).toLocaleString(),
percentile: getWN8Percentile(currentWN8),
career: Math.round(diff.careerWN8).toLocaleString(),
};
})(),
{
title: 'Damage',
current: Math.round(
diff.list.reduce(
(accumulator, { stats }) =>
accumulator + stats.all.damage_dealt,
0,
) /
diff.list.reduce(
(accumulator, { stats }) =>
accumulator + stats.all.battles,
0,
),
).toLocaleString(),
career: Math.round(
tankStatsArray.reduce(
(accumulator, stats) =>
accumulator + stats.all.damage_dealt,
0,
) /
tankStatsArray.reduce(
(accumulator, stats) => accumulator + stats.all.battles,
0,
),
).toLocaleString(),
},
]}
/>
)}
{diff?.list
.sort((a, b) => b.stats.last_battle_time - a.stats.last_battle_time)
.map(({ stats, tankopedia, career, careerWN8, currentWN8 }) => (
<Breakdown.Row
key={stats.tank_id}
title={tankopedia?.name ?? `Unknown tank ${stats.tank_id}`}
type="tank"
tankType={tankopedia?.type}
treeType={(() => {
if (tankopedia?.is_collectible) return TreeTypeEnum.Collector;
if (tankopedia?.is_premium) return TreeTypeEnum.Premium;
})()}
stats={[
{
title: 'Battles',
current: stats.all.battles.toLocaleString(),
career: career.all.battles.toLocaleString(),
},
{
title: 'Winrate',
current: `${(
100 *
(stats.all.wins / stats.all.battles)
).toFixed(2)}%`,
career: `${(
100 *
(career.all.wins / career.all.battles)
).toFixed(2)}%`,
delta:
stats.all.wins / stats.all.battles -
career.all.wins / career.all.battles,
},
{
title: 'WN8',
current:
currentWN8 === undefined
? undefined
: Math.round(currentWN8).toLocaleString(),
career:
careerWN8 === undefined
? undefined
: Math.round(careerWN8).toLocaleString(),
percentile:
currentWN8 === undefined
? undefined
: getWN8Percentile(currentWN8),
},
{
title: 'Damage',
current: Math.round(
stats.all.damage_dealt / stats.all.battles,
).toLocaleString(),
career: Math.round(
career.all.damage_dealt / career.all.battles,
).toLocaleString(),
delta:
stats.all.damage_dealt / stats.all.battles -
career.all.damage_dealt / career.all.battles,
},
]}
/>
))}
</Breakdown.Root>
</PageWrapper>
);
+3 -3
View File
@@ -5,13 +5,13 @@ import { TREE_TYPE_ICONS, TreeTypeEnum } from '../../Tanks';
import { RowStat } from './RowStat';
interface RowProps {
type: 'tank' | 'summary';
type?: 'tank' | 'summary';
title: string;
minimized?: boolean;
treeType?: TreeTypeEnum;
tankType?: TankType;
rows: {
stats: {
title: string;
current?: number | string;
career?: number | string;
@@ -77,7 +77,7 @@ export function Row(props: RowProps) {
paddingBottom: 8,
}}
>
{props.rows.map((row, index) => (
{props.stats.map((row, index) => (
<RowStat
minimized={props.minimized}
key={index}
+1 -1
View File
@@ -23,7 +23,7 @@ export function Button({ color, style, ...props }: ButtonProps) {
backgroundColor:
theme.colors[`componentInteractive${BUTTON_COLORS[color]}`],
borderRadius: 4,
border: theme.borderStyles[`interactive${BUTTON_COLORS[color]}`],
border: 'none',
cursor: 'pointer',
padding: 8,
display: 'flex',
-1
View File
@@ -14,7 +14,6 @@ export const SearchBar = forwardRef<HTMLInputElement, ComponentProps<'input'>>(
display: 'flex',
padding: 8,
borderRadius: 4,
border: theme.borderStyles.interactive,
backgroundColor: theme.colors.componentInteractive,
color: theme.colors.textLowContrast,
fontSize: 16,
+1 -1
View File
@@ -138,7 +138,7 @@ export default async function today(
treeType={tankopediaEntry ? await getTreeType(id) : undefined}
title={showTotal && id === 0 ? 'Total' : await resolveTankName(id)}
minimized={showTotal ? index > maximized : index + 1 > maximized}
rows={[
stats={[
{
title: 'Battles',
current: tankStats.battles.toLocaleString(),
+1
View File
@@ -14,6 +14,7 @@ type Session =
id: number;
nickname: string;
tankStats: NormalizedTankStats;
time: number;
};
export const useSession = create<Session>()(