# d20
[](https://crates.io/crates/d20) [](https://docs.rs/d20/)
**D20** is a simple crate designed to evaluate _roll expressions_. A _roll expression_ is an
english-language string that reflects the intent of a dungeon or game master to perform a
particular roll.
For example, in a tabletop game you may frequently hear phrases like _"roll 2d10"_, or
_"roll 3d6 and add 5"_. These are roll expressions, and the components within them are
what we call _die roll terms_. A _die roll term_ is either a term that calls for the rolling
of an n-sided die x times (e.g. 3d6) or a modifier that simply adds or subtracts a constant value
from the larger expression.
Examples of valid _roll expressions_ include:
* 3d6
* 2d10 + 5
* 1d20-3
* +6
* -2
* 3d10+5d100-21+7
Roll expressions can have arbitrary length and complexity, and it is perfectly legal for the final result
of a roll expression to be negative after applying modifiers.
## Usage
```rust
let r = d20::roll_dice("3d6 + 4").unwrap();
assert!(r.total > 6);
let r = d20::roll_dice("1d1-3").unwrap();
assert_eq!(r.total, -2);
// Malformed expressions return a descriptive error instead of panicking.
assert!(d20::roll_dice("roll four chickens and add six ferrets").is_err());
```
Every failure mode is reported through the `d20::D20Error` enum, and no input —
however malformed — will ever panic. Counts, sides, and modifiers are supported
up to the documented `MAX_DICE` / `MAX_SIDES` / `MAX_MODIFIER` limits; values
beyond them return a clear error rather than overflowing.
### Inspecting Results
A `Roll` exposes the per-term breakdown via `terms: Vec<TermResult>`. Each
`TermResult` is either a `Dice { multiplier, sides, rolls }` recording the
individual die results, or a `Modifier(i32)` constant — so the data is
self-describing (modifiers are not faked as single-element rolls):
```rust
use d20::TermResult;
let r = d20::roll_dice("3d6 + 2").unwrap();
for term in &r.terms {
match term {
TermResult::Dice { multiplier, sides, rolls } => {
println!("{multiplier}d{sides} rolled {rolls:?} (subtotal {})", term.subtotal());
}
TermResult::Modifier(n) => println!("modifier {n}"),
}
}
assert_eq!(r.total, r.terms.iter().map(TermResult::subtotal).sum());
```
### Iterating Roll
A valid `Roll` can be turned into an open-ended iterator via its `rolls()` method, providing successive
rolls of the given die roll expression.
_Note that it will be necessary to constrain the iterator via `take(n)`._
```rust
use d20::*;
let raw_stats: Vec<Roll> = d20::roll_dice("3d6").unwrap().rolls().take(6).collect();
println!("\nCHARACTER STATS:");
println!(" STR: {}", raw_stats[0].total);
println!(" INT: {}", raw_stats[1].total);
println!(" WIS: {}", raw_stats[2].total);
println!(" DEX: {}", raw_stats[3].total);
println!(" CON: {}", raw_stats[4].total);
println!(" CHA: {}", raw_stats[5].total);
```
### Deterministic Rolls
For reproducible results (tests, replays), supply your own RNG via
`roll_dice_with_rng` / `roll_range_with_rng`:
```rust
use rand::SeedableRng;
use rand::rngs::StdRng;
let mut rng = StdRng::seed_from_u64(42);
let a = d20::roll_dice_with_rng("3d6", &mut rng).unwrap();
let b = d20::roll_dice_with_rng("3d6", &mut StdRng::seed_from_u64(42)).unwrap();
assert_eq!(a, b); // same seed => same roll
```
### Range Rolls
If you are less concerned about dice rolls and require only a random number within a given range, `roll_range()`
will do just that.
```rust
let rg = d20::roll_range(1, 100).unwrap();
assert!((1..=100).contains(&rg));
```