Skip to content

noctisynth/noctisroll

Repository files navigation

NoctisRoll

A modern, modular TRPG dice rolling system implementing the OneDice standard.

Features

  • 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

Supported Dice Types

  • 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

Installation

Add to your Cargo.toml:

[dependencies]
noctisroll = "0.2"

For parser support (optional):

[dependencies]
noctisroll = { version = "0.2", features = ["parser"] }

Quick Start

Basic Usage

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"

Using Convenience Functions

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();

Dice Pools

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);

Parsing Dice Expressions (Optional Feature)

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();

Advanced Usage

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));
}

API Overview

Core Types

  • Dice: Trait for all dice types
  • ModifiableDice: Trait for dice that support operations (keep, drop, explode, etc.)
  • DieRoll: Represents a single die roll with metadata
  • RollResult: Complete result of a dice roll
  • DiceContext: Context for rolling with configuration

Error Handling

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),
}

Configuration

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);

OneDice Compatibility

NoctisRoll implements the complete OneDice V1 specification:

Supported Operators

  • d: Standard polyhedral dice
  • a: Infinite adding pool
  • c: Double cross pool
  • f/df: FATE/Fudge dice
  • k/q: Keep highest/lowest
  • p/b: Bonus/penalty dice
  • !: Exploding dice
  • a: Success threshold (dice pool mode)

Supported Expressions

  • Arithmetic: +, -, *, /, %, ^
  • Comparisons: >, <, >=, <=, ==, !=
  • Ternary: ? :
  • Parentheses for grouping
  • Functions: min(), max(), abs(), etc.

Examples

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 parse

Performance

NoctisRoll 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.

Contributing

Contributions are welcome! Please see CONTRIBUTING.md for details.

License

AGPL-3.0-only - See LICENSE for details.

Acknowledgments

  • OneDice for the comprehensive standard
  • All TRPG communities for inspiring this library
  • Contributors and testers

About

A grammar-based TRPG dice rolling system.

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages