A modern, modular TRPG dice rolling system implementing the OneDice standard.
- Full OneDice support: Implements all dice types from the OneDice specification
- Modular design: Each dice type is implemented as a separate, reusable module
- Type safety: Strongly typed API with compile-time guarantees
- Extensible: Easy to add new dice types and operations
- Performance: Optimized for both single rolls and batch operations
- Comprehensive error handling: Detailed error types for all failure cases
- Serialization support: All core types support Serde serialization
- Standard polyhedral dice (
d):2d20,d6,4d10k3, etc. - FATE/Fudge dice (
f/df):4dF,8df, etc. - Exploding dice (
!):2d6!6,4d10!≥8, etc. - Infinite adding pools (
a):5a8k6m10(World of Darkness style) - Double cross pools (
c):5c8m10(Double Cross style) - Bonus/penalty dice (
p/b):d100p2,d20b1(CoC style) - Success-based pools: Count successes instead of summing
Add to your Cargo.toml:
[dependencies]
noctisroll = "0.2"For parser support (optional):
[dependencies]
noctisroll = { version = "0.2", features = ["parser"] }use noctisroll::prelude::*;
// Roll 2d20 with advantage (keep highest)
let dice = StandardDice::new(2, 20).keep_highest(1);
let result = dice.roll();
println!("Roll: {}", result); // e.g., "2d20kh1 [17, 12] = 17"
// Roll FATE dice
let fate = FateDice::new(4);
let result = fate.roll();
println!("FATE roll: {}", result); // e.g., "4dF = +1"
// Roll exploding dice
let exploding = ExplodingDice::new(2, 6, 6); // Explode on 6
let result = exploding.roll();
println!("Exploding: {}", result); // e.g., "2d6!6 [6!, 4, 3] = 13"use noctisroll::dice::{d, f, x};
// Standard dice
let result = d(2, 20).keep_highest(1).roll();
// FATE dice
let result = f(4).roll();
// Exploding dice
let result = x(2, 6, 6).roll();use noctisroll::dice::{InfiniteAddingPool, DoubleCrossPool};
// Infinite adding pool (World of Darkness style)
let pool = InfiniteAddingPool::new(5, 8) // 5 dice, add on 8+
.with_success_threshold(6); // Success on 6+
let result = pool.roll();
println!("Successes: {}", result.success_count.unwrap());
// Double cross pool
let pool = DoubleCrossPool::new(5, 8); // 5 dice, add on 8+
let result = pool.roll();
println!("Total: {}", result.total);use noctisroll::prelude::*;
// Enable parser feature in Cargo.toml first
let expr = "2d20kh1 + 5";
let result = noctisroll::eval(expr).unwrap();
println!("Result: {}", result); // e.g., "2d20kh1 + 5 = 22"
// Or parse without evaluating
let dice = noctisroll::parser::Parser::new().parse("4d6k3").unwrap();
let result = dice.roll();use noctisroll::prelude::*;
use noctisroll::utils::{RollStatistics, format_detailed};
// Batch rolling
let dice = StandardDice::new(1, 20);
let results = noctisroll::utils::batch_roll(&dice, 100);
// Calculate statistics
let stats = RollStatistics::from_rolls(
&results.iter().flat_map(|r| &r.rolls).cloned().collect::<Vec<_>>()
);
println!("Average: {:.2}", stats.mean);
println!("Critical rate: {:.1}%",
stats.critical_successes as f64 / results.len() as f64 * 100.0);
// Format detailed output
for result in &results[..3] {
println!("{}", format_detailed(result));
}Dice: Trait for all dice typesModifiableDice: Trait for dice that support operations (keep, drop, explode, etc.)DieRoll: Represents a single die roll with metadataRollResult: Complete result of a dice rollDiceContext: Context for rolling with configuration
use noctisroll::error::{DiceError, DiceResult};
fn roll_safely(dice: &dyn Dice) -> DiceResult<RollResult> {
// All dice operations return Result types
Ok(dice.roll())
}
match roll_safely(&dice) {
Ok(result) => println!("Success: {}", result),
Err(DiceError::InvalidDice(msg)) => println!("Invalid dice: {}", msg),
Err(e) => println!("Error: {}", e),
}use noctisroll::core::{DiceConfig, DiceContext};
let config = DiceConfig {
default_sides: 100, // CoC default instead of D&D
detect_critical: true,
seed: Some(42), // Fixed seed for reproducible rolls
max_dice: 10000,
max_sides: 1000,
};
let mut ctx = DiceContext::with_config(config);
let roll = ctx.roll_die(20);NoctisRoll implements the complete OneDice V1 specification:
d: Standard polyhedral dicea: Infinite adding poolc: Double cross poolf/df: FATE/Fudge dicek/q: Keep highest/lowestp/b: Bonus/penalty dice!: Exploding dicea: Success threshold (dice pool mode)
- Arithmetic:
+,-,*,/,%,^ - Comparisons:
>,<,>=,<=,==,!= - Ternary:
? : - Parentheses for grouping
- Functions:
min(),max(),abs(), etc.
See the examples/ directory for more complete examples:
cargo run --example basic
cargo run --example fate
cargo run --example pool
cargo run --features parser --example parseNoctisRoll is optimized for performance:
- Zero allocations for simple dice rolls
- Lazy evaluation where possible
- Batch operations for multiple rolls
- Thread-safe implementation
Benchmarks are available in the benches/ directory.
Contributions are welcome! Please see CONTRIBUTING.md for details.
AGPL-3.0-only - See LICENSE for details.
- OneDice for the comprehensive standard
- All TRPG communities for inspiring this library
- Contributors and testers