def iter_standard_dice(): for die_size in [3, 4, 5, 6, 7, 8, 9, 10, 12]: metadata = { 'size' : die_size, 'threshold' : 0, 'feature' : 'standard die', 'faces' : [str(x) for x in range(1, die_size+1)], 'notes' : '', } yield Die.d(die_size), metadata
ax.set_ylabel('Chance (%) of hitting') ax.legend(['d20', 'd20 + d6 - 3.5', 'd20 + d12 - 6.5', 'd20 + d20 - 10.5']) plt.savefig('output/add_small_die_ccdf.png', dpi=dpi, bbox_inches="tight") # Coin flips. for coin_count in range(1, 11): fig = plt.figure(figsize=figsize) ax = plt.subplot(111) ax.grid() x = numpy.linspace(-3, 3, 1001) y = numpy.exp(-0.5 * numpy.square(x)) / numpy.sqrt(2 * numpy.pi) ax.plot(x, y, linestyle=':') coins = Die.d(coin_count, 2) gaussian = Die.gaussian(coins) ks = coins.ks_stat(gaussian) * 100.0 print('KS %d: %0.2f%%' % (coin_count, ks)) ax.plot((coins.outcomes() - coins.mean()) / coins.standard_deviation(), coins.pmf() * coins.standard_deviation(), marker='.') ax.set_xlim(-3, 3) ax.set_xlabel('Deviation from mean in SDs') ax.set_ylabel('Normalized probability') ax.set_ylim(0, 0.4) ax.set_title('%d coin(s): KS = %0.2f%%' % (coin_count, ks)) plt.savefig('output/frames/frame_%03d.png' % (coin_count - 1), dpi=dpi,
dpi = 120 def plot_extremeness(ax, die, **kwargs): extremeness = numpy.minimum(die.cdf(), die.ccdf()) ax.plot(die.outcomes(), extremeness, **kwargs) def semilogy_extremeness(ax, die, **kwargs): print('%0.3f | %0.3f' % (die.mean(), die.standard_deviation())) extremeness = numpy.minimum(die.cdf(), die.ccdf()) extremeness[extremeness > 0.5] = 1.0 ax.semilogy(die.outcomes(), 1.0 / extremeness, **kwargs) die_3d6 = Die.d(3, 6) points_3d6 = die_3d6.relabel(point_buy_to_use).repeat_and_sum(6) die_3d6r1 = Die.d(3, 5) + 3 points_3d6r1 = die_3d6r1.relabel(point_buy_to_use).repeat_and_sum(6) die_3d6r2 = Die.d(3, 4) + 6 points_3d6r2 = die_3d6r2.relabel(point_buy_to_use).repeat_and_sum(6) die_4d6kh3 = Die.d(6).repeat_and_keep_and_sum(4, keep_highest=3) points_4d6kh3 = die_4d6kh3.relabel(point_buy_to_use).repeat_and_sum(6) die_4d6r1kh3 = Die.d(5).repeat_and_keep_and_sum(4, keep_highest=3) + 3 points_4d6r1kh3 = die_4d6r1kh3.relabel(point_buy_to_use).repeat_and_sum(6) die_5d6kh3 = Die.d(6).repeat_and_keep_and_sum(5, keep_highest=3)
ax = plt.subplot(111) plot_pmf(ax, Die.from_faces([0] * 7 + [1] * 3).explode(100, chance=0.1), die_counts_simple, 'dNWoD') ax.set_xlim(left=left, right=right) ax.set_ylim(bottom=0) plt.savefig('output/sum_pool_nwod.png', dpi=dpi, bbox_inches="tight") # d6 fig = plt.figure(figsize=figsize) ax = plt.subplot(111) plot_pmf(ax, Die.d(6), die_counts_standard, 'd6') ax.set_xlim(left=left, right=right) ax.set_ylim(bottom=0) plt.savefig('output/sum_pool_d6.png', dpi=dpi, bbox_inches="tight") # half-life fig = plt.figure(figsize=figsize) ax = plt.subplot(111) plot_pmf(ax, Die.d(3, 2) - 3, die_counts_standard, '(3d2-3)') ax.set_xlim(left=left, right=right) ax.set_ylim(bottom=0) plt.savefig('output/sum_pool_3c.png', dpi=dpi, bbox_inches="tight")
fig = plt.figure(figsize=figsize) ax = plt.subplot(111) plot_opposed_fixed_side(ax, Die.coin(1 / 3), die_counts_simple, '(d6>=5)') ax.set_xlim(left=left, right=right) plt.savefig('output/add_pool_opposed_5plus.png', dpi=dpi, bbox_inches="tight") # exalted 2e fig = plt.figure(figsize=figsize) ax = plt.subplot(111) plot_opposed_fixed_side(ax, Die.from_faces([0] * 6 + [1] * 3 + [2]), die_counts_simple, 'Exalted2e') ax.set_xlim(left=left, right=right) plt.savefig('output/add_pool_opposed_exalted2e.png', dpi=dpi, bbox_inches="tight") # coin fig = plt.figure(figsize=figsize) ax = plt.subplot(111) plot_opposed_fixed_side(ax, Die.d(6), die_counts_simple, '6') ax.set_xlim(left=left, right=right) plt.savefig('output/add_pool_opposed_d6.png', dpi=dpi, bbox_inches="tight")
def iter_simple_dice(die_size): # standard die metadata = { 'size' : die_size, 'threshold' : 0, 'feature' : 'standard die', 'faces' : [str(x) for x in range(1, die_size+1)], 'notes' : '', } yield Die.d(die_size), metadata # exploding standard die (problem: VMR too high to be useful) """ metadata = { 'size' : die_size, 'threshold' : 0, 'feature' : 'exploding standard die', 'faces' : [str(x) for x in range(1, die_size+1)], 'notes' : '', } metadata['faces'][-1] += '!' yield Die.d(die_size).explode(10), metadata """ # odd standard dice if die_size % 2 == 0 and die_size >= 6 and die_size <= 10: metadata = { 'size' : die_size-1, 'threshold' : 0, 'feature' : 'standard die', 'faces' : [str(x) for x in range(1, die_size-1+1)], 'notes' : '', } yield Die.d(die_size-1), metadata # no feature for i in range(1, die_size): faces = [0]*i + [1]*(die_size-i) metadata = { 'size' : die_size, 'threshold' : i+1, 'feature' : 'none', 'faces' : [str(x) for x in faces], 'notes' : '', } if die_size == 6 and i == 3: metadata['notes'] = 'Burning Wheel' if die_size == 6 and i == 4: metadata['notes'] = 'Shadowrun 4e' yield Die.from_faces(faces), metadata # negative on 1 for i in range(2, die_size): faces = [-1] + [0]*(i-1) + [1]*(die_size-i) metadata = { 'size' : die_size, 'threshold' : i+1, 'feature' : '-1 success on bottom face', 'faces' : [str(x) for x in faces], 'notes' : '', } if die_size == 10 and i == 6: metadata['notes'] = 'Old World of Darkness' yield Die.from_faces(faces), metadata # double on max for i in range(1, die_size-1): faces = [0]*i + [1]*(die_size-i-1) + [2] metadata = { 'size' : die_size, 'threshold' : i+1, 'feature' : '2 successes on top face', 'faces' : [str(x) for x in faces], 'notes' : '', } if die_size == 10 and i == 6: metadata['notes'] = 'Exalted 2e' yield Die.from_faces(faces), metadata # explode for i in range(1, die_size): faces = [0]*i + [1]*(die_size-i) metadata = { 'size' : die_size, 'threshold' : i+1, 'feature' : 'explode on top face', 'faces' : [str(x) for x in faces], 'notes' : '', } metadata['faces'][-1] += '!' if die_size == 10 and i == 7: metadata['notes'] = 'New World of Darkness' yield Die.from_faces(faces).explode(100, chance=1/die_size), metadata """
def test_d_multiple(): assert Die.d(1, 2, 2).weights() == pytest.approx([2, 3, 2, 1])
from hdroller import Die result = '' base_bw_dice = 24 bw_scale = 2 for pbta_bonus in [-1, 0, 1, 2, 3]: num_bw_dice = int(bw_scale * pbta_bonus + base_bw_dice) bw_die = Die.coin(0.5).repeat_and_sum(num_bw_dice) pbta_high_chance = Die.d(2, 6) + pbta_bonus >= 11 pbta_mid_chance = Die.d(2, 6) + pbta_bonus >= 7 bw_target = base_bw_dice - (base_bw_dice // 2) bw_high_chance = bw_die >= bw_target + int(2 * bw_scale) bw_mid_chance = bw_die >= bw_target result += '\t'.join( str(x) for x in [ pbta_bonus, num_bw_dice, pbta_mid_chance, bw_mid_chance, pbta_high_chance, bw_high_chance ]) result += '\n' with open('output/pbta_bw.csv', mode='w') as outfile: outfile.write(result)
# pf2e fig = plt.figure(figsize=figsize) ax = plt.subplot(111) ax.set_xlabel('Number needed to hit') ax.set_ylabel('Mean damage') ax.grid(which='both') legend = [] x = numpy.arange(-35, 20) semilogy_mean_damage(ax, Die.d(12) + 4, x=x, damage=6.5, linestyle='--', zorder=2.1) legend.append('d12+4, damage = 6.5') semilogy_mean_damage_mos(ax, Die.d(24) - 1, damage_mult=1.0, x=x) legend.append('d24-2, damage = MoS + 1') semilogy_mean_damage_pf2e_mos(ax, Die.d(20) + 1, damage_mult=6.5, x=x) legend.append('d20, damage = 6.5 if hit, double if MoS >= 10') ax.set_xlim(left=-20, right=20) ax.set_ylim(bottom=1, top=50) ax.legend(legend, loc='upper right')
point_buy_pathfinder = { 3: -16, 4: -12, 5: -9, 6: -6, 7: -4, 8: -2, 9: -1, 10: 0, 11: 1, 12: 2, 13: 3, 14: 5, 15: 7, 16: 10, 17: 13, 18: 17, } die_3d6 = Die.d(3, 6) points_3d6 = die_3d6.relabel(point_buy_pathfinder).repeat_and_sum(6) start_time = time.perf_counter() repeated_result = points_3d6.keep_highest(9, 6) end_time = time.perf_counter() print('Repeated dice pool, 3d6x9 keep 6 highest') print(repeated_result) print('Computation time:', end_time - start_time)
import matplotlib.pyplot as plt figsize = (8, 4.5) dpi = 120 def opposed_keep_highest(x, half_life=3): attack_ratio = numpy.power(0.5, x / half_life) ccdf = attack_ratio / (attack_ratio + 1.0) return Die.from_ccdf(ccdf, x[0]) left = -20 right = 20 x = numpy.arange(left * 2, right * 2+1) okh = opposed_keep_highest(x) laplace = Die.laplace(half_life=3) - Die.coin(0.5) opposed_simple = Die.d(10).explode(3) - Die.d(10).explode(3) - Die.coin(0.5) exploding = Die.d(10).explode(3) + Die.d(1, 12) opposed_exploding = exploding - exploding - Die.coin(0.5) legend = [ 'Logistic, half-life = 3', 'Opposed d10! + d12', 'Laplace, half-life = 3', 'Opposed d10!', ] # ccdf fig = plt.figure(figsize=figsize) ax = plt.subplot(111)
plt.savefig('output/explode_non_opposed_ideal.png', dpi = dpi, bbox_inches = "tight") # actual fig = plt.figure(figsize=figsize) ax = plt.subplot(111) ax.set_xlabel('Target number') ax.set_ylabel('Chance of hitting') ax.grid(which = "both") ax.set_title('Actual') legend = [] for die_size in die_sizes: die = Die.d(die_size).explode(10) y = [die >= t for t in x] ax.semilogy(x, y) legend.append('d%d!' % die_size) ax.set_xticks(xticks) ax.set_xlim(left=0, right=20) ax.set_ylim(top=1, bottom=1e-2) ax.legend(legend, loc = 'lower left') plt.savefig('output/explode_non_opposed_actual.png', dpi = dpi, bbox_inches = "tight") # big-small fig = plt.figure(figsize=figsize) ax = plt.subplot(111)
6: -6, 7: -4, 8: -2, 9: -1, 10: 0, 11: 1, 12: 2, 13: 3, 14: 5, 15: 7, 16: 10, 17: 13, 18: 17, } points_3d6 = Die.d(3, 6).relabel(point_buy_pathfinder).repeat_and_sum(6) print(points_3d6.mean(), points_3d6.standard_deviation(), points_3d6.total_mass()) points_3d6_7 = Die.d(3, 6).relabel(point_buy_pathfinder).repeat_and_keep_and_sum2( 7, keep_highest=6) print(points_3d6_7.mean(), points_3d6_7.standard_deviation(), points_3d6_7.total_mass()) start = time.time() points_3d6_9 = Die.d(3, 6).relabel(point_buy_pathfinder).repeat_and_keep_and_sum2( 9, keep_highest=6) print(points_3d6_9.mean(), points_3d6_9.standard_deviation(), points_3d6_9.total_mass())
from hdroller import Die import numpy import matplotlib as mpl import matplotlib.pyplot as plt for i in range(3, 22, 2): median = Die.d(20).repeat_and_keep_and_sum(i, keep_middle=1) adv = Die.d(20).advantage(i) print('%dd20' % i) print(median.standard_deviation()) print(adv.standard_deviation()) multi_median = Die.d(20) for i in range(3): multi_median = multi_median.repeat_and_keep_and_sum(3, keep_middle=1) print('multimedian steps:', i+1) print(multi_median, multi_median.standard_deviation()) print(Die.d(20).repeat_and_keep_and_sum(3, keep_middle=1).repeat_and_keep_and_sum(7, keep_middle=1).standard_deviation())
def test_mix_weight(): outcomes = range(2, 13) mix_weights = [1, 2, 3, 4, 5, 6, 5, 4, 3, 2, 1] assert Die.mix(*outcomes, mix_weights=mix_weights).pmf() == pytest.approx( Die.d(2, 6).pmf())
ax.set_yticks(yticks_minor, minor=True) ax.set_yticklabels('%d' % t for t in yticks_major) ax.set_yticklabels(yticklabels_minor, minor=True) ax.set_ylim(bottom=0, top=top) ax2.set_yticks(yticks_major) ax2.set_yticks(yticks_minor, minor=True) ax2.set_yticklabels('%d' % t for t in yticks_major) ax2.set_yticklabels(yticklabels_minor, minor=True) ax2.set_ylim(bottom=0, top=top) plt.savefig('output/keep_highest_comparison_linear.png', dpi=dpi, bbox_inches="tight") fig = plt.figure(figsize=figsize) ax = plt.subplot(111) ax.set_xlabel('1 / miss chance') ax.set_ylabel('Number of dice / -log2(miss chance)') ax.grid(which='major', alpha=1.0) ax.grid(which='minor', alpha=0.25) miss_chances = numpy.logspace(-20, 0, base=2.0) for base_tn in range(20): plot_random_tn_curve(ax, Die.d6.explode(5), Die.d(2, 6), base_tn, miss_chances) plt.savefig('output/keep_highest_random_tn.png', dpi=dpi, bbox_inches="tight")