Dice Stats¶
Introduction¶
If you’re a new to this library you may want to look at the Tutorial.
If you’ve gone through that then the Terminology section would help understand the inner mechanics of the Dice class, but otherwise this document explains the public interface.
Public library¶
Advanced Dice statistics without the headaches.
More description to come.
-
class
dice_stats.
Dice
(chances=None, total_chance=Fraction(1, 1))¶ Dice class.
-
apply_dice
(chances, default=None)¶ Change chances to provided dice.
This changes the values of the host dice, to the values of the provided dice. It does so by changing the provided dice to the correct chance.
- Parameters
chances (
Dict
[Iterable
[Union
[Real
,Integral
]],Dice
]) – A mapping of all the Chances Value and dice to change.default (
Optional
[Dice
]) – A dice to use as the default when chances doesn’t cover all possible Chances Value.
- Return type
Dice
- Returns
A new Dice that acts as if these multiple dice rolls happen in one throw.
Say we’re playing a game where you roll a die to attack people. You normally do 1-6 damage, however sometimes there’s a chance to do 1-4 - say you had bad footing. To determine what dice you use use throw a d3 before using either the d6 or the d4. On a 1 you use the d4, otherwise it’s the d6. To find the chances is easy with this function.
print( Dice.from_dice(3) .apply_dice( {(1,): Dice.from_dice(4)}, Dice.from_dice(6), ) )
Dice[1]( 1: 19.4% 7/36 2: 19.4% 7/36 3: 19.4% 7/36 4: 19.4% 7/36 5: 11.1% 1/9 6: 11.1% 1/9 )
-
apply_functions
(chances, default=None, apply=<function Dice.<lambda>>)¶ Apply a callback to the provided chances.
This is the more complex version
dice_stats.Dice.apply_dice()
. With this version you can pass a callback that mutates the segment of the dice provided.This callback is given a segment of the host dice, this means that the callback has all the information that it should need, as this dice would have all the Chances Value and Chances Chance it needs. The Total Chance will also be the correct for that segment of the dice results.
The callback then has to return a dice object that that segment gets mutated to. For example if you’re using this to reroll dice, then one function would return the segment provided, where the other would return the host dice of sorts.
- Parameters
chances (
Dict
[Iterable
[Union
[Real
,Integral
]],Callable
[[Dice
],Dice
]]) – The mapping that holds the outputs.default (
Optional
[Callable
[[Dice
],Dice
]]) – The function to apply if there are missing values.apply (
Callable
[[Dice
],Dice
]) – A function to mutate each dice segment before they reach thechances
callback.
- Return type
Dice
- Returns
New dice that’s been fully mutated by the callbacks.
To expand on the previous example, if we have a d6 and we’re allowed to reroll 1s once, then we could use the following code.
Note
This functionality is already builtin to the
dice_stats.Dice
class. You can instead use thedice_stats.Dice.reroll()
method to do this.print( Dice.from_dice(6) .apply_functions( {(1,): lambda d: Dice.from_dice(6) @ d}, lambda d: d, ) )
Dice[1]( 1: 2.8% 1/36 2: 19.4% 7/36 3: 19.4% 7/36 4: 19.4% 7/36 5: 19.4% 7/36 6: 19.4% 7/36 )
-
as_total_chance
(total_chance)¶ Change Total Chance.
This adjusts each Chances Chance by the scale of the old and new Total Chance.
- Parameters
total_chance (
Union
[Fraction
,Dice
,int
]) – Desired Total Chance.- Return type
Dice
- Returns
A new dice with the specified Total Chance.
For example, say we have a d3 which has a 100% Total Chance, however it should be a 200% Total Chance. Then we use this function to change it. It will also change all the Chances Chance from 1/3 to 2/3.
d3 = Dice.from_dice(3) print(d3) print(d3.as_total_chance(Fraction(2, 1)))
Dice[1]( 1: 33.3% 1/3 2: 33.3% 1/3 3: 33.3% 1/3 ) Dice[2]( 1: 66.7% 2/3 2: 66.7% 2/3 3: 66.7% 2/3 )
-
copy
()¶ Create a copy of the current dice.
This allows other methods to easily mutate a copy of a provided dice without mutating the dice a user provides. This helps keep the purity of a lot of the functions in this library.
- Returns
A copy of this dice, including its type.
-
classmethod
from_dice
(sides, total_chance=Fraction(1, 1))¶ Make a dice from a number of sides.
This is a very useful convenience function, this is the most common way to build dice as it builds dice with an equal chance to land on all faces.
- Parameters
sides (
Integral
) – Amount of sides the n-sided dice has.total_chance (
Fraction
) – Total Chance.
- Return type
Dice
- Returns
A fair n-sided dice.
Below is an example of some of the smaller dice you can create with this.
print(Dice.from_dice(1)) print(Dice.from_dice(2)) print(Dice.from_dice(3)) print(Dice.from_dice(6))
Dice[1]( 1: 100.0% 1/1 ) Dice[1]( 1: 50.0% 1/2 2: 50.0% 1/2 ) Dice[1]( 1: 33.3% 1/3 2: 33.3% 1/3 3: 33.3% 1/3 ) Dice[1]( 1: 16.7% 1/6 2: 16.7% 1/6 3: 16.7% 1/6 4: 16.7% 1/6 5: 16.7% 1/6 6: 16.7% 1/6 )
-
classmethod
from_empty
(value=0)¶ Create an empty chance.
A Chances Value must be specified and so defaults to 0 as the unwanted value.
- Parameters
value (
Union
[Real
,Integral
]) – Chances Value of the failure value.- Returns
An empty dice.
-
classmethod
from_external
(chances, total_chance)¶ Create a dice from the Chances form.
This doesn’t use the Internal Chances form, and so eases use when not using that form.
-
classmethod
from_full
(chances, total_chance=Fraction(1, 1))¶ From full Internal Chances.
This is a helper function to build dice from the Internal Chances. Allowing a different Total Chance.
- Parameters
chances (
Dict
[Union
[Real
,Integral
],Fraction
]) – Internal Chances object.total_chance (
Fraction
) – Total Chance.
- Returns
A new dice with the provided chances.
-
classmethod
from_partial
(chances=None, total_chance=Fraction(1, 1))¶ From partial Internal Chances.
This is a helper function to build dice from the Internal Chances, where the chance’s always total 1. By defaulting all undefined Chances Chance to the chance of 0, rather than erroring.
This may become deprecated in the future, as it seems like there is no use for this method.
- Parameters
chances (
Optional
[Dict
[Union
[Real
,Integral
],Fraction
]]) – Internal Chances object.total_chance (
Fraction
) – Total Chance.
- Returns
A new dice with the provided chances.
-
classmethod
from_prev_total_chance
(chances, chance, prev_chance)¶ Change a dice from one Total Chance to another.
This is a deprecated function, this is likely better expressed by using
dice_stats.Dice.from_external()
anddice_stats.Dice.as_total_chance()
.- Parameters
chance (
Fraction
) – New Total Chance amount.prev_chance (
Fraction
) – Old Total Chance amount.
- Return type
Dice
- Returns
A dice with new chances.
-
get
(k[, d]) → D[k] if k in D, else d. d defaults to None.¶
-
items
() → a set-like object providing a view on D's items¶
-
keys
() → a set-like object providing a view on D's keys¶
-
max
(other=None)¶ Get the maximum result from two dice.
Say you’re playing a game which allows you to roll two dice and take the highest as your result. To do this you would need a cartesian max, which is what this function performs.
- Parameters
other (
Optional
[Dice
]) – Another dice to get the maximum value from. If unset then it uses itself.- Return type
Dice
- Returns
A new dice of the chances of getting the maximum.
Say you have can roll two d6s and use the maximum, that would simply be:
print(Dice.from_dice(6).max())
Dice[1]( 1: 2.8% 1/36 2: 8.3% 1/12 3: 13.9% 5/36 4: 19.4% 7/36 5: 25.0% 1/4 6: 30.6% 11/36 )
-
partition
(values)¶ Remove multiple Chances Value from the dice.
Split the dice into two. This removes the values provided in the values argument out of the dice, and then returns the rest of the values in a separate dice.
- Parameters
values (
Iterable
[Union
[Real
,Integral
]]) – Selection of Chances Value to remove from the dice.- Return type
Tuple
[Dice
,Dice
]- Returns
Returns two different dice, one with the wanted values, the other with the unwanted values.
For the most part this is used internally when using one of the apply functions or the reroll function. However its existence helps makes these functions possible and easy to implement.
To showcase this we’ll define a basic reroll function using this function.
Note
This functionality is already builtin to the
dice_stats.Dice
class. You can instead use thedice_stats.Dice.reroll()
method to do this.def reroll(dice, values): rerolled, kept = dice.partition(values) return Dice.sum([ kept, dice.as_total_chance(rerolled.total_chance), ]) print(reroll(Dice.from_dice(6), (1,)))
Dice[1]( 1: 2.8% 1/36 2: 19.4% 7/36 3: 19.4% 7/36 4: 19.4% 7/36 5: 19.4% 7/36 6: 19.4% 7/36 )
-
reroll
(values)¶ Reroll specified values.
Since this is a common operation on dice this is some sugar for it. It should be noted that whilst this handles the simple instances of rerolling, there are some more complex situations that can only be handled through custom functions or the
dice_stats.Dice.apply_functions()
method.For the simple situation of being able to reroll a range of values once then this is all you need.
- Parameters
values (
Iterable
[Union
[Real
,Integral
]]) – Collection of Chances Value.- Return type
Dice
- Returns
A new dice factoring in rerolls.
print(Dice.from_dice(6).reroll((1,)))
Dice[1]( 1: 2.8% 1/36 2: 19.4% 7/36 3: 19.4% 7/36 4: 19.4% 7/36 5: 19.4% 7/36 6: 19.4% 7/36 )
-
classmethod
sum
(dice)¶ Add multiple dice together, increasing their Total Chance.
This is mostly used when you have multiple partial dice that you need to add together only adding the chances together. For example lets say we have a 2/3 chance for our outcome to be a d6 or a 1/3 chance that the outcome is a d4 then the total outcome would be the result of those two dice added together, but not in the common form -
d6 + d4
.print(Dice.sum([ Dice.from_dice(6, total_chance=Fraction(2, 3)), Dice.from_dice(4, total_chance=Fraction(1, 3)), ]))
Which correctly results in:
Dice[1]( 1: 19.4% 7/36 2: 19.4% 7/36 3: 19.4% 7/36 4: 19.4% 7/36 5: 11.1% 1/9 6: 11.1% 1/9 )
As we can see, the result totals to 100%, and there is a significantly higher chance to get a 1-4 then 5 or 6. This shows pretty clearly that the outcome is only adding the chances together.
For the most part you can normally use a different function than this one if you need to get this sort of output. Lets say the game we’re playing says to roll a d3 to decide between either the d4 or the d6 to be rolled to get the result. On a 1 you roll the d4 otherwise it’s the d6, which could be expressed as:
print( Dice.from_dice(3) .apply_dice( {(1,): Dice.from_dice(4)}, Dice.from_dice(6) ) )
Dice[1]( 1: 19.4% 7/36 2: 19.4% 7/36 3: 19.4% 7/36 4: 19.4% 7/36 5: 11.1% 1/9 6: 11.1% 1/9 )
- Return type
Dice
-
property
total_chance
¶ Total Chance of the values of this dice.
- Return type
- Returns
The Total Chance of the dice.
-
values
() → an object providing a view on D's values¶
-
Terminology¶
Chances¶
These are made up of pairs of two items, a Chances Value and a Chances Chance. These are contained in a mapping where the mapping’s key is the Chances Value, and the value is the Chances Chance.
This swap of terminology may cause some confusion so I think it should be noted again that the mappings value is not the Chances Value.
Chances Value¶
This is the value on the dice. For a standard d6 this would mean that the dice has 6 values ranging from 1 to 6. And so the the Chances would have six Chances Value.
Chances Chance¶
This is the chance of the value being an outcome. For a standard d6 this would mean that all the chances are 1/6. And so the the Chances would have six Chances Chance all with the same value.
These are all provided as fractions.Fraction
so that there are
no floating point issues when calculating the chances. This also allows
high precision in the chances we calculate.
Note
The Internal Chances Chance is slightly different to the public one.
Total Chance¶
This is the total chance of the dice, this is important as the Chances and the Internal Chances total to different amounts, as described in the latter. Whilst this may at first seem bizarre, some games allow dice to have an over, or under, 100% chance. Say we have a game where you roll a d10;
on a 1 you miss,
on a 10 you hit twice, otherwise
you hit once.
This could be inferred as hitting 100% of the time, and missing 10%. Making the total chance of the dice 110%. Whilst interpreting the rules this way are entirely down to you. It leaves the option open for you to go down this route if you want to.
This attribute is also used on some of the more high level functions. This is as they split a dice into say two results, ones that are successful and ones that are unsuccessful. Having this attribute contained within the dice makes it simpler to handle the splitting and then combining of those dice.
Internal Chances¶
These are the internal chances of the dice object. These are stored in a
dict
where the Internal Chances Chance total 1.
This has two major benefits:
Allows us to at runtime check if there’s a critical error with the dice object and exit gracefully, rather than fail silently.
Whilst I’m confident the code works due to tests. I also know that this feature helped development and debugging when I messed up different functions. It’s an extra fail safe to know the code isn’t completely broken.
It may also help you if you start doing some fancy things with this library, as if you break the state of the Internal Chances then you know straight away, not after hours of debugging.
Keeping a mutable dictionary private, and keeping the amount of code that touch it to a minimum helps keep the logic of the Dice object reasonable. Passing a dice object to a function shouldn’t mutate the object. At every step of development I’ve kept immutability to be a core concept for the dice class.
Given that creating dice statistics are complex enough, this can keep me, and hopefully you, to be at peace when making complex dice statistic functions. And should prevent easy to overlook issues with a mutable class.
Internal Chances Chance¶
This is slightly different to the Chances Chance. This, the internal one, totals to 1, allowing some runtime validity checks. However the public Chances Chance total to whatever Total Chance is.
For the most part, these are likely to be the same.