Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
dndsci-5e/lds2.py /
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
511 lines (446 sloc)
14.8 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import random | |
| import math | |
| import itertools | |
| import json | |
| log_location = 'lds2_output.csv' | |
| chars = [ | |
| { | |
| 'full_name' : 'Daring Duelist', | |
| 'health' : 6, | |
| 'attack' : 5, | |
| 'block' : 0, | |
| 'range' : 1, | |
| }, | |
| { | |
| 'full_name' : 'Bludgeon Bandit', | |
| 'health' : 6, | |
| 'attack' : 4, | |
| 'block' : 1, | |
| 'range' : 1, | |
| }, | |
| { | |
| 'full_name' : 'Silent Samurai', | |
| 'health' : 6, | |
| 'attack' : 3, | |
| 'block' : 2, | |
| 'range' : 1, | |
| }, | |
| { | |
| 'full_name' : 'Lamellar Legionary', | |
| 'health' : 6, | |
| 'attack' : 2, | |
| 'block' : 3, | |
| 'range' : 1, | |
| }, | |
| { | |
| 'full_name' : 'Granite Golem', | |
| 'health' : 6, | |
| 'attack' : 1, | |
| 'block' : 4, | |
| 'range' : 1, | |
| }, | |
| { | |
| 'full_name' : 'Flamethrower Felon', | |
| 'health' : 6, | |
| 'attack' : 4, | |
| 'block' : 0, | |
| 'range' : 2, | |
| }, | |
| { | |
| 'full_name' : 'Captain Chakram', | |
| 'health' : 6, | |
| 'attack' : 3, | |
| 'block' : 1, | |
| 'range' : 2, | |
| }, | |
| { | |
| 'full_name' : 'Jaunty Javelineer', | |
| 'health' : 6, | |
| 'attack' : 2, | |
| 'block' : 2, | |
| 'range' : 2, | |
| }, | |
| { | |
| 'full_name' : 'Hammer Hurler', | |
| 'health' : 6, | |
| 'attack' : 1, | |
| 'block' : 3, | |
| 'range' : 2, | |
| }, | |
| { | |
| 'full_name' : 'Professor Pyro', | |
| 'health' : 6, | |
| 'attack' : 3, | |
| 'block' : 0, | |
| 'range' : 3, | |
| }, | |
| { | |
| 'full_name' : 'Matchlock Marauder', | |
| 'health' : 6, | |
| 'attack' : 2, | |
| 'block' : 1, | |
| 'range' : 3, | |
| }, | |
| { | |
| 'full_name' : 'Rugged Ranger', | |
| 'health' : 6, | |
| 'attack' : 1, | |
| 'block' : 2, | |
| 'range' : 3, | |
| }, | |
| { | |
| 'full_name' : 'Thunder Tyrant', | |
| 'health' : 6, | |
| 'attack' : 2, | |
| 'block' : 0, | |
| 'range' : 4, | |
| }, | |
| { | |
| 'full_name' : 'Amazon Archer', | |
| 'health' : 6, | |
| 'attack' : 1, | |
| 'block' : 1, | |
| 'range' : 4, | |
| }, | |
| { | |
| 'full_name' : 'Wily Wizard', | |
| 'health' : 6, | |
| 'attack' : 1, | |
| 'block' : 0, | |
| 'range' : 5, | |
| }, | |
| ] | |
| used_names = [] | |
| for c in chars: | |
| c['name'] = c['full_name'][0] | |
| assert( c['name'] not in used_names ) | |
| used_names.append(c['name']) | |
| global_verbose = False | |
| def get_team(): | |
| team = random.sample(chars,3) | |
| team = [c.copy() for c in team] | |
| team = sorted(team , key = lambda char: (char['range'] * 10) - char['block']) | |
| return(team) | |
| def exec_attack(attack_team, defense_team, attacker_name): | |
| attacker = [c for c in attack_team if c['name'] == attacker_name] | |
| if(len(attacker) == 0): | |
| if(global_verbose): | |
| print(attacker_name + ' has been KOd in previous rounds, skipping\n') | |
| return() | |
| assert(len(attacker) == 1) | |
| attacker = attacker[ 0 ] | |
| if(attacker['damage_taken'] >= attacker['health']): | |
| raise('AIIIIE') | |
| if(global_verbose): | |
| print(attacker_name + ' has been KOd earlier this round, skipping\n') | |
| return() | |
| attacker_pos = attack_team.index(attacker) | |
| attacker_range = attacker['range'] | |
| remaining_range = attacker_range - attacker_pos | |
| if(remaining_range < 1 ): | |
| if(global_verbose): | |
| print(attacker_name + ' is too far back to attack, skipping\n') | |
| return() | |
| if( len(defense_team) == 0 ): | |
| raise('AIIIEE') | |
| target_pos = min(remaining_range - 1, len(defense_team) - 1) | |
| if(target_pos < 0 ): | |
| raise('AUGH') | |
| target = defense_team[target_pos] | |
| if(global_verbose): | |
| print(attacker_name + ' targets ' + target['name'] + '\n') | |
| dam = max(1, attacker['attack'] - target['block']) | |
| target['damage_taken'] = target['damage_taken'] + dam | |
| if(global_verbose and target['damage_taken'] >= target['health']): | |
| print(target['name'] + ' KOd\n') | |
| print('{} takes {} damage, now at {}\n'.format(target['name'], dam, target['damage_taken'])) | |
| def play_game(a_team, b_team, shuffle_seed=None): | |
| round_num = 1 | |
| inits = [] | |
| for char in a_team: | |
| inits.append(['a', char['name']]) | |
| char['damage_taken'] = 0 | |
| for char in b_team: | |
| inits.append(['b', char['name']]) | |
| char['damage_taken'] = 0 | |
| if shuffle_seed is None: | |
| random.shuffle(inits) | |
| else: | |
| # seed is an array of 0-5 in some order | |
| new_inits = [] | |
| for i in range(0,6): | |
| new_inits.append(inits[shuffle_seed[i]]) | |
| inits = new_inits | |
| while( True ): | |
| if(global_verbose): | |
| print('\n---ROUND {}---\n'.format(round_num)) | |
| round_num = round_num + 1 | |
| for i in inits: | |
| attack_team = a_team if i[0] == 'a' else b_team | |
| defense_team = b_team if i[0] == 'a' else a_team | |
| attacker_name = i[1] | |
| exec_attack(attack_team, defense_team, attacker_name) | |
| a_team = [c for c in a_team if c['health'] > c['damage_taken']] | |
| b_team = [c for c in b_team if c['health'] > c['damage_taken']] | |
| #check for win | |
| if(len(a_team) == 0): | |
| assert(len(b_team) > 0) | |
| return('B') | |
| if(len(b_team) == 0): | |
| assert(len(a_team) > 0) | |
| return('A') | |
| def list_teams(): | |
| return(list(itertools.combinations(chars, 3))) | |
| def team_from_string(team): | |
| team_chars = [ team[0], team[1], team[2]] | |
| team_chars = [ c for c in chars if c['name'] in team_chars ] | |
| team_chars = [c.copy() for c in team_chars ] | |
| team_chars = sorted(team_chars, key = lambda char: (char['range'] * 10) - char['block']) | |
| return(team_chars) | |
| def generate_winrate_cache(): | |
| team_strings = [] | |
| teams = list_teams() | |
| for t in teams: | |
| team_strings.append(t[0]['name'] + t[1]['name'] + t[2]['name']) | |
| shuffle_seeds = list( itertools.permutations([0,1,2,3,4,5])) | |
| winstruct = {} | |
| for t in team_strings: | |
| winstruct[t] = {} | |
| count = 0 | |
| for i in team_strings: | |
| i_team = team_from_string(i) | |
| for j in team_strings: | |
| if i == j: | |
| winstruct[i][j] = 0.5 | |
| elif i < j: | |
| count = count + 1 | |
| if count%100 == 0: | |
| print(count) | |
| j_team = team_from_string(j) | |
| i_wins = 0 | |
| j_wins = 0 | |
| for seed in shuffle_seeds: | |
| res = play_game(i_team, j_team, seed) | |
| if res == 'A': | |
| i_wins = i_wins + 1 | |
| else: | |
| j_wins = j_wins + 1 | |
| winstruct[i][j] = i_wins / len(shuffle_seeds) | |
| winstruct[j][i] = j_wins / len(shuffle_seeds) | |
| my_json = json.dumps(winstruct) | |
| f = open('winstruct.json', 'w') | |
| f.write(my_json) | |
| f.close() | |
| def dataset_random_team(): | |
| while True: | |
| team = get_team() | |
| pop = sum([c['popularity'] for c in team]) # char pop is 1-2, team 3-6 | |
| if pop > (6*random.random()): | |
| return(team) | |
| def write_log_row(log_row, mode='a'): | |
| log_string = ','.join([str(e) for e in log_row])+"\n" | |
| f = open(log_location, mode) | |
| f.write(log_string) | |
| def setup_logs(): | |
| log_row = [ | |
| 'Green Team Char 1', | |
| 'Green Team Char 2', | |
| 'Green Team Char 3', | |
| 'Blue Team Char 1', | |
| 'Blue Team Char 2', | |
| 'Blue Team Char 3', | |
| 'Winning Team' | |
| ] | |
| write_log_row(log_row, mode='w') | |
| def gen_dataset(): | |
| setup_logs() | |
| for c in chars: | |
| c['popularity'] = 1 + random.random() | |
| rows_to_make = 131826 | |
| i = 0 | |
| while i < rows_to_make: | |
| if i%10000 == 0: | |
| print(i) | |
| team_a = dataset_random_team() | |
| team_b = dataset_random_team() | |
| result = play_game(team_a, team_b) | |
| row = [] | |
| random.shuffle(team_a) | |
| random.shuffle(team_b) | |
| for char in team_a: | |
| row.append(char['full_name']) | |
| for char in team_b: | |
| row.append(char['full_name']) | |
| if result == 'A': | |
| row.append('Green') | |
| else: | |
| row.append('Blue') | |
| write_log_row(row) | |
| i = i + 1 | |
| def retrieve_data(apply_filter = lambda x : True): | |
| f = open('lds2_downloaded_data.csv') | |
| index_row = f.readline() | |
| index_row = index_row.replace('\n', '') | |
| cols = index_row.split(',') | |
| data = [] | |
| i = 0 | |
| while True: | |
| if i%1e5 == 0: | |
| print(i) | |
| i = i + 1 | |
| row = f.readline() | |
| row = row.replace('\n', '') | |
| if row is None: | |
| break | |
| row = row.split(',') | |
| if(len(row) < len(cols)): | |
| break | |
| row_data = {} | |
| col_index = 0 | |
| while col_index < len(cols): | |
| row_data[cols[col_index]] = row[col_index] | |
| col_index= col_index + 1 | |
| if apply_filter(row_data) == True: | |
| data.append(row_data) | |
| print(len(data)) | |
| return(data) | |
| random.seed('LDS2') | |
| #gen_dataset() | |
| data = retrieve_data() | |
| #generate_winrate_cache() | |
| team_strings = [] | |
| teams = list_teams() | |
| for t in teams: | |
| team_strings.append(t[0]['name'] + t[1]['name'] + t[2]['name']) | |
| with open('winstruct.json', 'r') as file: | |
| winstruct = json.load(file) | |
| char_data = {} | |
| melee_data = [[0,0], [0,0], [0,0], [0,0]] | |
| team_data = {} | |
| for c in chars: | |
| char_data[c['full_name']] = { 'wins' : 0, 'losses' : 0} | |
| melee_names = [c['full_name'] for c in chars if c['range'] == 1] | |
| for row in data: | |
| green_chars = [ | |
| row['Green Team Char 1'], | |
| row['Green Team Char 2'], | |
| row['Green Team Char 3'], | |
| ] | |
| blue_chars = [ | |
| row['Blue Team Char 1'], | |
| row['Blue Team Char 2'], | |
| row['Blue Team Char 3'], | |
| ] | |
| green_win = 1 if row['Winning Team'] == 'Green' else 0 | |
| winning_chars = green_chars if green_win else blue_chars | |
| for c in winning_chars: | |
| char_data[c]['wins'] = char_data[c]['wins'] + 1 | |
| losing_chars = blue_chars if green_win else green_chars | |
| for c in losing_chars: | |
| char_data[c]['losses'] = char_data[c]['losses'] + 1 | |
| winning_melee_count = len([w for w in winning_chars if w in melee_names]) | |
| losing_melee_count = len([w for w in losing_chars if w in melee_names]) | |
| melee_data[winning_melee_count][0] = melee_data[winning_melee_count][0] + 1 | |
| melee_data[losing_melee_count][1] = melee_data[losing_melee_count][1] + 1 | |
| winning_team = [c[0] for c in winning_chars] | |
| winning_team.sort() | |
| winning_team = ''.join(winning_team) | |
| losing_team = [c[0] for c in losing_chars] | |
| losing_team.sort() | |
| losing_team = ''.join(losing_team) | |
| if winning_team not in team_data.keys(): | |
| team_data[winning_team] = { 'wins' : 0, 'losses' : 0} | |
| if losing_team not in team_data.keys(): | |
| team_data[losing_team] = { 'wins' : 0, 'losses' : 0} | |
| team_data[winning_team]['wins'] = team_data[winning_team]['wins'] + 1 | |
| team_data[losing_team]['losses'] = team_data[losing_team]['losses'] + 1 | |
| i = 0 | |
| while i < 4: | |
| wins = melee_data[i][0] | |
| losses = melee_data[i][1] | |
| winrate = wins / ( wins + losses ) | |
| print('Teams with {} melee win {:.2f}%\n'.format(i, winrate*100)) | |
| i = i + 1 | |
| char_names = char_data.keys() | |
| for c in char_names: | |
| wins = char_data[c]['wins'] | |
| losses = char_data[c]['losses'] | |
| winrate = wins / ( wins + losses ) | |
| print('{} wins {:.2f}%\n'.format(c, winrate*100)) | |
| teams = team_data.keys() | |
| winstruct_teams = winstruct.keys() | |
| team_winrates = [] | |
| for t in teams: | |
| wins = team_data[t]['wins'] | |
| losses = team_data[t]['losses'] | |
| winrate = wins / ( wins + losses ) | |
| team_winrates.append([t, winrate]) | |
| team_winrates.sort(key = lambda x : x[1], reverse=True) | |
| i = 0 | |
| while i < 5: | |
| good_team = team_winrates[i] | |
| t = good_team[0] | |
| winrate = good_team[1] | |
| winstruct_team_name = [ w for w in winstruct_teams if w[0] in t and w[1] in t and w[2] in t ] | |
| assert(len(winstruct_team_name) == 1 ) | |
| winstruct_team_name = winstruct_team_name[0] | |
| win_vs_npc = winstruct[winstruct_team_name]['SFR'] | |
| print('Team {} has winrate of {:.2f}% against field and {:.2f}% against NPC'.format(t,winrate * 100, win_vs_npc * 100)) | |
| i = i + 1 | |
| details = [] | |
| for t in team_strings: | |
| team_struct = winstruct[ t ] | |
| winrates = team_struct.values() | |
| avg_winrate = sum(winrates) / len(winrates) | |
| distinct = [] | |
| for w in winrates: | |
| if w not in distinct: | |
| distinct.append(w) | |
| loses_to = [] | |
| for o in team_strings: | |
| if team_struct[o] == 0: | |
| loses_to.append(o) | |
| details.append({ 'team' : t, 'avg_winrate' : avg_winrate, 'num_distinct' : len(distinct), 'loses_to' : loses_to}) | |
| char_names = [c['name'] for c in chars] | |
| char_details = [] | |
| for c in char_names: | |
| team_matches = [t for t in team_strings if c in t] | |
| detail_matches = [d for d in details if d['team'] in team_matches] | |
| winrates = [d['avg_winrate'] for d in detail_matches] | |
| char_details.append({ 'char' : c, 'avg_winrate' : sum(winrates) / len(winrates)}) | |
| char_details = sorted(char_details, key=lambda x: x['avg_winrate']) | |
| for e in char_details: | |
| print( '{} wins {:.2f}% of the time\n'.format(e['char'], e['avg_winrate'] * 100)) | |
| max_winrate = max([ x['avg_winrate'] for x in details]) | |
| best_team = [x for x in details if x['avg_winrate'] == max_winrate][0]['team'] | |
| melee = [c['name'] for c in chars if c['range'] == 1] | |
| count_win_by_melee = [[0,0], [0,0], [0,0], [0,0]] | |
| for t in team_strings: | |
| melee_count = 0 | |
| winrate = [d['avg_winrate'] for d in details if d['team'] == t][0] | |
| for c in t: | |
| if c in melee: | |
| melee_count = melee_count + 1 | |
| count_win_by_melee[melee_count][0] = count_win_by_melee[melee_count][0] + 1 | |
| count_win_by_melee[melee_count][1] = count_win_by_melee[melee_count][1] + winrate | |
| num_melee = 0 | |
| for e in count_win_by_melee: | |
| print('With {} melee chars:\n'.format(num_melee)) | |
| print('{} distinct teams, avg winrate {:.2f}%\n'.format(e[0], 100 * e[1] / e[0] )) | |
| num_melee = num_melee + 1 | |
| rangestruct = {} | |
| for c in chars: | |
| rangestruct[c['name']] = c['range'] | |
| npc_team = 'SFR' | |
| good_teams = [] | |
| for t in team_strings: | |
| winrate = winstruct[t][npc_team] | |
| if winrate > 0.5: | |
| good_teams.append([t,winrate]) | |
| good_teams.sort(key=lambda x: x[1]) | |
| for e in good_teams: | |
| t = e[0] | |
| w = e[1] | |
| ranges = [ rangestruct[x] for x in t ] | |
| rangestring = ''.join([str(x) for x in ranges]) | |
| print('{} ({}) wins {:.1f}% of the time'.format(t, rangestring, w*100)) |