Created
March 24, 2020 14:26
-
-
Save iemcd/ea71e0b911de6c8e9cf696bd8358e141 to your computer and use it in GitHub Desktop.
Partitioning Hands of Dice
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
#!/usr/bin/python3 | |
import math | |
import numpy as np | |
#import pandas as pd | |
import matplotlib as mpl | |
import matplotlib.pyplot as plt | |
def decrement(X): | |
if sum(X) == len(X): #this is a kludge to make the while loop stop | |
X.pop() | |
X.append(0) | |
return X | |
X.sort(reverse=True) | |
Y = X.pop() | |
if Y > 1: | |
Y -= 1 | |
X.append(Y) | |
elif Y == 1: | |
decrement(X) | |
X.append(X[-1]) | |
return X | |
def karmarkar_karp(X): | |
Y = list(X) | |
while len(Y) > 1: | |
Y.sort() | |
diff = Y.pop() - Y.pop() | |
Y.append(diff) | |
return Y[0] | |
def count_perms(X): | |
cts = [0] | |
cts *= max(X) | |
den = 1 | |
for i in X: | |
cts[i-1] += 1 #i-1 accounts for the zero-indexing | |
for i in cts: | |
den = den*math.factorial(i) | |
num = math.factorial(len(X)) | |
return num/den | |
def mechanic(no_dice, no_sides): | |
roll = [no_sides] | |
roll *= no_dice | |
dice = [] | |
while sum(roll) > (len(roll)-1): | |
dice += [list(roll)] | |
decrement(roll) | |
return dice | |
#def single_odds(rem_list, tot_list, odd_list): | |
# rem = np.array(rem_list) | |
# tot = np.array(tot_list) | |
# odd = np.array(odd_list) | |
# # these arrays are "horizontal" | |
# lo_scores = 0.5*(tot - rem) | |
# hi_scores = 0.5*(tot + rem) | |
# scores = np.hstack((lo_scores, hi_scores)) | |
# stretch_odds = 0.5*np.hstack((odd,odd)) | |
# df = pd.DataFrame(np.transpose(np.vstack((scores,stretch_odds)))) | |
# dist = pd.pivot_table(df, values=1, index=0, aggfunc=np.sum) | |
# return dist | |
m7d6 = mechanic(7,6) | |
m7d6_sum = [] | |
for roll in m7d6: | |
m7d6_sum.append(sum(roll)) | |
m7d6_diff = [] | |
for roll in m7d6: | |
m7d6_diff.append(karmarkar_karp(roll)) | |
m7d6_perms = [] | |
for roll in m7d6: | |
m7d6_perms.append(count_perms(roll)) | |
m7d6_perms_arr = np.array(m7d6_perms) | |
m7d6_odds = (1/sum(m7d6_perms_arr))*m7d6_perms_arr | |
m7d6_xrange = np.array(range(min(m7d6_sum),max(m7d6_sum))) | |
w, h = mpl.figure.figaspect(0.32) #aspect found by trial-and-error to look OK | |
fig1 = plt.figure(figsize=(w,h)) | |
ax11 = plt.hexbin(m7d6_sum, m7d6_diff, gridsize=(17,3), cmap='binary') | |
#gridsizes are half of ranges, rounded down (one unit of a hexbin is apparently a 4x4 section of hexagons) | |
plt.xlabel('Maximum Difference (Sum)') | |
plt.ylabel('Minimum Difference') | |
plt.box(None) | |
plt.tick_params(length=0) | |
#plt.show() | |
fig1.savefig('figure1.png', bbox_inches='tight') | |
m7d6_sum_np = np.array(m7d6_sum) | |
m7d6_diff_np = np.array(m7d6_diff) | |
m7d6_hi = 0.5*(m7d6_sum_np + m7d6_diff_np) | |
m7d6_lo = 0.5*(m7d6_sum_np - m7d6_diff_np) | |
m7d6_half = 0.5*m7d6_sum_np | |
fig2, ax21 = plt.subplots() | |
n1, bins1, patches1 = ax21.hist(np.hstack((m7d6_hi, m7d6_lo)), np.arange(2.5,25.5,1),density=1, rwidth=0.5, label='7d6, Partitioned') | |
n2, bins2, patches2 = ax21.hist(m7d6_half, np.arange(3.25,21.75,0.5), density=1, histtype='step', label='7d6, Halved') | |
#considered bins1=bins2, but there are also half-points in the second one | |
plt.xlabel('Individual Score') | |
plt.box(None) | |
plt.tick_params(length=0) | |
ax21.set_yticklabels(['']*len(ax21.get_yticklabels())) | |
plt.legend(frameon=False) | |
#plt.show() | |
fig2.savefig('figure2.png', bbox_inches='tight') | |
count = [3, 4, 5, 6, 7, 8] #defining numbers of dice to roll & partition | |
sides = [4, 6, 8, 10, 12, 20] #defining the common dice | |
def chance_diff(no_dice, no_sides): | |
rolls = mechanic(no_dice, no_sides) | |
rolls_diff = np.array([karmarkar_karp(hand) for hand in rolls], dtype=bool) | |
# rolls_diff != 0 #this comparison seems equivalent to "dtype=bool", but causes numerical errors at scale | |
rolls_perms = np.array([count_perms(hand) for hand in rolls]) | |
chance = sum( rolls_diff * rolls_perms / sum(rolls_perms)) | |
return chance | |
#def avg_diff(no_dice, no_sides): | |
# rolls = mechanic(no_dice, no_sides) | |
# #rolls_sum = np.array([sum(hand) for hand in rolls]) #considered "normalizing" by this value, decided against it | |
# rolls_diff = np.array([karmarkar_karp(hand) for hand in rolls]) | |
# rolls_perms = np.array([count_perms(hand) for hand in rolls]) | |
# avg = sum(rolls_diff * rolls_perms / sum(rolls_perms)) | |
# return avg | |
chances = np.array([[chance_diff(a, b) for b in sides] for a in count]) | |
fig3, ax31 = plt.subplots() | |
im = ax31.imshow(chances, cmap='binary', norm=mpl.colors.Normalize(0,1)) | |
ax31.tick_params(top=True, bottom=False, labeltop=True, labelbottom=False, length=0) | |
ax31.set_xticks(np.arange(len(sides))) | |
ax31.set_xticklabels(['d{}'.format(sides[a]) for a in np.arange(len(sides))]) | |
ax31.set_yticks(np.arange(len(count))) | |
ax31.set_yticklabels(count) | |
for i in range(len(count)): | |
for j in range(len(sides)): | |
text = ax31.text(j, i, '{:4.2f}'.format(chances[i, j]), ha='center', va='center', color='w') | |
for edge, spine in ax31.spines.items(): | |
spine.set_visible=False | |
plt.box(None) | |
#plt.show() | |
fig3.savefig('figure3.png', bbox_inches='tight') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment