dice_nom/lib.rs
1//! # dice-nom
2//!
3//! A comprehensive dice rolling library that utilizes the nom parser for randomly generating
4//! numbers to support role-playing games. This library provides parsing capabilities for
5//! complex dice expressions including exploding dice, target numbers, success levels,
6//! and various modifiers commonly used in tabletop RPGs.
7//!
8//! ## Features
9//!
10//! - **Complex Dice Expressions**: Parse strings like `"3d6+2"`, `"4d8!"`, or `"2d20^1"`
11//! - **Dice Operations**: Exploding dice, advantage/disadvantage, target numbers
12//! - **Pool Operations**: Keep highest/lowest, take middle values, group matching
13//! - **Arithmetic**: Addition, subtraction with proper precedence
14//! - **Comparison Operations**: Compare dice pools with various operators
15//! - **Success Systems**: Target-based and threshold-based success counting
16//!
17//! ## Quick Start
18//!
19//! ```rust
20//! use dice_nom::{parse, roller};
21//! use rand::prelude::*;
22//!
23//! let mut rng = rand::rng();
24//!
25//! // Simple dice rolling
26//! let simple_roller = roller(2, 6, None);
27//! let result = simple_roller.generate(&mut rng);
28//! println!("2d6: {}", result);
29//!
30//! // Parse complex expressions
31//! let generator = parse("3d6+4").unwrap();
32//! let result = generator.generate(&mut rng);
33//! println!("3d6+4: {}", result);
34//! ```
35//!
36//! ## Dice Operators
37//!
38//! | Operator | Description | Example |
39//! |----------|-------------|---------|
40//! | `!` | Explode on max | `3d6!` |
41//! | `!!` | Explode until not max | `2d8!!` |
42//! | `*` | Explode each die on max | `4d6*` |
43//! | `**` | Explode each until not max | `3d10**` |
44//! | `++n` | Add n to each die | `2d4++2` |
45//! | `--n` | Subtract n from each die | `3d6--1` |
46//! | `` `n `` | Keep lowest n dice | `4d6`3` |
47//! | `^n` | Keep highest n dice | `4d6^3` |
48//! | `~n` | Keep middle n dice | `5d6~3` |
49//! | `ADV` | Roll twice, keep higher | `1d20ADV` |
50//! | `DIS` | Roll twice, keep lower | `1d20DIS` |
51//! | `Y` | Keep largest group | `5d6Y` |
52//!
53//! ## Target and Success Operators
54//!
55//! | Operator | Description | Example |
56//! |----------|-------------|---------|
57//! | `[n]` | Count dice ≥ n as hits | `6d6[4]` |
58//! | `(n)` | Count dice ≤ n as hits | `4d10(3)` |
59//! | `{n}` | Success if total ≥ n | `3d6{12}` |
60//! | `{n,m}` | Success levels every m over n | `2d10{15,5}` |
61//!
62//! ## Comparison Operators
63//!
64//! Compare two dice expressions:
65//!
66//! ```rust
67//! use dice_nom::parse;
68//! use rand::prelude::*;
69//!
70//! let mut rng = rand::rng();
71//! let contest = parse("3d6 > 2d8+1").unwrap();
72//! let result = contest.generate(&mut rng);
73//! println!("Contest result: {}", result.sum()); // 1 if left wins, 0 if right wins
74//! ```
75//!
76//! ## Module Organization
77//!
78//! - [`generators`]: Core generator types and implementations
79//! - [`results`]: Result types for dice rolls and pools
80//! - [`parsers`]: Nom-based parsers for dice expressions
81
82pub mod results;
83
84pub mod generators;
85use generators::{Generator, PoolGenerator};
86
87pub mod parsers;
88
89/// roller builds a simple `PoolGenerator` that can randomly generate dice rolls.
90///
91/// * Examples
92///
93/// ```
94/// use rand::prelude::*;
95/// let mut rng = rand::rng();
96/// let roller = dice_nom::roller(3, 6, Some("**"));
97/// assert_eq!(roller.count, 3);
98/// assert_eq!(roller.range, 6);
99/// assert_eq!(roller.op, Some(dice_nom::generators::PoolOp::ExplodeEachUntil(None)));
100///
101/// let pool = roller.generate(&mut rng);
102/// assert!(pool.count() >= 3);
103/// assert!(pool.sum() >= 3);
104/// ```
105pub fn roller(count: i32, range: i32, op: Option<&str>) -> PoolGenerator {
106 let op = match op {
107 Some(s) => match parsers::pool_op_parser(s) {
108 Ok((_, op)) => Some(op),
109 Err(_) => None,
110 },
111 None => None,
112 };
113 PoolGenerator { count, range, op }
114}
115
116/// parse builds a generator from the given input string. If any of the string
117/// can be parsed a generator is returned. If no generator can be built then
118/// an error is returned with the input string.
119///
120/// * Examples
121///
122/// ```
123/// use rand::prelude::*;
124/// let mut rng = rand::rng();
125/// let g = dice_nom::parse("2d4! + 2d6! < 3d8!");
126/// assert!(g.is_ok());
127/// if let Ok(g) = g {
128/// let results = g.generate(&mut rng);
129/// assert!(!results.rhs.is_none());
130/// }
131///
132/// let g = dice_nom::parse("attack badger");
133/// assert!(!g.is_ok());
134/// assert_eq!(g, Err("attack badger"));
135/// ```
136pub fn parse(input: &str) -> Result<Generator, &str> {
137 match parsers::generator_parser(input) {
138 Ok((_, g)) => Ok(g),
139 Err(_) => Err(input),
140 }
141}