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

Design Philosophy

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)
pub type DetailedRoll {
  DetailedRoll(
    basic_roll: BasicRoll,
    individual_rolls: List(Int),
    total: Int,
  )
}

Constructors

  • DetailedRoll(
      basic_roll: BasicRoll,
      individual_rolls: List(Int),
      total: 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

  • MissingSeparator

    No “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+”)

  • MalformedInput

    Malformed 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 Int parameter (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 Int parameter (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"))
Search Document