{"id":20719640,"url":"https://github.com/dvdagames/js-die-roller","last_synced_at":"2025-07-23T05:33:26.160Z","repository":{"id":41792773,"uuid":"161248351","full_name":"DVDAGames/js-die-roller","owner":"DVDAGames","description":"Easy d20 syntax parsing and better random die rolling in TypeScript","archived":false,"fork":false,"pushed_at":"2025-03-04T15:10:17.000Z","size":1229,"stargazers_count":0,"open_issues_count":14,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-04T15:41:21.297Z","etag":null,"topics":["d20","dice","die-rolling","dnd"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/@dvdagames/js-die-roller","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/DVDAGames.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":["ericrallen"],"patreon":null,"open_collective":null,"ko_fi":"ericrallen","tidelift":null,"custom":"https://supporters.eff.org/donate/"}},"created_at":"2018-12-10T23:09:08.000Z","updated_at":"2024-03-16T13:01:53.000Z","dependencies_parsed_at":"2025-01-17T22:39:37.410Z","dependency_job_id":null,"html_url":"https://github.com/DVDAGames/js-die-roller","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/DVDAGames/js-die-roller","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DVDAGames%2Fjs-die-roller","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DVDAGames%2Fjs-die-roller/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DVDAGames%2Fjs-die-roller/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DVDAGames%2Fjs-die-roller/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/DVDAGames","download_url":"https://codeload.github.com/DVDAGames/js-die-roller/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DVDAGames%2Fjs-die-roller/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266624712,"owners_count":23958299,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-23T02:00:09.312Z","response_time":66,"last_error":null,"robots_txt_status":null,"robots_txt_updated_at":null,"robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["d20","dice","die-rolling","dnd"],"created_at":"2024-11-17T03:17:54.720Z","updated_at":"2025-07-23T05:33:26.143Z","avatar_url":"https://github.com/DVDAGames.png","language":"TypeScript","funding_links":["https://github.com/sponsors/ericrallen","https://ko-fi.com/ericrallen","https://supporters.eff.org/donate/"],"categories":[],"sub_categories":[],"readme":"# Roller (v3)\n\nSimple, intuitive die rolling in a JavaScript Class with better random seeding\nfor all of your Tabletop Roleplaying Game (TTRPG) needs.\n\n**NOTE**: Roller is currently in a Beta state and should be used with that in\nmind. Please file an [Issue](https://github.com/DVDAGames/js-die-roller/issues)\nfor any bugs you discover.\n\n## Getting Started\n\nInstall the Roller library from npm:\n\n```sh\nnpm install --save @dvdagames/js-die-roller\n```\n\nImport the Roller class and start slinging some dice:\n\n```ts\nimport Roller, type { RollerDieNotation } from '@dvdagames/js-die-roller'\n\nconst dice = new Roller()\n\nconst { result } = dice.roll('1d20');\n```\n\n## Result Object\n\nWhen you call `roll()`, it returns a `RollerRollResult` object with the\nfollowing properties:\n\n```ts\ninterface RollerRollResult {\n  // The final aggregated value as a single number\n  total: number\n\n  // The array of rolls after any modifications (drop, min, max)\n  rolls: number[]\n\n  // The original array of all dice rolls before any modifications\n  originalRolls: number[]\n\n  // The original array of fate rolls before converting them to 1, 0, and -1\n  fateRolls: ('-' | '□' | '+')[]\n\n  // The notation used for the roll\n  notation: string\n\n  // Detailed breakdown for each roll\n  breakdown: RollerRoll[]\n}\n```\n\n#### Example Results\n\n```json\n{\n  \"notation\": \"1d20\",\n  \"breakdown\": [\n    {\n      \"1d20: 0\": 7\n    }\n  ],\n  \"total\": 7,\n  \"rolls\": [7],\n  \"originalRolls\": [7],\n  \"fateRolls\": []\n}\n```\n\n### Basic Rolls\n\nRoller supports basic rolling of dice of **any** size, can perform basic\narithmetic with modifiers, and comes with out of the box support for several\ncommon TTRPG functions.\n\n```ts\n// the d20 system (d4, d6, d8, d10, d12, d20)\nconst d20 = () =\u003e dice.roll('1d20').result\n\n// the fate/fudge system (-, +, □)\nconst fate = () =\u003e dice.roll(`4dF`).result\n\nconst magicMissiles = (\n  spellLevel = 1,\n  extraMissiles = 0 //\n): RollerRollResult[] =\u003e {\n  // 3 missiles for spell level 1, +1 missile for each level upcast\n  // some items, effects, etc. can grant extra magic missiles\n  const numberOfMissiles = 3 + extraMissiles + spellLevel - 1\n\n  return Array.from({ length: numberOfMissiles }, () =\u003e {\n    return dice.roll(`1d4 + 1`).result\n  })\n}\n\nconst rollForStat = () =\u003e dice.roll('drop(4d6)').result\n\nconst advantage = () =\u003e dice.roll('max(2d20)').result\n\nconst disadvantage = () =\u003e dice.roll('min(2d20)').result\n\nconst fireball = () =\u003e dice.roll('8d6').result\n\nconst successes = () =\u003e dice.roll('count(6, 6d6)').result\n\nconst damage = (dieSize, numberOfDice, modifier) =\u003e\n  dice.roll(`sum(${numberOfDice}${dieSize} + ${modifier})`).result\n\nconst average = (dieSize: RollerDieNotation, numberOfDice = 1) =\u003e\n  dice.roll(`avg(${numberOfDice}${dieSize})`).result\n```\n\n**Note**: Roller comes with support for the standard array of TTRPG dice ()`d4`,\n`d6`, `d8`, `d10`, `d12`, \u0026 `d20`), as well as support for any arbitrary number\nprefixed with `d`, like `d2` for a coin flip or `d37` for whatever reason you\nmight need it. You can use `dF` for Fate/Fudge Dice.\n\nFunctions can also be chained in interesing ways, for example, to use a common\nmethod for generating player Ability Scores, like `4d6` drop the lowest 7 times\nand then drop the lowest score:\n\n```ts\nconst statRoller = new Roller(\n  'drop(sum(drop(4d6)), sum(drop(4d6)), sum(drop(4d6)), sum(drop(4d6)), sum(drop(4d6)), sum(drop(4d6)), sum(drop(4d6)))'\n)\n\n// get an array of ability scores like: [9, 16, 16, 15, 15, 13]\nconst abilityScoreArray = statRoller.result.rolls\n```\n\n### Fate/Fudge Dice\n\nBy default the Fate/Fudge Dice in Roller are the standard balanced style with 2\n`+` faces, 2 `-` faces, and 2 blank faces (which are represented by `□`).\n\nWhen rolling standard dice, the `originalRolls` array will contain the dice\nbefore any `drop()`, `count()`, etc. has been applied, but when rolling fate\ndice `originalRolls` will be an empty array, and there will be a `fateRolls`\narray that has the `+`, `□`, and `-` symbols for each roll. The `rolls` array\nwill incude the `+1`, `0`, and `-1` values associated with the `fateRolls`. The\n`total` value will be the sum of the `rolls` array.\n\n#### Alternate Fudge Dice\n\nIf you want to enable the alternate Fudge Dice with 1 success face, 1 failure\nface, and 4 neutral faces, you can set the `defaultFateNeutralCount`, which is\n`2` by default, but can be set to `4`.\n\n```ts\nconst fateDice = new Roller({\n  options: {\n    defaultFateNeutralCount: 4,\n  },\n})\n```\n\n### Functions\n\nRoller comes loaded with several utility functions:\n\n- `min`: Take the minimum roll from a set of dice rolls: `min(2d20)`\n- `max`: Take the maximum roll from a set of dice rolls: `max(2d20)`\n- `sum`: Calculate the sum of a set of dice rolls: `sum(8d6)`\n- `avg`: Calculate the average of a set of dice rolls: `avg(4d6)`\n- `drop`: Drop the lowest value from a set of dice rolls: `drop(4d6)`\n- `count`: Count the number of times a specific value appears: `count(6, 8d6)`\n\n### Constructor Overloading\n\nYou can also roll from the `Roller()` constructor directly if you won't need to\nreuse the Roller instance:\n\n```ts\nconst roll = new Roller('1d20').result\n```\n\n### Advanced Interface\n\nYou can also use the `Roller()` constructor to generate an advanced interface\nfor a Tabletop Roleplaying Game (TTRPG) character or game mechanics by defining\nvariables and named rolls.\n\nLet's see an exmaple of creating a Level 1 Fighter (DnD Fifth Edition) from\nscratch.\n\nRather than just hardcoding the configuration object, let's also use Roller to\ngenerate our Ability Scores and then programmatically generate some variables\nand functions, like our ability score modifiers and saving throws:\n\n```ts\n// the ability scores in DnD Fifth Edition\nconst statNames = ['STR', 'DEX', 'CON', 'INT', 'WIS', 'CHA']\n\n// 4d6 drop the lowest\n// the method we're going to use for generating an ability score\nconst statRoll = 'sum(drop(4d6))'\n\n// roll 7 scores and drop the lowest\n// the method we're gong to use for generating our stat block\nconst statsToRoll = statNames.length + 1\n\n// generate a complext d20 syntax: drop(sum(drop(4d6)), sum(drop(4d6)), ...)\nconst statsRoll = `drop(${Array.from({ length: statsToRoll })\n  .map(() =\u003e statRoll)\n  .join(', ')})`\n\n// generate our new scores array\nconst statsGenerator = new Roller(statsRoll)\n\nconst abilityScores = statNames.reduce((scores, stat, index) =\u003e {\n  const score = statsGenerator.rolls[i]\n  scores[stat] = {\n    score: score,\n\n    // dnd5e stat modifiers are floor((STAT - 10) / 2)\n    modifier: Math.floor((score - 10) / 2),\n  }\n\n  return scores\n}, {})\n\nconst Fighter = new Roller({\n  variables: {\n    level: 1,\n    proficiency: 2,\n    ...Object.entries(abilityScores).reduce((statBlock, [stat, values]) =\u003e {\n      statBlock = {\n        ...statBlock,\n        [stat.toLowerCase()]: values.score,\n        [`${stat.toLowerCase()}Mod`]: values.modifier,\n      }\n\n      return statBlock\n    }, {}),\n  },\n  map: {\n    // when we use a variable in our roll, we always prefix it\n    // with a `$` so Roller knows to look it up in the variables object\n    initiative: '1d20 + $dexMod',\n    longsword: {\n      hit: '1d20 + $strMod + $proficiency',\n      dmg: {\n        '1h': '1d8 + $strMod',\n        '2h': '1d10 + $strMod',\n      },\n    },\n    saves: statNames.reduce((saves, stat) =\u003e {\n      saves[stat] = `1d20 + $${stat.toLowerCase()}Mod${\n        proficiencies.includes(stat) ? ` + $proficiency` : ''\n      }`\n\n      return saves\n    }, {}),\n  },\n})\n```\n\nNow we can call the rolls defined in our `map` object directly and Roller will\nsubstitute the necessary variables from our `variables` object, roll the\nappropriate dice, and calculate the total for us.\n\nHere are some examples:\n\n```ts\n// something came up that we're proficient at, but don't have a specific stat tied to\nFighter.roll('1d20 + $proficiency').total\n\n// it's time for some combat; roll initiative\nFighter.roll('initiative').total\n\n// let's try to hit the enemy\nFighter.roll('longsword.hit').total\n\n// we hit; roll for damage\nFighter.roll('longsword.dmg.2h').total\n\n// the enemy druid is trying to entangle us; make a saving throw\nFighter.roll('saves.STR').total\n```\n\nThis consistent return structure makes it easy to work with roll results\nregardless of what operations were performed:\n\n```ts\n// Get a single total value\nconst damage = dice.roll('8d6').total\n\n// Get individual dice results after operations\nconst statRolls = dice.roll('drop(4d6)').rolls // [5, 3, 6] (lowest dropped)\n\n// Get all original dice values before any operations\nconst allDice = dice.roll('drop(4d6)').originalRolls // [1, 5, 3, 6] (includes lowest)\n```\n\n## Why another die rolling implementation?\n\nI found a lot of other die rolling libraries lacking in features and unable to\nexplain how their random numbers were obtained. I wanted to try my hand at\nmaking the kind of program I wanted to use and also at learning a bit more about\nhow well I could generate random numbers.\n\n## What's different about Roller?\n\n### Basic Features\n\n- **Better Random Values**: Roller attempts to reduces bias and leverages the\n  `crypto` API to generate it's random rolls\n- **Standard Die Notation**: Roller parses and executes almost all standard die\n  notation, it leverages a light Abstract Syntax Tree (AST) implementation to\n  achieve this and make it easier to adjust in the future\n\nRoller supports some pretty advanced features along with parsing most standard\ndie notation (`NdX + B` where `N` is how many dice to roll, `X` is the size of\nthe die being rolled, and `B` is some other modifier to add to the roll).\n\nRoller attempts to use the `crypto` API to attempt to generate better random\nvalues and then further attempts to reduce bias in the randomly generated\nvalues, based on the great research about\n[Generating random integers from random bytes](http://dimitri.xyz/random-ints-from-random-bits/)\nfrom [Dimitri DeFigueiredo Ph.D.](http://dimitri.xyz/about/)\n\n## Playground\n\nYou can check out the example Character implemented in the `examples/demo.js`\nfile by cloning this repo and running `npm run demo` and trying out some of his\nsaved rolls and sling some regular dice, too.\n\nHere are some examples rolls to test:\n\n- `xbow.hit`\n- `sacred-flame.dmg`\n\n## Roll Fairness\n\nThis library undergoes extensive statistical testing to ensure fair and random\ndice rolls. We use\n[chi-square goodness-of-fit](https://www.statology.org/chi-square-goodness-of-fit-test/)\ntests to verify that the distribution of results matches theoretical\nexpectation.\n\nTo view detailed statistical analysis:\n\n```\nnpm run fairness\n```\n\nThis generates a [`FAIRNESS.md`](./FAIRNESS.md) file with detailed distribution\ncharts and chi-square test results for all the common die sizes (`d4`, `d6`,\n`d8`, `d10`, `d12`, `d20`).\n\nAdditionally, we've compared our implementation with the standard JavaScript\n`Math.random()` approach:\n\n```\nnpm run compare-implementations\n```\n\nThis generates an\n[`IMPLEMENTATION_COMPARISON.md`](./IMPLEMENTATION_COMPARISON.md) file that\ncompares the statistical fairness of Roller against a typical `Math.random()`\ndice rolling implementation. The analysis, conducted with millions of dice\nrolls, shows that both implementations provide statistically fair dice rolls,\nwith Roller offering particularly good performance for larger dice (d10, d20)\nthat are common in tabletop RPGs. Roller also offers additional features and\nflexibility through its advanced API beyond just fair dice rolling.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdvdagames%2Fjs-die-roller","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdvdagames%2Fjs-die-roller","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdvdagames%2Fjs-die-roller/lists"}