npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@dvdagames/js-die-roller

v3.1.0

Published

Rolling complex die notation with variables and functions

Readme

Roller (v3)

Simple, intuitive die rolling in a JavaScript Class with better random seeding for all of your Tabletop Roleplaying Game (TTRPG) needs.

NOTE: Roller is currently in a Beta state and should be used with that in mind. Please file an Issue for any bugs you discover.

Getting Started

Install the Roller library from npm:

npm install --save @dvdagames/js-die-roller

Import the Roller class and start slinging some dice:

import Roller, type { RollerDieNotation } from '@dvdagames/js-die-roller'

const dice = new Roller()

const { result } = dice.roll('1d20');

Result Object

When you call roll(), it returns a RollerRollResult object with the following properties:

interface RollerRollResult {
  // The final aggregated value as a single number
  total: number

  // The array of rolls after any modifications (drop, min, max)
  rolls: number[]

  // The original array of all dice rolls before any modifications
  originalRolls: number[]

  // The original array of fate rolls before converting them to 1, 0, and -1
  fateRolls: ('-' | '□' | '+')[]

  // The notation used for the roll
  notation: string

  // Detailed breakdown for each roll
  breakdown: RollerRoll[]
}

Example Results

{
  "notation": "1d20",
  "breakdown": [
    {
      "1d20: 0": 7
    }
  ],
  "total": 7,
  "rolls": [7],
  "originalRolls": [7],
  "fateRolls": []
}

Basic Rolls

Roller supports basic rolling of dice of any size, can perform basic arithmetic with modifiers, and comes with out of the box support for several common TTRPG functions.

// the d20 system (d4, d6, d8, d10, d12, d20)
const d20 = () => dice.roll('1d20').result

// the fate/fudge system (-, +, □)
const fate = () => dice.roll(`4dF`).result

const magicMissiles = (
  spellLevel = 1,
  extraMissiles = 0 //
): RollerRollResult[] => {
  // 3 missiles for spell level 1, +1 missile for each level upcast
  // some items, effects, etc. can grant extra magic missiles
  const numberOfMissiles = 3 + extraMissiles + spellLevel - 1

  return Array.from({ length: numberOfMissiles }, () => {
    return dice.roll(`1d4 + 1`).result
  })
}

const rollForStat = () => dice.roll('drop(4d6)').result

const advantage = () => dice.roll('max(2d20)').result

const disadvantage = () => dice.roll('min(2d20)').result

const fireball = () => dice.roll('8d6').result

const successes = () => dice.roll('count(6, 6d6)').result

const damage = (dieSize, numberOfDice, modifier) =>
  dice.roll(`sum(${numberOfDice}${dieSize} + ${modifier})`).result

const average = (dieSize: RollerDieNotation, numberOfDice = 1) =>
  dice.roll(`avg(${numberOfDice}${dieSize})`).result

Note: Roller comes with support for the standard array of TTRPG dice ()d4, d6, d8, d10, d12, & d20), as well as support for any arbitrary number prefixed with d, like d2 for a coin flip or d37 for whatever reason you might need it. You can use dF for Fate/Fudge Dice.

Functions can also be chained in interesing ways, for example, to use a common method for generating player Ability Scores, like 4d6 drop the lowest 7 times and then drop the lowest score:

const statRoller = new Roller(
  'drop(sum(drop(4d6)), sum(drop(4d6)), sum(drop(4d6)), sum(drop(4d6)), sum(drop(4d6)), sum(drop(4d6)), sum(drop(4d6)))'
)

// get an array of ability scores like: [9, 16, 16, 15, 15, 13]
const abilityScoreArray = statRoller.result.rolls

Fate/Fudge Dice

By default the Fate/Fudge Dice in Roller are the standard balanced style with 2 + faces, 2 - faces, and 2 blank faces (which are represented by ).

When rolling standard dice, the originalRolls array will contain the dice before any drop(), count(), etc. has been applied, but when rolling fate dice originalRolls will be an empty array, and there will be a fateRolls array that has the +, , and - symbols for each roll. The rolls array will incude the +1, 0, and -1 values associated with the fateRolls. The total value will be the sum of the rolls array.

Alternate Fudge Dice

If you want to enable the alternate Fudge Dice with 1 success face, 1 failure face, and 4 neutral faces, you can set the defaultFateNeutralCount, which is 2 by default, but can be set to 4.

const fateDice = new Roller({
  options: {
    defaultFateNeutralCount: 4,
  },
})

Functions

Roller comes loaded with several utility functions:

  • min: Take the minimum roll from a set of dice rolls: min(2d20)
  • max: Take the maximum roll from a set of dice rolls: max(2d20)
  • sum: Calculate the sum of a set of dice rolls: sum(8d6)
  • avg: Calculate the average of a set of dice rolls: avg(4d6)
  • drop: Drop the lowest value from a set of dice rolls: drop(4d6)
  • count: Count the number of times a specific value appears: count(6, 8d6)

Constructor Overloading

You can also roll from the Roller() constructor directly if you won't need to reuse the Roller instance:

const roll = new Roller('1d20').result

Advanced Interface

You can also use the Roller() constructor to generate an advanced interface for a Tabletop Roleplaying Game (TTRPG) character or game mechanics by defining variables and named rolls.

Let's see an exmaple of creating a Level 1 Fighter (DnD Fifth Edition) from scratch.

Rather than just hardcoding the configuration object, let's also use Roller to generate our Ability Scores and then programmatically generate some variables and functions, like our ability score modifiers and saving throws:

// the ability scores in DnD Fifth Edition
const statNames = ['STR', 'DEX', 'CON', 'INT', 'WIS', 'CHA']

// 4d6 drop the lowest
// the method we're going to use for generating an ability score
const statRoll = 'sum(drop(4d6))'

// roll 7 scores and drop the lowest
// the method we're gong to use for generating our stat block
const statsToRoll = statNames.length + 1

// generate a complext d20 syntax: drop(sum(drop(4d6)), sum(drop(4d6)), ...)
const statsRoll = `drop(${Array.from({ length: statsToRoll })
  .map(() => statRoll)
  .join(', ')})`

// generate our new scores array
const statsGenerator = new Roller(statsRoll)

const abilityScores = statNames.reduce((scores, stat, index) => {
  const score = statsGenerator.rolls[i]
  scores[stat] = {
    score: score,

    // dnd5e stat modifiers are floor((STAT - 10) / 2)
    modifier: Math.floor((score - 10) / 2),
  }

  return scores
}, {})

const Fighter = new Roller({
  variables: {
    level: 1,
    proficiency: 2,
    ...Object.entries(abilityScores).reduce((statBlock, [stat, values]) => {
      statBlock = {
        ...statBlock,
        [stat.toLowerCase()]: values.score,
        [`${stat.toLowerCase()}Mod`]: values.modifier,
      }

      return statBlock
    }, {}),
  },
  map: {
    // when we use a variable in our roll, we always prefix it
    // with a `$` so Roller knows to look it up in the variables object
    initiative: '1d20 + $dexMod',
    longsword: {
      hit: '1d20 + $strMod + $proficiency',
      dmg: {
        '1h': '1d8 + $strMod',
        '2h': '1d10 + $strMod',
      },
    },
    saves: statNames.reduce((saves, stat) => {
      saves[stat] = `1d20 + $${stat.toLowerCase()}Mod${
        proficiencies.includes(stat) ? ` + $proficiency` : ''
      }`

      return saves
    }, {}),
  },
})

Now we can call the rolls defined in our map object directly and Roller will substitute the necessary variables from our variables object, roll the appropriate dice, and calculate the total for us.

Here are some examples:

// something came up that we're proficient at, but don't have a specific stat tied to
Fighter.roll('1d20 + $proficiency').total

// it's time for some combat; roll initiative
Fighter.roll('initiative').total

// let's try to hit the enemy
Fighter.roll('longsword.hit').total

// we hit; roll for damage
Fighter.roll('longsword.dmg.2h').total

// the enemy druid is trying to entangle us; make a saving throw
Fighter.roll('saves.STR').total

This consistent return structure makes it easy to work with roll results regardless of what operations were performed:

// Get a single total value
const damage = dice.roll('8d6').total

// Get individual dice results after operations
const statRolls = dice.roll('drop(4d6)').rolls // [5, 3, 6] (lowest dropped)

// Get all original dice values before any operations
const allDice = dice.roll('drop(4d6)').originalRolls // [1, 5, 3, 6] (includes lowest)

Why another die rolling implementation?

I found a lot of other die rolling libraries lacking in features and unable to explain how their random numbers were obtained. I wanted to try my hand at making the kind of program I wanted to use and also at learning a bit more about how well I could generate random numbers.

What's different about Roller?

Basic Features

  • Better Random Values: Roller attempts to reduces bias and leverages the crypto API to generate it's random rolls
  • Standard Die Notation: Roller parses and executes almost all standard die notation, it leverages a light Abstract Syntax Tree (AST) implementation to achieve this and make it easier to adjust in the future

Roller supports some pretty advanced features along with parsing most standard die notation (NdX + B where N is how many dice to roll, X is the size of the die being rolled, and B is some other modifier to add to the roll).

Roller attempts to use the crypto API to attempt to generate better random values and then further attempts to reduce bias in the randomly generated values, based on the great research about Generating random integers from random bytes from Dimitri DeFigueiredo Ph.D.

Playground

You can check out the example Character implemented in the examples/demo.js file by cloning this repo and running npm run demo and trying out some of his saved rolls and sling some regular dice, too.

Here are some examples rolls to test:

  • xbow.hit
  • sacred-flame.dmg

Roll Fairness

This library undergoes extensive statistical testing to ensure fair and random dice rolls. We use chi-square goodness-of-fit tests to verify that the distribution of results matches theoretical expectation.

To view detailed statistical analysis:

npm run fairness

This generates a FAIRNESS.md file with detailed distribution charts and chi-square test results for all the common die sizes (d4, d6, d8, d10, d12, d20).

Additionally, we've compared our implementation with the standard JavaScript Math.random() approach:

npm run compare-implementations

This generates an IMPLEMENTATION_COMPARISON.md file that compares the statistical fairness of Roller against a typical Math.random() dice rolling implementation. The analysis, conducted with millions of dice rolls, shows that both implementations provide statistically fair dice rolls, with Roller offering particularly good performance for larger dice (d10, d20) that are common in tabletop RPGs. Roller also offers additional features and flexibility through its advanced API beyond just fair dice rolling.