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
debug_rounds = False
debug_matches = False
use_crit_rules = True
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
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.
randoms = create_randoms(100)
run_matches(randoms,100,'randoms')
FloatProgress(value=0.0, description='Contest: ', max=4950.0)
Performing 4950 matches of 100 rounds each Completed 4039465 turns in 833 seconds
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')
plot_correl('data/gen/randoms_100.csv','ac',subtitle='randomly-generated creatures')
plot_correl('data/gen/randoms_100.csv','init',subtitle='randomly-generated creatures')
plot_correl('data/gen/randoms_100.csv','hit_mod',subtitle='randomly-generated creatures')
plot_correl('data/gen/randoms_100.csv','att_num',subtitle='randomly-generated creatures')
plot_correl('data/gen/randoms_100.csv','dam_avg',subtitle='randomly-generated creatures')
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')
plot_correl('data/gen/randoms_100.csv','hp',subtitle='randomly-generated creatures')
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')
use_crit_rules = False
randoms_nocrit = create_randoms(100)
run_matches(randoms_nocrit,100,'randoms_nocrit')
FloatProgress(value=0.0, description='Contest: ', max=4950.0)
Performing 4950 matches of 100 rounds each Completed 2880172 turns in 640 seconds
plot_correl('data/gen/randoms_nocrit_100.csv','ac',subtitle='randomly-generated creatures (no crit rules)')
plot_correl('data/gen/randoms_nocrit_100.csv','init',subtitle='randomly-generated creatures (no crit rules)')
plot_correl('data/gen/randoms_nocrit_100.csv','hit_mod',subtitle='randomly-generated creatures (no crit rules)')
plot_correl('data/gen/randoms_nocrit_100.csv','att_num',subtitle='randomly-generated creatures (no crit rules)')
plot_correl('data/gen/randoms_nocrit_100.csv','dam_avg',subtitle='randomly-generated creatures (no crit rules)')
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)')
plot_correl('data/gen/randoms_nocrit_100.csv','hp',subtitle='randomly-generated creatures (no crit rules)')
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)')
use_crit_rules = True
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.
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.
generics = pandas.read_csv('data/src/generics.csv')
run_matches(generics,100,'generics')
FloatProgress(value=0.0, description='Contest: ', max=528.0)
Performing 528 matches of 100 rounds each Completed 132224 turns in 37 seconds
plot_correl('data/gen/generics_100.csv','cr',subtitle='single monster')
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.
fighters = pandas.read_csv('data/src/fighters.csv')
run_matches(fighters,100,'fighters')
FloatProgress(value=0.0, description='Contest: ', max=190.0)
Performing 190 matches of 100 rounds each Completed 100132 turns in 60 seconds
plot_correl('data/gen/fighters_100.csv','cr',subtitle='party of 4 fighters')
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.
wizards = pandas.read_csv('data/src/wizards.csv')
run_matches(wizards,100,'wizards')
FloatProgress(value=0.0, description='Contest: ', max=190.0)
Performing 190 matches of 100 rounds each Completed 58682 turns in 17 seconds
plot_correl('data/gen/wizards_100.csv','cr',subtitle='party of 4 wizards')
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.
generics_to30 = pandas.read_csv('data/src/generics_to30.csv')
run_matches(generics_to30,100,'generics_to30')
FloatProgress(value=0.0, description='Contest: ', max=435.0)
Performing 435 matches of 100 rounds each Completed 446681 turns in 378 seconds
plot_correl('data/gen/generics_to30_100.csv','cr',subtitle='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'
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')
FloatProgress(value=0.0, description='Contest: ', max=2628.0)
Performing 2628 matches of 100 rounds each Completed 820577 turns in 315 seconds
plot_correl('data/gen/characters_monster_100.csv','cr',subtitle='party of 4 characters and single generic monsters')
We then run a contest between our party of four fighers, party of four wizards, and 1-30 CR 1 monsters
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')
FloatProgress(value=0.0, description='Contest: ', max=2415.0)
Performing 2415 matches of 100 rounds each Completed 1342159 turns in 866 seconds
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')
We then run a contest between our party of four fighers, party of four wizards, single monsters, and 1-30 CR 1 monsters
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')
FloatProgress(value=0.0, description='Contest: ', max=5253.0)
Performing 5253 matches of 100 rounds each Completed 2312682 turns in 1319 seconds
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')
r=0.919240
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))
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))
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))
creatures = pandas.read_csv('data/src/creatures.csv')
run_matches(creatures,100,'creatures')
FloatProgress(value=0.0, description='Contest: ', max=52326.0)
Performing 52326 matches of 100 rounds each Completed 25959209 turns in 3930 seconds
plot_correl('data/gen/creatures_100.csv','cr',subtitle='SRD monsters',logx=True)
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)
progs = create_progressives(100,roll_adv)
run_matches(progs,100,'progs')
FloatProgress(value=0.0, description='Contest: ', max=4950.0)
Performing 4950 matches of 100 rounds each Completed 1541902 turns in 362 seconds
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')
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')
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))
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))