Libraries

In [25]:
import sys
#!{sys.executable} -m pip install pandas
#!{sys.executable} -m pip install seaborn
import numpy as np
import pandas
import seaborn as sns
import warnings
import time
from sklearn.neighbors import KNeighborsRegressor
from sklearn.model_selection import train_test_split
from ipywidgets import FloatProgress
from IPython.display import display, Markdown, Latex
%matplotlib inline

Debugging Flags

In [2]:
debug_rounds = False
debug_matches = False
use_crit_rules = True

Functions

In [8]:
def d20():
    return np.random.randint(1,21)

def do_match_and_update(a,b,rounds,df):
    wins = do_match(a,b,rounds)
    a_wins = wins[0]
    b_wins = wins[1]
    turns = wins[2]
    a_id = a[0]
    b_id = b[0]
    df.at[a_id,'round_wins'] = df.at[a_id,'round_wins'] + a_wins
    df.at[b_id,'round_wins'] = df.at[b_id,'round_wins'] + b_wins
    if a_wins > b_wins:
        df.at[a_id,'match_wins'] = df.at[a_id,'match_wins'] + 1
    else:
        df.at[b_id,'match_wins'] = df.at[b_id,'match_wins'] + 1
    return [df,turns]

def calc_match_and_update(a,b,df):
    wins = calc_match(a,b)
    a_wins = wins[0]
    b_wins = wins[1]
    a_id = a[0]
    b_id = b[0]
    if a_wins > b_wins:
        df.at[a_id,'calc_wins'] = df.at[a_id,'calc_wins'] + 1
    else:
        df.at[b_id,'calc_wins'] = df.at[b_id,'calc_wins'] + 1
    return df

def do_match(a,b,rounds):
    a_wins = 0
    b_wins = 0
    turns = 0
    for _ in np.arange(rounds):
        wins = do_round(a,b)
        a_wins = a_wins + wins[0]
        b_wins = b_wins + wins[1]
        turns = turns + wins[2]
    if debug_matches:
        print ("After %d rounds %s won %d times and %s won %d times" % (rounds,a.name,a_wins,b.name,b_wins))
    return [a_wins,b_wins,turns]

def calc_match(a,b):
    a_prob_hit_turn = (20 - b.ac) * 0.05
    if (use_crit_rules):
        if (a_prob_hit_turn > .95):
            a_prob_hit_turn = .95
        if (a_prob_hit_turn < 0.05):
            a_prob_hit_turn = 0.05
    a_avg_dam_max = a.dam_avg * a.att_num
    a_avg_dam_per_turn = a_prob_hit_turn * a_avg_dam_max
    a_turns_towin = np.ceil(b.hp / a_avg_dam_per_turn)
    b_prob_hit_turn = (20 - a.ac) * 0.05
    if (use_crit_rules):
        if (b_prob_hit_turn > .95):
            b_prob_hit_turn = .95
        if (b_prob_hit_turn < 0.05):
            b_prob_hit_turn = 0.05
    b_avg_dam_max = b.dam_avg * b.att_num
    b_avg_dam_per_turn = b_prob_hit_turn * b.dam_avg
    b_turns_towin = np.ceil(a.hp / b_avg_dam_per_turn)
    if a_turns_towin == b_turns_towin:
        if a.init >= b.init:
            return [1,0]
        else:
            return [0,1]
    else:
        if a_turns_towin < b_turns_towin:
            return [1,0]
        else:
            return [0,1]
    """Something went wrong here, nobody wins"""
    raise RuntimeError('calc_match match ended in a draw')
    return [0,0]

def do_round( a, b ):
    a_init = d20() + a.init
    if debug_rounds:
        print("%s rolled %d init" % (a.name,a_init))
    b_init = d20() + b.init
    if debug_rounds:
        print("%s rolled %d init" % (b.name,b_init))
    if (a_init >= b_init):
        first = a.copy()
        second = b.copy()
        in_order = True
    else:
        first = b.copy()
        second = a.copy()
        in_order = False
    round = 0
    while (first.hp > 0 and second.hp > 0):
        if debug_rounds:
            print("    Round %d: %s hp is %d and %s hp is %d" % (round+1, first.name,first.hp,second.name,second.hp))
        round = round + 1
        first,second = do_attack(first,second)
        if (second.hp <= 0):
            if debug_rounds:
                print("%s is dead" % second.name)
            if in_order:
                return [1,0,round] #a went first and won
            else:
                return [0,1,round] #b went first and won
        else:
            second,first = do_attack(second,first)
            if (first.hp <= 0):
                if debug_rounds:
                    print("%s is dead" % first.name)
                if in_order:
                    return [0,1,round] #b went second and won
                else:
                    return [1,0,round] #a went second and won
    """Something went wrong here, nobody wins"""
    raise RuntimeError('do_round match ended in a draw')
    return [0,0]
    
def do_attack( attacker, target ):
    if debug_rounds:
        print ("        %s makes %d attacks against %s" % (attacker.name, attacker.att_num, target.name))
    for _ in np.arange(attacker.att_num):
        attack = d20() + attacker.hit_mod
        if (use_crit_rules):
            attack_hits = (attack != 1 and (attack == 20 or attack >= target.ac))
        else:
            attack_hits = (attack >= target.ac)
        if (attack_hits):
            target.hp = target.hp - attacker.dam_avg
            if debug_rounds:
                print("            %s hits %s for %d damage" % (attacker.name, target.name, attacker.dam_avg ))
        else:
            if debug_rounds:
                print("            %s misses %s" % (attacker.name, target.name))
    return [attacker, target]

def run_matches(creatures,rounds,name="creatures"):
    start = time.time()
    
    rows = creatures.shape[0]
    creatures['match_wins'] = np.zeros(rows)
    creatures['round_wins'] = np.zeros(rows)
    creatures['calc_wins'] = np.zeros(rows)

    matches = ((rows-1)**2 + rows - 1)/2 #nth triangular number for n-1

    f = FloatProgress(min=0, max=matches, description="Contest: ")
    display(f)

    turns = 0
    print("Performing %d matches of %d rounds each" % (matches,rounds))
    for i in np.arange(rows):
        a = creatures.take([i]).to_records()[0]
        for j in np.arange(i+1,rows):
            b = creatures.take([j]).to_records()[0]
            creatures,new_turns = do_match_and_update(a,b,rounds,creatures)
            turns = turns + new_turns
            creatures = calc_match_and_update(a,b,creatures)
            f.value = f.value + 1
    
    filename = "data/gen/%s_%02d.csv" % (name,rounds)
    creatures.to_csv(filename)
    
    end = time.time()
    seconds = round(end-start)
    print("Completed %d turns in %d seconds" % (turns,seconds))

def creature_from_party(df):
    """name,cr,ac,hp,init,att_num,hit_mod,dam_avg"""
    rows = df.shape[0]
    df2 = pandas.DataFrame()
    df2['name'] =  [ "Party of %d" % rows ]
    df2['cr'] = [ np.floor(df['cr'].sum()) ]
    df2['ac'] = [ np.floor(df['ac'].mean()) ]
    df2['hp'] = [ np.floor(df['hp'].sum()) ]
    df2['init'] = [ round(df['init'].mean(),2) ]
    df2['att_num'] = [ np.floor(df['att_num'].sum()) ]
    df2['hit_mod'] = [ np.floor(df['hit_mod'].mean()) ]
    df2['dam_avg'] = [ np.floor(df['dam_avg'].mean()) ]
    return df2

def d20_adv(n=None):
    return roll_adv(1,21,n)

def d20_dis(n=None):
    return roll_dis(1,21,n)

def d20_mid(n=None):
    return roll_mid(1,21,n)

def roll(s=1,e=21,n=None):
    return np.random.randint(s,e,n)

def roll_adv(s=1,e=21,n=None):
    r1 = np.random.randint(s,e,n)
    r2 = np.random.randint(s,e,n)
    if n:
        return np.fromiter(map(max,r1,r2),int)
    else:
        return max(r1,r2)
    
def roll_dis(s=1,e=21,n=None):
    r1 = np.random.randint(s,e,n)
    r2 = np.random.randint(s,e,n)
    if n:
        return np.fromiter(map(min,r1,r2),int)
    else:
        return min(r1,r2)

def mid(r1,r2,r3):
    if r2 > r1 > r3 or r3 > r1 > r2:
        return r1
    elif r1 > r2 > r3 or r3 > r2 > r1:
        return r2
    else:
        return r3
        
def roll_mid(s=1,e=21,n=None):
    r1 = np.random.randint(s,e,n)
    r2 = np.random.randint(s,e,n)
    r3 = np.random.randint(s,e,n)
    if n:
        return np.fromiter(map(mid, r1,r2,r3),int)
    else:
        return mid(r1,r2,r3)
    
def gen_init(amount,f=roll):
    return f(-5,6,amount)

def gen_ac(amount,f=roll):
    return f(13,22,amount)

def gen_hp(amount,f=roll):
    return f(10,811,amount)

def gen_att_num(amount,f=roll):
    return f(1,6,amount)

def gen_hit_mod(amount,f=roll):
    return f(3,15,amount)

def gen_dam_avg(amount,f=roll):
    return f(2,62,amount)

def create_randoms(size,f=roll):
    """name,cr,ac,hp,init,att_num,hit_mod,dam_avg"""
    randoms = pandas.DataFrame()
    randoms['name'] = np.repeat([''],size)
    randoms['cr'] = np.zeros(size)
    randoms['ac'] = gen_ac(size)
    randoms['hp'] = gen_hp(size)
    randoms['init'] = gen_init(size)
    randoms['att_num'] = gen_att_num(size)
    randoms['hit_mod'] = gen_hit_mod(size)
    randoms['dam_avg'] = gen_dam_avg(size)
    return randoms

def create_progressives(size,f=roll):
    """name,cr,ac,hp,init,att_num,hit_mod,dam_avg"""
    progs = pandas.DataFrame()
    progs['name'] = np.repeat([''],size)
    progs['cr'] = np.zeros(size)
    ac = np.sort(gen_ac(size))
    progs['ac'] = ac
    hp = np.sort(gen_hp(size))
    progs['hp'] = hp
    init = np.sort(gen_init(size))
    progs['init'] = init
    att_num = np.sort(gen_att_num(size))
    progs['att_num'] = att_num
    hit_mod = np.sort(gen_hit_mod(size))
    progs['hit_mod'] = hit_mod
    dam_avg = np.sort(gen_dam_avg(size))
    progs['dam_avg'] = dam_avg
    return progs
    
def create_cr_averages(creatures):
    creatures_avg = creatures.groupby('cr').mean().apply(np.round).astype(int).reset_index()
    creatures_avg['name'] = creatures_avg['cr']
    return creatures_avg

def plot_correl(file,col,logx=False,order=1,subtitle=''):
    df = pandas.read_csv(file)
    return plot_correl_df(df,col,logx=logx,order=order,subtitle=subtitle)

def plot_correl_df(df,col,logx=False,order=1,subtitle=''):
    rows = df.shape[0]
    df['percent_wins'] = df['match_wins'] / (rows-1)
    max_col = df.loc[df[col].idxmax()][col]
    df['percent_' + col] = df[col] / max_col
    df = df.sort_values(['percent_wins'],ascending=False)
    warnings.filterwarnings('ignore')
    r = np.corrcoef(df['percent_'+col],df['percent_wins'])[0][1]
    if len(subtitle) > 0:
        display(Markdown('### Correlation between %s and wins: %s' % (col,subtitle)))
    else:
        display(Markdown('### Correlation between %s and wins' % col))
    if 'type' in df.columns:
        plt = sns.lmplot( x='percent_'+col, y='percent_wins', data=df, fit_reg=False, hue='type', legend=True)
    else:
        if np.absolute(r) < 0.3:
            df.plot.scatter('percent_' + col,'percent_wins')
        else:
            sns.regplot(df['percent_'+col],df['percent_wins'],logx=logx,order=order)
    display(Markdown("r=%2f" % r))

def knn_predict_cr(train_set,predict_set,n_neighbors=5):
    train_cols = ['hp','dam_avg','att_num','hit_mod','ac','init']
    predict_cols = ['cr']
    knn5 = KNeighborsRegressor(n_neighbors=5)
    knn5.fit(train_set[train_cols], train_set[predict_cols])
    predictions = knn5.predict(predict_set[train_cols])
    predict_set['predicted_cr'] = predictions
    predict_set.sort_values(['predicted_cr'],ascending=False)
    return predict_set

def is_success(s):
    v1 = s[0]
    v2 = s[1]
    return (abs(v1-v2) <= 1)

def calc_success(df,col1='cr',col2='predicted_cr'):
    rows = df.shape[0]
    successes = df[[col1,col2]].apply(is_success,axis=1)
    success_count = np.count_nonzero(successes)
    return success_count/rows

Correlation Between Combat Success and Attributes

In this experiment, we randomly generate a matrix of creature attributes and run a contest to determine the success rate of each combination. Examining the correaltion between each randomly-generated attribute and resulting success rate gives us an indication of its influence over combat success in context with the other attributes involved in the fundamental mechanic of melee combat.

In [123]:
randoms = create_randoms(100)
run_matches(randoms,100,'randoms')
Performing 4950 matches of 100 rounds each
Completed 4039465 turns in 833 seconds
In [16]:
randoms = pandas.read_csv('data/gen/randoms_100.csv')
ax = pandas.plotting.scatter_matrix(randoms.drop(['name','cr','Unnamed: 0','round_wins','calc_wins'],axis=1),alpha=0.2, figsize=(7, 7), diagonal='kde')
In [124]:
plot_correl('data/gen/randoms_100.csv','ac',subtitle='randomly-generated creatures')

Correlation between ac and wins: randomly-generated creatures

r=0.307892

In [125]:
plot_correl('data/gen/randoms_100.csv','init',subtitle='randomly-generated creatures')

Correlation between init and wins: randomly-generated creatures

r=-0.106624

In [127]:
plot_correl('data/gen/randoms_100.csv','hit_mod',subtitle='randomly-generated creatures')

Correlation between hit_mod and wins: randomly-generated creatures

r=0.117214

In [128]:
plot_correl('data/gen/randoms_100.csv','att_num',subtitle='randomly-generated creatures')

Correlation between att_num and wins: randomly-generated creatures

r=0.495110

In [129]:
plot_correl('data/gen/randoms_100.csv','dam_avg',subtitle='randomly-generated creatures')

Correlation between dam_avg and wins: randomly-generated creatures

r=0.628548

In [130]:
randoms = pandas.read_csv('data/gen/randoms_100.csv')
randoms['dam_max'] = randoms['dam_avg'] * randoms['att_num']
plot_correl_df(randoms,'dam_max',logx=True,subtitle='randomly-generated creatures')

Correlation between dam_max and wins: randomly-generated creatures

r=0.737025

In [131]:
plot_correl('data/gen/randoms_100.csv','hp',subtitle='randomly-generated creatures')

Correlation between hp and wins: randomly-generated creatures

r=0.554969

In [132]:
randoms = pandas.read_csv('data/gen/randoms_100.csv')
randoms['dam_max'] = randoms['dam_avg'] * randoms['att_num']
randoms['hp_dam'] = np.sqrt(randoms['dam_max'] * randoms['hp'])
plot_correl_df(randoms,'hp_dam',order=2,subtitle='randomly-generated creatures')

Correlation between hp_dam and wins: randomly-generated creatures

r=0.935537

In [4]:
use_crit_rules = False
randoms_nocrit = create_randoms(100)
run_matches(randoms_nocrit,100,'randoms_nocrit')
Performing 4950 matches of 100 rounds each
Completed 2880172 turns in 640 seconds
In [5]:
plot_correl('data/gen/randoms_nocrit_100.csv','ac',subtitle='randomly-generated creatures (no crit rules)')

Correlation between ac and wins: randomly-generated creatures (no crit rules)

r=0.135715

In [6]:
plot_correl('data/gen/randoms_nocrit_100.csv','init',subtitle='randomly-generated creatures (no crit rules)')

Correlation between init and wins: randomly-generated creatures (no crit rules)

r=-0.007726

In [7]:
plot_correl('data/gen/randoms_nocrit_100.csv','hit_mod',subtitle='randomly-generated creatures (no crit rules)')

Correlation between hit_mod and wins: randomly-generated creatures (no crit rules)

r=0.274752

In [8]:
plot_correl('data/gen/randoms_nocrit_100.csv','att_num',subtitle='randomly-generated creatures (no crit rules)')

Correlation between att_num and wins: randomly-generated creatures (no crit rules)

r=0.466934

In [9]:
plot_correl('data/gen/randoms_nocrit_100.csv','dam_avg',subtitle='randomly-generated creatures (no crit rules)')

Correlation between dam_avg and wins: randomly-generated creatures (no crit rules)

r=0.568584

In [10]:
randoms_nocrit = pandas.read_csv('data/gen/randoms_nocrit_100.csv')
randoms_nocrit['dam_max'] = randoms_nocrit['dam_avg'] * randoms_nocrit['att_num']
plot_correl_df(randoms_nocrit,'dam_max',logx=True,subtitle='randomly-generated creatures (no crit rules)')

Correlation between dam_max and wins: randomly-generated creatures (no crit rules)

r=0.656042

In [11]:
plot_correl('data/gen/randoms_nocrit_100.csv','hp',subtitle='randomly-generated creatures (no crit rules)')

Correlation between hp and wins: randomly-generated creatures (no crit rules)

r=0.626884

In [12]:
randoms_nocrit = pandas.read_csv('data/gen/randoms_nocrit_100.csv')
randoms_nocrit['dam_max'] = randoms_nocrit['dam_avg'] * randoms_nocrit['att_num']
randoms_nocrit['hp_dam'] = np.sqrt(randoms_nocrit['dam_max'] * randoms_nocrit['hp'])
plot_correl_df(randoms_nocrit,'hp_dam',order=2,subtitle='randomly-generated creatures (no crit rules)')

Correlation between hp_dam and wins: randomly-generated creatures (no crit rules)

r=0.938111

In [13]:
use_crit_rules = True

Generic Characters and Creatures

In this experiment, we generate characters and creatures by level or challenge rating (respectively) using published guidelines, and run contests for each type of individual creature or party to ensure a strong correlation between challenge rating and combat success rate and even distribution/progression for that type.

Generic Creatures

These creatures were generated using an interpretation of the guidelines on p. 274 of Dungeon Master's Guide (Wizards of the Coast, December 2014) in attempt to simulate a "typical" creature of each challenge rating from 0.125 - 30.

In [68]:
generics = pandas.read_csv('data/src/generics.csv')
run_matches(generics,100,'generics')
Performing 528 matches of 100 rounds each
Completed 132224 turns in 37 seconds
In [69]:
plot_correl('data/gen/generics_100.csv','cr',subtitle='single monster')

Correlation between cr and wins: single monster

r=0.999187

Generic Party of Four Fighters

The level of a party of four characters is meant to correspond to the challenge rating of a comparable single creature, or the sum of challenge ratings of a group of creatures. The human fighter is overwhelmingly the most popular character type, and arguably one of the most effective in melee combat. Here we assemble a party of four generic human fighters using an interpretation of the guidelines on pp. 70-75 of the Player's Handbook (Wizards of the Coast, August 2014). We exclude all but the most basic elements of character progress in relation to hp, ac, att_num, and dam_avg, assuming some upgrade in armour and ability score improvements (strength and constitution) as described.

In [70]:
fighters = pandas.read_csv('data/src/fighters.csv')
run_matches(fighters,100,'fighters')
Performing 190 matches of 100 rounds each
Completed 100132 turns in 60 seconds
In [178]:
plot_correl('data/gen/fighters_100.csv','cr',subtitle='party of 4 fighters')

Correlation between cr and wins: party of 4 fighters

r=1.000000

Generic Party of Four Wizards

The elven wizard is popular character type that relies on spellcasting to make powerful attacks, and is generally amongst the most weak in melee combat. Here we assemble a party of four generic elven wizards using an interpretation of the guidelines on pp. 112-119 of the Player's Handbook (Wizards of the Coast, August 2014). In order to simulate a more accurate wizard, we allow this character to cast the shocking grasp cantrip, a melee attack spell whose attack modifier and damage output will progress as the character progresses. We exclude all but the most basic elements of character progress in relation to hp, ac, att_num, and dam_avg, assuming ability score improvements (intelligence and constitution) as described.

In [7]:
wizards = pandas.read_csv('data/src/wizards.csv')
run_matches(wizards,100,'wizards')
Performing 190 matches of 100 rounds each
Completed 58682 turns in 17 seconds
In [8]:
plot_correl('data/gen/wizards_100.csv','cr',subtitle='party of 4 wizards')

Correlation between cr and wins: party of 4 wizards

r=0.995489

Many Weaker Monsters

Since there is some positive correlation between number of attacks and combat effectiveness, here we assemble a group of between 1 and 30 monsters with a challenge rating of 1. This provides a linear increase in number of attacks far beyond that of a single typical CR 30 monster.

In [72]:
generics_to30 = pandas.read_csv('data/src/generics_to30.csv')
run_matches(generics_to30,100,'generics_to30')
Performing 435 matches of 100 rounds each
Completed 446681 turns in 378 seconds
In [73]:
plot_correl('data/gen/generics_to30_100.csv','cr',subtitle='1-30 CR 1 monster(s)')

Correlation between cr and wins: 1-30 CR 1 monster(s)

r=1.000000

Contests Between Characters and Creatures

Party Characters versus Single Monster

Next, we run a contest between our party of four fighters, our party of four wizards, and our single monsters

In [13]:
fighters = pandas.read_csv('data/src/fighters.csv')
fighters['type'] = 'party of 4 fighters'
wizards = pandas.read_csv('data/src/wizards.csv')
wizards['type'] = 'party of 4 wizards'
generics = pandas.read_csv('data/src/generics.csv')
generics['type'] = 'single monster'
mixed = generics.append(fighters).append(wizards)
mixed = mixed.reset_index(drop=True)
run_matches(mixed,100,'characters_monster')
Performing 2628 matches of 100 rounds each
Completed 820577 turns in 315 seconds
In [14]:
plot_correl('data/gen/characters_monster_100.csv','cr',subtitle='party of 4 characters and single generic monsters')

Correlation between cr and wins: party of 4 characters and single generic monsters

r=0.920837

Party of Characters versus Many Weaker Monsters

We then run a contest between our party of four fighers, party of four wizards, and 1-30 CR 1 monsters

In [15]:
fighters = pandas.read_csv('data/src/fighters.csv')
fighters['type'] = 'party of 4 fighters'
wizards = pandas.read_csv('data/src/wizards.csv')
wizards['type'] = 'party of 4 wizards'
generics_to30 = pandas.read_csv('data/src/generics_to30.csv')
generics_to30['type'] = '1-30 CR 1 monster(s)'
mixed_to30 = generics_to30.append(fighters).append(wizards)
mixed_to30 = mixed_to30.reset_index(drop=True)
run_matches(mixed_to30,100,'characters_to30monsters')
Performing 2415 matches of 100 rounds each
Completed 1342159 turns in 866 seconds
In [16]:
mixed_to30 = pandas.read_csv('data/gen/characters_to30monsters_100.csv')
plot_correl_df(mixed_to30,'cr',subtitle='party of 4 characters and 1-30 CR 1 monsters')

Correlation between cr and wins: party of 4 characters and 1-30 CR 1 monsters

r=0.882281

Party of Characters, Single Monster, and Many Weaker Monsters

We then run a contest between our party of four fighers, party of four wizards, single monsters, and 1-30 CR 1 monsters

In [5]:
generics = pandas.read_csv('data/src/generics.csv')
generics['type'] = 'single monster'
generics_to30 = pandas.read_csv('data/src/generics_to30.csv')
generics_to30['type'] = '1-30 CR 1 monster(s)'
fighters = pandas.read_csv('data/src/fighters.csv')
fighters['type'] = 'party of 4 fighters'
wizards = pandas.read_csv('data/src/wizards.csv')
wizards['type'] = 'party of 4 wizards'
mixed_all = generics.append(generics_to30).append(fighters).append(wizards)
mixed_all = mixed_all.reset_index(drop=True)
run_matches(mixed_all,100,'mixed_all')
Performing 5253 matches of 100 rounds each
Completed 2312682 turns in 1319 seconds
In [6]:
mixed_to30 = pandas.read_csv('data/gen/mixed_all_100.csv')
plot_correl_df(mixed_to30,'cr',subtitle='party of 4 characters, single monster, and 1-30 CR 1 monsters')

Correlation between cr and wins: party of 4 characters, single monster, and 1-30 CR 1 monsters

r=0.919240

In [172]:
generics = pandas.read_csv('data/src/generics.csv')
fighters = pandas.read_csv('data/src/fighters.csv')
fighters = knn_predict_cr(generics,fighters)
display(Markdown('## Classifying fighters using k-nearest neighbours (k=5) trained on generic monsters'))
display(Markdown('### Correlation between %s and %s' % ('cr','predicted_cr')))
r = np.corrcoef(fighters['cr'],fighters['predicted_cr'])[0][1]
a = calc_success(fighters)
display(Markdown('r=%2f, a=%2f' % (r,a)))
ax = fighters.plot.scatter('cr','predicted_cr',ylim=(0,30),xlim=(0,30))

Classifying fighters using k-nearest neighbours (k=5) trained on generic monsters

Correlation between cr and predicted_cr

r=0.979563, a=0.150000

In [173]:
generics = pandas.read_csv('data/src/generics.csv')
wizards = pandas.read_csv('data/src/wizards.csv')
wizards = knn_predict_cr(generics,wizards)
display(Markdown('## Classifying wizards using k-nearest neighbours (k=5) trained on generic monsters'))
display(Markdown('### Correlation between %s and %s' % ('cr','predicted_cr')))
r = np.corrcoef(wizards['cr'],wizards['predicted_cr'])[0][1]
a = calc_success(wizards)
display(Markdown('r=%2f, a=%2f' % (r,a)))
ax = wizards.plot.scatter('cr','predicted_cr',ylim=(0,20),xlim=(0,20))

Classifying wizards using k-nearest neighbours (k=5) trained on generic monsters

Correlation between cr and predicted_cr

r=0.990688, a=0.550000

In [174]:
generics = pandas.read_csv('data/src/generics.csv')
creatures = pandas.read_csv('data/src/creatures.csv')
creatures = knn_predict_cr(generics,creatures)
display(Markdown('## Classifying SRD monsters using k-nearest neighbours (k=5) trained on generic monsters'))
display(Markdown('### Correlation between %s and %s' % ('cr','predicted_cr')))
r = np.corrcoef(creatures['cr'],creatures['predicted_cr'])[0][1]
a = calc_success(creatures)
display(Markdown('r=%2f, a=%2f' % (r,a)))
ax = creatures.plot.scatter('cr','predicted_cr',ylim=(0,30),xlim=(0,30))

Classifying SRD monsters using k-nearest neighbours (k=5) trained on generic monsters

Correlation between cr and predicted_cr

r=0.924585, a=0.515432

In [135]:
creatures = pandas.read_csv('data/src/creatures.csv')
run_matches(creatures,100,'creatures')
Performing 52326 matches of 100 rounds each
Completed 25959209 turns in 3930 seconds
In [21]:
plot_correl('data/gen/creatures_100.csv','cr',subtitle='SRD monsters',logx=True)

Correlation between cr and wins: SRD monsters

r=0.749239

In [23]:
generics = pandas.read_csv('data/src/generics.csv')
creatures = pandas.read_csv('data/gen/creatures_100.csv')
creatures = knn_predict_cr(generics,creatures)
plot_correl_df(creatures,'predicted_cr',subtitle='SRD monsters',logx=True)

Correlation between predicted_cr and wins: SRD monsters

r=0.643651

In [194]:
progs = create_progressives(100,roll_adv)
run_matches(progs,100,'progs')
Performing 4950 matches of 100 rounds each
Completed 1541902 turns in 362 seconds
In [195]:
generics = pandas.read_csv('data/src/generics.csv')
creatures = pandas.read_csv('data/gen/progs_100.csv')
creatures = knn_predict_cr(generics,creatures)
plot_correl_df(creatures,'predicted_cr',subtitle='progressively-increasing random monsters')

Correlation between predicted_cr and wins: progressively-increasing random monsters

r=0.969457

In [196]:
generics = pandas.read_csv('data/src/generics.csv')
creatures = pandas.read_csv('data/gen/randoms_100.csv')
creatures = knn_predict_cr(generics,creatures)
plot_correl_df(creatures,'predicted_cr',subtitle='random monsters')

Correlation between predicted_cr and wins: random monsters

r=0.547144

In [24]:
generics = pandas.read_csv('data/src/generics.csv')
creatures = pandas.read_csv('data/src/creatures.csv')
creatures = knn_predict_cr(creatures,generics)
display(Markdown('## Classifying generic monsters using k-nearest neighbours (k=5) trained on SRD monsters'))
display(Markdown('### Correlation between %s and %s' % ('cr','predicted_cr')))
r = np.corrcoef(creatures['cr'],creatures['predicted_cr'])[0][1]
a = calc_success(creatures)
display(Markdown('r=%2f, a=%2f' % (r,a)))
ax = creatures.plot.scatter('cr','predicted_cr',ylim=(0,30),xlim=(0,30))

Classifying generic monsters using k-nearest neighbours (k=5) trained on SRD monsters

Correlation between cr and predicted_cr

r=0.970249, a=0.212121

In [29]:
creatures = pandas.read_csv('data/src/creatures.csv')
train, test = train_test_split(creatures, test_size=0.2)
creatures = knn_predict_cr(train,test)
display(Markdown('## Classifying SRD monsters using k-nearest neighbours (k=5) trained on SRD monsters'))
display(Markdown('### Correlation between %s and %s' % ('cr','predicted_cr')))
r = np.corrcoef(creatures['cr'],creatures['predicted_cr'])[0][1]
a = calc_success(creatures)
display(Markdown('r=%2f, a=%2f' % (r,a)))
ax = creatures.plot.scatter('cr','predicted_cr',ylim=(0,30),xlim=(0,30))

Classifying SRD monsters using k-nearest neighbours (k=5) trained on SRD monsters

Correlation between cr and predicted_cr

r=0.965150, a=0.676923

In [ ]: