Replay player's WR added to stats

This commit is contained in:
Jylpah committed 2020-02-15 15:26:32 +02:00
1 parent 15dddcf1b7
commit 691deb7c80
1 file changed
+99 -50
+99 -50
View File
@@ -40,7 +40,8 @@ replay_summary_flds = [
'map_name', 'map_name',
'battle_duration', 'battle_duration',
'protagonist', 'protagonist',
'title' 'title',
'mastery_badge'
] ]
replay_details_flds = [ replay_details_flds = [
@@ -115,12 +116,16 @@ class BattleRecordCategory():
RESULT_CAT_FRMT = '{:>17s}' RESULT_CAT_FRMT = '{:>17s}'
@classmethod @classmethod
def get_result_categories(cls, extended_stats: bool = False): def get_result_categories(cls, extended_cats: list = None):
if extended_stats: if extended_cats != None:
return cls._result_categories_default + cls._result_categories_extended return cls._result_categories_default + extended_cats
else: else:
return cls._result_categories_default return cls._result_categories_default
@classmethod
def get_extra_categories(cls) -> list:
return cls._result_categories_extended
def __init__(self, cat_name : str): def __init__(self, cat_name : str):
self.category_name = cat_name self.category_name = cat_name
self.category = collections.defaultdict(def_value_BattleRecord) self.category = collections.defaultdict(def_value_BattleRecord)
@@ -137,6 +142,7 @@ class BattleRecordCategory():
def get_sub_categories(self): def get_sub_categories(self):
return self.category.keys() return self.category.keys()
def record_result(self, result: dict): def record_result(self, result: dict):
try: try:
cat = None cat = None
@@ -161,7 +167,8 @@ class BattleRecordCategory():
def print_results(self): def print_results(self):
try: try:
print(' '.join(self.get_headers())) first_btl_record = list(self.category.values())[0]
print(' '.join(first_btl_record.get_headers(self.category_name)))
for row in self.get_results(): for row in self.get_results():
print(' : '.join(row)) print(' : '.join(row))
except KeyError as err: except KeyError as err:
@@ -170,6 +177,7 @@ class BattleRecordCategory():
bu.error(str(err)) bu.error(str(err))
return None return None
def get_results(self): def get_results(self):
try: try:
results = [] results = []
@@ -210,9 +218,13 @@ class BattleRecord():
'allies_wins' : [ 'Allies WR', 'Average WR of allies at the tier of their tank', 9, '{:9.2%}' ], 'allies_wins' : [ 'Allies WR', 'Average WR of allies at the tier of their tank', 9, '{:9.2%}' ],
'enemies_wins' : [ 'Enemies WR', 'Average WR of enemies at the tier of their tank', 10, '{:10.2%}' ], 'enemies_wins' : [ 'Enemies WR', 'Average WR of enemies at the tier of their tank', 10, '{:10.2%}' ],
'allies_battles' : [ 'Allies Btls', 'Average number battles of the allies', 11, '{:11.0f}' ], 'allies_battles' : [ 'Allies Btls', 'Average number battles of the allies', 11, '{:11.0f}' ],
'enemies_battles' : [ 'Enemies Btls', 'Average number battles of the enemies', 12, '{:12.0f}' ] 'enemies_battles' : [ 'Enemies Btls', 'Average number battles of the enemies', 12, '{:12.0f}' ],
'player_wins' : [ 'Player WR', 'Average WR of the player', 9, '{:9.2%}' ],
'player_battles' : [ 'Player Btls', 'Average number battles of the allies', 11, '{:11.0f}' ]
} }
_team_fields = [ 'wins', 'battles' ]
# fields to display in results # fields to display in results
_result_fields_default = [ _result_fields_default = [
'battles', 'battles',
@@ -220,6 +232,7 @@ class BattleRecord():
'damage_made', 'damage_made',
'enemies_spotted', 'enemies_spotted',
'top_tier', 'top_tier',
'player_wins',
'allies_wins', 'allies_wins',
'enemies_wins', 'enemies_wins',
'allies_battles', 'allies_battles',
@@ -244,49 +257,64 @@ class BattleRecord():
'dmg_block_rate' : [ 'damage_blocked', 'damage_received' ] 'dmg_block_rate' : [ 'damage_blocked', 'damage_received' ]
} }
_extended_stats = False
result_fields = list()
avg_fields = set()
ratio_fields = set()
RESULT_CAT_HEADER_FRMT = '{:_<17s}' RESULT_CAT_HEADER_FRMT = '{:_<17s}'
@classmethod @classmethod
def get_result_fields(cls, extended_stats: bool = False): def set_fields(cls, extended_stats: bool = False):
if extended_stats: try:
return cls._result_fields_default + cls._result_fields_extended cls._extended_stats = extended_stats
if cls._extended_stats:
cls.result_fields = cls._result_fields_default + cls._result_fields_extended
else: else:
return cls._result_fields_default cls.result_fields = cls._result_fields_default
cls.result_fields_ratio = set(cls._result_ratios.keys()) & set(cls.result_fields)
cls.avg_fields = set(cls.result_fields)
for field in ['battles', MISSING_STATS]:
if field in cls.avg_fields:
cls.avg_fields.remove(field)
cls.avg_fields.difference_update(set(cls._result_ratios.keys()))
for ratio in cls.result_fields_ratio:
cls.ratio_fields.add(cls._result_ratios[ratio][0])
cls.ratio_fields.add(cls._result_ratios[ratio][1])
except Exception as err:
bu.error('BattleRecord:' ,exception=err)
@classmethod
def get_result_fields(cls) -> set:
return cls.result_fields
@classmethod @classmethod
def get_fields_avg(cls) -> set: def get_fields_avg(cls) -> set:
avg_fields = set(cls.get_result_fields(True)) return cls.avg_fields
avg_fields.remove('battles')
avg_fields.remove(MISSING_STATS)
avg_fields.difference_update(cls._result_ratios.keys())
return avg_fields
@classmethod @classmethod
def get_fields_ratio(cls) -> set: def get_fields_ratio(cls) -> set:
ratio_fields = set() return cls.ratio_fields
for ratio in cls._result_ratios.keys():
ratio_fields.add(cls._result_ratios[ratio][0]) @classmethod
ratio_fields.add(cls._result_ratios[ratio][1]) def get_team_fields(cls) -> list:
return ratio_fields return cls._team_fields
def __init__(self, extended_stats: bool = False): def __init__(self):
try: try:
self.battles = 0 self.battles = 0
self.missing_stats = 0 self.missing_stats = 0
self.n_players = 0 self.n_players = 0
self.ratios_ready = False self.ratios_ready = False
self.results_ready = False self.results_ready = False
self.results = collections.defaultdict(def_value_zero) self.results = collections.defaultdict(def_value_zero)
self.avg_fields = self.get_fields_avg()
self.ratio_fields = self.get_fields_ratio()
self.result_fields = self.get_result_fields(extended_stats)
self.result_fields_ratio = set(self._result_ratios.keys()) & set(self.get_result_fields(extended_stats))
except KeyError as err: except KeyError as err:
bu.error('Key not found', err) bu.error('Key not found', err)
@@ -463,6 +491,7 @@ async def main(argv):
parser.add_argument('-id', dest='account_id', type=int, default=WG_ID, help='WG account_id to analyze') parser.add_argument('-id', dest='account_id', type=int, default=WG_ID, help='WG account_id to analyze')
parser.add_argument('-a', '--account', type=str, default=None, help='WG account nameto analyze. Format: ACCOUNT_NAME@SERVER') parser.add_argument('-a', '--account', type=str, default=None, help='WG account nameto analyze. Format: ACCOUNT_NAME@SERVER')
parser.add_argument('-x', '--extended', action='store_true', default=False, help='Print Extended stats') parser.add_argument('-x', '--extended', action='store_true', default=False, help='Print Extended stats')
parser.add_argument('-X', '--extra_categories', choices=BattleRecordCategory.get_extra_categories(), default=None, nargs='*', help='Print Extended categories')
parser.add_argument('--hist', action='store_true', default=False, help='Print player histograms (WR/battles)') parser.add_argument('--hist', action='store_true', default=False, help='Print player histograms (WR/battles)')
parser.add_argument('--stat_func', default='tank_tier', choices=STAT_FUNC.keys(), help='Select how to calculate for ally/enemy performance: tank-tier stats, global player stats') parser.add_argument('--stat_func', default='tank_tier', choices=STAT_FUNC.keys(), help='Select how to calculate for ally/enemy performance: tank-tier stats, global player stats')
parser.add_argument('-u', '--url', action='store_true', default=False, help='Print replay URLs') parser.add_argument('-u', '--url', action='store_true', default=False, help='Print replay URLs')
@@ -487,6 +516,8 @@ async def main(argv):
args.account_id = await wg.get_account_id(args.account) args.account_id = await wg.get_account_id(args.account)
bu.debug('WG account_id: ' + str(args.account_id)) bu.debug('WG account_id: ' + str(args.account_id))
BattleRecord.set_fields(args.extended)
#### Connect to MongoDB (TBD) #### Connect to MongoDB (TBD)
bu.debug('DB_SERVER: ' + DB_SERVER) bu.debug('DB_SERVER: ' + DB_SERVER)
bu.debug('DB_PORT: ' + str(DB_PORT)) bu.debug('DB_PORT: ' + str(DB_PORT))
@@ -544,7 +575,7 @@ async def main(argv):
teamresults = calc_team_stats(results, player_stats, stat_id_map, args) teamresults = calc_team_stats(results, player_stats, stat_id_map, args)
process_battle_results(teamresults, args) process_battle_results(teamresults, args)
if args.hist: if args.hist:
print('\nPlayer Historgrams______', end='', flush=True) print('\nPlayer Histograms______', end='', flush=True)
process_player_dist(results, player_stats, stat_id_map) process_player_dist(results, player_stats, stat_id_map)
bu.debug('Finished. Cleaning up..................') bu.debug('Finished. Cleaning up..................')
except Exception as err: except Exception as err:
@@ -588,9 +619,7 @@ def process_battle_results(results: dict, args : argparse.Namespace):
urls = collections.OrderedDict() urls = collections.OrderedDict()
categories = {} categories = {}
cats = result_categories_default cats = BattleRecordCategory.get_result_categories(args.extra_categories)
if args.extended:
cats = result_categories.keys()
for cat in cats: for cat in cats:
categories[cat] = BattleRecordCategory(cat) categories[cat] = BattleRecordCategory(cat)
@@ -619,6 +648,7 @@ def process_battle_results(results: dict, args : argparse.Namespace):
async def process_player_stats(players, N_workers: int, args : argparse.Namespace, db : motor.motor_asyncio.AsyncIOMotorDatabase) -> dict: async def process_player_stats(players, N_workers: int, args : argparse.Namespace, db : motor.motor_asyncio.AsyncIOMotorDatabase) -> dict:
"""Start stat_workers to retrieve and store players' stats""" """Start stat_workers to retrieve and store players' stats"""
## Create queue of player-tank pairs to find stats for ## Create queue of player-tank pairs to find stats for
try:
statsQ = asyncio.Queue() statsQ = asyncio.Queue()
bu.debug('Create player stats queue: ' + str(len(players)) + ' players') bu.debug('Create player stats queue: ' + str(len(players)) + ' players')
stat_id_map = {} stat_id_map = {}
@@ -663,6 +693,9 @@ async def process_player_stats(players, N_workers: int, args : argparse.Namespac
bu.debug('Returning player_stats') bu.debug('Returning player_stats')
return (player_stats, stat_id_map) return (player_stats, stat_id_map)
except Exception as err:
bu.error(exception=err)
return None
def remap_stat_id(stat_id_map: dict, stat_id_remap: dict): def remap_stat_id(stat_id_map: dict, stat_id_remap: dict):
@@ -682,13 +715,7 @@ def calc_team_stats(results: list, player_stats : dict, stat_id_map : dict, arg
## Bug here?? ## Bug here??
#stat_types = player_stats[list(player_stats.keys())[0]].keys() #stat_types = player_stats[list(player_stats.keys())[0]].keys()
stat_types = list() stat_types = list()
p_team_fields = re.compile('^allies_(.+)$') stat_types = BattleRecord.get_team_fields()
for field in result_fields:
m = p_team_fields.match(field)
if m != None:
stat_types.append(m.group(1))
bu.debug('stat_types: ' + ','.join(stat_types))
for result in results: for result in results:
try: try:
@@ -697,6 +724,8 @@ def calc_team_stats(results: list, player_stats : dict, stat_id_map : dict, arg
n_allies = collections.defaultdict(def_value_zero) n_allies = collections.defaultdict(def_value_zero)
allies_stats = collections.defaultdict(def_value_zero) allies_stats = collections.defaultdict(def_value_zero)
#bu.debug('Processing Allies') #bu.debug('Processing Allies')
for ally in result['allies']: for ally in result['allies']:
# Player itself is not in 'allies': see read_replay_JSON() # Player itself is not in 'allies': see read_replay_JSON()
ally_mapped = stat_id_map[ally] ally_mapped = stat_id_map[ally]
@@ -708,13 +737,6 @@ def calc_team_stats(results: list, player_stats : dict, stat_id_map : dict, arg
allies_stats[stat] += player_stats[ally_mapped][stat] allies_stats[stat] += player_stats[ally_mapped][stat]
n_allies[stat] += 1 n_allies[stat] += 1
#bu.debug('Processing Allies\' avg stats')
for stat in stat_types:
if n_allies[stat] > 0:
result['allies_' + str(stat)] = allies_stats[stat] / n_allies[stat]
else:
bu.debug('No allies stats for: ' + str(result))
#bu.debug('Processing Enemies') #bu.debug('Processing Enemies')
n_enemies = collections.defaultdict(def_value_zero) n_enemies = collections.defaultdict(def_value_zero)
enemies_stats = collections.defaultdict(def_value_zero) enemies_stats = collections.defaultdict(def_value_zero)
@@ -728,12 +750,23 @@ def calc_team_stats(results: list, player_stats : dict, stat_id_map : dict, arg
enemies_stats[stat] += player_stats[enemy_mapped][stat] enemies_stats[stat] += player_stats[enemy_mapped][stat]
n_enemies[stat] += 1 n_enemies[stat] += 1
#bu.debug('Processing Enemies\' avg stats') #bu.debug('Processing avg stats')
player_mapped = stat_id_map[result['player']]
for stat in stat_types: for stat in stat_types:
if n_allies[stat] > 0:
result['allies_' + str(stat)] = allies_stats[stat] / n_allies[stat]
else:
bu.debug('No allies stats for: ' + str(result))
if n_enemies[stat] > 0: if n_enemies[stat] > 0:
result['enemies_' + str(stat)] = enemies_stats[stat] / n_enemies[stat] result['enemies_' + str(stat)] = enemies_stats[stat] / n_enemies[stat]
else: else:
bu.debug('No allies stats for: ' + str(result)) bu.debug('No enemies stats for: ' + str(result))
if player_stats[player_mapped][stat] != None:
result['player_' + str(stat)] = player_stats[player_mapped][stat]
result[N_PLAYERS] = n_players result[N_PLAYERS] = n_players
result[MISSING_STATS] = missing_stats result[MISSING_STATS] = missing_stats
return_list.append(result) return_list.append(result)
@@ -1067,6 +1100,7 @@ async def replay_reader(queue: asyncio.Queue, readerID: int, args : argparse.Nam
# if (account_id != None): # if (account_id != None):
playerstanks.update(set(result['allies'])) playerstanks.update(set(result['allies']))
playerstanks.update(set(result['enemies'])) playerstanks.update(set(result['enemies']))
playerstanks.update(set([ result['player'] ]))
results.append(result) results.append(result)
bu.debug('Marking task ' + str(replayID) + ' done') bu.debug('Marking task ' + str(replayID) + ' done')
@@ -1090,9 +1124,7 @@ async def read_replay_JSON(replay_json: dict, args : argparse.Namespace) -> dict
try: try:
result['battle_start_timestamp'] = int(replay_json['data']['summary']['battle_start_timestamp']) result['battle_start_timestamp'] = int(replay_json['data']['summary']['battle_start_timestamp'])
# TBD... # TBD...
# protagonist = int(replay_json['data']['summary']['protagonist']) protagonist = int(replay_json['data']['summary']['protagonist'])
# protagonist_tank = int()
# result['protagonist'] = get_stat_id(protagonist, protagonist_tank, result['battle_start_timestamp'])
if account_id == None: if account_id == None:
account_id = replay_json['data']['summary']['protagonist'] account_id = replay_json['data']['summary']['protagonist']
@@ -1120,11 +1152,15 @@ async def read_replay_JSON(replay_json: dict, args : argparse.Namespace) -> dict
result['enemies'] = set() result['enemies'] = set()
btl_duration = 0 btl_duration = 0
btl_tier = 0 btl_tier = 0
protagonist_tank = None
for player in replay_json['data']['summary']['details']: for player in replay_json['data']['summary']['details']:
btl_duration = max(btl_duration, player['time_alive']) btl_duration = max(btl_duration, player['time_alive'])
player_tank_tier = wg.get_tank_data(player['vehicle_descr'], 'tier') player_tank_tier = wg.get_tank_data(player['vehicle_descr'], 'tier')
btl_tier = max(btl_tier, player_tank_tier) btl_tier = max(btl_tier, player_tank_tier)
if (protagonist != None) and (player['dbid'] == protagonist):
protagonist_tank = player['vehicle_descr']
if player['dbid'] == account_id: if player['dbid'] == account_id:
# player itself is not put in results['allies'] # player itself is not put in results['allies']
tmp = {} tmp = {}
@@ -1154,6 +1190,9 @@ async def read_replay_JSON(replay_json: dict, args : argparse.Namespace) -> dict
else: else:
result['enemies'].add(get_stat_id(tmp_account_id, tmp_tank_id, tmp_battletime)) result['enemies'].add(get_stat_id(tmp_account_id, tmp_tank_id, tmp_battletime))
## Rather use 'player' than incomprehensible 'protagonist'...
result['player'] = get_stat_id(protagonist, protagonist_tank, result['battle_start_timestamp'])
bu.debug('Player stats_id: ' + result['player'])
# remove platoon buddy from stats # remove platoon buddy from stats
if result['squad_index'] != None: if result['squad_index'] != None:
for player in replay_json['data']['summary']['details']: for player in replay_json['data']['summary']['details']:
@@ -1200,17 +1239,27 @@ def get_stat_id(account_id: int, tank_id: int, battletime: int) -> str:
def get_stat_id_tank_tier(stat_id_str: str) -> str: def get_stat_id_tank_tier(stat_id_str: str) -> str:
"""Return stat_id = account_id:tank_tier""" """Return stat_id = account_id:tank_tier"""
try:
stat_id = stat_id_str.split(':') stat_id = stat_id_str.split(':')
tank_tier = wg.get_tank_tier(int(stat_id[1])) tank_tier = wg.get_tank_tier(int(stat_id[1]))
battle_time = (int(stat_id[2]) // BATTLE_TIME_BUCKET) * BATTLE_TIME_BUCKET battle_time = (int(stat_id[2]) // BATTLE_TIME_BUCKET) * BATTLE_TIME_BUCKET
return ':'.join([ stat_id[0], str(tank_tier), str(battle_time) ]) return ':'.join([ stat_id[0], str(tank_tier), str(battle_time) ])
except Exception as err:
bu.error('Stats_id: ' + stat_id_str)
bu.error(exception=err)
return None
def get_stat_id_player(stat_id_str: str) -> str: def get_stat_id_player(stat_id_str: str) -> str:
"""Return stat_id = account_id:tank_tier""" """Return stat_id = account_id:tank_tier"""
try:
stat_id = stat_id_str.split(':') stat_id = stat_id_str.split(':')
battle_time = (int(stat_id[2]) // BATTLE_TIME_BUCKET) * BATTLE_TIME_BUCKET battle_time = (int(stat_id[2]) // BATTLE_TIME_BUCKET) * BATTLE_TIME_BUCKET
return ':'.join([ stat_id[0], str(battle_time) ]) return ':'.join([ stat_id[0], str(battle_time) ])
except Exception as err:
bu.error('Stats_id: ' + stat_id_str)
bu.error(exception=err)
return None
### main() ### main()