### 2 unstable releases

0.2.0 | Apr 29, 2024 |
---|---|

0.1.0 | Apr 24, 2024 |

#**1025** in Parser implementations

**LGPL-3.0-or-later**

99KB

1.5K
SLoC

# Tyche

Tyche is a library for parsing, rolling, and explaining the results of tabletop dice.

It also has a simple CLI app binary that evaluates a given expression.

The eventual goal is full compatibility with FoundryVTT's dice syntax with some convenient extensions.

## Features

- Parsing dice expressions, simple or complex
- Arithmetic
- Addition:
`2d6``+``2` - Subtraction:
`2d6``-``2` - Multiplication:
`2d6``*``2` - Division (integer, rounded down):
`2d6``/``2` - Division (integer, rounded up):
`2d6 \``2` - Standard mathematical order of operations
- Parenthetical grouping:
`(`2d6`+``2``)``*``2`

- Addition:
- Dice modifiers
- Keep highest (advantage):

,`2d20kh``2d20k`- With specific amount to keep:
`3d20kh2`

- With specific amount to keep:
- Keep lowest (disadvantage):
`2d20kl`- With specific amount to keep:
`3d20kl2`

- With specific amount to keep:
- Reroll (once):
`4d6r`- With specific condition:

,`4d6r``>``4`

,`4d6r``>=``5`

,`4``d6r``<`3

,`4``d6r``<`=2`4d6r4`

- With specific condition:
- Reroll (recursive):
`4d6rr`- With specific condition:

,`4d6rr``>``4`

,`4d6rr``>=``5`

,`4``d6rr``<`3

,`4``d6rr``<`=2`4d6rr4`

- With specific condition:
- Explode (recursive):
`4d6x`- With specific condition:

,`4d6x``>``4`

,`4d6x``>=``5`

,`4``d6x``<`3

,`4``d6x``<`=2`4d6x4`

- With specific condition:
- Explode (once):
`4d6xo`- With specific condition:

,`4d6xo``>``4`

,`4d6xo``>=``5`

,`4``d6xo``<`3

,`4``d6xo``<`=2`4d6xo4`

- With specific condition:
- Minimum:
`4d8min3` - Maximum:
`4d8max6`

- Keep highest (advantage):
- Dice modifier chaining (applied in the order they're specified):

,`4d6rr1x``>``4``8d8min2kh4xo` - Roller abstractions, allowing custom die rolling behavior

## Installation

### Library

Run

or add the following to your project's Cargo.toml file:`cargo`` add tyche`

`[``dependencies``]`
`tyche ``=` `"`0.2.0`"`

### Binary (CLI app)

Run

.`cargo`` install tyche --features build-binary`

Assuming Cargo's bin directory is in your

`$``PATH`

, use the app with `tyche`

or `tyche ``<`dice expression`>`

.## Library usage

There are three main types that you'll start with while using Tyche:

: a struct containing a dice count, number of sides for each die, and modifiers that should be applied to any resulting rolls, representing a set of dice, e.g.`Dice`

.`4d8x`

: an AST-like tree structure enum of individual components of a dice expression, capable of representing complex sets of mathematical operations including dice, e.g.`Expr`

.`(`2d6`+``2``)``*``2`

: a trait for rolling individual die values and entire sets of`Roller`

. Sometimes referred to as simply "RNG" for the sake of brevity. There are a few`Dice`

implementations available out of the box:`Roller`

### Parsing

All parsing requires the

feature of the crate to be enabled (which it is by default).`parse`

Tyche uses the chumsky parser generator to parse all strings in a *nearly* zero-copy and wicked-fast fashion.

Most conveniently, parsing can be done by utilizing the standard

implementations for the relevant types
(`FromStr`

, `Dice`

, `Expr`

, and `Modifier`

):`Condition`

`use` `tyche``::``{`
`dice``::``modifier``::``{`Condition`,` Modifier`}``,`
Dice`,` Expr`,`
`}``;`
`let` expr`:` Expr `=` `"`4d6rr<3 + 2d8 - 4`"``.``parse``(``)``?``;`
`let` dice`:` Dice `=` `"`4d6rr<3`"``.``parse``(``)``?``;`
`let` modifier`:` Modifier `=` `"`rr<3`"``.``parse``(``)``?``;`
`let` cond`:` Condition `=` `"`<3`"``.``parse``(``)``?``;`

Alternatively, you can directly use the parsers for each type via its associated

implementation
or the functions in the `GenParser`

module.`tyche ::`parse

### Manually creating Dice

Programmatically constructing

to roll is painless, even with lots of chained modifiers,
thanks to its use of the builder pattern.`Dice`

`use` `tyche``::``{``dice``::``modifier``::`Condition`,` Dice`}``;`
`//` Simple set of dice, no modifiers: 2d20
`let` d2d20 `=` `Dice``::`new`(``2``,` `20``)``;`
`//` Exploding dice: 4d6x
`let` d4d6x `=` `Dice``::`builder`(``)`
`.``count``(``4``)`
`.``sides``(``6``)`
`.``explode``(``None``,` `true``)`
`.``build``(``)``;`
`//` Chained modifiers: 6d6rr1x
`let` d6d6rr1x `=` `Dice``::`builder`(``)`
`.``count``(``6``)`
`.``sides``(``6``)`
`.``reroll``(``Condition``::`Eq`(``1``)``,` `true``)`
`.``explode``(``None``,` `true``)`
`.``build``(``)``;`

### Rolling Dice

All rolling of dice is performed by a

implementation.`Roller`

The most suitable "default" roller implementation is

, which generates random numbers for die values using a
`FastRand`

instance.`fastrand ::`Rng

`use` `tyche``::``dice``::``roller``::`FastRand `as` FastRandRoller`;`
`//` Create a FastRand roller with the default thread-local fastrand::Rng
`let` `mut` roller `=` `FastRandRoller``::`default`(``)``;`
`//` Create a FastRand roller with a custom-seeded fastrand::Rng
`let` rng `=` `fastrand``::``Rng``::`with_seed`(``0x750c38d574400``)``;`
`let` `mut` roller `=` `FastRandRoller``::`new`(`rng`)``;`

Once you have a roller, you can roll a single die at a time or a whole set of

with it:`Dice`

`use` `tyche``::``dice``::``{``roller``::`FastRand `as` FastRandRoller`,` Dice`,` Roller`}``;`
`let` `mut` roller `=` `FastRandRoller``::`default`(``)``;`
`//` Roll a single 20-sided die
`let` die `=` roller`.``roll_die``(``20``)``;`
`//` Roll two six-sided dice
`let` dice `=` `Dice``::`new`(``2``,` `6``)``;`
`let` rolled `=` roller`.``roll``(``&`dice`,` `true``)``?``;`

Rolling a single die results in a

instance, whereas rolling a set of `DieRoll`

returns a `Dice`

instance.`Rolled`

#### Working with rolled dice sets

is a struct that ties multiple `Rolled`

s together with the `DieRoll`

that were rolled to generate them so
it can accurately describe what happened during the roll and application of any modifiers that were on the dice.`Dice`

Using a

result, you can easily total the results of all rolled dice and/or build a string that contains the
original dice set along with a list of each individual die roll.`Rolled`

`use` `tyche``::``{`
`dice``::``{``roller``::`FastRand `as` FastRandRoller`,` Dice`,` Roller`}``,`
`expr``::`Describe`,`
`}``;`
`//` Build and roll 4d6kh2
`let` `mut` roller `=` `FastRandRoller``::`default`(``)``;`
`let` dice `=` `Dice``::`builder`(``)`
`.``count``(``4``)`
`.``sides``(``6``)`
`.``keep_high``(``2``)`
`.``build``(``)``;`
`let` rolled `=` roller`.``roll``(``&`dice`,` `true``)``?``;`
`//` Total the results
`let` total `=` rolled`.``total``(``)``?``;`
`assert!``(``(``2``..``=``12``)``.``contains``(``&`total`)``)``;`
`//` Build a detailed string about the rolls
`//` The resulting string will look like "4d6kh2[2 (d), 5, 6, 4 (d)]"
`let` described `=` rolled`.``to_string``(``)``;`
`//` This is the same as the to_string() call above, except only two die rolls will be listed.
`//` The resulting string will look like "4d6kh2[2 (d), 5, 2 more...]"
`let` limited `=` rolled`.``describe``(``Some``(``2``)``)``;`

#### Working with individual die rolls

A

contains the final value of the die alongside information about any changes that were made to it and the
source of said changes.`DieRoll`

##### Added rolls

When a modifier (rather than the original dice set) causes the addition of a new

to a `DieRoll`

set,
the roll's `Rolled`

field is set to `added_by`

. An added roll will be marked as such in strings.`Some``(`source_modifier`)`

Modifiers that can result in additional die rolls are:

- Explode
- Reroll

##### Dropped rolls

When a modifier causes the removal of a

from a `DieRoll`

set,
the roll's `Rolled`

field is set to `dropped_by`

. A dropped roll will not be affected by any further
modifiers, is not included when totaling the rolled set, and will be marked as dropped in strings.`Some``(`source_modifier`)`

Modifiers that can result in dropped die rolls are:

- Reroll
- Keep highest
- Keep lowest

##### Changed rolls

When a modifier directly manipulates the value of a

in a `DieRoll`

set, the roll's `Rolled`

field has a
`changes`

item added to it that describes the change made and the modifier that caused it.`ValChange`

Modifiers that can result in changed die rolls are:

- Minimum
- Maximum

### Working with expressions

trees are essentially always obtained from parsing an expression string, as manually creating them would be
quite cumbersome.`Expr`

Once you have an

variant, it can be evaluated to produce an `Expr`

expression tree.
`Evaled`

expression trees are nearly identical to their originating `Evaled`

tree, except any Dice variants have their
dice rolled. This separation allows for describing an expression in a detailed way both before and after rolling dice it
contains, in addition to a few other utilities.`Expr`

`use` `tyche``::``{`
`dice``::``roller``::`FastRand `as` FastRandRoller`,`
`expr``::``{`Describe`,` Expr`}``,`
`}``;`
`//` Parse a nice dice expression
`let` expr`:` Expr `=` `"`4d6 + 2d8 - 2`"``.``parse``(``)``?``;`
`//` This expression is definitely not deterministic (it contains dice sets)
`assert!``(``!`expr`.``is_deterministic``(``)``)``;`
`//` Build a string for the expression - in this case, it'll be identical to the original parsed string
`assert_eq!``(`expr`.``to_string``(``)``,` `"`4d6 + 2d8 - 2`"``)``;`
`//` Evaluate the expression, rolling any dice sets it contains
`let` `mut` roller `=` `FastRandRoller``::`default`(``)``;`
`let` evaled `=` expr`.``eval``(``&``mut` roller`)``?``;`
`//` Build a detailed string about the evaluated expression
`//` The resulting string will look like "4d6[3, 2, 6, 2] + 2d8[5, 4] - 2"
`let` described `=` evaled`.``to_string``(``)``;`
`//` This is the same as the to_string() call above, except only two die rolls in each set will be listed.
`//` The resulting string will look like "4d6[3, 2, 2 more...] + 2d8[5, 4] - 2"
`let` limited `=` evaled`.``describe``(``Some``(``2``)``)``;`
`//` Calculate the final result of the expression
`let` total `=` evaled`.``calc``(``)``?``;`
`assert!``(``(``4``..``=``38``)``.``contains``(``&`total`)``)``;`

## Contributing

All contributions are welcome! Try to keep PRs relatively small in scope (single feature/fix/refactor at a time) and word your commits descriptively.

## License

Tyche is licensed under the LGPLv3 license.

#### Dependencies

~2.5–4.5MB

~80K SLoC