Skip to content

tilttek/dice

✅ published

Dice notation parsed, checked, normalised and rolled — and every die kept to show how the result came.

tilttek/dice reads a dice expression such as 4d6kh3, d20 adv, 3d10!!, 6d10>=7f1, 8d10c[>=6]-@c[=1], ceil(1d6/3), (1d8+2)*2, 1d4 or 8d10; @1c[>=6]; "$2 successes", refuses an invalid one with the position of the error, writes it back in a canonical form, and rolls it. Instructions refer to each other, branch on a result, draw from lists of texts and write sentences: a result is a number, a text or a list. A roll keeps each die, each reroll, each explosion, what was kept or dropped, and each total. It also rolls custom dice whose faces are symbols, reads and writes the notation of other languages (2W6 in German), reads a command of Rolisteam's DiceParser, resolves the macros an application keeps ($attack), writes an expression in the dialect of a virtual tabletop, and makes rolls that anyone can verify or that replay die for die. It does not draw dice, animate them, keep a journal or know who rolls: a page, a bundle or an application does that.

It is for any PHP code that rolls dice for people: a dice roller page, a play-by-post forum, a character sheet, a virtual table, a game book. tilttek/dice-bundle integrates it with Symfony; an application without Symfony takes the library alone.

It is a plain PHP library with no dependency beyond the ctype, mbstring and sodium extensions. Its randomness is a Random\Randomizer the caller passes: a secure engine in production, a fixed seed in tests, the seed of a verifiable or replayable roll. The parser is a hand-written lexer and recursive-descent parser, without regular expressions on the input.

The design is small: a DiceParser gives a Pool of expression trees — a RolisteamReader too, from DiceParser's notation —, a Roller rolls a pool into a PoolResult, a NotationWriter and the Dialect classes write a pool back, FairRoller and Replayer make verifiable and replayable rolls, and DiceRoller puts them in one object.

Status

PartState
Notation: dice, d%, Fudge dice weighted or not, dice on a range, custom dice, counts and sides rolled first, lists of values, the dice before, poolsImplemented and tested
Modifiers: rerolls repeated or once, unique dice, explosions spread, compound, penetrating or limited, dice all the same, floors and ceilings, transformations, filters, keep and drop, advantage, successes and failures, occurrences, groups, critical marks, look tokens, sortingImplemented and tested
Validators: values, ranges, comparisons, remainders, &, `and^`, and their scope
Integer arithmetic with the rounding the caller gives; floor, ceil, round, abs, min, max, repeat, remainder and powerImplemented and tested
Errors with their position; limits on dice, sides, explosions, rerolls, repetitions, instructions, length and textsImplemented and tested
Canonical form and the notations of other languages (German, Danish, Norwegian, Swedish built in)Implemented and tested
Rolls that keep every die; custom dice counted by symbol and summed when their faces countImplemented and tested
Verifiable rolls by seed commitment; replayable rollsImplemented and tested
Dialects: Roll20, Foundry VTT, Avrae and Rolisteam's DiceParser, with the constructs each cannot write reportedImplemented and tested
Instructions, references, the dice of instructions bound or merged, groups, text lists, sentences, if and switch, repeat(), commentsImplemented and tested
The reader of Rolisteam's DiceParser notation, fuzzedImplemented and tested
Macros named by token, $name, resolved by the applicationImplemented and tested
Exceptions under one DiceException interfaceImplemented and tested

Documentation

DocumentWhat it covers
InstallationRequirements and installing.
NotationEvery construct, the validators and their scope, the order of the modifiers, the canonical form, and the errors.
Instructions and resultsInstructions and references, groups, text lists, texts and sentences, branches, repetitions, comments, and the results that are not numbers.
RollingRoller, the randomness, the results and what they keep, the repeated operators, the arithmetic and its rounding.
Custom diceFaces, labels, shapes and colours, the accessibility rule, tallies.
LanguagesNotation and Notations: the letters of a language, read and written.
DialectsWriting a pool for Roll20, Foundry VTT, Avrae and Rolisteam's DiceParser, and adding a dialect.
Rolisteam's DiceParserReading a DiceParser command into the library's tree, what is read and what is refused.
Macros$name read as a token, the Macros contract, its rules and its errors.
Verifiable and replayable rollsSeeds, commitments, client seeds and nonces; replay identifiers.
LimitsThe bounds against abuse and where they are checked.
Extension pointsWhat an application gives the library: macros, dialects, notations, custom dice, limits, randomness.
ExceptionsDiceException and its exceptions, with their messages.
TestingThe library's tests and mutation bar, and how to test code that rolls dice.
ReferenceEvery type of the library, with its role.

Quick start

composer require tilttek/dice
use Random\Engine\Secure;
use Random\Randomizer;
use Tilttek\Dice\DiceRoller;

$dice = new DiceRoller();
$pool = $dice->parse('4D6KH3, d20 adv');
$randomizer = new Randomizer(new Secure());

$pool->canonical();                                   // "4d6kh3, 2d20kh1"
$result = $dice->roll($pool, $randomizer);
$result->totals();                                    // [14, 17], for instance
$dice->translate($pool, 'roll20')->text();            // "/r 4d6kh3\n/r 2d20kh1"
$dice->write($dice->parse('2W6+1', 'de'));            // "2d6+1"
$dice->roll($dice->parse('1d6/3'), $randomizer, RoundingMode::PositiveInfinity)->totals(); // [2], rounded up
$dice->roll($dice->parse('1d20i[>=15]{"hit"}{"miss"}'), $randomizer)->outputs();         // ["miss"], for instance

Requirements

PHP 8.5 or later, with the ctype, mbstring and sodium extensions.

Contributing

Contributions are welcome on the PHIL monorepo: read the contribution guide and the code of conduct. Report a vulnerability privately, as the security policy explains, never in a public issue.

License

MIT, © Philippe Gamache, doing business as Le Gnome Archiviste; a Tilttek project. See LICENSE.