Matchups: implemented matchups
All checks were successful
pipeline / lint-and-format (push) Successful in 4m36s
pipeline / build-and-push-images (push) Successful in 1m50s

This commit is contained in:
2026-01-25 00:22:40 +01:00
parent 7a34c16087
commit 8f8fc0f1af
9 changed files with 761 additions and 71 deletions

View File

@@ -54,6 +54,14 @@ type Champion = {
name: string
alias: string
}
type MatchupData = {
championId: number
winrate: number
games: number
championName: string
championAlias: string
}
type LaneData = {
data: string
count: number
@@ -63,6 +71,7 @@ type LaneData = {
pickrate: number
runes: Array<Rune>
builds: Builds
matchups?: Array<MatchupData>
}
type ChampionData = {
champion: Champion
@@ -231,11 +240,17 @@ function handleMatch(match: any, champions: Map<number, ChampionData>) {
winningMatches: 0,
losingMatches: 0,
winrate: 0,
pickrate: 0
pickrate: 0,
matchups: []
}
champion.lanes.push(lane)
} else lane.count += 1
// Initialize matchups if not present
if (!lane.matchups) {
lane.matchups = []
}
// Winrate
if (participant.win) {
champion.winningMatches++
@@ -245,6 +260,38 @@ function handleMatch(match: any, champions: Map<number, ChampionData>) {
lane.losingMatches++
}
// Track counter matchups - find opponent in same lane
const opponentTeam = participant.teamId === 100 ? 200 : 100
const opponent = match.info.participants.find(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(p: any) => p.teamId === opponentTeam && p.teamPosition === participant.teamPosition
)
if (opponent) {
const opponentChampionId = opponent.championId
// Track this matchup for current champion
const matchup = lane.matchups.find(c => c.championId === opponentChampionId)
if (matchup) {
matchup.games += 1
if (participant.win) {
matchup.winrate = (matchup.winrate * (matchup.games - 1) + 1) / matchup.games
} else {
matchup.winrate = (matchup.winrate * (matchup.games - 1)) / matchup.games
}
} else {
const opponentChampion = champions.get(opponentChampionId)
lane.matchups.push({
championId: opponentChampionId,
winrate: participant.win ? 1 : 0,
games: 1,
championName: opponentChampion.champion.name,
championAlias: opponentChampion.champion.alias
})
}
}
// Runes
handleParticipantRunes(participant, lane.runes)
@@ -326,6 +373,38 @@ async function finalizeChampionStats(champion: ChampionData, totalMatches: numbe
lane.pickrate = lane.count / totalMatches
}
// Sort matchups by score (games * winrate) in descending order
for (const lane of champion.lanes) {
if (lane.matchups && lane.matchups.length > 0) {
// Filter out matchups with insufficient games (minimum 5 games)
const filteredMatchups = lane.matchups.filter(m => m.games >= 5)
// Sort by score (games * (winrate - 0.5)^2) descending
filteredMatchups.sort((a, b) => {
// Handle special case of exactly 50% winrate
if (a.winrate === 0.5 && b.winrate === 0.5) {
// Both have 50% winrate, sort by games (more games first)
return b.games - a.games
}
if (a.winrate === 0.5 || b.winrate === 0.5) {
// a has 50% winrate, b doesn't - b comes first
return b.winrate - a.winrate
}
if (a.winrate > 0.5 && b.winrate < 0.5) return -1
if (a.winrate < 0.5 && b.winrate > 0.5) return 1
if (a.winrate > 0.5) {
return b.games * (b.winrate - 0.5) ** 2 - a.games * (a.winrate - 0.5) ** 2
} else {
return -1 * b.games * (0.5 - b.winrate) ** 2 - -1 * a.games * (0.5 - a.winrate) ** 2
}
})
// Limit to top matchups (or keep all if we want comprehensive data)
lane.matchups = filteredMatchups
}
}
return {
name: champion.champion.name,
alias: champion.champion.alias.toLowerCase(),