dice_trio
A minimal, bulletproof dice rolling library following the Unix philosophy.
dice_trio does one thing exceptionally well: parse and roll standard dice expressions.
It provides a simple, reliable foundation for building game systems.
Quick Start
import dice_trio
// Your RNG function (MUST return 1-to-max inclusive)
let rng = fn(max) { your_random_implementation(max) }
// Roll some dice
dice_trio.roll("d6", rng) // Ok(4)
dice_trio.roll("2d6+3", rng) // Ok(11)
dice_trio.roll("d20-1", rng) // Ok(18)
RNG Contract Requirements
CRITICAL: Your RNG function MUST return values between 1 and max (inclusive). The dice library does not validate RNG output for performance reasons. Invalid RNG output will produce incorrect dice results.
Supported Notation
"d6"- Single six-sided die"2d6"- Two six-sided dice"d6+2"- Six-sided die plus 2"d20-1"- Twenty-sided die minus 1"3d6+5"- Three dice with modifier
Design Philosophy
- Unix Philosophy: Do one thing exceptionally well
- Maximum Approachability: Game systems should be simple to build
- RNG Injection: Bring your own randomness for testing/determinism
- Comprehensive Validation: Clear errors for invalid input
- Performance Tested: Handles extreme loads (1000d6, 100d100+50)
Types
Represents a parsed dice expression with count, sides, and modifier.
Examples
BasicRoll(roll_count: 2, side_count: 6, modifier: 3) // 2d6+3
BasicRoll(roll_count: 1, side_count: 20, modifier: -1) // d20-1
pub type BasicRoll {
BasicRoll(roll_count: Int, side_count: Int, modifier: Int)
}
Constructors
-
BasicRoll(roll_count: Int, side_count: Int, modifier: Int)
All possible errors that can occur when parsing or rolling dice expressions.
pub type DiceError {
MissingSeparator
InvalidCount(String)
InvalidSides(String)
InvalidModifier(String)
MalformedInput
}
Constructors
-
MissingSeparatorNo “d” separator found in input (e.g., “garbage”)
-
InvalidCount(String)Invalid dice count provided (e.g., “abc” in “abcd6” or negative counts)
-
InvalidSides(String)Invalid die sides provided (e.g., “-6” in “d-6”)
-
InvalidModifier(String)Invalid modifier provided (e.g., empty string in “d6+”)
-
MalformedInputMalformed input that doesn’t follow expected patterns
Values
pub fn detailed_roll(
dice_expression: String,
rng_fn: fn(Int) -> Int,
) -> Result(DetailedRoll, DiceError)
Parses and rolls a dice expression, returning detailed results with individual die values.
This function provides comprehensive roll information including each individual die result, the parsed dice components (count, sides, modifier), and the calculated total.
Parameters
dice_expression: Standard dice notation string ("d6","2d6+3", etc.)rng_fn: Function that takes a max value and returns a random 1-to-max number
RNG Function Contract
CRITICAL: Your RNG function must:
- Take an
Intparameter (the die size) - Return a value between 1 and that parameter (inclusive)
- For a d6, return 1, 2, 3, 4, 5, or 6
- Validate its own output ranges (dice library does not validate for performance)
Examples
// Simple detailed roll with fixed RNG for testing
let test_rng = fn(_) { 3 }
detailed_roll("2d6+5", test_rng)
// Ok(DetailedRoll(
// basic_roll: BasicRoll(roll_count: 2, side_count: 6, modifier: 5),
// individual_rolls: [3, 3]
// ))
// Error handling
detailed_roll("invalid", test_rng) // Error(MissingSeparator)
detailed_roll("0d6", test_rng) // Error(InvalidCount("0"))
pub fn parse(input: String) -> Result(BasicRoll, DiceError)
Parses a dice expression string into structured components.
Supports standard dice notation: XdY+Z where:
- X is dice count (optional, defaults to 1)
- Y is die sides (required)
- Z is modifier (optional, defaults to 0)
Examples
parse("d6")
// Ok(BasicRoll(roll_count: 1, side_count: 6, modifier: 0))
parse("2d6+3")
// Ok(BasicRoll(roll_count: 2, side_count: 6, modifier: 3))
parse("d20-1")
// Ok(BasicRoll(roll_count: 1, side_count: 20, modifier: -1))
parse("invalid")
// Error(MissingSeparator)
pub fn parse_count(c: String) -> Result(Int, DiceError)
Parses the dice count portion of a dice expression.
Empty strings default to 1 (for expressions like “d6”). Validates that counts are positive integers.
Examples
parse_count("") // Ok(1) - defaults to 1
parse_count("2") // Ok(2) - explicit count
parse_count("0") // Error(InvalidCount("0"))
parse_count("-1") // Error(InvalidCount("-1"))
pub fn parse_sides_and_modifier(
snm: String,
) -> Result(#(Int, Int), DiceError)
Parses the sides and modifier portion of a dice expression.
Handles the part after “d” in expressions like “6+3”, “20-1”, or just “6”. Returns a tuple of (sides, modifier).
Examples
parse_sides_and_modifier("6") // Ok(#(6, 0)) - no modifier
parse_sides_and_modifier("6+3") // Ok(#(6, 3)) - positive modifier
parse_sides_and_modifier("20-1") // Ok(#(20, -1)) - negative modifier
parse_sides_and_modifier("6++1") // Error(MalformedInput)
pub fn roll(
dice_expression: String,
rng_fn: fn(Int) -> Int,
) -> Result(Int, DiceError)
Parses and rolls a dice expression, returning the total result.
This is the main function for dice rolling. It parses the expression and then uses your provided RNG function to generate random numbers for each die.
Parameters
dice_expression: Standard dice notation string ("d6","2d6+3", etc.)rng_fn: Function that takes a max value and returns a random 1-to-max number
RNG Function Contract
CRITICAL: Your RNG function must:
- Take an
Intparameter (the die size) - Return a value between 1 and that parameter (inclusive)
- For a d6, return 1, 2, 3, 4, 5, or 6
- Validate its own output ranges (dice library does not validate for performance)
Examples
// Simple roll with fixed RNG for testing
let test_rng = fn(_) { 3 }
roll("d6", test_rng) // Ok(3)
roll("2d6+5", test_rng) // Ok(11) // 3 + 3 + 5
// Error handling
roll("invalid", test_rng) // Error(MissingSeparator)
roll("0d6", test_rng) // Error(InvalidCount("0"))