For quite some time there has been a discussion in my board game group whether is better:

Scenario A: (The 47 Method)

Roll 4 * 6 sided dice

Pick the 3 best dice that were rolled

This generates a a total from 3 to 18

Perform this task 7 times, pick the 6 best sets.

Scenario B: (The 56 Method)

Roll 5 * 6 sided dice

pick the 3 best dice that were rolled

this generates a total from 3 to 18

perform this task 6 times

The benefits of each one is clear, with scenario A you can remove 1 terribly rolled set but with scenario B you have a better chance of getting good sets but take the risk of getting a terrible set you can’t get rid of.

We are perfectly aware that we could google this but where is the fun in that ?

One member of our group planned on doing a mathematical proof but hasn’t gotten around to it and I decided to simply perform a mathematical experiment.

I though I would get away with using bash but when I realized the calculations would take ~35 hours for just 100K simulations I decided to rewrite more efficient code. I ended up writing this Python code that performs 1M simulations in less then 4 minutes:

(expand code section to read the code)

import random import numpy as np import sys print roll_turns = int(sys.argv[2]) dice_used = int(sys.argv[1]) times_to_run = int(sys.argv[3]) def roll_dice(): return random.randint(1,6) results = [ ] d1s = [ ] d2s = [ ] d3s = [ ] d4s = [ ] d5s = [ ] d6s = [ ] print(str(dice_used) + " Dice rolled " + str(roll_turns) + " times.") print(str(times_to_run) + " iterations") for x in range(0,times_to_run): dice_sets = [ ] for t in range(0,roll_turns): dice_rolls = [ ] for d in range(0,dice_used): dice_rolls.append(roll_dice()) sorted_rolls = sorted(dice_rolls,reverse=True) cut_rolls = sorted_rolls[:3] sum_rolls = sum(cut_rolls) dice_sets.append(sum_rolls) sorted_dice_sets = sorted(dice_sets,reverse=True) cut_sets = sorted_dice_sets[:6] sumset = np.sum(cut_sets,0) d1s.append(cut_sets[0]) d2s.append(cut_sets[1]) d3s.append(cut_sets[2]) d4s.append(cut_sets[3]) d5s.append(cut_sets[4]) d6s.append(cut_sets[5]) avg_dice = np.average(cut_sets) results.append(avg_dice) avg_total = np.average(results) print print("Name | MIN | MAX | AVG ") print("Set 1 | %s | %s | %s") %(str(min(d1s)),str(max(d1s)),str(np.mean(d1s))) print("Set 2 | %s | %s | %s") %(str(min(d2s)),str(max(d2s)),str(np.mean(d2s))) print("Set 3 | %s | %s | %s") %(str(min(d3s)),str(max(d3s)),str(np.mean(d3s))) print("Set 4 | %s | %s | %s") %(str(min(d4s)),str(max(d4s)),str(np.mean(d4s))) print("Set 5 | %s | %s | %s") %(str(min(d5s)),str(max(d5s)),str(np.mean(d5s))) print("Set 6 | %s | %s | %s") %(str(min(d6s)),str(max(d6s)),str(np.mean(d6s))) print("Name | MAX | MIN | AVG ") print("Total average is: " + str(avg_total)[0:4]) print("Total average is: " + str(avg_total)) print print file = "res/dice_sets" + str(dice_used) + str(roll_turns) + str(times_to_run) + ".txt" f1=open(file, "a+") f1.write(str(results)) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 import random import numpy as np import sys print roll_turns = int ( sys . argv [ 2 ] ) dice_used = int ( sys . argv [ 1 ] ) times_to_run = int ( sys . argv [ 3 ] ) def roll_dice ( ) : return random . randint ( 1 , 6 ) results = [ ] d1s = [ ] d2s = [ ] d3s = [ ] d4s = [ ] d5s = [ ] d6s = [ ] print ( str ( dice_used ) + " Dice rolled " + str ( roll_turns ) + " times." ) print ( str ( times_to_run ) + " iterations" ) for x in range ( 0 , times_to_run ) : dice_sets = [ ] for t in range ( 0 , roll_turns ) : dice_rolls = [ ] for d in range ( 0 , dice_used ) : dice_rolls . append ( roll_dice ( ) ) sorted_rolls = sorted ( dice_rolls , reverse = True ) cut_rolls = sorted_rolls [ : 3 ] sum_rolls = sum ( cut_rolls ) dice_sets . append ( sum_rolls ) sorted_dice_sets = sorted ( dice_sets , reverse = True ) cut_sets = sorted_dice_sets [ : 6 ] sumset = np . sum ( cut_sets , 0 ) d1s . append ( cut_sets [ 0 ] ) d2s . append ( cut_sets [ 1 ] ) d3s . append ( cut_sets [ 2 ] ) d4s . append ( cut_sets [ 3 ] ) d5s . append ( cut_sets [ 4 ] ) d6s . append ( cut_sets [ 5 ] ) avg_dice = np . average ( cut_sets ) results . append ( avg_dice ) avg_total = np . average ( results ) print print ( "Name | MIN | MAX | AVG " ) print ( "Set 1 | %s | %s | %s" ) % ( str ( min ( d1s ) ) , str ( max ( d1s ) ) , str ( np . mean ( d1s ) ) ) print ( "Set 2 | %s | %s | %s" ) % ( str ( min ( d2s ) ) , str ( max ( d2s ) ) , str ( np . mean ( d2s ) ) ) print ( "Set 3 | %s | %s | %s" ) % ( str ( min ( d3s ) ) , str ( max ( d3s ) ) , str ( np . mean ( d3s ) ) ) print ( "Set 4 | %s | %s | %s" ) % ( str ( min ( d4s ) ) , str ( max ( d4s ) ) , str ( np . mean ( d4s ) ) ) print ( "Set 5 | %s | %s | %s" ) % ( str ( min ( d5s ) ) , str ( max ( d5s ) ) , str ( np . mean ( d5s ) ) ) print ( "Set 6 | %s | %s | %s" ) % ( str ( min ( d6s ) ) , str ( max ( d6s ) ) , str ( np . mean ( d6s ) ) ) print ( "Name | MAX | MIN | AVG " ) print ( "Total average is: " + str ( avg_total ) [ 0 : 4 ] ) print ( "Total average is: " + str ( avg_total ) ) print print file = "res/dice_sets" + str ( dice_used ) + str ( roll_turns ) + str ( times_to_run ) + ".txt" f1 = open ( file , "a+" ) f1 . write ( str ( results ) )

The results of this program were consistent with earlier version of it which had less detail. Yes, the 56 Method produces higher overall stats with a considerable higher average score and a higher highest score and we could therefor say: The 56 method won, experiment over.

But on the downside the 56 Method has a lower lowest score and it is up to personal taste whether it is better to get a higher highest stat at the expanse of the lowest stat or not

$python dicor.py 4 7 1000000 4 Dice rolled 7 times. 1000000 iterations Name | MIN | MAX | AVG Set 1 | 9 | 18 | 15.855957 Set 2 | 8 | 18 | 14.485461 Set 3 | 6 | 18 | 13.393136 Set 4 | 6 | 17 | 12.370043 Set 5 | 4 | 17 | 11.303114 Set 6 | 3 | 16 | 10.054374 Name | MAX | MIN | AVG Total average is: 12.9 Total average is: 12.9103475 ------------------------------------- $python dicor.py 5 6 1000000 5 Dice rolled 6 times. 1000000 iterations Name | MIN | MAX | AVG Set 1 | 10 | 18 | 16.437414 Set 2 | 9 | 18 | 15.20922 Set 3 | 7 | 18 | 14.14286 Set 4 | 6 | 18 | 13.06095 Set 5 | 3 | 17 | 11.799312 Set 6 | 3 | 17 | 9.93538 Name | MAX | MIN | AVG Total average is: 13.4 Total average is: 13.430856 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 $ python dicor . py 4 7 1000000 4 Dice rolled 7 times . 1000000 iterations Name | MIN | MAX | AVG Set 1 | 9 | 18 | 15.855957 Set 2 | 8 | 18 | 14.485461 Set 3 | 6 | 18 | 13.393136 Set 4 | 6 | 17 | 12.370043 Set 5 | 4 | 17 | 11.303114 Set 6 | 3 | 16 | 10.054374 Name | MAX | MIN | AVG Total average is : 12.9 Total average is : 12.9103475 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - $ python dicor . py 5 6 1000000 5 Dice rolled 6 times . 1000000 iterations Name | MIN | MAX | AVG Set 1 | 10 | 18 | 16.437414 Set 2 | 9 | 18 | 15.20922 Set 3 | 7 | 18 | 14.14286 Set 4 | 6 | 18 | 13.06095 Set 5 | 3 | 17 | 11.799312 Set 6 | 3 | 17 | 9.93538 Name | MAX | MIN | AVG Total average is : 13.4 Total average is : 13.430856

Here is the data side by side.

As we can see, the difference is the lowest in the high/low range but highest in the mid-range

Set 47 Method AVG 56 Method AVG Difference Set 1 15,86 16,44 0,58 Set 2 14,49 15,21 0,72 Set 3 13,39 14,14 0,75 Set 4 12,37 13,06 0,69 Set 5 11,30 11,80 0,50 Set 6 10,05 9,94 -0,12 Average: 0,52

Here we have the number distribution curve from each generation visualized with a great tool called permeter

47 Method:

56 Method:

Final words:

Having rolled 58 million dice I am not sure which I would pick, I am however pretty sure when I decide to play D&D again, a single dice toss will not care what I calculated.