Package: pydiceroll
Dice RollerHighlightsfromdice_rollerimports,d,rng,x,r,kh,kl,dl,dhdefroll_info(roll:BaseDice):print(f"For dice '{roll}' min is{roll.min()}and max is{roll.max()}")d20=d(20)roll_info(d20)# For dice 'd20' min is 1 and max is 20d20.roll()# Get one roll resultd20.generate(10)# Generates 10 rolls as numpy arraymodifier=s(5)# Scalar, `dice_roller` converts integers to Scalar automatically, if you use suggested mathematical api.roll_info(modifier)# For dice '5' min is 5 and max is 5roll_info(d20+modifier)# For dice '(d20 + 5)' min is 6 and max is 25fudge_dice=rng(-1,2)# fudge dice4@fudge_dice# roll 4 fudge dices, add results together(d20-4)>=1# roll d20-4, ensure result greater or equal to 1d20*2+d(2)-1# roll d20, multiply by 2, add d2, sub 1dh(10@d20,drop=5)# roll 10 d20, drop 5 highest and return sum of restkh(5@d20,keep=d(2))# roll 5 d20, keep d2 (new reroll value each time) highest and return their sumd(6).r==rng(1,3)# roll d6, rerolls on 1 or 2 (new reroll value each time), 1 reroll max (default)d(20).reroll(reroll_limit=10)==1# roll d20, rerolls on 1, max 10 rerollsd(6).x>=5# roll d6, explodes on 5 and 6. Maximum 100 explodes (default)d(6).explode(explode_depth=2)>4# roll d6, explodes on 5 and 6. Maximum 2 explodes10@d(10)*10@d(10)# roll 2 sets of 10d10 and multiply results(4@d(4))@d(10)# roll 4d4 of d10 dices# roll d6 of (roll d4 of d20 dice, keep 1 highest) and drop d4 lowest. Ensure (d4 explodes on 4) <= result <= (d100 reroll <= 50).(dl(d(6)@kl(d(4)@d(20)),drop=d(4))>=(d(4).x==4))<=(d(100).r<=50)# Some codeattack_roll=kh(2@d20)+d(4)+3# Roll to hit with advantage, use bless (+d4) and add +3 modifierroll_info(attack_roll)# For dice '(2d20kh + d4 + 3)' min is 5 and max is 27attack_results=attack_roll.generate(10_000)# Generates 10000 rollshit_ac=attack_results[attack_results>=16]# numpy stuffdamage_roll=2@(x()==6)(d(6))+5# Roll 2d6 (explode on 6), add 5roll_info(damage_roll)# For dice '(2d6x6 + 5)' min is 7 and max is 17reroll_ones=(r(reroll_limit=1)==1)# Create reroll ones modifier (max 1 reroll)skill_check_roll=reroll_ones(d20)+4# Roll d20 and reroll ones (max 1 reroll), add 4skill_check_roll=(d20.r==1)+4# Roll d20 and reroll ones (max 1 reroll), add 4 - another approachroll_info(skill_check_roll)# For dice '(d20r1 + 4)' min is 5 and max is 24kl(2@skill_check_roll).roll()# roll skill check roll with disadvantageTutorialBasicsLet's jump in:fromdice_rollerimportDiced20=Dice(20)print(f"Rolling{d20}...")roll=d20.roll()print(f"Rolled{roll}")And now we have shiny new rolled dice.rolling d20 ...
Rolled 6Main feature of this library - ability to roll large amount of rolls simultaneously, using magical powers ofnumpy.fromdice_rollerimportDiced20=Dice(20)rolls=d20.generate(100)print(rolls,type(rolls))[ 7  1  5 10 12 11  8 20  5 16  9  6  3 12 12 11 19  2  8 20  7 13  1 20
  6  1 20 18  2 16  1  2 13  5  5  7  5 15  6 17 16  4  7 15 20  3  2 10
 19 20 11  6 13 11 15  1 19 19 10  4  3  9  7 14 16 12 12 19  5  2  6  9
 15 18 14  8  5 19 15 17  6  8 17 18 17  9  7 19  2 18  7 17  1 16 18 11
  5 12  5 12] <class 'numpy.ndarray'>But for now let's keep things simple and focus on the other features. One important thing you need to remember now - almost all things fromdice_rollersupports batch generation withgenerate(total)method.For future understanding, lets also use some importantdice_rollerapis. Here we can check possible extremes of the dice roll outcome:fromdice_rollerimportDiced20=Dice(20)print(f"For dice '{d20}' min is{d20.min()}and max is{d20.max()}")# For dice 'd20' min is 1 and max is 20dice_rollerhas some optimizations, which helps to calculate extreme values without expensive computations.So far looks too boring, let's add some modifiers to this roll.Most dice rolls result in a number that is the sum of any dice rolled, and simple math modifiers can be used to increase or decrease this result after rolls are made.fromdice_rollerimportDiced20=Dice(20)modified_d20=d20+5print(f"For dice '{modified_d20}' min is{modified_d20.min()}and max is{modified_d20.max()}")print(f"Rolled:{modified_d20.roll()}")For dice '(d20 + 5)' min is 6 and max is 25
Rolled: 21Here we adding constant modifier to our d20 dice.dice_rollerobjects support some mathematical operations overload, to provide simple dsl-like api.Supported basic arithmetical operations:+,-,*,/.fromdice_rollerimportDice,BaseDicedefroll_info(roll:BaseDice):print(f"For dice '{roll}' min is{roll.min()}and max is{roll.max()}")d20=Dice(20)roll_info(d20+4)roll_info(d20-4)roll_info(d20*4)roll_info(d20/4)For dice '(d20 + 4)' min is 5 and max is 24
For dice '(d20 - 4)' min is -3 and max is 16
For dice '(d20 * 4)' min is 4 and max is 80
For dice '(d20 / 4)' min is 0 and max is 5Important note: in current state, all division operation is true division. Dicesd20 / 2andd20 // 2will calculate result in same way, using rules of true division.But this is not all, we can simply replace constant modifier with another dice:fromdice_rollerimportDice,BaseDicedefroll_info(roll:BaseDice):print(f"For dice '{roll}' min is{roll.min()}and max is{roll.max()}")d20=Dice(20)d4=Dice(4)roll_info(d20+d4)roll_info(d20-d4)roll_info(d20*d4)roll_info(d20/d4)For dice '(d20 + d4)' min is 2 and max is 24
For dice '(d20 - d4)' min is -3 and max is 19
For dice '(d20 * d4)' min is 1 and max is 80
For dice '(d20 / d4)' min is 0 and max is 20Some StatisticsAs you can see in last example, possible minimal and maximal values are not changed. Let's find other differences and check more features ofdice_roller:importnumpyasnpfromdice_rollerimportDice,BaseDicedefstatistical_report(dice:BaseDice):rolls=dice.generate(1_000_000)print(f"Statistics for '{dice}'")print(f"Mean:{np.mean(rolls)}")print(f"Std :{np.std(rolls)}")print(f"Var :{np.var(rolls)}")statistical_report(Dice(20)+4)print("-"*40)statistical_report(Dice(20)+Dice(4))Statistics for '(d20 + 4)'
Mean: 14.500655
Std : 5.766562196922445
Var : 33.25323957097502
----------------------------------------
Statistics for '(d20 + d4)'
Mean: 13.006027
Std : 5.878449002523625
Var : 34.556162675271And let's ask ChatGPT to explain this difference:Mean: The average roll result is higher for(d20 + 4)compared to(d20 + d4), reflecting that adding a constant leads to a higher average outcome than adding another dice roll due to less variability.Standard Deviation (Std): Indicates less variability in outcomes for(d20 + 4)than for(d20 + d4). This means that adding a constant results in outcomes that are closer to the average, whereas adding another dice introduces more spread in the results.Variance (Var): Confirms the trend seen in the standard deviation, with(d20 + 4)showing slightly less variance than(d20 + d4), meaning the outcomes for the former are more tightly clustered around the mean.Overall: Adding a constant to a d20 roll produces slightly higher and more consistent outcomes compared to adding the roll of a d4, which introduces more randomness and variability into the results.Generating large amount of the rolls and then calculating required metric if simple approach, but it will require extra memory and cpu usage just to generate samples for statistics calculation.dice_rolleralso suggest another way to calculate statistics.
Usingdycelibrary for modeling arbitrarily complex dice mechanics,dice_rollerbrings to you perfectly designed API and ability to perform probability modeling in one package!fromdice_rollerimportddice=d(8)+d(12)h=dice.histogram()print(h.format(scaled=True))avg |   11.00
std |    4.14
var |   17.17
  2 |   1.04% |######
  3 |   2.08% |############
  4 |   3.12% |##################
  5 |   4.17% |########################
  6 |   5.21% |###############################
  7 |   6.25% |#####################################
  8 |   7.29% |###########################################
  9 |   8.33% |#################################################
 10 |   8.33% |#################################################
 11 |   8.33% |#################################################
 12 |   8.33% |#################################################
 13 |   8.33% |#################################################
 14 |   7.29% |###########################################
 15 |   6.25% |#####################################
 16 |   5.21% |###############################
 17 |   4.17% |########################
 18 |   3.12% |##################
 19 |   2.08% |############
 20 |   1.04% |######histogram()method of theBaseDicereturnsdyce.Hobject. Returned histogram will contain finite discrete outcomes, with all modifiers applied to it.Unfortunately, becausedice_rollerrequires each transformation to return only one deterministic integer for eachroll(), it not supportsdyce.P(Dice pools) on cases with multiple dices rolled (like 2d6). Despite this,dice_rollerwill correctly calculate the histogram for this case.fromdice_rollerimportd,khprint(kh(5@d(6),keep=2).histogram().format(scaled=True))avg |    9.93
std |    1.71
var |    2.91
  2 |   0.01% |
  3 |   0.06% |
  4 |   0.40% |
  5 |   1.03% |##
  6 |   2.71% |#####
  7 |   5.21% |##########
  8 |   9.98% |#####################
  9 |  15.43% |################################
 10 |  21.81% |#############################################
 11 |  23.73% |##################################################
 12 |  19.62% |#########################################Let's compare two approaches:fromdice_rollerimportBaseDice,d,kh# Roll to hit with advantage, use bless (+d4) and add +5 modifierr=kh(2@d(20))+d(4)+5defstats_simulation(r:BaseDice):rolls=r.generate(1_000_000)print(f"Mean:{np.mean(rolls)}")print(f"Std :{np.std(rolls)}")print(f"Var :{np.var(rolls)}")defstats_histogram(r:BaseDice):h=r.histogram()mu=h.mean()print(f"Mean:{mu}")print(f"Std :{h.stdev(mu)}")print(f"Var :{h.variance(mu)}")stats_simulation(r)print("-"*40)stats_histogram(r)Mean: 21.3224029
Std : 4.841713051190826
Var : 23.44218527007158
----------------------------------------
Mean: 21.325
Std : 4.841939177643606
Var : 23.444375000000036In comparing two methods of statistical analysis for those rolls, both simulation and histogram approaches yield remarkably similar results.
Let's compare execution time:r=kh(2@d(20))+d(4)+5%timeitr.generate(1_000_000)%timeitr.histogram()2.27 s ± 18.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
1.01 ms ± 6.98 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)Running the simulation takes a good couple of seconds, while the histogram method is lightning fast. So, if you're looking for quick and reliable dice roll insights, the histogram way is a no-brainer.Dice TypesScalarLet's return back to dices. Second important dice type after regularDiceisScalar. This type of dice simply returns constant value.
Many overload operations logic converts integers to theScalarautomatically, before applying modifications to original dice.
But if you use objects, likeMaxfromdice_rollerin your code, prefer to convert your constants into this type.Scalaris alsoBaseDice, so it implements all required API, it is useful in combinations with other dices, but you can always generate huge array of constant, using this dice.fromdice_rollerimportScalarplus5=Scalar(5)print(f"For dice '{plus5}' min is{plus5.min()}and max is{plus5.max()}")# For dice '5' min is 5 and max is 5print(plus5.roll())# 5print(plus5.generate(10))# [5 5 5 5 5 5 5 5 5 5]RangeDiceIfDicesupports only positive integers (in general), withRangeDiceyou can specify own ranges for the dice.
Range specified as in pythonranges(e.g.[start, ..., end)), but step is not available (yet)Here is example of how to createfudgedice.fromdice_rollerimportRangeDicefudge=RangeDice(-1,2)print(f"For dice '{fudge}' min is{fudge.min()}and max is{fudge.max()}")# For dice 'd[-1 to 2]' min is -1 and max is 1Dice Types ConclusionHere is 3 most important dice types fromdice_roller:Dice- default dice class. You should provide amount of sides, and it will roll values form 1 ton_sides(inclusive). Also available asdalias indice_roller.Scalar- Constant dice, which will always be rolled into constant value, mainly used fordice_rollermagic. Also available assalias indice_roller.RangeDice- Dice with little bit more freedom. Also available asrngalias indice_roller.Some examples of using aliases:fromdice_rollerimportd,s,rngd20=d(20)one=s(1)fudge=rng(-1,2)Dice OperationsWe already slipped through some basics arithmetical operations. What else we can do with our dices?LimitsAs you can see in basic arithmetic examles below, diced20-4may outcome negative minimal value. It's because lowest result of the d20 roll is1, and1-4=-3.We can ensure dice outcome will be in bounds by providing limit, both with comparison operators overload or withMinandMaxwrapper:fromdice_rollerimportDiced20=Dice(20)safe_roll=(d20-4)>=1# roll of the (d20 - 4) must be greater or equal to 1roll_info(safe_roll)For dice '(d20 - 4)>=1' min is 1 and max is 16dice_rollersupport different types of limits:fromdice_rollerimportdd20=d(20)d20>=19# greater or equal to 19d20>19# greater then 19d20<=2# less or equal to 2d20<2# less then 2# You can also use dices to construct limit, compared dice will be rolled first. New value will be rolled each time.d20>=d(4)# d20 greater or equal to value of d4d20<=d(4)# d20 less or equal to value of d4As you see, here is no==operator supported. This has no sense - in case of==, any roll can be reduced to simpleScalar.Multiple DicesFirst thing we can make after rolling some dices - roll even more dices!Python’s matrix multiplication operator (@) is used to express the number of a particular die (roughly equivalent to the "d" operator in common notations).Your D&D wizard throws fireball, and you want to roll 8d6 fire damage? Easy:fromdice_rollerimportDiced6=Dice(6)dice_8d6=8@d6print(f"Fireball deals '{dice_8d6}' damage:")print(f"At least:{dice_8d6.min()}")print(f"At most:{dice_8d6.max()}")print(f"Average:{dice_8d6.average():.0f}")print(f"Rolled{dice_8d6.roll():.0f}")Fireball deals '8d6' damage:
At least: 8
At most: 48
Average: 28
Rolled 27You can also create<dice>roll of<dice>:fromdice_rollerimportDicedice_d4d6=Dice(4)@Dice(6)In this case,dice_rollerwill first rolld4for amount ofd6dices to roll, and then roll this amount ofd6and add them together to calculate outcome.Keep HighestThis modifier causes thedice_rollerto keep and add together a number of dice you specify, selecting the highest of the roll results available. Without a specified args it will keep the single highest number of two. If the number of dice to roll (of) is less than the number of dice being kept (keep) then it will keep all the rolls made.Let's simulate D&D 5e "Advantage"fromdice_rollerimportDice,KeepHighestd20advantage=KeepHighest(2@Dice(20))# by default, selects 1 highest dice of 2 dice rolled.UsingKeepHighestwill have sense only when you use it with multiple dices, created with@operator. If you use it with one dice, outcome will be same as not usingKeepHighestat all:KeepHighest(Dice(20)) ~ Dice(20)You can override amount of tries and amount of rolls, which will be added in final roll:fromdice_rollerimportDice,kh# kh is alias for KeepHighestd20_keep_2_high_of_5=kh(5@Dice(20),keep=2)# Keeps and add together 2 highest rolls of 5 rolledYou can use complex dice expressions with KeepHighest,keepfield also supportsBaseDiceobject:fromdice_rollerimportd,khkh(4@d(20),keep=d(4))# roll 4d20, keep d4 highestkh(d(4)@d(20),keep=(d(4)/2)>=1)# roll d4 of d20 dice, keep d4/2 (at least one) highestkh(d(6)@kh(d(4)@d(20)),keep=d(4))# roll d6 of (roll d4 of d20 dice, keep 1 highest) and keep d4 highestKeep LowestThis modifier causes thedice_rollerto keep and add together a number of dice you specify, selecting the lowest of the roll results available. Without a specified args it will keep the single lowest number of two. If the number of dice to roll (of) is less than the number of dice being kept (keep) then it will keep all the rolls made.Let's simulate D&D 5e "Disadvantage"fromdice_rollerimportDice,KeepLowestd20disadvantage=KeepLowest(2@Dice(20))# by default, selects 1 lowest dice of 2 dice rolled.UsingKeepLowestwill have sense only when you use it with multiple dices, created with@operator. If you use it with one dice, outcome will be same as not usingKeepLowestat all:KeepLowest(Dice(20)) ~ Dice(20)You can override amount of tries and amount of rolls, which will be added in final roll:fromdice_rollerimportDice,kl# kl is alias for KeepLowestd20_keep_2_low_of_5=kl(5@Dice(20),keep=2)# Keeps and add together 2 lowest rolls of 5 rolledYou can use complex dice expressions with KeepLowest,keepfield also supportsBaseDiceobject:fromdice_rollerimportd,klkl(4@d(20),keep=d(4))# roll 4d20, keep d4 lowestkl(d(4)@d(20),keep=(d(4)/2)>=1)# roll d4 of d20 dice, keep d4/2 (at least one) lowestkl(d(6)@kl(d(4)@d(20)),keep=d(4))# roll d6 of (roll d4 of d20 dice, keep 1 lowest) and keep d4 lowestDrop HighestThis modifier causes thedice_rollerto drop a number of dice you specify, selecting the highest of the roll results available. Rest of the rolls added together. If no args specified, then it will drop the one highest number rolled of two. If the number of dice to roll (of) is less than the number of dice to drop (drop), then it will keep all the rolls made.We can also simulate D&D 5e "Disadvantage" mechanic with this modifier:fromdice_rollerimportDice,DropHighestd20disadvantage=DropHighest(2@Dice(20))# by default, drops 1 highest dice of 2 dice rolled.UsingDropHighestwill have sense only when you use it with multiple dices, created with@operator. If you use it with one dice, outcome rolls will always be 0, because modifier drops single roll from 1:DropHighest(Dice(20)) ~ 0You can override amount of tries and amount of rolls, which will dropped from final roll:fromdice_rollerimportDice,dh# dh is alias for DropHighestd20_drop_high_2of5=dh(5@Dice(20),drop=2)# Drop 2 highest rolls and add together rest of 5 rolledYou can use complex dice expressions with DropHighest,dropfield also supportsBaseDiceobject:fromdice_rollerimportd,dhdh(4@d(20),drop=d(4))# roll 4d20, drop d4 highestdh(d(4)@d(20),drop=(d(4)/2)>=1)# roll d4 of d20 dice, drop d4/2 (at least one) highestdh(d(6)@dh(d(4)@d(20)),drop=d(4))# roll d6 of (roll d4 of d20 dice, drop 1 highest) and drop d4 highestDrop LowestThis modifier causes thedice_rollerto drop a number of dice you specify, selecting the lowest of the roll results available. Rest of the rolls added together. If no args specified, then it will drop the one lowest number rolled of two. If the number of dice to roll (of) is less than the number of dice to drop (drop), then it will keep all the rolls made.We can simulate D&D 5e  "Advantage" mechanic with this modifier:fromdice_rollerimportDice,DropLowestd20advantage=DropLowest(2@Dice(20))# by default, drops 1 lowest dice of 2 dice rolled.UsingDropLowestwill have sense only when you use it with multiple dices, created with@operator. If you use it with one dice, outcome rolls will always be 0, because modifier drops single roll from 1:DropLowest(Dice(20)) ~ 0You can override amount of tries and amount of rolls, which will dropped from final roll:fromdice_rollerimportDice,dl# dl is alias for DropLowestd20_drop_low_2of5=dl(5@Dice(20),drop=2)# Drop 2 lowest rolls and add together rest of 5 rolledYou can use complex dice expressions with DropLowest,dropfield also supportsBaseDiceobject:fromdice_rollerimportd,dldl(4@d(20),drop=d(4))# roll 4d20, drop d4 highestdl(d(4)@d(20),drop=(d(4)/2)>=1)# roll d4 of d20 dice, drop d4/2 (at least one) lowestdl(d(6)@dl(d(4)@d(20)),drop=d(4))# roll d6 of (roll d4 of d20 dice, drop 1 lowest) and drop d4 lowestRerollRerolls the die based on the set condition, keeping the outcome regardless of whether it is better. Reroll will only reroll the die and use new result, for continual rerolling with sum seeExplodebelow.This modifier requires specifying extra condition. Here is example:fromdice_rollerimportDiced20_reroll_ones=Dice(20).r==1# Reroll ones on d20Reroll also supports functional api. You can create and store reroll modifier in following way:fromdice_rollerimportReroll,Dicereroll_ones=(Reroll()==1)d20_reroll_ones=reroll_ones(Dice(20))Also, take into account - result ofReroll()orr()is not dice itself, it is modifier wrapper. You need to apply this modifier to dice to perform rolls.fromdice_rollerimportr,d(r()==1).roll()# is not okay(r()==6)(some_dice).roll()# is okayBy default, roll can be rerolled once, but you can override this behavior:fromdice_rollerimportr,d# r is alias for Reroll# dice may be rerolled 100 times, if has sequential ones on d20 rolld=(d(20).reroll(reroll_limit=100)==1)# For functional APIreroll_ones=(r(reroll_limit=100)==1)d20_reroll_ones_100_times_max=explode_on_6(Dice(6))Rerollsupports several comparison operations:fromdice_rollerimportr,dd20=d(20)d20.r==10d20.r>10d20.r>=10d20.r<10d20.r<=10reroll_on_6=(r()==6)reroll_on_gt_5=(r()>5)reroll_on_ge_5=(r()>=5)reroll_on_lt_5=(r()<5)reroll_on_le_5=(r()<=5)You can also provide dices forReroll, let's call it "Reroll Dice". In this case, "Reroll Dice" will be rolled first, and then, if dice outcomes into required dice, it will be rerolled. "Reroll Dice" rolled after each reroll step.fromdice_rollerimportr,RangeDice,Diced3_with_magic=(Dice(3).r==RangeDice(1,3))# rerolls on 1 or 2reroll_1_or_2=(x()==RangeDice(1,3)))# rerolls on 1 or 2d3_with_magic=reroll_1_or_2(Dice(3))Logic example:Rolls "Reroll Dice" (RangeDice in this case), get 1Rolls dice, get 1Need to reroll, proceed to next loopRolls "Reroll Dice", get 2ReRolls dice, get 2Need to reroll, proceed to next loopRolls "Reroll Dice", get 1ReRolls dice, get 3 - no need to reroll, returnExplodeExplode rerolls a die continually based on the set condition, so that each occurrence of the number rolls again, continually adding to the total result.This modifier requires specifying extra condition. Here is example:fromdice_rollerimportDiced6_explode_on_6=Dice(6).x==6# Explode on 6 on d6 diceReroll also supports functional api. You can create and store reroll modifier in following way:fromdice_rollerimportExplode,Diceexplode_on_6=(Explode()==6)d6_explode_on_6=explode_on_6(Dice(6))Also, take into account - result ofExplode()orx()is not dice itself, it is modifier wrapper. You need to apply this modifier to dice to perform rolls.fromdice_rollerimportx,d(x()==6).roll()# is not okay(x()==6)(some_dice).roll()# is okayBy default, roll can be exploded 100 times, but you can override this behavior during constructingExplodeinstance:fromdice_rollerimportx,Dice# x is alias for Explode# dice may be exploded only 5 times sequentially on d6 roll# If you lucky enough, you will obtain result of `np.sum([6,6,6,6,6])`d=(d(20).explode(explode_depth=5)==1)# For functional APIexplode_on_6=(x(explode_depth=5)==6)d6_explode_on_6_5_times=explode_on_6(Dice(6))Explodesupports several comparison operations:fromdice_rollerimportd,xd6=d(6)d6.x==6d6.x>5d6.x>=5d6.x<2d6.x<=2explode_on_6=(x()==6)explode_on_gt_5=(x()>5)explode_on_ge_5=(x()>=5)explode_on_lt_5=(x()<2)explode_on_le_5=(x()<=2)You can also provide dices forReroll, let's call it "Explode Dice". In this case, "Explode Dice" will be rolled first, and then, if dice outcomes into required dice, it will be exploded. "Explode Dice" rolled after each exploding step.fromdice_rollerimportx,RangeDice,Diced6_explode_on_5_or_6=(Dice(3).x==RangeDice(5,7))# explodes on 5 or 6explode_on_5_or_6=(x()==RangeDice(5,7)))# explodes on 5 or 6d6_explode_on_5_or_6=explode_on_5_or_6(Dice(6))Logic example:Rolls "Explode Dice" (RangeDice in this case), get 6Rolls dice, get 6Need to explode, adding roll to overall result and proceed to next loopRolls "Explode Dice", get 5Rolls new dice, get 5Need to explode, adding roll to overall result and proceed to next loopRolls "Explode Dice", get 6Rolls dice, get 3 - adding to overall result, but here is no need to explode further, return