<img src="./images/public-domain.svg" alt="Public Domain" align="right" width="20%" height="auto"/>
A complete dice expression has these parts:
The smallest dice expression is just a die type, eg, d6
meaning roll a
single, regular 6-sided die.
See Dice Expression Syntax and
Examples, below, for more interesting expressions.
Try ./roll --demo
for a demonstration, or ./roll --demo --verbose
to
see more in how dice expressions work.
Running ./roll
presents an interactive prompt for entering and evaluating
dice expressions.
Note — CI presently consistently fails owing to troubles with command line interations for features like tab completion.
Use ./mvnw
(Maven) or ./batect build
(Batect) to build, run tests, and
create a demo program.
Use ./roll
or ./batect demo
to run the demo.
CI uses Batect to verify builds and behavior, so an easy way for you to check your changes before pushing to GitHub.
Try ./roll --help
to see this help on the command line:
Usage:
roll [-hrvV] [--copyright] [--demo] [--no-history] [-C[=WHEN]] [-m=MINIMUM]
[-P=PROMPT] [-s=SEED] [--] [@<filename>...] [EXPRESSION(s)...] [COMMAND]
Description:
Roll dice expressions.
Parameters:
[@<filename>...] One or more argument files containing options.
[EXPRESSION(s)...] Dice expressions to roll.
Options:
-C, --color[=WHEN] Choose color output (always, yes, force, auto, tty,
if-tty, never, no, none).
Default with no option is 'auto'.
Default with option but no WHEN is 'always'.
--copyright Show the copyright and exit.
--demo Run the demo and exit.
-h, --help Show this help message and exit.
-m, --minimum=MINIMUM Fail roll results below MINIMUM.
Default with no option is no minimum.
--no-history Do not save history from the REPL.
-P, --prompt=PROMPT Change the REPL prompt from '🎲 '.
-r, --result-only Show only roll results.
-s, --seed=SEED Fix RNG seed to SEED for repeatable roll results.
-v, --verbose Show die rolls as they happens.
-V, --version Print version information and exit.
-- This option can be used to separate command-line
options from the list of positional parameters.
Commands:
clear clear the screen
history list command history excluding this command
options view or change options
Input modes:
roll
Run the REPL.
roll <expression(s)>
Show roll results of dice expression(s) and exit.
echo <expression(s)> | roll
Show roll result of dice expression(s) read from STDIN and exit.
Output examples:
roll --seed=1 2d4 2d4 (normal)
2d4 4
2d4 7
roll --seed=1 --verbose 2d4 2d4 (verbose)
---
roll(d4) -> 1
roll(d4) -> 3
2d4 -> 4
---
roll(d4) -> 4
roll(d4) -> 3
2d4 -> 7
Files:
~/.roll_history
This file preserves input history across runs of the REPL.
Error messages:
Incomplete dice expression '<EXPRESSION>'
More characters were expected at the end of EXPRESSION.
Unexpected '<CHAR>' (at position <POS>) in dice expression '<EXPRESSION>'
CHAR was not expected in EXPRESSION at position POS (starting from 1).
Result <ROLL> is below the minimum result of <NUMBER>
ROLL is too low for the NUMBER in the --minimum option.
Exploding on <NUMBER> will never finish in dice expression '<EXPRESSION>'
NUMBER is too low for the number of sides on the die.
History disabled because of the --no-history option
Read a history command ('!' first character) but option set for no history.
Exit codes:
0 Successful completion
1 Bad dice expression
2 Bad program usage
130 REPL interrupted (SIGINT)
Parsing dice expressions turns out to be an interesting programming problem. This project implements a mashup of several dice expression syntaxes, drawing inspiration from:
This project supports these types of expressions:
[N]'B'D['r'R]['h'[K]|'m'[K]|'n'[K]|'l'[K]][!|!Z]['x'M|'*'M][+EXP|-EXP...][+A|-A]
d
(dice are 1 to D) or z
(dice are 0 to D-1)%
for percentile dice (100-sided dice)h
)n
)m
)l
)For example, in D&D a d20 roll with advantage is "2d20h" and with disadvantage is "2d20l", and in Star Wars an exploding d6 roll is "d6!".
All characters are case-insensitive, eg, d6
and D6
are the same
expression.
Whitespace is supported only:
+
and -
operators between single dice expressionsNotes:
1 + 2
does not workSee TODO for further improvements.
d6
-- roll 1 6-sided die; "dD" is the minimal possible expressiond6x2
-- roll 1 6-sided die, double the resultz6
-- roll 1 6-sided die zero-based (0-5); "zD" is the minimal possible
expression2d%+1
-- roll percentile dice 2 times, sum, and add 1 to the result3d6r1!
-- roll 3 6-sided dice, rerolling 1s, "explode" on 6s3d6r1!5
-- roll 3 6-sided dice, rerolling 1s, "explode" on 5s or 6s2d4+2d6h1
-- roll 2 4-sided dice, sum; roll 2 6-sided dice keeping the
highest roll; add both resultsThe demo examples (look at
demoExpressions
) cover all supported examples.
Running ./roll
with arguments or input starts an interactive REPL
(read-evaluate-print loop).
The REPL includes many features, courtesy of
Picocli and JLine3,
including:
Ctrl-A
or up/down arrow~/.roll_history
) and expansion (!!
)In a terminal, output is colorized.
In a README.md
it looks like:
$ ./roll
🎲 3d6
3d6 8
$ ./roll --verbose
🎲 3d6
---
roll(d6) -> 4
roll(d6) -> 2
roll(d6) -> 1
3d6 -> 7
🎲
The code falls into two halves:
roll
shell scriptThe key method is dice(random, reporter)
in the companion object of
DiceParser
.
This creates a reuseable parser and roller.
The random
parameter is a Kotlin Random
, and defaults to the system RNG.
The reporter
parameter is a
RollReporter
and defaults to "do nothing" (ie, no reporting).
The simplest example is:
val dice = dice() // Static import
val result = dice.roll("3d6")
println(result.resultValue)
A fancier example might be:
val dice = dice(Random(1)) { rolledDice ->
with(rolledDice) {
val die = when (dieBase) {
ONE -> "d$dieSides"
ZERO -> "z$dieSides"
}
val trace = when (this) {
is PlainRoll -> "rolled $die was $roll"
is PlainReroll -> "rerolled $die was $roll"
is ExplodedRoll -> "exploded $die >= $explodeHigh was $roll"
is ExplodedReroll -> "exploded reroll $die >= $explodeHigh is $roll"
is DroppedRoll -> "dropped $die was $roll"
}
println(trace)
}
}
val result = dice.roll("2d20h")
// Above tracing prints here
println("result is ${result.resultValue}")
And would output:
rolled d20 was 6
rolled d20 was 17
dropped d20 was 6
result is 17
At each top-level part of a dice expression parse (eg, die sides), the parser saves a local value internally. By the end of the dice expression, this includes:
The parser uses a stack for some cases:
+
/-
sign (add/subtract)roll
and main()
:Multiple modes of operation:
--help
have colorized outputmain()
as well as supporting codeCtrl-a
, et al)Remember to distinguish STDOUT and STDERR, helpful when using ./roll
in
scripts.
Much gratitude to the authors of these libraries:
2d6/2
floor
, ceil
, etc., to round rolls down/up