2024-06-29 19:43:46 -05:00
import { chunk , times } from 'lodash' ;
2024-05-19 17:50:01 -05:00
import { argv } from 'process' ;
2024-05-18 21:12:11 -05:00
import { Region , REGIONS } from '../src/constants/regions' ;
2024-06-28 14:12:58 -05:00
import { getAccountInfo } from '../src/core/blitz/getAccountInfo' ;
import getTankStats from '../src/core/blitz/getTankStats' ;
2024-06-26 21:22:03 -05:00
import { idToRegion } from '../src/core/blitz/idToRegion' ;
2024-05-19 17:50:01 -05:00
import {
AverageDefinitions ,
2024-06-26 21:07:54 -05:00
AverageDefinitionsEntry ,
AverageDefinitionsEntrySubPartial ,
2024-07-08 12:41:06 -05:00
AverageDefinitionsManifest ,
Samples ,
2024-05-19 17:50:01 -05:00
} from '../src/core/blitzkit/averageDefinitions' ;
2024-07-08 12:41:06 -05:00
import {
averageDefinitionsAllStatsKeys ,
emptySamples ,
} from '../src/core/blitzkit/averageDefinitions/constants' ;
2024-06-28 20:08:50 -05:00
import { commitAssets } from '../src/core/blitzkit/commitAssets' ;
2024-06-26 21:22:03 -05:00
import { fetchPreDiscoveredIds } from '../src/core/blitzkit/fetchPreDiscoveredIds' ;
2024-06-28 20:08:50 -05:00
import { encodeToBase64 } from '../src/core/protobuf/encodeToBase64' ;
2024-07-08 12:41:06 -05:00
import { IndividualTankStats } from '../src/types/tanksStats' ;
2024-05-18 21:12:11 -05:00
2024-06-26 14:07:27 -05:00
interface DataPoint {
x : number ;
y : number ;
w : number ;
}
2024-07-08 12:41:06 -05:00
const MINUTE = 60 * 1000 ;
const HOUR = 60 * MINUTE ;
const DAY = 24 * HOUR ;
2024-07-18 19:03:37 -05:00
// const MAX_ACTIVITY_TIME = 120 * DAY;
// const MIN_BATTLES = 5000;
2024-07-08 12:41:06 -05:00
const RUN_TIME = 5 * HOUR + 55 * MINUTE ;
2024-06-28 14:12:58 -05:00
const THREADS = 10 ;
const PLAYER_IDS_PER_CALL = 100 ;
2024-05-18 21:12:11 -05:00
2024-06-28 14:12:58 -05:00
const startTime = Date . now ();
2024-05-19 17:50:01 -05:00
const production = argv . includes ( '--production' );
2024-06-26 21:22:03 -05:00
const preDiscoveredIds = await fetchPreDiscoveredIds ( ! production );
2024-06-29 19:43:46 -05:00
const playerIds : Record < Region , number [][] > = {
asia : chunk (
preDiscoveredIds . filter (( id ) => idToRegion ( id ) === 'asia' ),
PLAYER_IDS_PER_CALL ,
),
com : chunk (
preDiscoveredIds . filter (( id ) => idToRegion ( id ) === 'com' ),
PLAYER_IDS_PER_CALL ,
),
eu : chunk (
preDiscoveredIds . filter (( id ) => idToRegion ( id ) === 'eu' ),
PLAYER_IDS_PER_CALL ,
),
2024-06-26 21:22:03 -05:00
};
let regionIndex = 0 ;
2024-07-08 12:41:06 -05:00
const samples : Samples = { ... emptySamples };
2024-06-26 21:22:03 -05:00
const availableRegions = [... REGIONS ];
2024-06-28 14:12:58 -05:00
const tankIds : number [] = [];
2024-07-08 12:41:06 -05:00
const tanksSorted : Record < number , IndividualTankStats [] > = {};
2024-06-28 14:12:58 -05:00
let postWorkRequested = false ;
2024-05-19 17:50:01 -05:00
2024-06-28 14:12:58 -05:00
times ( THREADS , async () => {
while ( availableRegions . length > 0 && startTime + RUN_TIME > Date . now ()) {
const region = availableRegions [ regionIndex ];
2024-06-29 10:49:32 -05:00
const regionIds = playerIds [ region ];
2024-06-29 19:43:46 -05:00
const [ ids ] = regionIds . splice (
Math . floor ( Math . random () * regionIds . length ),
1 ,
2024-06-29 10:49:32 -05:00
);
2024-06-28 14:12:58 -05:00
const accountInfo = await getAccountInfo ( region , ids , undefined , {
fields : 'last_battle_time,statistics.all.battles' ,
});
2024-05-18 21:12:11 -05:00
2024-06-28 14:12:58 -05:00
const filteredIds = ids . filter (( _ , index ) => {
const info = accountInfo [ index ];
2024-07-08 12:46:56 -05:00
if ( info === null ) return false ;
2024-07-08 12:41:06 -05:00
const timeSinceLastActivity = Date . now () - info . last_battle_time * 1000 ;
samples . total ++ ;
if ( timeSinceLastActivity <= 120 * DAY ) samples . d_120 ++ ;
if ( timeSinceLastActivity <= 90 * DAY ) samples . d_90 ++ ;
if ( timeSinceLastActivity <= 60 * DAY ) samples . d_60 ++ ;
if ( timeSinceLastActivity <= 30 * DAY ) samples . d_30 ++ ;
if ( timeSinceLastActivity <= 7 * DAY ) samples . d_7 ++ ;
if ( timeSinceLastActivity <= 1 * DAY ) samples . d_1 ++ ;
2024-07-20 13:50:01 -05:00
return true ;
2024-07-18 19:03:37 -05:00
// return (
// timeSinceLastActivity <= MAX_ACTIVITY_TIME &&
// info.statistics.all.battles > MIN_BATTLES
// );
2024-06-28 14:12:58 -05:00
});
2024-07-08 12:41:06 -05:00
2024-06-28 14:12:58 -05:00
const players = await Promise . all (
2024-07-08 12:41:06 -05:00
filteredIds . map (( id ) =>
getTankStats ( region , id , {
2024-07-08 19:41:56 -05:00
fields : 'last_battle_time,battle_life_time,all,tank_id' ,
2024-07-08 12:41:06 -05:00
}),
),
2024-06-28 14:12:58 -05:00
);
2024-05-19 17:50:01 -05:00
2024-06-28 14:12:58 -05:00
players . forEach (( tanks ) => {
tanks ? . forEach (( tank ) => {
if ( tank . all . battles === 0 ) return ;
2024-06-26 14:07:27 -05:00
2024-06-28 14:12:58 -05:00
if ( ! tankIds . includes ( tank . tank_id )) {
2024-07-08 19:41:56 -05:00
console . log (
`Found new tank: ${ tank . tank_id } ( ${ tankIds . length + 1 } )` ,
);
2024-06-28 14:12:58 -05:00
tankIds . push ( tank . tank_id );
tanksSorted [ tank . tank_id ] = [];
}
2024-05-19 17:50:01 -05:00
2024-07-08 12:41:06 -05:00
tanksSorted [ tank . tank_id ]. push ( tank );
2024-06-26 14:07:27 -05:00
});
2024-05-19 17:50:01 -05:00
});
2024-06-29 10:49:32 -05:00
if ( regionIds . length === 0 && availableRegions . includes ( region )) {
2024-06-28 14:12:58 -05:00
availableRegions . splice ( availableRegions . indexOf ( region ), 1 );
}
regionIndex = ( regionIndex + 1 ) % availableRegions . length ;
}
if ( ! postWorkRequested ) {
postWorkRequested = true ;
postWork ();
}
});
async function postWork() {
2024-05-19 17:50:01 -05:00
console . log (
2024-07-13 14:23:31 -05:00
`Generating statistics based on ${ samples . d_120 . toLocaleString () } players ( ${ samples . total . toLocaleString () } checked in total) and ${ tankIds . length } tanks...` ,
2024-05-19 17:50:01 -05:00
);
2024-06-30 20:34:22 -05:00
const averages : Record < number , AverageDefinitionsEntry > = {};
2024-05-19 17:50:01 -05:00
tankIds . forEach (( id ) => {
2024-07-13 14:23:31 -05:00
const tanksSortedEntry = tanksSorted [ id ];
2024-07-18 19:03:37 -05:00
// const tanks = tanksSortedEntry.filter(
// (tank) => Date.now() - tank.last_battle_time * 1000 <= MAX_ACTIVITY_TIME,
// );
const tanks = tanksSortedEntry ;
2024-06-26 21:07:54 -05:00
const entry : AverageDefinitionsEntrySubPartial = {
mu : {},
sigma : {},
r : {},
2024-07-08 12:41:06 -05:00
samples : { ... emptySamples },
2024-06-26 21:07:54 -05:00
};
2024-07-08 12:41:06 -05:00
tanks . forEach (( tank ) => {
const timeSinceLastActivity = Date . now () - tank . last_battle_time * 1000 ;
entry . samples . total ++ ;
if ( timeSinceLastActivity <= 120 * DAY ) entry . samples . d_120 ++ ;
if ( timeSinceLastActivity <= 90 * DAY ) entry . samples . d_90 ++ ;
if ( timeSinceLastActivity <= 60 * DAY ) entry . samples . d_60 ++ ;
if ( timeSinceLastActivity <= 30 * DAY ) entry . samples . d_30 ++ ;
if ( timeSinceLastActivity <= 7 * DAY ) entry . samples . d_7 ++ ;
if ( timeSinceLastActivity <= 1 * DAY ) entry . samples . d_1 ++ ;
});
2024-07-01 11:44:08 -05:00
const dataWY = tanks . map (( tank ) => ({
2024-07-08 12:41:06 -05:00
w : tank.all.battles ,
y : tank.all.wins / tank . all . battles ,
2024-07-01 11:44:08 -05:00
}));
2024-05-19 17:50:01 -05:00
2024-06-26 14:07:27 -05:00
averageDefinitionsAllStatsKeys . forEach (( key ) => {
2024-07-01 11:44:08 -05:00
const data : DataPoint [] = dataWY . map (({ w , y }, index ) => ({
w ,
2024-07-08 12:41:06 -05:00
x :
( key === 'battle_life_time'
? tanks [ index ]. battle_life_time
: tanks [ index ]. all [ key ]) / w ,
2024-07-01 11:44:08 -05:00
y ,
2024-06-26 14:07:27 -05:00
}));
2024-06-28 14:12:58 -05:00
if ( data . length === 0 ) return ;
2024-06-26 14:07:27 -05:00
function sum ( slicer : ( data : DataPoint ) => number ) {
return data . reduce (
( accumulator , data ) => accumulator + slicer ( data ),
0 ,
2024-05-19 17:50:01 -05:00
);
2024-06-26 14:07:27 -05:00
}
2024-05-19 17:50:01 -05:00
2024-06-26 14:07:27 -05:00
const sum_w = sum (({ w }) => w );
const sum_wx = sum (({ w , x }) => w * x );
const sum_wy = sum (({ w , y }) => w * y );
2024-06-28 14:12:58 -05:00
const x_bar = sum_wx / sum_w ;
const y_bar = sum_wy / sum_w ;
2024-07-01 11:44:08 -05:00
// undo normalization for mu to get a simple average
2024-07-08 12:41:06 -05:00
const mu = sum (({ w , x }) => w * x ) / samples . d_120 ;
2024-05-19 17:50:01 -05:00
2024-06-28 14:12:58 -05:00
const sigma_numerator = sum (({ w , x }) => w * ( x - x_bar ) ** 2 );
2024-06-26 21:07:54 -05:00
const sigma = Math . sqrt ( sigma_numerator / sum_w );
2024-06-28 14:12:58 -05:00
const r_numerator = sum (({ w , x , y }) => w * ( x - x_bar ) * ( y - y_bar ));
const r_denominator_x = sum (({ w , x }) => w * ( x - x_bar ) ** 2 );
const r_denominator_y = sum (({ w , y }) => w * ( y - y_bar ) ** 2 );
2024-06-26 14:07:27 -05:00
const r = r_numerator / Math . sqrt ( r_denominator_x * r_denominator_y );
2024-06-26 21:07:54 -05:00
entry . mu [ key ] = mu ;
entry . sigma [ key ] = sigma ;
2024-06-28 20:08:50 -05:00
entry . r [ key ] = isNaN ( r ) ? 0 : r ;
2024-06-26 14:07:27 -05:00
});
2024-06-26 21:07:54 -05:00
averages [ id ] = entry as AverageDefinitionsEntry ;
2024-05-19 17:50:01 -05:00
});
2024-07-08 12:41:06 -05:00
const time = Date . now ();
const latest = Math . round ( time / DAY );
2024-06-30 20:34:22 -05:00
const averageDefinitions = {
averages ,
2024-07-08 12:41:06 -05:00
samples ,
time ,
2024-06-30 20:34:22 -05:00
} satisfies AverageDefinitions ;
2024-07-08 12:41:06 -05:00
const manifest : AverageDefinitionsManifest = {
version : 1 ,
latest ,
};
2024-06-28 20:08:50 -05:00
commitAssets (
'averages' ,
[
{
2024-07-08 12:41:06 -05:00
path : `averages/ ${ latest } .pb` ,
2024-06-30 20:34:22 -05:00
content : await encodeToBase64 (
2024-08-25 09:21:17 -05:00
'blitzkit/blitzkit.AverageDefinitions' ,
2024-06-30 20:34:22 -05:00
averageDefinitions ,
),
2024-06-28 20:08:50 -05:00
encoding : 'base64' ,
},
2024-07-08 12:41:06 -05:00
{
path : 'averages/manifest.json' ,
content : JSON.stringify ( manifest ),
encoding : 'utf-8' ,
},
2024-06-28 20:08:50 -05:00
],
production ,
);
2024-05-19 17:50:01 -05:00
}