Skip to content

Instantly share code, notes, and snippets.

@iemcd
Created March 24, 2020 14:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save iemcd/ea71e0b911de6c8e9cf696bd8358e141 to your computer and use it in GitHub Desktop.
Save iemcd/ea71e0b911de6c8e9cf696bd8358e141 to your computer and use it in GitHub Desktop.
Partitioning Hands of Dice
#!/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