DotDice 1.6.0

dotnet add package DotDice --version 1.6.0
                    
NuGet\Install-Package DotDice -Version 1.6.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="DotDice" Version="1.6.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="DotDice" Version="1.6.0" />
                    
Directory.Packages.props
<PackageReference Include="DotDice" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add DotDice --version 1.6.0
                    
#r "nuget: DotDice, 1.6.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package DotDice@1.6.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=DotDice&version=1.6.0
                    
Install as a Cake Addin
#tool nuget:?package=DotDice&version=1.6.0
                    
Install as a Cake Tool

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 (Add or Subtract)
  • Single rolls (like "2d6") have null for 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% or d100 - Percentile dice
  • dF - 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 for ro=#)
  • rc<#, rc>#, rc=#, rc# - Reroll repeatedly until the condition is no longer met (capped by MaxRerolls, 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 once
  • 5d6>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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.6.0 85 9/7/2026
1.5.0 114 7/6/2026
1.4.1 204 10/23/2025
1.4.0 135 9/28/2025
1.3.0 211 8/31/2025
1.0.1 207 5/19/2025
1.0.0 203 5/19/2025