Dice toolkit for .Net. Construct dice with interesting behaviors.
- Installation
- How To Use
- Types of Dice
- IRandomizable
- Real World Examples
- Building Advanced Dice
- Contributing
- Future Work
- Acknowledgements
dotnet add package NDice
// OR
Install-Package NDice
var die = new Die(); // Creates a new six-sided die
int result = die.Roll();
A fair die. It is not weighted, and as random as its randomizer can be.
Die with one or more sides weighted to have a higher probablility of occurrence.
Weighted die that obeys the Gambler's Fallacy. The weight shifts after each roll, so that the longer a side has not been rolled, the higher its probability of occurrence for the next roll.
A tapper's die behaves like a fair die, until it is tapped. Then it behaves like a weighted die until it is tapped again.
Dice can be constructed with a default of six sides, or any number of sides:
var fairDie = new Die();
var weightedDie = new WeightedDie();
var bigFairDie = new Die(225);
NOTE: Most types of weighted dice constructed with no weights will behave like a fair die
WeightedDie
and dice derived from it (e.g. GamblersDie
) can be constructed with weights for each side, using a series of numbers or an array.
Percentages can be used, but the total weight must equal 1. Try to use nicely dividable percentages, otherwise some precision may be lost.
// Six-sided die with side 4 heavily weighted
var luckyFour = new WeightedDie(1, 1, 1, 5, 1, 1);
int probablyFour = luckyFour.Roll();
int alsoProbablyFour = luckyFour.Roll();
// Ten-sided gambler's die, with side 2 heavily weighted
int[] weights = new int[] { 1, 5, 1, 1, 1, 1, 1, 1, 1, 1 };
var firstRollLikelyTwo = new GamblersDie(weights);
int likelyTwo = firstRollLikelyTwo.Roll();
int likelyAnythingButTwo = firstRollLikelyTwo.Roll();
double[] percentWeights = new double(0.125, 0.125, 0.5, 0.125, 0.125);
var percentWeightedDie = new WeightedDie(percentWeights);
By default System.Random
is used to roll the die (SystemRandomizer
), but a generic interface IRandomizable
can be implemented and used with any die. Below is a terrible randomizer that uses the current time to generate a "random" number.
public class SecondsRandomizer : IRandomizable
{
public int Get(int maxValue)
{
int.TryParse(DateTime.Now.ToString("ss"), out int seconds);
return seconds % maxValue;
}
}
And it can be used as the first parameter when constructing a die:
var rnd = new SecondsRandomizer();
var die0 = new Die(rnd, 8);
var die1 = new WeightedDie(rnd, 1, 1, 1, 7, 1, 1, 1);
var die2 = new GamblersDie(rnd, 20);
Alternative randomizers are available and can be used as examples when creating your own. They are available as a metapackage NDice.Randomizers
or individually as described in the NDice.Randomizers
page.
Die | Code |
---|---|
D20 |
var d20 = new Die(20); |
Average Die |
var avgDie = new WeightedDie(1, 2, 2, 1); |
Double Deuce |
var d2Die = new WeightedDie(1, 2, 1, 1, 1); |
Each die can be built with a builder. Using a builder allows for more customization, such as adding string labels for each side and using a custom randomizer.
The number of sides can be specified with .WithSides(int)
. This can be omitted on dice that are built with weights or labels. If omitted and no weights or labels are defined the default number of sides (6) is used.
String labels can be added to any die using .WithLabels(params string[])
. The builder will set the number of sides to the count of the labels, so .WithSides()
is unnecessary.
Randomizers can be added one of two ways: an instance of IRandomizable
, or a lambda that is equivalent to IRandomizable.Get()
. If omitted the default SystemRandomizer
is used.
As an example, here is a Die
built with the SecondsRandomizer
mentioned above:
Die die = new DieBuilder()
.WithSides(10)
.WithRandomizer(maxValue =>
{
int.TryParse(DateTime.Now.ToString("ss"), out int seconds);
return seconds % maxValue;
});
Weights can be added to WeightedDie
and its derivatives using .WithWeights(params int[])
or .WithWeights(params double[])
, the same as the normal constructor. The builder will set the number of sides to the count of the weights, so .WithSides()
is unnecessary.
These set the TappersDie.Tapped
property to true or false.
var die = new DieBuilder().WithSides(4).WithLabels("Blue", "Purple", "Orange", "Red").Build();
// Use explicit type instead of var to avoid calling .Build()
GamblersDie gdie = new GamblersDieBuilder()
.WithWeights(1, 6, 4, 4, 2, 4)
.WithRandomizer(new SystemCryptoRandomizer());
// Long chain
var tdie = new TappersDieBuilder()
.WithSides(5)
.WithWeights(1, 4, 1, 1, 1)
.WithLabels("One", "Two", "Three", "Four", "Five")
.Tapped()
.WithRandomizer(maxValue => new System.Random().Next(maxValue))
.Build();
See CONTRIBUTING.md.
- More real world examples
- Include common dice like the examples (another package)
- More dice algorithms! (statisticians/dice enthusiasts needed. PRs welcome)
- Percentages/ratios for weight
- Built-in labels
- Fluent die builder
- Abstraction for randomizer, so other libs/algorithms may be used
- Extension package for other implementations of
IRandomizable
- More tests!
- Exceptions!
This wouldn't have been made if not for stumbling upon xori/gamblers-dice.