Compare commits
3 Commits
19a9226dac
...
dae65c8fa2
| Author | SHA1 | Date | |
|---|---|---|---|
|
dae65c8fa2
|
|||
|
0f84b9a707
|
|||
|
b7435f0884
|
@@ -32,11 +32,28 @@ async function setupDatabase() {
|
|||||||
console.log(`🎯 Latest patch version: ${latestPatch}`);
|
console.log(`🎯 Latest patch version: ${latestPatch}`);
|
||||||
|
|
||||||
// Check if data directory exists and has files
|
// Check if data directory exists and has files
|
||||||
|
// Support both old format (patch_matches.json) and new platform-specific format (patch_PLATFORM_matches.json)
|
||||||
console.log('🔍 Checking for data files...');
|
console.log('🔍 Checking for data files...');
|
||||||
|
const platforms = ['EUW1', 'EUN1', 'NA1', 'KR'];
|
||||||
const dataFiles = [
|
const dataFiles = [
|
||||||
{ path: 'patches.json', required: true, description: 'Patches data' },
|
{ path: 'patches.json', required: true, description: 'Patches data' }
|
||||||
{ path: `${latestPatch}_matches.json`, required: true, description: 'Match data' }
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Check for platform-specific match files
|
||||||
|
let foundPlatformFiles = [];
|
||||||
|
for (const platform of platforms) {
|
||||||
|
const platformFile = `${latestPatch}_${platform}_matches.json`;
|
||||||
|
const fullPath = path.join(dataDir, platformFile);
|
||||||
|
if (fs.existsSync(fullPath)) {
|
||||||
|
foundPlatformFiles.push(platform);
|
||||||
|
dataFiles.push({ path: platformFile, required: false, description: `Match data for ${platform}` });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no platform-specific files found, look for old format
|
||||||
|
if (foundPlatformFiles.length === 0) {
|
||||||
|
dataFiles.push({ path: `${latestPatch}_matches.json`, required: true, description: 'Match data' });
|
||||||
|
}
|
||||||
|
|
||||||
let filesExist = true;
|
let filesExist = true;
|
||||||
for (const file of dataFiles) {
|
for (const file of dataFiles) {
|
||||||
@@ -80,14 +97,36 @@ async function setupDatabase() {
|
|||||||
|
|
||||||
// 6. Check existing matches count and import if needed
|
// 6. Check existing matches count and import if needed
|
||||||
console.log('Checking existing matches count...');
|
console.log('Checking existing matches count...');
|
||||||
const matchCount = await getMatchCount(latestPatch);
|
|
||||||
console.log(`📊 Current matches in database: ${matchCount}`);
|
|
||||||
|
|
||||||
if (matchCount < 100) {
|
// Check for platform-specific collections or fall back to old format
|
||||||
console.log('📥 Importing matches (this may take a while)...');
|
const existingPlatforms = await getExistingPlatforms(latestPatch);
|
||||||
await importMatchesData(latestPatch);
|
|
||||||
|
if (existingPlatforms.length > 0) {
|
||||||
|
console.log(`📊 Found platform-specific collections: ${existingPlatforms.join(', ')}`);
|
||||||
|
let totalMatches = 0;
|
||||||
|
for (const platform of existingPlatforms) {
|
||||||
|
const count = await getMatchCount(latestPatch, platform);
|
||||||
|
console.log(` ${platform}: ${count} matches`);
|
||||||
|
totalMatches += count;
|
||||||
|
}
|
||||||
|
console.log(`📊 Total matches in database: ${totalMatches}`);
|
||||||
|
|
||||||
|
if (totalMatches < 100) {
|
||||||
|
console.log('📥 Importing matches (this may take a while)...');
|
||||||
|
await importMatchesData(latestPatch, foundPlatformFiles);
|
||||||
|
} else {
|
||||||
|
console.log('✅ Skipping matches import - sufficient data already present');
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log('✅ Skipping matches import - sufficient data already present');
|
const matchCount = await getMatchCount(latestPatch);
|
||||||
|
console.log(`📊 Current matches in database: ${matchCount}`);
|
||||||
|
|
||||||
|
if (matchCount < 100) {
|
||||||
|
console.log('📥 Importing matches (this may take a while)...');
|
||||||
|
await importMatchesData(latestPatch, foundPlatformFiles);
|
||||||
|
} else {
|
||||||
|
console.log('✅ Skipping matches import - sufficient data already present');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 7. Fetch CDragon data for the current patch
|
// 7. Fetch CDragon data for the current patch
|
||||||
@@ -277,19 +316,42 @@ async function importPatchesData() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function importMatchesData(patchVersion) {
|
async function importMatchesData(patchVersion, foundPlatformFiles = []) {
|
||||||
const matchesFile = path.join(__dirname, '../data', `${patchVersion}_matches.json`);
|
const dataDir = path.join(__dirname, '../data');
|
||||||
const collectionName = patchVersion;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = execSync(
|
// If platform-specific files were found, import each one
|
||||||
`node ${path.join(__dirname, 'process-matches.js')} ${matchesFile} ${collectionName} 1000`,
|
if (foundPlatformFiles.length > 0) {
|
||||||
{
|
for (const platform of foundPlatformFiles) {
|
||||||
stdio: 'inherit',
|
const matchesFile = path.join(dataDir, `${patchVersion}_${platform}_matches.json`);
|
||||||
env: { ...process.env, MONGO_URI: getMongoUri() }
|
const collectionName = `${patchVersion}_${platform}`;
|
||||||
|
|
||||||
|
if (fs.existsSync(matchesFile)) {
|
||||||
|
console.log(`📥 Importing matches for ${platform}...`);
|
||||||
|
execSync(
|
||||||
|
`node ${path.join(__dirname, 'process-matches.js')} ${matchesFile} ${collectionName} 1000`,
|
||||||
|
{
|
||||||
|
stdio: 'inherit',
|
||||||
|
env: { ...process.env, MONGO_URI: getMongoUri() }
|
||||||
|
}
|
||||||
|
);
|
||||||
|
console.log(`✅ Matches import completed for ${platform}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
);
|
} else {
|
||||||
console.log('✅ Matches import completed');
|
// Fall back to old format (single file without platform suffix)
|
||||||
|
const matchesFile = path.join(dataDir, `${patchVersion}_matches.json`);
|
||||||
|
const collectionName = patchVersion;
|
||||||
|
|
||||||
|
execSync(
|
||||||
|
`node ${path.join(__dirname, 'process-matches.js')} ${matchesFile} ${collectionName} 1000`,
|
||||||
|
{
|
||||||
|
stdio: 'inherit',
|
||||||
|
env: { ...process.env, MONGO_URI: getMongoUri() }
|
||||||
|
}
|
||||||
|
);
|
||||||
|
console.log('✅ Matches import completed');
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Failed to import matches:', error);
|
console.error('❌ Failed to import matches:', error);
|
||||||
throw error;
|
throw error;
|
||||||
@@ -344,13 +406,14 @@ async function fetchCDragonData() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getMatchCount(patchVersion) {
|
async function getMatchCount(patchVersion, platform = null) {
|
||||||
const client = new MongoClient(getMongoUri());
|
const client = new MongoClient(getMongoUri());
|
||||||
await client.connect();
|
await client.connect();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const db = client.db('matches');
|
const db = client.db('matches');
|
||||||
const collection = db.collection(patchVersion);
|
const collectionName = platform ? `${patchVersion}_${platform}` : patchVersion;
|
||||||
|
const collection = db.collection(collectionName);
|
||||||
const count = await collection.countDocuments();
|
const count = await collection.countDocuments();
|
||||||
return count;
|
return count;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -361,6 +424,33 @@ async function getMatchCount(patchVersion) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getExistingPlatforms(patchVersion) {
|
||||||
|
const client = new MongoClient(getMongoUri());
|
||||||
|
await client.connect();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const db = client.db('matches');
|
||||||
|
const collections = await db.listCollections().toArray();
|
||||||
|
const collectionNames = collections.map(c => c.name);
|
||||||
|
|
||||||
|
const platforms = ['EUW1', 'EUN1', 'NA1', 'KR'];
|
||||||
|
const existingPlatforms = [];
|
||||||
|
|
||||||
|
for (const platform of platforms) {
|
||||||
|
if (collectionNames.includes(`${patchVersion}_${platform}`)) {
|
||||||
|
existingPlatforms.push(platform);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return existingPlatforms;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Failed to get existing platforms:', error);
|
||||||
|
return [];
|
||||||
|
} finally {
|
||||||
|
await client.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function getMongoUri() {
|
function getMongoUri() {
|
||||||
return process.env.MONGO_URI || 'mongodb://root:password@localhost:27017/buildpath?authSource=admin';
|
return process.env.MONGO_URI || 'mongodb://root:password@localhost:27017/buildpath?authSource=admin';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -137,7 +137,10 @@ if (route.path.startsWith('/tierlist/')) {
|
|||||||
<h3 style="font-size: 18px; font-weight: 200; opacity: 0.5">Loading stats...</h3>
|
<h3 style="font-size: 18px; font-weight: 200; opacity: 0.5">Loading stats...</h3>
|
||||||
</template>
|
</template>
|
||||||
<h3 style="font-size: 18px; font-weight: 200; margin-top: 10px; margin-bottom: 10px">
|
<h3 style="font-size: 18px; font-weight: 200; margin-top: 10px; margin-bottom: 10px">
|
||||||
EUW Challenger only
|
Challenger only
|
||||||
|
</h3>
|
||||||
|
<h3 style="font-size: 18px; font-weight: 200; margin-top: 10px; margin-bottom: 10px">
|
||||||
|
EUW/EUNE/NA/KR
|
||||||
</h3>
|
</h3>
|
||||||
<NuxtLink to="/about"><h3>About</h3></NuxtLink>
|
<NuxtLink to="/about"><h3>About</h3></NuxtLink>
|
||||||
<h2 style="font-size: 10px; font-weight: 200; margin-top: 3px; margin-right: 10px">
|
<h2 style="font-size: 10px; font-weight: 200; margin-top: 3px; margin-right: 10px">
|
||||||
|
|||||||
@@ -1,19 +1,43 @@
|
|||||||
import type { MongoClient } from 'mongodb'
|
import type { MongoClient } from 'mongodb'
|
||||||
import { connectToDatabase, fetchLatestPatch } from '../utils/mongo'
|
import { connectToDatabase, fetchLatestPatch, getAvailablePlatforms } from '../utils/mongo'
|
||||||
|
|
||||||
async function fetchGameCount(client: MongoClient, patch: string) {
|
async function fetchGameCount(client: MongoClient, patch: string) {
|
||||||
const database = client.db('matches')
|
const database = client.db('matches')
|
||||||
|
|
||||||
|
// Check for platform-specific collections
|
||||||
|
const platforms = await getAvailablePlatforms(client, patch)
|
||||||
|
|
||||||
|
if (platforms.length > 0) {
|
||||||
|
// Sum counts from all platform-specific collections
|
||||||
|
let totalCount = 0
|
||||||
|
const platformCounts: Record<string, number> = {}
|
||||||
|
|
||||||
|
for (const platform of platforms) {
|
||||||
|
const collection = database.collection(`${patch}_${platform}`)
|
||||||
|
const count = await collection.countDocuments()
|
||||||
|
platformCounts[platform] = count
|
||||||
|
totalCount += count
|
||||||
|
}
|
||||||
|
|
||||||
|
return { total: totalCount, platforms: platformCounts }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to old format (single collection)
|
||||||
const matches = database.collection(patch)
|
const matches = database.collection(patch)
|
||||||
const count = await matches.countDocuments()
|
const count = await matches.countDocuments()
|
||||||
return count
|
return { total: count, platforms: {} }
|
||||||
}
|
}
|
||||||
|
|
||||||
export default defineEventHandler(async _ => {
|
export default defineEventHandler(async _ => {
|
||||||
const client = await connectToDatabase()
|
const client = await connectToDatabase()
|
||||||
const latestPatch = await fetchLatestPatch(client)
|
const latestPatch = await fetchLatestPatch(client)
|
||||||
const gameCount = await fetchGameCount(client, latestPatch)
|
const gameCountData = await fetchGameCount(client, latestPatch)
|
||||||
|
|
||||||
await client.close()
|
await client.close()
|
||||||
|
|
||||||
return { patch: latestPatch, count: gameCount }
|
return {
|
||||||
|
patch: latestPatch,
|
||||||
|
count: gameCountData.total,
|
||||||
|
platformCounts: gameCountData.platforms
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import { MongoClient } from 'mongodb'
|
import { MongoClient } from 'mongodb'
|
||||||
|
|
||||||
|
// Available platforms for region-specific match data
|
||||||
|
const PLATFORMS = ['EUW1', 'EUN1', 'NA1', 'KR'] as const
|
||||||
|
type Platform = (typeof PLATFORMS)[number]
|
||||||
|
|
||||||
async function connectToDatabase() {
|
async function connectToDatabase() {
|
||||||
// Create a MongoClient with a MongoClientOptions object to set the Stable API version
|
// Create a MongoClient with a MongoClientOptions object to set the Stable API version
|
||||||
let uri = `mongodb://${process.env.MONGO_USER}:${process.env.MONGO_PASS}@${process.env.MONGO_HOST}`
|
let uri = `mongodb://${process.env.MONGO_USER}:${process.env.MONGO_PASS}@${process.env.MONGO_HOST}`
|
||||||
@@ -22,4 +26,25 @@ async function fetchLatestPatch(client: MongoClient) {
|
|||||||
return latestPatch!.patch as string
|
return latestPatch!.patch as string
|
||||||
}
|
}
|
||||||
|
|
||||||
export { connectToDatabase, fetchLatestPatch }
|
/**
|
||||||
|
* Get available platforms for a given patch by checking which match collections exist
|
||||||
|
* Note: Match collections are platform-specific (e.g., "15.1_EUW1")
|
||||||
|
* Champion collections are aggregated across all platforms (e.g., "15.1")
|
||||||
|
*/
|
||||||
|
async function getAvailablePlatforms(client: MongoClient, patch: string): Promise<Platform[]> {
|
||||||
|
const matchesDb = client.db('matches')
|
||||||
|
const collections = await matchesDb.listCollections().toArray()
|
||||||
|
const collectionNames = collections.map(c => c.name)
|
||||||
|
|
||||||
|
const availablePlatforms: Platform[] = []
|
||||||
|
for (const platform of PLATFORMS) {
|
||||||
|
if (collectionNames.includes(`${patch}_${platform}`)) {
|
||||||
|
availablePlatforms.push(platform)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return availablePlatforms
|
||||||
|
}
|
||||||
|
|
||||||
|
export { connectToDatabase, fetchLatestPatch, getAvailablePlatforms, PLATFORMS }
|
||||||
|
export type { Platform }
|
||||||
|
|||||||
125
match_collector/api.ts
Normal file
125
match_collector/api.ts
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
type Match = {
|
||||||
|
metadata: {
|
||||||
|
dataVersion: string
|
||||||
|
matchId: string
|
||||||
|
participants: string[]
|
||||||
|
}
|
||||||
|
info: {
|
||||||
|
endOfGameResult: string
|
||||||
|
frameInterval: number
|
||||||
|
gameId: number
|
||||||
|
participants: Participant[]
|
||||||
|
teams: Team[]
|
||||||
|
}
|
||||||
|
timeline: Timeline
|
||||||
|
}
|
||||||
|
|
||||||
|
type Timeline = {
|
||||||
|
metadata: {
|
||||||
|
dataVersion: string
|
||||||
|
matchId: string
|
||||||
|
participants: string[]
|
||||||
|
}
|
||||||
|
info: {
|
||||||
|
endOfGameResult: string
|
||||||
|
frameInterval: number
|
||||||
|
gameId: number
|
||||||
|
participants: {
|
||||||
|
participantId: number
|
||||||
|
puuid: string
|
||||||
|
}[]
|
||||||
|
frames: Frame[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Team = {
|
||||||
|
bans: Ban[]
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
objectives: any
|
||||||
|
teamId: number
|
||||||
|
win: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
type Ban = any
|
||||||
|
|
||||||
|
type Participant = {
|
||||||
|
allInPing: number
|
||||||
|
assistMePings: number
|
||||||
|
assists: number
|
||||||
|
baronKills: number
|
||||||
|
bountyLevel: number
|
||||||
|
champExperience: number
|
||||||
|
champLevel: number
|
||||||
|
championId: number
|
||||||
|
championName: string
|
||||||
|
commandPings: number
|
||||||
|
championTransform: number
|
||||||
|
consumablesPurchased: number
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
challenges: any
|
||||||
|
damageDealtToBuildings: number
|
||||||
|
deaths: number
|
||||||
|
item0: number
|
||||||
|
item1: number
|
||||||
|
item2: number
|
||||||
|
item3: number
|
||||||
|
item4: number
|
||||||
|
item5: number
|
||||||
|
item6: number
|
||||||
|
itemsPurchased: number
|
||||||
|
kills: number
|
||||||
|
lane: string
|
||||||
|
participantId: number
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
perks: any
|
||||||
|
puuid: string
|
||||||
|
summoner1Id: number
|
||||||
|
summoner2Id: number
|
||||||
|
summonerId: string
|
||||||
|
teamId: number
|
||||||
|
teamPosition: string
|
||||||
|
win: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
type Frame = {
|
||||||
|
events: Event[]
|
||||||
|
participantFrames: {
|
||||||
|
'1': ParticipantFrame
|
||||||
|
'2': ParticipantFrame
|
||||||
|
'3': ParticipantFrame
|
||||||
|
'4': ParticipantFrame
|
||||||
|
'5': ParticipantFrame
|
||||||
|
'6': ParticipantFrame
|
||||||
|
'7': ParticipantFrame
|
||||||
|
'8': ParticipantFrame
|
||||||
|
'9': ParticipantFrame
|
||||||
|
'10': ParticipantFrame
|
||||||
|
}
|
||||||
|
timestamp: number
|
||||||
|
}
|
||||||
|
|
||||||
|
type ParticipantFrame = {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
championStats: any
|
||||||
|
currentGold: number
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
damageStats: any
|
||||||
|
goldPerSecond: number
|
||||||
|
jungleMinionsKilled: number
|
||||||
|
level: number
|
||||||
|
minionsKilled: number
|
||||||
|
participantId: number
|
||||||
|
position: {
|
||||||
|
x: number
|
||||||
|
y: number
|
||||||
|
}
|
||||||
|
timeEnemySpentControlled: number
|
||||||
|
totalGold: number
|
||||||
|
xp: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
type Event = any
|
||||||
|
|
||||||
|
export { Match, Timeline, Team, Ban, Participant, Frame, Event }
|
||||||
@@ -1,14 +1,7 @@
|
|||||||
function sameArrays(array1: Array<number>, array2: Array<number>) {
|
|
||||||
if (array1.length != array2.length) return false
|
|
||||||
for (const e of array1) {
|
|
||||||
if (!array2.includes(e)) return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
import { MongoClient } from 'mongodb'
|
import { MongoClient } from 'mongodb'
|
||||||
import {
|
import {
|
||||||
ItemTree,
|
ItemTree,
|
||||||
|
GoldAdvantageTag,
|
||||||
treeInit,
|
treeInit,
|
||||||
treeMerge,
|
treeMerge,
|
||||||
treeCutBranches,
|
treeCutBranches,
|
||||||
@@ -16,6 +9,16 @@ import {
|
|||||||
treeMergeTree,
|
treeMergeTree,
|
||||||
areTreeSimilars
|
areTreeSimilars
|
||||||
} from './item_tree'
|
} from './item_tree'
|
||||||
|
|
||||||
|
import { Match, Timeline, Participant, Frame } from './api'
|
||||||
|
|
||||||
|
function sameArrays(array1: Array<number>, array2: Array<number>) {
|
||||||
|
if (array1.length != array2.length) return false
|
||||||
|
for (const e of array1) {
|
||||||
|
if (!array2.includes(e)) return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
const itemDict = new Map()
|
const itemDict = new Map()
|
||||||
|
|
||||||
async function itemList() {
|
async function itemList() {
|
||||||
@@ -107,8 +110,7 @@ type ChampionData = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to create rune configuration from participant
|
// Helper function to create rune configuration from participant
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
function createRuneConfiguration(participant: Participant): Rune {
|
||||||
function createRuneConfiguration(participant: any): Rune {
|
|
||||||
const primaryStyle = participant.perks.styles[0].style
|
const primaryStyle = participant.perks.styles[0].style
|
||||||
const secondaryStyle = participant.perks.styles[1].style
|
const secondaryStyle = participant.perks.styles[1].style
|
||||||
const selections: Array<number> = []
|
const selections: Array<number> = []
|
||||||
@@ -126,8 +128,7 @@ function createRuneConfiguration(participant: any): Rune {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Find or create a build for the given rune keystone
|
// Find or create a build for the given rune keystone
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
function findOrCreateBuild(builds: Builds, participant: Participant): Build {
|
||||||
function findOrCreateBuild(builds: Builds, participant: any): Build {
|
|
||||||
const keystone = participant.perks.styles[0].selections[0].perk
|
const keystone = participant.perks.styles[0].selections[0].perk
|
||||||
const runeConfig = createRuneConfiguration(participant)
|
const runeConfig = createRuneConfiguration(participant)
|
||||||
|
|
||||||
@@ -166,24 +167,72 @@ function findOrCreateBuild(builds: Builds, participant: any): Build {
|
|||||||
return newBuild
|
return newBuild
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Calculate gold advantage at the time of item purchase
|
||||||
|
// Returns 'ahead', 'behind', or 'even' based on gold difference
|
||||||
|
function calculateGoldAdvantage(
|
||||||
|
match: Match,
|
||||||
|
frame: Frame,
|
||||||
|
participantIndex: number
|
||||||
|
): GoldAdvantageTag {
|
||||||
|
const GOLD_THRESHOLD = 1000 // 1000 gold difference threshold
|
||||||
|
|
||||||
|
const participantFrames = [
|
||||||
|
frame.participantFrames[1],
|
||||||
|
frame.participantFrames[2],
|
||||||
|
frame.participantFrames[3],
|
||||||
|
frame.participantFrames[4],
|
||||||
|
frame.participantFrames[5],
|
||||||
|
frame.participantFrames[6],
|
||||||
|
frame.participantFrames[7],
|
||||||
|
frame.participantFrames[8],
|
||||||
|
frame.participantFrames[9],
|
||||||
|
frame.participantFrames[10]
|
||||||
|
]
|
||||||
|
|
||||||
|
// Find the participant's team
|
||||||
|
const participantFrame = participantFrames[participantIndex - 1]
|
||||||
|
const participantGold = participantFrame.totalGold
|
||||||
|
if (!participantFrame) return 'even'
|
||||||
|
|
||||||
|
const participant = match.info.participants.find(
|
||||||
|
x => x.participantId == participantFrame.participantId
|
||||||
|
)!
|
||||||
|
|
||||||
|
const opponent = match.info.participants.find(
|
||||||
|
x => x.teamPosition === participant.teamPosition && x.teamId != participant.teamId
|
||||||
|
)
|
||||||
|
if (opponent == undefined) return 'even'
|
||||||
|
|
||||||
|
const opponentGold = participantFrames.find(
|
||||||
|
x => x.participantId == opponent.participantId
|
||||||
|
)!.totalGold
|
||||||
|
|
||||||
|
const goldDiff = participantGold - opponentGold
|
||||||
|
|
||||||
|
if (goldDiff >= GOLD_THRESHOLD) return 'ahead'
|
||||||
|
if (goldDiff <= -GOLD_THRESHOLD) return 'behind'
|
||||||
|
return 'even'
|
||||||
|
}
|
||||||
|
|
||||||
function handleMatchBuilds(
|
function handleMatchBuilds(
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
match: Match,
|
||||||
timeline: any,
|
participant: Participant,
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
participant: any,
|
|
||||||
participantIndex: number,
|
participantIndex: number,
|
||||||
builds: Builds
|
builds: Builds,
|
||||||
|
platform?: string
|
||||||
) {
|
) {
|
||||||
|
const timeline: Timeline = match.timeline
|
||||||
|
|
||||||
// Find or create the build for this participant's rune configuration
|
// Find or create the build for this participant's rune configuration
|
||||||
const build = findOrCreateBuild(builds, participant)
|
const build = findOrCreateBuild(builds, participant)
|
||||||
build.count += 1
|
build.count += 1
|
||||||
|
|
||||||
const items: Array<number> = []
|
const items: Array<{ itemId: number; goldAdvantage: GoldAdvantageTag; platform?: string }> = []
|
||||||
for (const frame of timeline.info.frames) {
|
for (const frame of timeline.info.frames) {
|
||||||
for (const event of frame.events) {
|
for (const event of frame.events) {
|
||||||
if (event.participantId != participantIndex) continue
|
if (event.participantId != participantIndex) continue
|
||||||
if (event.type == 'ITEM_UNDO') {
|
if (event.type == 'ITEM_UNDO') {
|
||||||
if (items.length > 0 && items[items.length - 1] == event.beforeId) {
|
if (items.length > 0 && items[items.length - 1].itemId == event.beforeId) {
|
||||||
items.pop()
|
items.pop()
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
@@ -251,7 +300,9 @@ function handleMatchBuilds(
|
|||||||
if (itemInfo.to.length != 0 && (items.length >= 1 || participant.teamPosition === 'UTILITY'))
|
if (itemInfo.to.length != 0 && (items.length >= 1 || participant.teamPosition === 'UTILITY'))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
items.push(event.itemId)
|
// Calculate gold advantage at time of purchase
|
||||||
|
const goldAdvantage = calculateGoldAdvantage(match, frame, participantIndex)
|
||||||
|
items.push({ itemId: event.itemId, goldAdvantage, platform })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,13 +313,12 @@ function handleMatchBuilds(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
function handleMatch(match: Match, champions: Map<number, ChampionData>, platform?: string) {
|
||||||
function handleMatch(match: any, champions: Map<number, ChampionData>) {
|
|
||||||
let participantIndex = 0
|
let participantIndex = 0
|
||||||
for (const participant of match.info.participants) {
|
for (const participant of match.info.participants) {
|
||||||
participantIndex += 1
|
participantIndex += 1
|
||||||
const championId = participant.championId
|
const championId = participant.championId
|
||||||
const champion = champions.get(championId)
|
const champion = champions.get(championId)!
|
||||||
|
|
||||||
// Lanes
|
// Lanes
|
||||||
let lane = champion.lanes.find(x => x.data == participant.teamPosition)
|
let lane = champion.lanes.find(x => x.data == participant.teamPosition)
|
||||||
@@ -333,7 +383,7 @@ function handleMatch(match: any, champions: Map<number, ChampionData>) {
|
|||||||
matchup.winrate = (matchup.winrate * (matchup.games - 1)) / matchup.games
|
matchup.winrate = (matchup.winrate * (matchup.games - 1)) / matchup.games
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const opponentChampion = champions.get(opponentChampionId)
|
const opponentChampion = champions.get(opponentChampionId)!
|
||||||
|
|
||||||
lane.matchups.push({
|
lane.matchups.push({
|
||||||
championId: opponentChampionId,
|
championId: opponentChampionId,
|
||||||
@@ -346,17 +396,19 @@ function handleMatch(match: any, champions: Map<number, ChampionData>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Items and runes (builds)
|
// Items and runes (builds)
|
||||||
handleMatchBuilds(match.timeline, participant, participantIndex, lane.builds)
|
handleMatchBuilds(match, participant, participantIndex, lane.builds, platform)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleMatchList(
|
async function handleMatchList(
|
||||||
client: MongoClient,
|
client: MongoClient,
|
||||||
patch: string,
|
patch: string,
|
||||||
champions: Map<number, ChampionData>
|
champions: Map<number, ChampionData>,
|
||||||
|
platform?: string
|
||||||
) {
|
) {
|
||||||
const database = client.db('matches')
|
const database = client.db('matches')
|
||||||
const matches = database.collection(patch)
|
const collectionName = platform ? `${patch}_${platform}` : patch
|
||||||
|
const matches = database.collection(collectionName)
|
||||||
const allMatches = matches.find()
|
const allMatches = matches.find()
|
||||||
const totalMatches: number = await matches.countDocuments()
|
const totalMatches: number = await matches.countDocuments()
|
||||||
|
|
||||||
@@ -366,7 +418,7 @@ async function handleMatchList(
|
|||||||
'\rComputing champion stats, game entry ' + currentMatch + '/' + totalMatches + ' ... '
|
'\rComputing champion stats, game entry ' + currentMatch + '/' + totalMatches + ' ... '
|
||||||
)
|
)
|
||||||
currentMatch += 1
|
currentMatch += 1
|
||||||
handleMatch(match, champions)
|
handleMatch(match as unknown as Match, champions, platform)
|
||||||
}
|
}
|
||||||
|
|
||||||
return totalMatches
|
return totalMatches
|
||||||
@@ -389,10 +441,10 @@ function splitMergeOnStarterItem(build: Build, championName: string): BuildWithS
|
|||||||
) {
|
) {
|
||||||
const startItems = []
|
const startItems = []
|
||||||
let items = build.items.children[0]
|
let items = build.items.children[0]
|
||||||
startItems.push({ data: build.items.children[0].data, count: build.items.children[0].count })
|
startItems.push({ data: build.items.children[0].data!, count: build.items.children[0].count })
|
||||||
build.items.children[0].data = undefined
|
build.items.children[0].data = undefined
|
||||||
if (build.items.children.length > 1) {
|
if (build.items.children.length > 1) {
|
||||||
startItems.push({ data: build.items.children[1].data, count: build.items.children[1].count })
|
startItems.push({ data: build.items.children[1].data!, count: build.items.children[1].count })
|
||||||
build.items.children[1].data = undefined
|
build.items.children[1].data = undefined
|
||||||
items = treeMergeTree(build.items.children[0], build.items.children[1])
|
items = treeMergeTree(build.items.children[0], build.items.children[1])
|
||||||
}
|
}
|
||||||
@@ -420,7 +472,7 @@ function splitMergeOnStarterItem(build: Build, championName: string): BuildWithS
|
|||||||
items: c,
|
items: c,
|
||||||
bootsFirstCount: build.bootsFirstCount,
|
bootsFirstCount: build.bootsFirstCount,
|
||||||
count: c.count,
|
count: c.count,
|
||||||
startItems: [{ data: c.data, count: c.count }],
|
startItems: [{ data: c.data!, count: c.count }],
|
||||||
suppItems: build.suppItems,
|
suppItems: build.suppItems,
|
||||||
boots: build.boots
|
boots: build.boots
|
||||||
})
|
})
|
||||||
@@ -567,7 +619,7 @@ async function finalizeChampionStats(champion: ChampionData, totalMatches: numbe
|
|||||||
// Summoner spells
|
// Summoner spells
|
||||||
lane.summonerSpells.forEach(x => (x.pickrate = x.count / lane.count))
|
lane.summonerSpells.forEach(x => (x.pickrate = x.count / lane.count))
|
||||||
lane.summonerSpells.sort((a, b) => b.count - a.count)
|
lane.summonerSpells.sort((a, b) => b.count - a.count)
|
||||||
lane.summonerSpells = lane.summonerSpells.filter(x => x.pickrate >= 0.05)
|
lane.summonerSpells = lane.summonerSpells.filter(x => x.pickrate! >= 0.05)
|
||||||
|
|
||||||
// Cleaning up builds
|
// Cleaning up builds
|
||||||
cleanupLaneBuilds(lane)
|
cleanupLaneBuilds(lane)
|
||||||
@@ -647,7 +699,7 @@ async function championList() {
|
|||||||
return list.slice(1)
|
return list.slice(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function makeChampionsStats(client: MongoClient, patch: string) {
|
async function makeChampionsStats(client: MongoClient, patch: string, platforms: string[] = []) {
|
||||||
const globalItems = await itemList()
|
const globalItems = await itemList()
|
||||||
for (const item of globalItems) {
|
for (const item of globalItems) {
|
||||||
itemDict.set(item.id, item)
|
itemDict.set(item.id, item)
|
||||||
@@ -656,7 +708,7 @@ async function makeChampionsStats(client: MongoClient, patch: string) {
|
|||||||
const list = await championList()
|
const list = await championList()
|
||||||
console.log('Generating stats for ' + list.length + ' champions')
|
console.log('Generating stats for ' + list.length + ' champions')
|
||||||
|
|
||||||
// Pre-generate list of champions
|
// Pre-generate list of champions (shared across all platforms)
|
||||||
const champions: Map<number, ChampionData> = new Map()
|
const champions: Map<number, ChampionData> = new Map()
|
||||||
for (const champion of list) {
|
for (const champion of list) {
|
||||||
champions.set(champion.id, {
|
champions.set(champion.id, {
|
||||||
@@ -667,19 +719,28 @@ async function makeChampionsStats(client: MongoClient, patch: string) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loop through all matches to generate stats
|
// Process matches from all platforms, merging into the same champions map
|
||||||
const totalMatches = await handleMatchList(client, patch, champions)
|
let totalMatches = 0
|
||||||
|
for (const platform of platforms) {
|
||||||
|
console.log(`\n=== Processing matches from platform: ${platform} ===`)
|
||||||
|
const platformMatches = await handleMatchList(client, patch, champions, platform)
|
||||||
|
totalMatches += platformMatches
|
||||||
|
console.log(`Processed ${platformMatches} matches from ${platform}`)
|
||||||
|
}
|
||||||
|
|
||||||
// Finalize and save stats for every champion
|
console.log(`\n=== Total matches processed: ${totalMatches} ===`)
|
||||||
|
|
||||||
|
// Finalize and save stats to a single champions collection
|
||||||
const database = client.db('champions')
|
const database = client.db('champions')
|
||||||
const collection = database.collection(patch)
|
const collection = database.collection(patch)
|
||||||
for (const champion of list) {
|
for (const champion of list) {
|
||||||
const championInfo = await finalizeChampionStats(champions.get(champion.id), totalMatches)
|
const championInfo = await finalizeChampionStats(champions.get(champion.id)!, totalMatches)
|
||||||
await collection.updateOne({ id: champion.id }, { $set: championInfo }, { upsert: true })
|
await collection.updateOne({ id: champion.id }, { $set: championInfo }, { upsert: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create alias-index for better key-find
|
// Create alias-index for better key-find
|
||||||
await collection.createIndex({ alias: 1 })
|
await collection.createIndex({ alias: 1 })
|
||||||
|
console.log(`Stats saved to collection: ${patch}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default { makeChampionsStats }
|
export default { makeChampionsStats }
|
||||||
|
|||||||
@@ -1,10 +1,26 @@
|
|||||||
const base = 'https://euw1.api.riotgames.com'
|
|
||||||
const api_key = process.env.RIOT_API_KEY
|
const api_key = process.env.RIOT_API_KEY
|
||||||
const sleep_minutes = 12
|
const sleep_minutes = 12
|
||||||
|
|
||||||
import { MongoClient } from 'mongodb'
|
import { MongoClient } from 'mongodb'
|
||||||
|
|
||||||
import champion_stat from './champion_stat'
|
import champion_stat from './champion_stat'
|
||||||
|
import { Match } from './api'
|
||||||
|
|
||||||
|
// Region configuration: platform -> regional routing value
|
||||||
|
const PLATFORMS: Record<string, string> = {
|
||||||
|
EUW1: 'EUROPE',
|
||||||
|
EUN1: 'EUROPE',
|
||||||
|
NA1: 'AMERICAS',
|
||||||
|
KR: 'ASIA'
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPlatformBaseUrl(platform: string): string {
|
||||||
|
return `https://${platform.toLowerCase()}.api.riotgames.com`
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRegionalBaseUrl(region: string): string {
|
||||||
|
return `https://${region.toLowerCase()}.api.riotgames.com`
|
||||||
|
}
|
||||||
|
|
||||||
main()
|
main()
|
||||||
|
|
||||||
@@ -24,42 +40,51 @@ async function main() {
|
|||||||
'Connected to database, latest patch ' + latestPatch + ' was epoch: ' + latestPatchTime
|
'Connected to database, latest patch ' + latestPatch + ' was epoch: ' + latestPatchTime
|
||||||
)
|
)
|
||||||
|
|
||||||
const alreadySeenGameList = await alreadySeenGames(client, latestPatch)
|
|
||||||
console.log('We already have ' + alreadySeenGameList.length + ' matches for this patch !')
|
|
||||||
|
|
||||||
console.log('Using RIOT_API_KEY: ' + api_key)
|
console.log('Using RIOT_API_KEY: ' + api_key)
|
||||||
if (api_key != null && api_key != undefined && api_key != '') {
|
if (api_key != null && api_key != undefined && api_key != '') {
|
||||||
const challengerLeague = await fetchChallengerLeague()
|
// Iterate through all platforms
|
||||||
console.log('ChallengerLeague: got ' + challengerLeague.entries.length + ' entries')
|
for (const [platform, region] of Object.entries(PLATFORMS)) {
|
||||||
|
console.log(`\n=== Processing platform: ${platform} (region: ${region}) ===`)
|
||||||
|
|
||||||
const gameList = []
|
const alreadySeenGameList = await alreadySeenGames(client, latestPatch, platform)
|
||||||
let i = 0
|
console.log(
|
||||||
for (const challenger of challengerLeague.entries) {
|
'We already have ' + alreadySeenGameList.length + ' matches for this patch/platform !'
|
||||||
console.log('Entry ' + i + '/' + challengerLeague.entries.length + ' ...')
|
)
|
||||||
const puuid = challenger.puuid
|
|
||||||
const challengerGameList = await summonerGameList(puuid, latestPatchTime)
|
const challengerLeague = await fetchChallengerLeague(platform)
|
||||||
for (const game of challengerGameList) {
|
console.log('ChallengerLeague: got ' + challengerLeague.entries.length + ' entries')
|
||||||
if (!gameList.includes(game) && !alreadySeenGameList.includes(game)) {
|
|
||||||
gameList.push(game)
|
const gameList: string[] = []
|
||||||
|
let i = 0
|
||||||
|
for (const challenger of challengerLeague.entries) {
|
||||||
|
console.log('Entry ' + i + '/' + challengerLeague.entries.length + ' ...')
|
||||||
|
const puuid = challenger.puuid
|
||||||
|
const challengerGameList = await summonerGameList(puuid, latestPatchTime, region)
|
||||||
|
for (const game of challengerGameList) {
|
||||||
|
if (!gameList.includes(game) && !alreadySeenGameList.includes(game)) {
|
||||||
|
gameList.push(game)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
i++
|
||||||
}
|
}
|
||||||
i++
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('Games: got ' + gameList.length + ' entries')
|
console.log('Games: got ' + gameList.length + ' entries for ' + platform)
|
||||||
i = 0
|
i = 0
|
||||||
for (const game of gameList) {
|
for (const game of gameList) {
|
||||||
console.log('Entry ' + i + '/' + gameList.length + ' ...')
|
console.log('Entry ' + i + '/' + gameList.length + ' ...')
|
||||||
const gameMatch = await match(game)
|
// Determine region from matchId (format: REGION_matchId)
|
||||||
const gameTimeline = await matchTimeline(game)
|
const matchRegion = game.split('_')[0]
|
||||||
gameMatch.timeline = gameTimeline
|
const gameMatch = await match(game, matchRegion)
|
||||||
await saveMatch(client, gameMatch, latestPatch)
|
const gameTimeline = await matchTimeline(game, matchRegion)
|
||||||
i++
|
gameMatch.timeline = gameTimeline
|
||||||
|
await saveMatch(client, gameMatch, latestPatch, platform)
|
||||||
|
i++
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Generating stats...')
|
console.log('Generating stats...')
|
||||||
await champion_stat.makeChampionsStats(client, latestPatch)
|
await champion_stat.makeChampionsStats(client, latestPatch, Object.keys(PLATFORMS))
|
||||||
|
|
||||||
console.log('All done. Closing client.')
|
console.log('All done. Closing client.')
|
||||||
await client.close()
|
await client.close()
|
||||||
@@ -105,17 +130,18 @@ async function connectToDatabase() {
|
|||||||
return client
|
return client
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchLatestPatchDate(client) {
|
async function fetchLatestPatchDate(client: MongoClient) {
|
||||||
const database = client.db('patches')
|
const database = client.db('patches')
|
||||||
const patches = database.collection('patches')
|
const patches = database.collection('patches')
|
||||||
const latestPatch = await patches.find().limit(1).sort({ date: -1 }).next()
|
const latestPatch = await patches.find().limit(1).sort({ date: -1 }).next()
|
||||||
return [latestPatch.patch, Math.floor(latestPatch.date.valueOf() / 1000)]
|
return [latestPatch!.patch, Math.floor(latestPatch!.date.valueOf() / 1000)]
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchChallengerLeague() {
|
async function fetchChallengerLeague(platform: string) {
|
||||||
const queue = 'RANKED_SOLO_5x5'
|
const queue = 'RANKED_SOLO_5x5'
|
||||||
const endpoint = `/lol/league/v4/challengerleagues/by-queue/${queue}`
|
const endpoint = `/lol/league/v4/challengerleagues/by-queue/${queue}`
|
||||||
const url = `${base}${endpoint}?api_key=${api_key}`
|
const baseUrl = getPlatformBaseUrl(platform)
|
||||||
|
const url = `${baseUrl}${endpoint}?api_key=${api_key}`
|
||||||
|
|
||||||
const challengerLeagueResponse = await handleRateLimit(new URL(url))
|
const challengerLeagueResponse = await handleRateLimit(new URL(url))
|
||||||
|
|
||||||
@@ -125,10 +151,10 @@ async function fetchChallengerLeague() {
|
|||||||
return challengerLeague
|
return challengerLeague
|
||||||
}
|
}
|
||||||
|
|
||||||
async function summonerGameList(puuid, startTime) {
|
async function summonerGameList(puuid: string, startTime: string, region: string) {
|
||||||
const base = 'https://europe.api.riotgames.com'
|
const baseUrl = getRegionalBaseUrl(region)
|
||||||
const endpoint = `/lol/match/v5/matches/by-puuid/${puuid}/ids`
|
const endpoint = `/lol/match/v5/matches/by-puuid/${puuid}/ids`
|
||||||
const url = `${base}${endpoint}?queue=420&type=ranked&startTime=${startTime}&api_key=${api_key}`
|
const url = `${baseUrl}${endpoint}?queue=420&type=ranked&startTime=${startTime}&api_key=${api_key}`
|
||||||
|
|
||||||
const gameListResponse = await handleRateLimit(new URL(url))
|
const gameListResponse = await handleRateLimit(new URL(url))
|
||||||
handleError(gameListResponse)
|
handleError(gameListResponse)
|
||||||
@@ -137,10 +163,10 @@ async function summonerGameList(puuid, startTime) {
|
|||||||
return gameList
|
return gameList
|
||||||
}
|
}
|
||||||
|
|
||||||
async function match(matchId) {
|
async function match(matchId: string, region: string) {
|
||||||
const base = 'https://europe.api.riotgames.com'
|
const baseUrl = getRegionalBaseUrl(region)
|
||||||
const endpoint = `/lol/match/v5/matches/${matchId}`
|
const endpoint = `/lol/match/v5/matches/${matchId}`
|
||||||
const url = `${base}${endpoint}?api_key=${api_key}`
|
const url = `${baseUrl}${endpoint}?api_key=${api_key}`
|
||||||
|
|
||||||
const matchResponse = await handleRateLimit(new URL(url))
|
const matchResponse = await handleRateLimit(new URL(url))
|
||||||
handleError(matchResponse)
|
handleError(matchResponse)
|
||||||
@@ -149,10 +175,10 @@ async function match(matchId) {
|
|||||||
return match
|
return match
|
||||||
}
|
}
|
||||||
|
|
||||||
async function matchTimeline(matchId) {
|
async function matchTimeline(matchId: string, region: string) {
|
||||||
const base = 'https://europe.api.riotgames.com'
|
const baseUrl = getRegionalBaseUrl(region)
|
||||||
const endpoint = `/lol/match/v5/matches/${matchId}/timeline`
|
const endpoint = `/lol/match/v5/matches/${matchId}/timeline`
|
||||||
const url = `${base}${endpoint}?api_key=${api_key}`
|
const url = `${baseUrl}${endpoint}?api_key=${api_key}`
|
||||||
|
|
||||||
const timelineResponse = await handleRateLimit(new URL(url))
|
const timelineResponse = await handleRateLimit(new URL(url))
|
||||||
handleError(timelineResponse)
|
handleError(timelineResponse)
|
||||||
@@ -161,17 +187,19 @@ async function matchTimeline(matchId) {
|
|||||||
return timeline
|
return timeline
|
||||||
}
|
}
|
||||||
|
|
||||||
async function alreadySeenGames(client, latestPatch) {
|
async function alreadySeenGames(client: MongoClient, latestPatch: string, platform: string) {
|
||||||
const database = client.db('matches')
|
const database = client.db('matches')
|
||||||
const matches = database.collection(latestPatch)
|
const collectionName = `${latestPatch}_${platform}`
|
||||||
|
const matches = database.collection(collectionName)
|
||||||
|
|
||||||
const alreadySeen = await matches.distinct('metadata.matchId')
|
const alreadySeen = await matches.distinct('metadata.matchId')
|
||||||
return alreadySeen
|
return alreadySeen
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveMatch(client, match, latestPatch) {
|
async function saveMatch(client: MongoClient, match: Match, latestPatch: string, platform: string) {
|
||||||
const database = client.db('matches')
|
const database = client.db('matches')
|
||||||
const matches = database.collection(latestPatch)
|
const collectionName = `${latestPatch}_${platform}`
|
||||||
|
const matches = database.collection(collectionName)
|
||||||
await matches.insertOne(match)
|
await matches.insertOne(match)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,10 +214,15 @@ async function runWithPreloadedData() {
|
|||||||
const [latestPatch] = await fetchLatestPatchDate(client)
|
const [latestPatch] = await fetchLatestPatchDate(client)
|
||||||
console.log(`Latest patch: ${latestPatch}`)
|
console.log(`Latest patch: ${latestPatch}`)
|
||||||
|
|
||||||
// Check if we have matches for this patch
|
// Check if we have matches for this patch (including platform-specific collections)
|
||||||
const matchesDb = client.db('matches')
|
const matchesDb = client.db('matches')
|
||||||
const collections = await matchesDb.listCollections().toArray()
|
const collections = await matchesDb.listCollections().toArray()
|
||||||
const patchCollections = collections.map(c => c.name).filter(name => name === latestPatch)
|
const collectionNames = collections.map(c => c.name)
|
||||||
|
|
||||||
|
// Find collections for this patch (both global and platform-specific)
|
||||||
|
const patchCollections = collectionNames.filter(
|
||||||
|
name => name === latestPatch || name.startsWith(`${latestPatch}_`)
|
||||||
|
)
|
||||||
|
|
||||||
if (patchCollections.length === 0) {
|
if (patchCollections.length === 0) {
|
||||||
console.error(`❌ No match data found for patch ${latestPatch}`)
|
console.error(`❌ No match data found for patch ${latestPatch}`)
|
||||||
@@ -198,13 +231,21 @@ async function runWithPreloadedData() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Found ${patchCollections.length} match collection(s)`)
|
console.log(
|
||||||
|
`Found ${patchCollections.length} match collection(s): ${patchCollections.join(', ')}`
|
||||||
|
)
|
||||||
|
|
||||||
// Generate stats for each patch with data
|
// Extract platforms from collection names (e.g., "15.1_EUW1" -> "EUW1")
|
||||||
for (const patch of patchCollections) {
|
const platforms = patchCollections
|
||||||
console.log(`Generating stats for patch ${patch}...`)
|
.filter(name => name.startsWith(`${latestPatch}_`))
|
||||||
await champion_stat.makeChampionsStats(client, patch)
|
.map(name => name.replace(`${latestPatch}_`, ''))
|
||||||
console.log(`Stats generated for patch ${patch}`)
|
|
||||||
|
// Generate stats for each platform
|
||||||
|
if (platforms.length > 0) {
|
||||||
|
await champion_stat.makeChampionsStats(client, latestPatch, platforms)
|
||||||
|
} else {
|
||||||
|
// Fallback for old-style collections without platform suffix
|
||||||
|
await champion_stat.makeChampionsStats(client, latestPatch)
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('🎉 All stats generated successfully!')
|
console.log('🎉 All stats generated successfully!')
|
||||||
|
|||||||
@@ -1,15 +1,41 @@
|
|||||||
|
type GoldAdvantageTag = 'ahead' | 'behind' | 'even'
|
||||||
|
|
||||||
|
type PlatformCounts = {
|
||||||
|
euw: number
|
||||||
|
eun: number
|
||||||
|
na: number
|
||||||
|
kr: number
|
||||||
|
}
|
||||||
|
|
||||||
type ItemTree = {
|
type ItemTree = {
|
||||||
data: number | undefined
|
data: number | undefined
|
||||||
count: number
|
count: number
|
||||||
children: Array<ItemTree>
|
children: Array<ItemTree>
|
||||||
|
|
||||||
|
// Gold advantage tracking
|
||||||
|
boughtWhen: {
|
||||||
|
aheadCount: number
|
||||||
|
behindCount: number
|
||||||
|
evenCount: number
|
||||||
|
meanGold: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// Platform tracking
|
||||||
|
platformCount: PlatformCounts
|
||||||
|
}
|
||||||
|
|
||||||
|
function initPlatformCounts(): PlatformCounts {
|
||||||
|
return { euw: 0, eun: 0, na: 0, kr: 0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
function treeInit(): ItemTree {
|
function treeInit(): ItemTree {
|
||||||
return { data: undefined, count: 0, children: [] }
|
return {
|
||||||
}
|
data: undefined,
|
||||||
|
count: 0,
|
||||||
function treeNode(data: number, count: number): ItemTree {
|
children: [],
|
||||||
return { data: data, count: count, children: [] }
|
boughtWhen: { aheadCount: 0, behindCount: 0, evenCount: 0, meanGold: 0 },
|
||||||
|
platformCount: initPlatformCounts()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -21,31 +47,68 @@ function nodeMerge(itemtree: ItemTree, node: ItemTree) {
|
|||||||
let next: ItemTree | null = null
|
let next: ItemTree | null = null
|
||||||
|
|
||||||
// Try to find an existing node in this tree level with same item
|
// Try to find an existing node in this tree level with same item
|
||||||
for (const node of itemtree.children) {
|
for (const child of itemtree.children) {
|
||||||
if (node.data == item) {
|
if (child.data == item) {
|
||||||
node.count += 1
|
child.count += 1
|
||||||
next = node
|
|
||||||
|
child.boughtWhen.aheadCount += node.boughtWhen.aheadCount
|
||||||
|
child.boughtWhen.evenCount += node.boughtWhen.evenCount
|
||||||
|
child.boughtWhen.behindCount += node.boughtWhen.behindCount
|
||||||
|
|
||||||
|
// Merge platform counts
|
||||||
|
child.platformCount.euw += node.platformCount.euw
|
||||||
|
child.platformCount.eun += node.platformCount.eun
|
||||||
|
child.platformCount.na += node.platformCount.na
|
||||||
|
child.platformCount.kr += node.platformCount.kr
|
||||||
|
|
||||||
|
next = child
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If not found, add item node at this level
|
// If not found, add item node at this level
|
||||||
if (next == null) {
|
if (next == null && item !== undefined) {
|
||||||
next = treeNode(item, count)
|
next = {
|
||||||
|
data: item,
|
||||||
|
count: count,
|
||||||
|
children: [],
|
||||||
|
boughtWhen: { ...node.boughtWhen },
|
||||||
|
platformCount: { ...node.platformCount }
|
||||||
|
}
|
||||||
itemtree.children.push(next)
|
itemtree.children.push(next)
|
||||||
}
|
}
|
||||||
|
|
||||||
return next
|
return next!
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Merge a full build path with an existing item tree
|
* Merge a full build path with an existing item tree
|
||||||
*/
|
*/
|
||||||
function treeMerge(itemtree: ItemTree, items: Array<number>) {
|
function treeMerge(
|
||||||
|
itemtree: ItemTree,
|
||||||
|
items: Array<{ itemId: number; goldAdvantage: GoldAdvantageTag; platform?: string }>
|
||||||
|
) {
|
||||||
let current = itemtree
|
let current = itemtree
|
||||||
|
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
current = nodeMerge(current, { data: item, count: 1, children: [] })
|
const platformKey = item.platform ? item.platform.toLowerCase() : null
|
||||||
|
current = nodeMerge(current, {
|
||||||
|
data: item.itemId,
|
||||||
|
count: 1,
|
||||||
|
boughtWhen: {
|
||||||
|
aheadCount: item.goldAdvantage == 'ahead' ? 1 : 0,
|
||||||
|
evenCount: item.goldAdvantage == 'even' ? 1 : 0,
|
||||||
|
behindCount: item.goldAdvantage == 'behind' ? 1 : 0,
|
||||||
|
meanGold: 0
|
||||||
|
},
|
||||||
|
children: [],
|
||||||
|
platformCount: {
|
||||||
|
euw: platformKey === 'euw1' ? 1 : 0,
|
||||||
|
eun: platformKey === 'eun1' ? 1 : 0,
|
||||||
|
na: platformKey === 'na1' ? 1 : 0,
|
||||||
|
kr: platformKey === 'kr' ? 1 : 0
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,7 +117,13 @@ function treeCutBranches(itemtree: ItemTree, thresholdCount: number, thresholdPe
|
|||||||
while (itemtree.children.length > thresholdCount) {
|
while (itemtree.children.length > thresholdCount) {
|
||||||
const leastUsedBranch = itemtree.children.reduce(
|
const leastUsedBranch = itemtree.children.reduce(
|
||||||
(a, b) => (Math.min(a.count, b.count) == a.count ? a : b),
|
(a, b) => (Math.min(a.count, b.count) == a.count ? a : b),
|
||||||
{ data: undefined, count: +Infinity, children: [] }
|
{
|
||||||
|
data: undefined,
|
||||||
|
count: +Infinity,
|
||||||
|
children: [],
|
||||||
|
boughtWhen: { aheadCount: 0, behindCount: 0, evenCount: 0, meanGold: 0 },
|
||||||
|
platformCount: initPlatformCounts()
|
||||||
|
}
|
||||||
)
|
)
|
||||||
itemtree.children.splice(itemtree.children.indexOf(leastUsedBranch), 1)
|
itemtree.children.splice(itemtree.children.indexOf(leastUsedBranch), 1)
|
||||||
}
|
}
|
||||||
@@ -88,7 +157,19 @@ function treeClone(tree: ItemTree): ItemTree {
|
|||||||
return {
|
return {
|
||||||
data: tree.data,
|
data: tree.data,
|
||||||
count: tree.count,
|
count: tree.count,
|
||||||
children: tree.children.map(child => treeClone(child))
|
children: tree.children.map(child => treeClone(child)),
|
||||||
|
boughtWhen: {
|
||||||
|
aheadCount: tree.boughtWhen.aheadCount,
|
||||||
|
behindCount: tree.boughtWhen.behindCount,
|
||||||
|
evenCount: tree.boughtWhen.evenCount,
|
||||||
|
meanGold: tree.boughtWhen.meanGold
|
||||||
|
},
|
||||||
|
platformCount: {
|
||||||
|
euw: tree.platformCount.euw,
|
||||||
|
eun: tree.platformCount.eun,
|
||||||
|
na: tree.platformCount.na,
|
||||||
|
kr: tree.platformCount.kr
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,6 +180,17 @@ function treeMergeTree(t1: ItemTree, t2: ItemTree): ItemTree {
|
|||||||
// Merge counts for the root
|
// Merge counts for the root
|
||||||
t1.count += t2.count
|
t1.count += t2.count
|
||||||
|
|
||||||
|
// Merge platform counts
|
||||||
|
t1.platformCount.euw += t2.platformCount.euw
|
||||||
|
t1.platformCount.eun += t2.platformCount.eun
|
||||||
|
t1.platformCount.na += t2.platformCount.na
|
||||||
|
t1.platformCount.kr += t2.platformCount.kr
|
||||||
|
|
||||||
|
// Merge boughtWhen
|
||||||
|
t1.boughtWhen.aheadCount += t2.boughtWhen.aheadCount
|
||||||
|
t1.boughtWhen.evenCount += t2.boughtWhen.evenCount
|
||||||
|
t1.boughtWhen.behindCount += t2.boughtWhen.behindCount
|
||||||
|
|
||||||
// Merge children from t2 into t1
|
// Merge children from t2 into t1
|
||||||
for (const child2 of t2.children) {
|
for (const child2 of t2.children) {
|
||||||
// Find matching child in t1 (same data value)
|
// Find matching child in t1 (same data value)
|
||||||
@@ -174,4 +266,14 @@ function areTreeSimilars(t1: ItemTree, t2: ItemTree): number {
|
|||||||
return Math.max(0, Math.min(1, similarity))
|
return Math.max(0, Math.min(1, similarity))
|
||||||
}
|
}
|
||||||
|
|
||||||
export { ItemTree, treeMerge, treeInit, treeCutBranches, treeSort, treeMergeTree, areTreeSimilars }
|
export {
|
||||||
|
ItemTree,
|
||||||
|
PlatformCounts,
|
||||||
|
GoldAdvantageTag,
|
||||||
|
treeMerge,
|
||||||
|
treeInit,
|
||||||
|
treeCutBranches,
|
||||||
|
treeSort,
|
||||||
|
treeMergeTree,
|
||||||
|
areTreeSimilars
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user