DotDice 1.6.0
dotnet add package DotDice --version 1.6.0
NuGet\Install-Package DotDice -Version 1.6.0
<PackageReference Include="DotDice" Version="1.6.0" />
<PackageVersion Include="DotDice" Version="1.6.0" />
<PackageReference Include="DotDice" />
paket add DotDice --version 1.6.0
#r "nuget: DotDice, 1.6.0"
#:package DotDice@1.6.0
#addin nuget:?package=DotDice&version=1.6.0
#tool nuget:?package=DotDice&version=1.6.0
DotDice
A flexible and extensible dice rolling library for .NET applications, designed to support a wide variety of tabletop role-playing games (RPGs) and board games.
Features
- Support for multiple dice types:
- Standard dice (d4, d6, d8, d10, d12, d20, etc.)
- Percentile dice (d100)
- Fudge/Fate dice (-1, 0, +1)
- Comprehensive set of dice modifiers:
- Keep highest/lowest dice
- Drop highest/lowest dice
- Reroll dice (once or multiple times)
- Exploding dice
- Compounding dice
- Success counting
- Failure counting
- Constant modifiers (+/-X)
- Support for complex dice mechanics from various tabletop games:
- D&D (advantage/disadvantage, ability checks, critical hits)
- Savage Worlds (wild die, exploding dice)
- World of Darkness/Storyteller (success counting)
- Shadowrun (success counting)
- FATE/Fudge (fudge dice)
- Call of Cthulhu (percentile tests)
- Arithmetic roll expressions (e.g.,
3d20+5d6-1d4+1) - Detailed evaluation results with individual die events and structural grouping information
- Extensible architecture for adding custom dice types and modifiers
Installation
Install DotDice via NuGet:
dotnet add package DotDice
Usage
Simple String-Based API
The easiest way to use DotDice is through the string extension methods:
using DotDice.Extension;
// Basic dice rolls
int result1 = "1d6".ParseRoll(); // Roll a six-sided die
int result2 = "3d6".ParseRoll(); // Roll three six-sided dice
int result3 = "1d20+5".ParseRoll(); // Roll d20 with +5 modifier
Console.WriteLine($"1d6: {result1}");
Console.WriteLine($"3d6: {result2}");
Console.WriteLine($"1d20+5: {result3}");
Arithmetic Roll Expressions
DotDice supports complex arithmetic expressions combining multiple dice rolls and constants:
// Multiple dice types in one expression
int damage = "2d6+1d4+3".ParseRoll(); // Sword + dagger + strength modifier
int healing = "3d4+2d6".ParseRoll(); // Healing potion + spell bonus
// Complex expressions like those found in RPG systems
int abilityCheck = "1d20+1d4-2".ParseRoll(); // D&D ability check with guidance and penalty
int shadowrunTest = "5d6>4+3d6>4".ParseRoll(); // Shadowrun: attribute + skill dice
Console.WriteLine($"Damage: {damage}");
Console.WriteLine($"Ability Check: {abilityCheck}");
Detailed Evaluation Results
For applications that need to know what happened during the roll (individual die results, which dice were dropped, etc.), use the detailed evaluation API:
using DotDice.Extension;
using DotDice.Evaluator;
// Get detailed results
var detailedResult = "2d6kh1".ParseRollDetailed(); // Roll 2d6, keep highest
Console.WriteLine($"Final Value: {detailedResult.Value}");
Console.WriteLine("Individual Events:");
foreach (var evt in detailedResult.Events)
{
Console.WriteLine($" Die: {evt.DieType}, Value: {evt.Value}, Status: {evt.Status}");
// evt.Type is the DieEventType: Initial, Reroll, Explosion, or Compound.
// Note: rerolled and exploded dice keep their original DieType (e.g. Basic);
// use evt.Type, not evt.DieType, to tell how an event was generated.
// evt.DieType is always Basic, Percent or Fudge, and null for a synthetic event
// such as a success count or a constant modifier.
// evt.Significance shows if it was a maximum or minimum roll.
// evt.Status shows if the die was Kept, Dropped (keep/drop), or Discarded (reroll,
// compound, or counted by a success/failure modifier).
}
Success and failure counting appends a count event and marks the dice it counted as
Discarded, so they no longer add their face value to the total but are still there to
render. Each counted die carries evt.Success, which is Success, Failure or
Neutral:
var wod = "6d10>8f<2".ParseRollDetailed();
foreach (var evt in wod.Events.Where(e => e.DieType != null))
{
Console.WriteLine($" {evt.Value}: {evt.Success}");
}
Console.WriteLine($"Net successes: {wod.Value}");
Arithmetic Expression Grouping
For complex arithmetic expressions like "3d20-4d4+5", the detailed results include structural information about which dice belonged to which group and what operations separated them:
var result = "3d20kh1-4d4+5".ParseRollDetailed();
// Group events by their roll group
var groups = result.Events.GroupBy(e => e.GroupId).ToList();
// Handle grouped events (arithmetic expressions)
var groupedEvents = groups.Where(g => g.Key != null).ToList();
foreach (var group in groupedEvents)
{
var op = group.First().GroupOperator;
var keptDice = group.Where(e => e.Status == DieStatus.Kept).ToList();
var droppedDice = group.Where(e => e.Status == DieStatus.Dropped).ToList();
Console.WriteLine($"Group {group.Key} ({op}): Total = {keptDice.Sum(e => e.Value)}");
if (keptDice.Any())
Console.WriteLine($" Kept: {string.Join(", ", keptDice.Select(e => e.Value))}");
if (droppedDice.Any())
Console.WriteLine($" Dropped: {string.Join(", ", droppedDice.Select(e => e.Value))}");
}
// Handle single roll events (no grouping information)
var singleRollEvents = groups.Where(g => g.Key == null).SelectMany(g => g).ToList();
if (singleRollEvents.Any())
{
Console.WriteLine("Single Roll Events (no grouping):");
foreach (var evt in singleRollEvents)
{
Console.WriteLine($" Die: {evt.DieType}, Value: {evt.Value}, Status: {evt.Status}");
}
}
// Output example:
// Group 0 (Add): Total = 18
// Kept: 18
// Dropped: 5, 12
// Group 1 (Subtract): Total = 14
// Kept: 3, 4, 3, 4
// Group 2 (Add): Total = 5
// Kept: 5
Key Properties for Grouping:
GroupId: Unique identifier for each roll group (0, 1, 2, etc.)GroupOperator: The arithmetic operator for this group (AddorSubtract)- Single rolls (like "2d6") have
nullfor both properties to maintain backward compatibility
This makes it easy to:
- Reconstruct the original expression structure
- Show which dice were affected by modifiers in each group
- Display results in a user-friendly grouped format
- Implement custom logic based on roll groups
Advanced API (Direct Object Creation)
For more complex scenarios or when you need fine-grained control, you can create roll objects directly:
using DotDice.Evaluator;
using DotDice.Parser;
// Create an evaluator
var evaluator = new DiceEvaluator();
// Roll 3d6 (three six-sided dice)
var basicRoll = new BasicRoll(3, new DieType.Basic(6), new List<Modifier>());
int result = evaluator.Evaluate(basicRoll);
Console.WriteLine($"3d6: {result}");
// Roll 1d20 with +5 modifier
var attackRoll = new BasicRoll(1, new DieType.Basic(20),
new List<Modifier> { new ConstantModifier(ArithmeticOperator.Add, 5) });
int attackResult = evaluator.Evaluate(attackRoll);
Console.WriteLine($"1d20+5: {attackResult}");
Using Modifiers
// Roll 4d6, drop the lowest die (common for D&D ability scores)
var abilityRoll = new BasicRoll(4, new DieType.Basic(6),
new List<Modifier> { new DropModifier(1, false) });
int abilityScore = evaluator.Evaluate(abilityRoll);
Console.WriteLine($"4d6 drop lowest: {abilityScore}");
// Roll with advantage (2d20 keep highest)
var advantageRoll = new BasicRoll(2, new DieType.Basic(20),
new List<Modifier> { new KeepModifier(1, true) });
int advantageResult = evaluator.Evaluate(advantageRoll);
Console.WriteLine($"Advantage (2d20kh1): {advantageResult}");
// Exploding dice (d6, explode on 6)
var explodingRoll = new BasicRoll(1, new DieType.Basic(6),
new List<Modifier> { new ExplodeModifier(ComparisonOperator.Equal, 6) });
int explodeResult = evaluator.Evaluate(explodingRoll);
Console.WriteLine($"1d6 exploding: {explodeResult}");
Success-Based Systems
// Shadowrun style: roll 5d6, count successes (5 or 6)
var shadowrunRoll = new BasicRoll(5, new DieType.Basic(6),
new List<Modifier> { new SuccessModifier(ComparisonOperator.GreaterThan, 4) });
int successes = evaluator.Evaluate(shadowrunRoll);
Console.WriteLine($"Shadowrun (5d6, threshold 5): {successes} successes");
// World of Darkness: roll 4d10, 8+ success, 10s explode
var wodRoll = new BasicRoll(4, new DieType.Basic(10), new List<Modifier> {
new ExplodeModifier(ComparisonOperator.Equal, 10),
new SuccessModifier(ComparisonOperator.GreaterThan, 7) });
int wodSuccesses = evaluator.Evaluate(wodRoll);
Console.WriteLine($"World of Darkness: {wodSuccesses} successes");
Evaluator Settings
Rerolls, explosions, and compounds are capped to prevent infinite loops. The caps are configurable per evaluator (each must be at least 1):
var evaluator = new DiceEvaluator();
evaluator.MaxExplosions = 50; // default 100, per die
evaluator.MaxCompounds = 50; // default 100, per die
evaluator.MaxRerolls = 5; // default 10, per die (rc only; ro always rerolls once)
// A seed can be supplied for reproducible rolls
var seeded = new DiceEvaluator(42);
Advanced Multi-Roll Scenarios
More complex game systems may require multiple rolls that interact with each other. You can use the results of multiple evaluations:
// Savage Worlds trait test with wild die
var traitRoll = new BasicRoll(1, new DieType.Basic(8),
new List<Modifier> { new ExplodeModifier(ComparisonOperator.Equal, 8) });
var wildDieRoll = new BasicRoll(1, new DieType.Basic(6),
new List<Modifier> { new ExplodeModifier(ComparisonOperator.Equal, 6) });
int traitResult = evaluator.Evaluate(traitRoll);
int wildDieResult = evaluator.Evaluate(wildDieRoll);
int finalResult = Math.Max(traitResult, wildDieResult);
Console.WriteLine($"Savage Worlds: Trait d8 = {traitResult}, Wild Die d6 = {wildDieResult}, Final = {finalResult}");
Supported Dice Notations
DotDice supports a comprehensive dice notation syntax:
Basic Dice
d#- Standard dice (d4, d6, d8, d10, d12, d20, etc.)d%ord100- Percentile dicedF- Fudge/Fate dice
Arithmetic Expressions
+- Addition (e.g.,1d6+3,2d6+1d4)-- Subtraction (e.g.,1d20-2,3d6-1d4)- Complex expressions:
3d20+5d6-1d4+1
Modifiers
kh#/kh- Keep highest # dice (defaults to 1)kl#/kl- Keep lowest # dice (defaults to 1)dh#/dh- Drop highest # dice (defaults to 1)dl#/dl- Drop lowest # dice (defaults to 1)ro<#,ro>#,ro=#,ro#- Reroll once if less than, greater than, or equal to # (ro#is shorthand forro=#)rc<#,rc>#,rc=#,rc#- Reroll repeatedly until the condition is no longer met (capped byMaxRerolls, default 10)!=#,!>#,!<#,!#,!- Explode if condition is met; bare!explodes on the die's maximum face^=#,^>#,^<#,^#,^(alias!!) - Compound if condition is met; bare^/!!compounds on the maximum face+#,-#- Add or subtract a constant value>#,<#,=#- Count successes (greater than, less than, or equal to #)f>#,f<#,f=#- Count failures; combined with a success modifier the result is successes minus failures
Examples
4d6kh3- Roll 4d6, keep highest 3 (D&D ability scores)2d20kh1- Roll 2d20, keep highest (D&D advantage)1d6!- Roll 1d6, explode on 6 (Savage Worlds)4d6ro1- Roll 4d6, reroll 1s once5d6>4- Roll 5d6, count successes of 5+ (Shadowrun)6d10>8f<2- Roll 6d10, successes on 9+ minus botches on 1s (World of Darkness)3d6+2d4-1- Roll 3d6 plus 2d4 minus 1
Contributing
Contributions are welcome! Feel free to submit issues or pull requests on GitHub.
License
DotDice is licensed under the MIT license. See the LICENSE file for details.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- Pidgin (>= 3.3.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.