Extend PyRC Diceroller: Scripting Advanced Roll Mechanics
PyRC Diceroller is a compact Python library for simulating dice rolls commonly used in tabletop RPGs. This article shows practical ways to extend its capabilities with scripts for advanced roll mechanics: custom dice, rerolls, exploding dice, conditional modifiers, pooled results, and logging. Examples assume a minimal PyRC-style API: a roll(expression) function returning integers or lists; adapt names to your actual library.
1. Design goals and approach
- Keep extensions small and composable (pure functions where possible).
- Accept standard dice notation (e.g., 3d6+2) and add operators for advanced mechanics.
- Provide both one-off helper functions and a small scriptable framework for rulesets.
2. Parsing extended notation
Extend the parser to recognize extra operators:
!or!>— explode on maximum (e.g., 1d6!).rX— reroll values ≤ X (e.g., 4d6r1 reroll 1s).khN/klN— keep highest/lowest N (e.g., 4d6kh3).pN— pool top N results from multiple rolls.>N/— count successes (e.g., 10d10>7). Use a lightweight regex-based tokenizer to split core dice terms and modifiers, then apply modifiers in a deterministic order: roll → reroll → explode → keep/drop → modifiers → tally.
Example parser outline (pseudocode):
tokenize(expression)for each token: if token is NdM: base_rolls = roll_ndm(N,M) apply reroll rules to base_rolls apply explode rules (append new rolls when triggered) apply keep/drop (kh/kl) apply arithmetic modifiers (+, -,, /) if success-count operator present, convert to countsreturn final result (value and breakdown)
3. Implementing core mechanics
-
Exploding dice
- After rolling a die, if it equals the explosion threshold (usually the die maximum), roll another die and add it; repeat while explosions occur.
- Protect against infinite loops by limiting recursion depth or total additional rolls.
-
Rerolls
- Reroll specific values once or until above a threshold. Support
r1(reroll ones once) andro1(reroll ones until not one). - Decide whether original values count if reroll yields same value.
- Reroll specific values once or until above a threshold. Support
-
Keep/Drop highest/lowest
- After all rolls complete, sort and slice to keep required number, returning both kept and dropped lists for transparency.
-
Success counting / Target numbers
- Convert each die result to ⁄0 by comparing to a target (e.g.,
>=or>), then sum successes. Allow exploding successes (each max also adds an extra success roll).
- Convert each die result to ⁄0 by comparing to a target (e.g.,
-
Conditional modifiers
- Apply context-based modifiers, e.g., add +2 if a particular roll in a pool exceeds a threshold.
4. Example helper functions (Python-style pseudocode)
- Explode:
def explode_rolls(rolls, die_max, cap=100): i = 0 while i < len(rolls) and len(rolls) < cap: if rolls[i] == die_max: rolls.append(randint(1, die_max)) i += 1 return rolls
- Reroll once:
def reroll_once(rolls, target_values): return [randint(1, die_max) if v in target_values else v for v in rolls]
- Keep highest N:
def keep_highest(rolls, n): kept = sorted(rolls, reverse=True)[:n] dropped = rolls.copy() for v in kept: dropped.remove(v) return kept, dropped
- Success counting:
def count_successes(rolls, threshold, inclusive=True): if inclusive: return sum(1 for r in rolls if r >= threshold) return sum(1 for r in rolls if r > threshold)
5. Composable rule pipeline
Create a Rule class and apply a pipeline so users can combine behaviors:
class Rule: def apply(self, rolls, die_max): …pipeline = [RerollRule(…), ExplodeRule(…), KeepRule(…), SuccessRule(…)]for rule in pipeline: rolls = rule.apply(rolls, die_max)
This makes adding new mechanics easy and keeps logic testable.
6. Scripting examples
-
4d6kh3 with reroll ones (r1) and explode on 6:
- Roll 4d6 → reroll ones once → explode sixes → keep highest 3 → sum.
-
Pooling for skill checks: roll 10d10>7, keep top 3 successes (p3):
- Roll 10 dice → count successes per die (≥8) → if pooled, choose the top 3 individual dice results to assign special bonuses
Leave a Reply