Skip to content

Instantly share code, notes, and snippets.

@iemcd
Created March 24, 2020 20:16
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/442f296e06da11ca9ebbea1f75c50294 to your computer and use it in GitHub Desktop.
Save iemcd/442f296e06da11ca9ebbea1f75c50294 to your computer and use it in GitHub Desktop.
Partitioning Hands of Dice, Better
#!/usr/bin/python3
# Starting over, a little cleaner and a little better
# Ian McDougall, March 2020
import math
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
### Dice Functions
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: #think of this like a really bad hash function
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
### Exploratory Functions
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
### Plotting Functions
def my_hexbin(ax, sums, diffs, odds):
#todo: force integer tick labels
xgrid = (max(sums) - min(sums))//2
ygrid = (max(diffs) - min(diffs))//2
ax.hexbin(sums, diffs, C=odds, gridsize=(xgrid, ygrid), cmap='Blues')
ax.set_xlabel('Maximum Difference (Sum)')
ax.set_ylabel('Minimum Difference')
ax.set_frame_on(False)
ax.tick_params(length=0)
return ax
def my_hist(ax, sums, diffs, odds, dice):
his = 0.5 * (sums + diffs)
los = 0.5 * (sums - diffs)
half = 0.5 * sums
ax.hist(
np.hstack((his, los)),
np.arange(min(los)-0.5, max(his)+1.5, 1),
density=1,
rwidth=0.5,
label=dice+', Partitioned',
weights=np.hstack((odds,odds))
)
ax.hist(
half,
np.arange(min(half)-0.25, max(half)+0.75, 0.5),
density=1,
histtype='step',
label=dice+', Halved',
weights=odds
)
ax.set_xlabel('Individual Scores')
ax.set_yticks([])
ax.set_frame_on(False)
ax.tick_params(length=0)
ax.legend(frameon=False)
return ax
### Testing with 7d6
m7d6 = mechanic(7,6)
m7d6_sum = np.array([sum(hand) for hand in m7d6])
m7d6_diff = np.array([karmarkar_karp(hand) for hand in m7d6])
m7d6_perms = np.array([count_perms(hand) for hand in m7d6])
w, h = mpl.figure.figaspect(0.29)
#aspect is from a hexagon (sqrt(3)/2) and the grid sizes
fig1, ax11 = plt.subplots(figsize=(w,h))
my_hexbin(ax11, m7d6_sum, m7d6_diff, m7d6_perms)
fig1.savefig('figure1.png', bbox_inches='tight')
fig2, ax21 = plt.subplots()
my_hist(ax21, m7d6_sum, m7d6_diff, m7d6_perms, '7d6')
fig2.savefig('figure2.png', bbox_inches='tight')
### Exploration
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
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)
fig3.savefig('figure3.png', bbox_inches='tight')
### Testing with 3d20
m3d20 = mechanic(3,20)
m3d20_sum = np.array([sum(hand) for hand in m3d20])
m3d20_diff = np.array([karmarkar_karp(hand) for hand in m3d20])
m3d20_perms = np.array([count_perms(hand) for hand in m3d20])
w, h = mpl.figure.figaspect(0.63)
fig4, ax41 = plt.subplots(figsize=(w,h))
my_hexbin(ax41, m3d20_sum, m3d20_diff, m3d20_perms)
fig4.savefig('figure4.png', bbox_inches='tight')
fig5, ax51 = plt.subplots()
my_hist(ax51, m3d20_sum, m3d20_diff, m3d20_perms, '3d20')
fig5.savefig('figure5.png', bbox_inches='tight')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment