D&D Math, how should I roll ?

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)
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
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.