import requests import requests_cache import json import csv import pandas from typing import List, Optional, Dict from pathlib import Path from hanabi.database import global_db_connection_manager from hanabi.live.hanab_live import parse_json_game, HanabLiveGameState from hanabi.live.compress import link from bdr import describe_game from endgames import analyze_game_cached, full_analyze_game_cached from games import get_game_json # Init db connection global_db_connection_manager.read_config() global_db_connection_manager.connect() session = requests_cache.CachedSession('.hanab-live.cache',expire_after=30000) OUT_PATH = Path('out') if not OUT_PATH.exists(): OUT_PATH.mkdir(parents=True) player_mapping = { 'RamaNoVarjan': 'Ramanujan', 'RamaNoVarjan2': 'Ramanujan', 'purplejoe2': 'PurpleJoe', 'PurpleJoeVar': 'PurpleJoe', 'PurpleJoeVar2': 'PurpleJoe', 'yagami_blank': 'Yagami', 'yagami_black': 'Yagami', 'MarkusKahlsen': 'Markus', 'NoVarkusKahlsen': 'Markus', 'NoVarkusKarlsen': 'Markus', 'spring': 'spring', 'str8tsknacker': 'str8tsknacker', 'novarknacker': 'str8tsknacker', 'StKildaFan': 'Kilda', 'noarv': 'Arv', 'arv': 'Arv', 'ElenaDhynho': 'Elena', 'ElenaDhynh0': 'Elena', 'Elenanovar': 'Elena', 'TimeHoodie': 'Hoodie', 'Hoodie': 'Hoodie', 'newduke': 'Duke', } allowed_team_members = ['Carunty', 'HelanaAshryvr', 'rz'] allowed_players = [s.lower() for s in player_mapping.keys()] + [s.lower() for s in allowed_team_members] excluded_games = [1056958, 1044951] player_cols = set() for _, p in player_mapping.items(): player_cols.add(p) class Entry: def __init__(self, game_id, seed, score, players, bdr=0): self.game_id = game_id self.seed = seed self.num_players = len(players) self.won = 1 if score == 25 else 0 self.players = players self.bdr = bdr def get_player_games(player: str): r = session.get('https://hanab.live/api/v1/history-full/{}?start={}'.format(player, 1044446)) if r.status_code == 200: return json.loads(r.text) def collect_player_games() -> Dict[int, Entry]: global_games = {} for player in player_mapping.keys(): print('Parsing games for player {}'.format(player)) games = get_player_games(player) for game in games: if game['options'].get('variantName', 'No Variant') != 'No Variant': continue game_id = game['id'] players = game['playerNames'] score = game['score'] seed = game['seed'] if len(players) not in [3,4,5]: continue if game_id in excluded_games: continue ok = True for player in players: if player.lower() not in allowed_players: ok = False if ok: global_games[game_id] = Entry(game_id, seed, score, players) return global_games def analyze_games(games): retval = {} for game_id in games.keys(): bdrs, termination = describe_game(get_game_json(game_id)) retval[game_id] = bdrs, termination return retval def analyze_endgames(games, strategy: Optional[str] = None): retval = {} for game_id in games: print('Analysing endgames {} of game {}'.format('with strategy {}'.format(strategy) if strategy is not None else '', game_id)) result = analyze_game_cached(game_id, strategy) retval[game_id] = result return retval def full_analyze_endgames(games, strategy: Optional[str] = None): retval = {} for game_id in games: print('Analysing all endgames {} of game {}'.format('with strategy {}'.format(strategy) if strategy is not None else '', game_id)) result = full_analyze_game_cached(game_id, strategy) retval[game_id] = result return retval def sort_players_by_num_games(games_dict): nums = {} for _, entry in games_dict.items(): for player in entry.players: col = player_mapping.get(player, None) if col is not None: num = nums.get(col, 0) nums[col] = num + 1 return sorted(player_cols, key = lambda col: -nums[col]) def lookup_val(endgame_dict, clue_modifier) -> str: if clue_modifier > 0: for lookup in range(clue_modifier, 0, -1): val = endgame_dict.get('+' + str(lookup), None) if val is not None: return val if clue_modifier < 0: for lookup in range(clue_modifier, 0): val = endgame_dict.get(str(lookup)) if val is not None: return val retval = endgame_dict.get('+0', None) return retval def make_endgame_tables(ids: List[int], strategy: Optional[str] = None): endgames = analyze_endgames(ids, strategy) postfix = '-{}'.format(strategy) if strategy is not None else '' main_fname = OUT_PATH / ('endgames' + postfix + '.csv') fieldnames = ['Game ID'] + [str(i) for i in range(1, 16)] with open(main_fname, 'w', newline='') as f: f.writelines([','.join(fieldnames), "\n"]) with open(main_fname, 'a', newline='') as f: writer = csv.DictWriter(f, fieldnames=fieldnames) for game_id, endgame in sorted(endgames.items()): endgame['Game ID'] = "{}".format(game_id, game_id) writer.writerow(endgame) special_fname = str(OUT_PATH / ('endgames' + postfix + '_modifier_{}.csv')) x = pandas.read_csv(main_fname) x.to_html(main_fname.with_suffix('.html'), escape=False) all_endgames = full_analyze_endgames(ids, strategy) fieldnames = ['Game ID'] + [str(i) for i in range(1, 11)] for clue_modifier in range(-2, 3): filename = special_fname.format(clue_modifier) with open(filename, 'w') as f: f.writelines([','.join(fieldnames), "\n"]) with open(filename, 'a') as f: writer = csv.DictWriter(f, fieldnames=fieldnames) for game_id, endgame in sorted(all_endgames.items()): # print(endgame) row = {'Game ID': game_id} for deck_size in range(1, 11): val = lookup_val(endgame.get(str(deck_size), {}), clue_modifier) if val is not None: row[str(deck_size)] = val else: print("WARN: No results found for game {} and deck size {}: {}".format(game_id, deck_size, endgame.get(str(deck_size)))) writer.writerow(row) print('processed file {}'.format(filename)) x = pandas.read_csv(filename) x.to_html(Path(filename).with_suffix('.html'), escape=False) def make_results_table(games, analysis): streaks = {} fieldnames = ['Game ID', 'Seed', 'Player #', 'Result', 'BDR'] fieldnames += sort_players_by_num_games(games) fieldnames += ['Other'] with open('out/games.csv', 'w', newline='') as f: f.writelines([','.join(fieldnames), "\n"]) with open('out/games.csv', 'a', newline='') as f: writer = csv.DictWriter(f, fieldnames=fieldnames) for game_id, entry in sorted(games.items()): bdrs, termination = analysis[game_id] row = { 'Game ID': "{}".format(entry.game_id, entry.game_id), 'Seed': "{}".format(entry.seed, entry.seed), 'Player #': entry.num_players, 'Result': 'Win' if entry.won else termination, 'BDR': len(bdrs), } for player in entry.players: col = player_mapping.get(player, None) if col is not None: if entry.won: streak = streaks.get(col, 0) streak += 1 streaks[col] = streak row[col] = streak else: streaks[col] = 0 row[col] = 0 else: num_others = row.get('Other', 0) row['Other'] = num_others + 1 writer.writerow(row) a = pandas.read_csv("out/games.csv") a.to_html("out/games.html", escape=False) def make_team_table(games, analysis): stats = {3: {}, 4: {}, 5: {}} for game_id, entry in games.items(): normalized_players = sorted(map(lambda player: player_mapping.get(player, 'Other'), entry.players)) if 'Other' in normalized_players: continue num_p = len(normalized_players) key = ", ".join(normalized_players) bdr, termination = analysis[game_id] if key not in stats[num_p]: stats[num_p][key] = { 'Games': 0, 'BDR': 0, 'Win': 0, 'Loss': 0, 'Discard crit': 0, 'Bomb crit': 0, 'Strikeout': 0, 'VTK': 0, 'Lost Endgame': 0 } stats[num_p][key]['Games'] += 1 stats[num_p][key]['BDR'] += len(bdr) if entry.won: stats[num_p][key]['Win'] += 1 else: stats[num_p][key]['Loss'] += 1 stats[num_p][key][termination] += 1 for num_p in range(3, 6): filename = Path('out/teams_{}.csv'.format(num_p)) with open(filename, 'w') as f: writer = csv.DictWriter(f, fieldnames=['Team', 'Games', 'Win', 'Loss', 'BDR', 'Discard crit', 'Bomb crit', 'Strikeout', 'VTK', 'Lost Endgame']) writer.writeheader() for team, row in sorted(stats[num_p].items(), key=lambda item: -item[1]['Games']): row['Team'] = team writer.writerow(row) x = pandas.read_csv(filename) x.to_html(filename.with_suffix('.html')) def create_replay_links(ids: List[int], strategy: str): outfile = Path('out/{}_links.csv'.format(strategy)) with open(outfile, 'w') as f: writer = csv.writer(f) writer.writerow(["Game ID", "Result", "{} Replay Link".format(strategy)]) for game_id in ids: replay = get_game_json(game_id, strategy) instance, actions = parse_json_game(replay) game = HanabLiveGameState(instance) for action in actions: game.make_action(action) bdrs, termination = describe_game(replay) writer.writerow([game_id, 'Win' if game.is_won() else termination, link(game)]) x = pandas.read_csv(outfile) x.to_html(outfile.with_suffix('.html'), render_links=True) def main(): games = collect_player_games() analysis = analyze_games(games) game_ids = sorted(int(key) for key in games.keys()) # This is the main table, tracking streaks, loss reasons, BDRs make_results_table(games, analysis) # This tracks team statistics make_team_table(games, analysis) # Additional endgame analysis stats: # For the real games make_endgame_tables(game_ids, None) # For the cheating strategy make_endgame_tables(game_ids, 'cheat') # For the information strategy make_endgame_tables(game_ids, 'info') # Create JSON replay links to the bot games to watch create_replay_links(game_ids, 'cheat') create_replay_links(game_ids, 'info') if __name__ == "__main__": main()