feat: split id and score

This commit is contained in:
TrèsAbhi committed 2023-11-14 20:56:27 -06:00
1 parent 0d1eb9b12e
commit 933c468e2d
1 file changed
+133 -126
+133 -126
View File
@@ -15,7 +15,6 @@ import { produce } from 'immer';
import { debounce, range } from 'lodash'; import { debounce, range } from 'lodash';
import { ChangeEvent, useEffect, useRef, useState } from 'react'; import { ChangeEvent, useEffect, useRef, useState } from 'react';
import { create } from 'zustand'; import { create } from 'zustand';
import { BlitzkriegRatingsLeaderboardEntry } from '../../../../scripts/buildRatingsLeaderboard';
import { RatingsInfo, RatingsPlayer } from '../../../commands/ratings'; import { RatingsInfo, RatingsPlayer } from '../../../commands/ratings';
import PageWrapper from '../../../components/PageWrapper'; import PageWrapper from '../../../components/PageWrapper';
import { LEAGUES } from '../../../constants/leagues'; import { LEAGUES } from '../../../constants/leagues';
@@ -46,18 +45,16 @@ interface NameCacheEntry {
clan?: string; clan?: string;
} }
type NameCache = Record<Region, Record<number, NameCacheEntry | null>>; type NameCache = Record<Region, Record<number, NameCacheEntry | null>>;
type PlayersCache = Record< type LeaderboardCache = Record<Region, Record<number, Record<number, number>>>;
Region,
Record<number, Record<number, BlitzkriegRatingsLeaderboardEntry>>
>;
type InfoCache = Record<Region, Record<number, RatingsInfo | undefined>>; type InfoCache = Record<Region, Record<number, RatingsInfo | undefined>>;
type ScoreCache = Record<Region, Record<number, Record<number, number>>>;
const useNameCache = create<NameCache>(() => ({ const useNameCache = create<NameCache>(() => ({
asia: {}, asia: {},
com: {}, com: {},
eu: {}, eu: {},
})); }));
const usePlayersCache = create<PlayersCache>(() => ({ const useLeaderboardCache = create<LeaderboardCache>(() => ({
asia: {}, asia: {},
com: {}, com: {},
eu: {}, eu: {},
@@ -67,6 +64,11 @@ const useInfoCache = create<InfoCache>(() => ({
com: {}, com: {},
eu: {}, eu: {},
})); }));
const useScoreCache = create<ScoreCache>(() => ({
asia: {},
com: {},
eu: {},
}));
export default function Page() { export default function Page() {
const [region, setRegion] = useState<Region>('com'); const [region, setRegion] = useState<Region>('com');
@@ -90,9 +92,10 @@ export default function Page() {
const [latestArchivedSeasonNumber, setLatestArchivedSeasonNumber] = useState< const [latestArchivedSeasonNumber, setLatestArchivedSeasonNumber] = useState<
number | null number | null
>(null); >(null);
const scores = useScoreCache()[region][season];
const names = useNameCache(); const names = useNameCache();
const players = usePlayersCache(); const leaderboard = useLeaderboardCache();
const positionInput = useRef<HTMLInputElement>(null); const positionInput = useRef<HTMLInputElement>(null);
const scoreInput = useRef<HTMLInputElement>(null); const scoreInput = useRef<HTMLInputElement>(null);
@@ -121,94 +124,91 @@ export default function Page() {
}, [season, region]); }, [season, region]);
// initial leagues caching for current season // initial leagues caching for current season
useEffect(() => { useEffect(() => {
if (season in players[region]) return; if (season in leaderboard[region]) return;
if (season === 0) { if (season === 0) {
(async () => { (async () => {
const newNameCache: Record<number, NameCacheEntry> = {}; const results = (
const results = await Promise.all( await Promise.all(
range(0, 5).map(async (league: number) => { range(0, 5).map((league: number) => {
const leaguePlayers = await getRatingsLeague(region, league); return getRatingsLeague(region, league);
leaguePositionCache.current[league] = }),
leaguePlayers.result[0].number - 1; )
leaguePlayers.result.forEach((player) => { )
newNameCache[player.spa_id] = { .map(({ result }) => result)
nickname: player.nickname, .flat();
clan: player.clan_tag,
}; useLeaderboardCache.setState(
produce((draft: LeaderboardCache) => {
if (!(season in draft[region])) draft[region][season] = {};
results.forEach((result) => {
draft[region][season][result.number - 1] = result.spa_id;
}); });
return leaguePlayers.result.reduce<
Record<number, BlitzkriegRatingsLeaderboardEntry>
>(
(accumulator, player) => ({
...accumulator,
[player.number - 1]: {
id: player.spa_id,
score: player.score,
} satisfies BlitzkriegRatingsLeaderboardEntry,
}),
{},
);
}),
);
const newPlayers = results.reduce<
Record<number, BlitzkriegRatingsLeaderboardEntry>
>(
(accumulator, result) => ({
...accumulator,
...result,
}),
{},
);
usePlayersCache.setState(
produce((draft: PlayersCache) => {
draft[region][season] = {
...draft[region][season],
...newPlayers,
};
}), }),
); );
useNameCache.setState( useNameCache.setState(
produce((draft: NameCache) => { produce((draft: NameCache) => {
draft[region] = { ...draft[region], ...newNameCache }; results.forEach((result) => {
draft[region][result.spa_id] = {
nickname: result.nickname,
clan: result.clan_tag,
};
});
}),
);
useScoreCache.setState(
produce((draft: ScoreCache) => {
if (!(season in draft[region])) draft[region][season] = {};
results.forEach((result) => {
draft[region][season][result.spa_id] = result.score;
});
}), }),
); );
})(); })();
} else { } else {
getArchivedRatingsLeaderboard(region, season).then((result) => { getArchivedRatingsLeaderboard(region, season).then((results) => {
usePlayersCache.setState( useLeaderboardCache.setState(
produce((draft: PlayersCache) => { produce((draft: LeaderboardCache) => {
draft[region][season] = { if (!(season in draft[region])) draft[region][season] = {};
...draft[region][season],
...result, results.forEach((result, index) => {
}; draft[region][season][index] = result.id;
});
}),
);
useScoreCache.setState(
produce((draft: ScoreCache) => {
if (!(season in draft[region])) draft[region][season] = {};
results.forEach((result) => {
draft[region][season][result.id] = result.score;
});
}), }),
); );
}); });
} }
}, [season in players[region]]); }, [season in leaderboard[region]]);
// 2nd page caching for current season // 2nd page caching for current season
useEffect(() => { useEffect(() => {
if ( if (
season === 0 && season === 0 &&
players !== null && leaderboard !== null &&
players[region][season] && leaderboard[region][season] &&
0 in players[region][season] 0 in leaderboard[region][season]
) { ) {
cacheNeighbors(players[region][season][0].id, ROWS_PER_PAGE * 3); cacheNeighbors(leaderboard[region][season][0], ROWS_PER_PAGE * 3);
} }
}, [players[region]?.[season] && 0 in players[region][season]]); }, [leaderboard[region]?.[season] && 0 in leaderboard[region][season]]);
// neighboring page caching for all seasons // neighboring page caching for all seasons
useEffect(() => { useEffect(() => {
if (!ratingsInfo || ratingsInfo?.detail) return; if (!ratingsInfo || ratingsInfo?.detail) return;
if (season === 0) { if (season === 0) {
const middleIndex = Math.round(page * ROWS_PER_PAGE + ROWS_PER_PAGE / 2); const middleIndex = Math.round(page * ROWS_PER_PAGE + ROWS_PER_PAGE / 2);
if (!(middleIndex in players[region][season])) return; if (!(middleIndex in leaderboard[region][season])) return;
const middleId = players[region][season][middleIndex].id; const middleId = leaderboard[region][season][middleIndex];
cacheNeighbors(middleId, ROWS_PER_PAGE * 3); cacheNeighbors(middleId, ROWS_PER_PAGE * 3);
} else { } else {
const ids = range( const ids = range(
@@ -218,7 +218,7 @@ export default function Page() {
(page + 1) * ROWS_PER_PAGE + ROWS_PER_PAGE, (page + 1) * ROWS_PER_PAGE + ROWS_PER_PAGE,
), ),
) )
.map((index) => players[region][season][index].id) .map((index) => leaderboard[region][season][index])
.filter(Boolean) .filter(Boolean)
.filter((id) => !(id in names[region])); .filter((id) => !(id in names[region]));
@@ -244,7 +244,8 @@ export default function Page() {
region, region,
season, season,
ratingsInfo?.detail, ratingsInfo?.detail,
players[region][season] !== undefined && 0 in players[region][season], leaderboard[region][season] !== undefined &&
0 in leaderboard[region][season],
]); ]);
// latest archived season number caching // latest archived season number caching
useEffect(() => {}, [ useEffect(() => {}, [
@@ -258,8 +259,8 @@ export default function Page() {
size: number, size: number,
): Promise<RatingsPlayer[]> { ): Promise<RatingsPlayer[]> {
const radius = Math.round(size / 2); const radius = Math.round(size / 2);
const targetPosition = Object.entries(players[region][season]).find( const targetPosition = Object.entries(leaderboard[region][season]).find(
([, player]) => id === player.id, ([, player]) => id === player,
)?.[0]; )?.[0];
if (targetPosition !== undefined) { if (targetPosition !== undefined) {
@@ -271,7 +272,7 @@ export default function Page() {
position <= targetPositionAsNumber + radius; position <= targetPositionAsNumber + radius;
position++ position++
) { ) {
isMissing = !(position in players[region][season]); isMissing = !(position in leaderboard[region][season]);
if (isMissing) break; if (isMissing) break;
} }
@@ -279,28 +280,32 @@ export default function Page() {
} }
const seedingPlayerPosition = parseInt( const seedingPlayerPosition = parseInt(
Object.entries(players[region][season]).find( Object.entries(leaderboard[region][season]).find(
([, player]) => id === player.id, ([, player]) => id === player,
)![0], )![0],
); );
const { neighbors } = await getRatingsNeighbors(region, id, size, true); const { neighbors } = await getRatingsNeighbors(region, id, size, true);
usePlayersCache.setState( useLeaderboardCache.setState(
produce((draft: PlayersCache) => { produce((draft: LeaderboardCache) => {
neighbors.forEach((neighbor, neighborIndex) => { neighbors.forEach((neighbor, neighborIndex) => {
// const position = neighbor.number - 1;
const position = neighborIndex - radius + seedingPlayerPosition; const position = neighborIndex - radius + seedingPlayerPosition;
if (!(position in draft[region][season])) { if (!(position in draft[region][season])) {
draft[region][season][position] = { draft[region][season][position] = neighbor.spa_id;
id: neighbor.spa_id, }
score: neighbor.score, });
} satisfies BlitzkriegRatingsLeaderboardEntry; }),
);
useScoreCache.setState(
produce((draft: ScoreCache) => {
neighbors.forEach((neighbor, neighborIndex) => {
if (!(neighbor.spa_id in draft[region][season])) {
draft[region][season][neighbor.spa_id] = neighbor.score;
} }
}); });
}), }),
); );
useNameCache.setState( useNameCache.setState(
produce((draft: NameCache) => { produce((draft: NameCache) => {
neighbors.forEach((neighbor) => { neighbors.forEach((neighbor) => {
@@ -325,8 +330,8 @@ export default function Page() {
); );
if (season === 0) { if (season === 0) {
const playerEntries = Object.entries(players[region][season]); const playerEntries = Object.entries(leaderboard[region][season]);
let closestPlayer: BlitzkriegRatingsLeaderboardEntry | null = null; let closestPlayerId: number | null = null;
let closestPosition: number | null = null; let closestPosition: number | null = null;
let closestPositionDistance = Infinity; let closestPositionDistance = Infinity;
@@ -337,11 +342,11 @@ export default function Page() {
if (distance < closestPositionDistance) { if (distance < closestPositionDistance) {
closestPositionDistance = distance; closestPositionDistance = distance;
closestPosition = position; closestPosition = position;
closestPlayer = player; closestPlayerId = player;
} }
}); });
if (closestPlayer === null || closestPosition === null) return; if (closestPlayerId === null || closestPosition === null) return;
if (closestPositionDistance === 0) { if (closestPositionDistance === 0) {
setHighlightedPlayer({ type: 'position', position: targetPosition }); setHighlightedPlayer({ type: 'position', position: targetPosition });
@@ -352,8 +357,7 @@ export default function Page() {
// 1 going down and -1 going up // 1 going down and -1 going up
const loadingDirection = Math.sign(targetPosition - closestPosition); const loadingDirection = Math.sign(targetPosition - closestPosition);
let seedingPlayer = (closestPlayer as BlitzkriegRatingsLeaderboardEntry) let seedingPlayer = closestPlayerId as number;
.id;
let targetPositionAcquired = false; let targetPositionAcquired = false;
let neighbors: RatingsPlayer[]; let neighbors: RatingsPlayer[];
@@ -385,7 +389,7 @@ export default function Page() {
} else { } else {
setPage(Math.floor(targetPosition / ROWS_PER_PAGE)); setPage(Math.floor(targetPosition / ROWS_PER_PAGE));
if (players) { if (leaderboard) {
setHighlightedPlayer({ setHighlightedPlayer({
type: 'position', type: 'position',
position: targetPosition, position: targetPosition,
@@ -399,25 +403,25 @@ export default function Page() {
const targetScore = scoreInput.current!.valueAsNumber; const targetScore = scoreInput.current!.valueAsNumber;
if (season === 0) { if (season === 0) {
const playerEntries = Object.entries(players[region][season]); const playerEntries = Object.entries(leaderboard[region][season]);
let closestPlayer: BlitzkriegRatingsLeaderboardEntry | null = null; let closestId: number | null = null;
let closestScore: number | null = null; let closestScore: number | null = null;
let closestScoreDistance = Infinity; let closestScoreDistance = Infinity;
let closesIndex = -1; let closesIndex = -1;
playerEntries.forEach(([positionAsString, player], index) => { playerEntries.forEach(([positionAsString, id]) => {
const position = parseInt(positionAsString); const position = parseInt(positionAsString);
const distance = Math.abs(player.score - targetScore); const distance = Math.abs(scores[id] - targetScore);
if (distance < closestScoreDistance) { if (distance < closestScoreDistance) {
closestScoreDistance = distance; closestScoreDistance = distance;
closestScore = player.score; closestScore = scores[id];
closestPlayer = player; closestId = id;
closesIndex = position; closesIndex = position;
} }
}); });
if (closestPlayer === null || closestScore === null) return; if (closestId === null || closestScore === null) return;
if (closestScoreDistance === 0) { if (closestScoreDistance === 0) {
setHighlightedPlayer({ type: 'id', id: closesIndex }); setHighlightedPlayer({ type: 'id', id: closesIndex });
@@ -427,16 +431,15 @@ export default function Page() {
const loadingDirection = Math.sign(closestScore - targetScore); const loadingDirection = Math.sign(closestScore - targetScore);
let targetPlayer: RatingsPlayer; let targetPlayer: RatingsPlayer;
let scoreAcquired = false; let scoreAcquired = false;
let seedingPlayer = closestPlayer as BlitzkriegRatingsLeaderboardEntry; let seedingId = closestId as number;
let seedingScore = scores[seedingId];
setLoadingProgress([0, closestScoreDistance]); setLoadingProgress([0, closestScoreDistance]);
while (!scoreAcquired) { while (!scoreAcquired) {
const neighbors = await cacheNeighbors(seedingPlayer.id, SEEDING_SIZE); const neighbors = await cacheNeighbors(seedingId, SEEDING_SIZE);
const seedingNeighbor = neighbors.at(loadingDirection === 1 ? -1 : 0)!; const seedingNeighbor = neighbors.at(loadingDirection === 1 ? -1 : 0)!;
seedingPlayer = { seedingId = seedingNeighbor.spa_id;
id: seedingNeighbor.spa_id, seedingScore = scores[seedingId];
score: seedingNeighbor.score,
};
setLoadingProgress([ setLoadingProgress([
closestScoreDistance - Math.abs(seedingNeighbor.score - targetScore), closestScoreDistance - Math.abs(seedingNeighbor.score - targetScore),
@@ -444,9 +447,9 @@ export default function Page() {
]); ]);
if (loadingDirection === 1) { if (loadingDirection === 1) {
scoreAcquired = seedingPlayer.score <= targetScore; scoreAcquired = seedingScore <= targetScore;
} else { } else {
scoreAcquired = seedingPlayer.score >= targetScore; scoreAcquired = seedingScore >= targetScore;
} }
if (scoreAcquired) { if (scoreAcquired) {
@@ -469,9 +472,9 @@ export default function Page() {
let playerIndex = -1; let playerIndex = -1;
for (let index = ratingsInfo.count; index >= 0; index--) { for (let index = ratingsInfo.count; index >= 0; index--) {
if (!(index in players[region][season])) continue; if (!(index in leaderboard[region][season])) continue;
if (players[region][season][index].score >= targetScore) { if (scores[leaderboard[region][season][index]] >= targetScore) {
playerIndex = index; playerIndex = index;
break; break;
} }
@@ -481,7 +484,7 @@ export default function Page() {
setPage(Math.floor(playerIndex / ROWS_PER_PAGE)); setPage(Math.floor(playerIndex / ROWS_PER_PAGE));
if (players) { if (leaderboard) {
setHighlightedPlayer({ setHighlightedPlayer({
type: 'position', type: 'position',
position: playerIndex, position: playerIndex,
@@ -649,18 +652,22 @@ export default function Page() {
const newPage = Math.floor(position / ROWS_PER_PAGE); const newPage = Math.floor(position / ROWS_PER_PAGE);
setPage(newPage); setPage(newPage);
if (players) { if (leaderboard) {
setHighlightedPlayer({ setHighlightedPlayer({
type: 'id', type: 'id',
id: players[region][season][position].id, id: leaderboard[region][season][position],
}); });
cacheNeighbors( cacheNeighbors(
players[region][season][position].id, leaderboard[region][season][position],
4 * ROWS_PER_PAGE - 2, 4 * ROWS_PER_PAGE - 2,
); );
} }
} else { } else {
if (!ratingsInfo || ratingsInfo.detail || !players) if (
!ratingsInfo ||
ratingsInfo.detail ||
!leaderboard
)
return; return;
const minScore = const minScore =
@@ -673,7 +680,8 @@ export default function Page() {
index++ index++
) { ) {
if ( if (
players[region][season][index].score < minScore scores[leaderboard[region][season][index]] <
minScore
) { ) {
firstPlayerIndex = index; firstPlayerIndex = index;
break; break;
@@ -685,7 +693,7 @@ export default function Page() {
setPage(Math.floor(firstPlayerIndex / ROWS_PER_PAGE)); setPage(Math.floor(firstPlayerIndex / ROWS_PER_PAGE));
setHighlightedPlayer({ setHighlightedPlayer({
type: 'id', type: 'id',
id: players[region][season][firstPlayerIndex].id, id: leaderboard[region][season][firstPlayerIndex],
}); });
} }
}} }}
@@ -732,18 +740,15 @@ export default function Page() {
skip: false; skip: false;
})[]; })[];
usePlayersCache.setState( useLeaderboardCache.setState(
produce((draft: PlayersCache) => { produce((draft: LeaderboardCache) => {
if (draft) { if (draft) {
values.map((player) => { values.forEach((player) => {
const playerWithTypes = const playerWithTypes =
player as typeof player & { skip: false }; player as typeof player & { skip: false };
draft[region][season][ draft[region][season][
playerWithTypes.number - 1 playerWithTypes.number - 1
] = { ] = playerWithTypes.spa_id;
id: playerWithTypes.spa_id,
score: 0,
};
}); });
} }
}), }),
@@ -774,7 +779,7 @@ export default function Page() {
`https://api.wotblitz.${region}/wotb/account/list/?application_id=${WARGAMING_APPLICATION_ID}&search=${encodedSearch}`, `https://api.wotblitz.${region}/wotb/account/list/?application_id=${WARGAMING_APPLICATION_ID}&search=${encodedSearch}`,
); );
const playerKeys = Object.keys( const playerKeys = Object.keys(
players[region][season], leaderboard[region][season],
); );
setSearchResults( setSearchResults(
@@ -784,9 +789,9 @@ export default function Page() {
number: parseInt( number: parseInt(
playerKeys.find( playerKeys.find(
(indexAsString) => (indexAsString) =>
players[region][season][ leaderboard[region][season][
parseInt(indexAsString) parseInt(indexAsString)
].id === searchedPlayer.account_id, ] === searchedPlayer.account_id,
)!, )!,
), ),
})) }))
@@ -837,7 +842,7 @@ export default function Page() {
index++ index++
) { ) {
if ( if (
players[region][season][index].id === leaderboard[region][season][index] ===
player.account_id player.account_id
) { ) {
playerIndex = index; playerIndex = index;
@@ -923,7 +928,7 @@ export default function Page() {
ratingsInfo?.detail ? 0 : (ratingsInfo?.count ?? 1) - 1, ratingsInfo?.detail ? 0 : (ratingsInfo?.count ?? 1) - 1,
), ),
).map((index) => { ).map((index) => {
const id = players[region][season]?.[index]?.id; const id = leaderboard[region][season]?.[index];
const clan = id ? names[region][id]?.clan : undefined; const clan = id ? names[region][id]?.clan : undefined;
const position = index + 1; const position = index + 1;
const highlight = highlightedPlayer const highlight = highlightedPlayer
@@ -997,7 +1002,9 @@ export default function Page() {
% %
</Table.Cell> </Table.Cell>
<Table.Cell align="right"> <Table.Cell align="right">
{players[region][season]?.[index]?.score.toLocaleString()} {(leaderboard[region][season]?.[index]
? scores[leaderboard[region][season][index]]?.toLocaleString()
: undefined) ?? '--'}
</Table.Cell> </Table.Cell>
</Table.Row> </Table.Row>
); );