# Wyrdbound Dice
A comprehensive dice rolling library for tabletop RPGs, designed to handle complex dice expressions with mathematical precision and extensive system support.
This library is designed for use in [wyrdbound](https://github.com/wyrdbound), a text-based RPG system that emphasizes narrative and player choice.
[](https://github.com/wyrdbound/wyrdbound-dice/actions/workflows/ci.yml)
[](https://www.python.org/downloads/)
[](https://opensource.org/licenses/MIT)
[](https://github.com/psf/black)
> 📣 This library is experimental and was built with much :heart: and [vibe coding](https://en.wikipedia.org/wiki/Vibe_coding). Please do not launch :rocket: or perform :brain: surgery using it. (Should be :a:-:ok: for your Table-Top application though!)
## Features
Wyrdbound Dice supports an extensive range of dice rolling mechanics used across many tabletop RPG systems:
### Basic Dice Rolling
- **Standard polyhedral dice**: `1d4`, `1d6`, `1d8`, `1d10`, `1d12`, `1d20`, `1d100`
- **Multiple dice**: `3d6`, `4d8`, etc.
- **Percentile dice**: `1d%` (displays as [tens, ones])
### Mathematical Operations
- **Arithmetic operations**: `2d6 + 3`, `1d20 - 2`, `1d6 × 4`, `1d10 ÷ 2`
- **Complex expressions**: `2d6 + 1d4 × 2 - 1`
- **Proper precedence**: Mathematical order of operations (PEMDAS/BODMAS)
- **Unicode operators**: Support for `×`, `÷`, `−`, and fullwidth characters
### Keep/Drop Mechanics
- **Keep highest**: `4d6kh3` (ability score generation), `2d20kh1` (advantage)
- **Keep lowest**: `4d6kl3`, `2d20kl1` (disadvantage)
- **Drop operations**: `4d6dh1` (drop highest), `4d6dl1` (drop lowest)
- **Multiple operations**: `5d6kh3kl1` (chain keep/drop operations)
### Reroll Mechanics
- **Unlimited rerolls**: `1d6r<=2` (reroll while ≤ 2)
- **Limited rerolls**: `1d6r1<=2` (reroll once), `1d6r3<=3` (reroll up to 3 times)
- **Comparison operators**: `<=`, `<`, `>=`, `>`, `=`
- **Alternate notation**: `1d6ro<=2` (reroll once)
- **Combine with keep/drop**: `4d6r<=2kh3` (reroll ≤2, keep highest 3)
### Exploding Dice
- **Simple explosion**: `1d6e` (explode on max value)
- **Explicit threshold**: `1d6e6`, `1d10e>=8`
- **Custom conditions**: `1d6e>=5` (explode on 5 or 6)
- **Multiple explosions**: Dice can explode repeatedly
### Fudge Dice (Fate Core/Accelerated)
- **Single Fudge die**: `1dF` (results: -1, 0, +1)
- **Standard Fate roll**: `4dF`
- **Symbol display**: Shows as `-, B, +`
- **Math operations**: Can be combined with other dice and modifiers
### System Shorthands
- **FUDGE**: `4dF` (Fate Core)
- **BOON**: `3d6kh2` (Traveller advantage)
- **BANE**: `3d6kl2` (Traveller disadvantage)
- **FLUX**: `1d6 - 1d6` (Traveller flux)
- **GOODFLUX**: Always positive flux (highest 1d6 - lowest 1d6)
- **BADFLUX**: Always negative flux (lowest 1d6 - highest 1d6)
- **PERC / PERCENTILE**: `1d%`
### Named Modifiers
- **Static modifiers**: `{"Strength": 3, "Proficiency": 2}`
- **Dice modifiers**: `{"Guidance": "1d4", "Bane": "-1d4"}`
- **Mixed modifiers**: Combine static numbers and dice expressions
### Advanced Features
- **Zero dice handling**: `0d6` returns 0
- **Negative dice**: `-1d6` returns negative result
- **Thread safety**: Safe for concurrent use
- **Error handling**: Clear exceptions for invalid conditions
- **Infinite condition detection**: Prevents impossible reroll/explode scenarios
## Installation
### For End Users
```bash
pip install wyrdbound-dice
```
> **Note**: This package is currently in development and not yet published to PyPI. For now, please use the development installation method below.
### For Development
If you want to contribute to the project or use the latest development version:
```bash
# Clone the repository
git clone https://github.com/wyrdbound/wyrdbound-dice.git
cd wyrdbound-dice
# Install in development mode
pip install -e .
```
### Optional Dependencies
For visualization features (graph tool):
```bash
pip install "wyrdbound-dice[visualization]"
```
For development:
```bash
pip install -e ".[dev]"
```
For both visualization and development:
```bash
pip install -e ".[dev,visualization]"
```
## Quick Start
```python
from wyrdbound_dice import Dice
# Basic roll
result = Dice.roll("1d20")
print(result.total) # 20
print(result) # 20 = 20 (1d20: 20)
# Complex expression
result = Dice.roll("2d6 + 1d4 × 2 + 3")
print(result) # 17 = 8 (2d6: 6, 2) + 3 (1d4: 3) x 2 + 3
# Advantage roll (D&D 5e)
result = Dice.roll("2d20kh1")
print(result) # 19 = 19 (2d20kh1: 19, 12)
# Reroll (D&D 5e - Great Weapon Fighting)
result = Dice.roll("2d6r1<=2")
print(result) # 12 = 12 (2d6r1<=2: 1, 2, 6, 6)
# Exploding dice (Savage Worlds)
result = Dice.roll("1d6e")
print(result) # 11 = 11 (1d6e6: 6, 5)
# Fate Core
result = Dice.roll("4dF + 2")
print(result) # 2 = 0 (4dF: +, B, -, B) + 2
# With named modifiers
modifiers = {"Strength": 3, "Proficiency": 2, "Bless": "1d4"}
result = Dice.roll("1d20", modifiers)
print(result) # 20 = 12 (1d20: 12) + 3 (Strength) + 2 (Proficiency) + 3 (Bless: 3 = 3 (1d4: 3))
```
## API Reference
### Main Classes
#### `Dice`
The main entry point for dice rolling.
**`Dice.roll(expression, modifiers=None)`**
- `expression` (str): Dice expression to evaluate
- `modifiers` (dict, optional): Named modifiers as `{name: value}` where value can be int or dice expression string
- Returns: `RollResultSet` object
#### `RollResultSet`
Contains the results of a dice roll.
**Properties:**
- `total` (int): Final calculated result
- `results` (list): List of individual `RollResult` objects
- `modifiers` (list): List of applied modifiers
- `__str__()`: Human-readable description of the complete roll
#### `RollResult`
Represents a single dice expression result.
**Properties:**
- `num` (int): Number of dice rolled
- `sides` (int/str): Number of sides (or "F" for Fudge, "%" for percentile)
- `rolls` (list): Final kept dice values
- `all_rolls` (list): All dice rolled (including rerolls, explosions)
- `total` (int): Sum of kept dice
### Exceptions
- **`ParseError`**: Invalid dice expression syntax
- **`DivisionByZeroError`**: Division by zero in expression
- **`InfiniteConditionError`**: Impossible reroll/explode condition
## Command Line Tools
### Roll Tool
Roll dice expressions from the command line:
```bash
# Basic usage
python tools/roll.py "1d20 + 5"
# Multiple rolls
python tools/roll.py "2d6" --count 10
# JSON output (single roll)
python tools/roll.py "1d20" --json
# JSON output (multiple rolls)
python tools/roll.py "1d6" --count 3 --json
```
**Options:**
- `-v, --verbose`: Show detailed breakdown
- `-n, --count N`: Roll N times
- `--json`: Output results as JSON
**JSON Output Format:**
Single roll returns an object:
```json
{
"result": 14,
"description": "14 = 14 (1d20: 14)"
}
```
Multiple rolls return an array:
```json
[
{
"result": 4,
"description": "4 = 4 (1d6: 4)"
},
{
"result": 6,
"description": "6 = 6 (1d6: 6)"
}
]
```
### Visualization Tool
Generate probability distributions and statistics:
```bash
# Basic distribution graph
python tools/graph.py "2d6"
# Complex expression with more samples
python tools/graph.py "1d20 + 5" --num-rolls 50000
# Specify output file
python tools/graph.py "4d6kh3" --output ability_scores.html
```
**Features:**
- Probability distribution histograms
- Statistical analysis (mean, mode, range)
- Comparison charts for multiple expressions
- Export to various image formats
## Supported Systems
WyrdBound Dice has been designed to support mechanics from many popular RPG systems:
- **D&D 5e / Pathfinder**: Advantage/disadvantage (`2d20kh1`/`2d20kl1`), ability scores (`4d6kh3`)
- **Savage Worlds**: Exploding dice (`1d6e`), wild dice, aces
- **Fate Core/Accelerated**: Fudge dice (`4dF`, `FUDGE`)
- **Traveller**: Boon/Bane (`BOON`/`BANE`), Flux dice (`FLUX`)
- **World of Darkness**: Dice pools with success counting (upcoming)
- **Shadowrun**: Exploding dice, glitch detection (upcoming)
## Examples
### Character Creation
```python
# D&D 5e ability scores
stats = []
for _ in range(6):
result = Dice.roll("4d6kh3")
stats.append(result.total)
# Traveller characteristics with modifiers
characteristics = Dice.roll("2d6", {"DM": 1})
```
### Combat Rolls
```python
# D&D 5e attack with advantage
attack = Dice.roll("2d20kh1 + 8") # +8 attack bonus
# Savage Worlds damage with ace
damage = Dice.roll("1d6e + 2")
# Fate Core with aspects
fate_roll = Dice.roll("4dF + 3", {"Aspect": 2})
```
### Complex Expressions
```python
# Fireball damage (8d6) with Metamagic (reroll 1s)
fireball = Dice.roll("8d6r1<=1")
# Sneak attack with multiple damage types
sneak = Dice.roll("1d8 + 3d6") # Rapier + sneak attack
# Mathematical complexity
complex_formula = Dice.roll("(2d6 + 3) × 2 + 1d4 - 1")
```
## Debug Logging
WyrdBound Dice includes comprehensive debug logging to help troubleshoot dice rolling issues and understand how expressions are parsed and evaluated.
### Enabling Debug Mode
```python
from wyrdbound_dice import Dice
# Enable debug logging for a roll
result = Dice.roll("2d6 + 3", debug=True)
```
### Debug Output Example
When debug mode is enabled, you'll see detailed step-by-step information:
```
DEBUG: [START] Rolling expression: '2d6 + 3'
DEBUG: [PROCESSING] Starting expression processing
DEBUG: NORMALIZED: '2d6 + 3'
DEBUG: [PARSER_SELECTION] Using precedence parser
DEBUG: [TOKENIZING] Tokenizing expression: '2d6 + 3'
DEBUG: Tokens: ['DICE(2d6)@0', 'PLUS(+)@3', 'NUMBER(3)@4']
DEBUG: [PARSING] Parsing tokens with precedence rules
DEBUG: [EVALUATING] Evaluating parsed expression
DEBUG: Rolling 1d6: 5
DEBUG: Rolling 1d6: 4
DEBUG: [RESULT] Expression evaluated to: 12
DEBUG: TOTAL 12 modifiers(0) = 12
DEBUG: [COMPLETE] Final result: 12
```
### What Debug Mode Shows
Debug logging provides insights into:
- **Expression normalization**: How input expressions are cleaned and processed
- **Shorthand expansion**: When shortcuts like "FUDGE" are expanded to "4dF"
- **Parser selection**: Whether the precedence parser or original parser is used
- **Tokenization**: How complex expressions are broken into tokens
- **Individual dice rolls**: Each die roll with specific results
- **Keep/drop operations**: Parsed keep/drop operations like "kh2"
- **Mathematical evaluation**: Step-by-step calculation of complex expressions
- **Modifier processing**: How modifiers are applied to results
- **Error handling**: Debug information even when errors occur
### Debug Examples
```python
# Simple dice with debug
result = Dice.roll("1d20", debug=True)
# Complex expression with debug
result = Dice.roll("2d6 * 2 + 1d4", debug=True)
# Keep operations with debug
result = Dice.roll("4d6kh3", debug=True)
# Shorthand expansion with debug
result = Dice.roll("FUDGE", debug=True)
# With modifiers and debug
modifiers = {"strength": 3, "magic_bonus": 2}
result = Dice.roll("1d20", modifiers=modifiers, debug=True)
```
### Custom Debug Loggers
You can inject your own logger to capture debug output using Python's standard logging interface:
```python
import logging
from wyrdbound_dice import Dice
from wyrdbound_dice.debug_logger import StringLogger
# Method 1: Use the built-in StringLogger for testing/API purposes
string_logger = StringLogger()
result = Dice.roll("2d6 + 3", debug=True, logger=string_logger)
# Get all the debug output as a string
debug_output = string_logger.get_logs()
print(debug_output)
# Clear the logger for reuse
string_logger.clear()
# Method 2: Use Python's standard logging module
# Create a custom logger with your preferred configuration
logger = logging.getLogger('my_dice_app')
logger.setLevel(logging.DEBUG)
# Add your own handler (file, web service, etc.)
handler = logging.FileHandler('dice_debug.log')
handler.setFormatter(logging.Formatter('%(asctime)s %(message)s'))
logger.addHandler(handler)
# Use with dice rolling
result = Dice.roll("1d20", debug=True, logger=logger)
# Method 3: Create a custom logger class
class WebAppLogger:
def debug(self, message):
# Send to your web app's logging system
app.logger.debug(message)
def info(self, message):
app.logger.info(message)
def warning(self, message):
app.logger.warning(message)
def error(self, message):
app.logger.error(message)
web_logger = WebAppLogger()
result = Dice.roll("1d20", debug=True, logger=web_logger)
```
### Logger Interface
Custom loggers should implement Python's standard logging interface methods:
```python
class MyCustomLogger:
def debug(self, message: str) -> None:
"""Log a debug message."""
...
def info(self, message: str) -> None:
"""Log an info message."""
...
def warning(self, message: str) -> None:
"""Log a warning message."""
...
def error(self, message: str) -> None:
"""Log an error message."""
...
class MyLogger:
def log(self, message: str) -> None:
# Your custom logging implementation
pass
```
### Command Line Debug
The `tools/roll.py` script also supports debug mode:
```bash
# Basic roll with debug
python tools/roll.py "2d6 + 3" --debug
# Complex expression with debug
python tools/roll.py "4d6kh3" --debug
# Multiple rolls with debug
python tools/roll.py "1d6" -n 3 --debug
# JSON output with debug information included
python tools/roll.py "2d6 + 3" --json --debug
# Help shows all options including debug
python tools/roll.py --help
```
When using `--json --debug`, the debug output is captured and included in the JSON response under a "debug" key:
```json
{
"result": 11,
"description": "11 = 8 (2d6: 4, 4) + 3",
"debug": "DEBUG: [START] Rolling expression: '2d6 + 3'\nDEBUG: [PROCESSING] Starting expression processing\n..."
}
```
### Debug Output Format
Debug messages are prefixed with `DEBUG:` and use structured labels like `[START]`, `[TOKENIZING]`, `[PARSING]`, etc. This makes it easy to follow the progression through the dice rolling engine and identify where issues might occur.
## Development
### Setting Up Development Environment
```bash
# Install the package with development dependencies
pip install -e ".[dev]"
# Install with both development and visualization dependencies
pip install -e ".[dev,visualization]"
# Or install development dependencies separately
pip install pytest pytest-cov black isort ruff
```
### Running Tests
```bash
# Run all tests
python -m pytest tests/
# Run with coverage
python -m pytest tests/ --cov=wyrdbound_dice
# Run with coverage and generate HTML report
python -m pytest tests/ --cov=wyrdbound_dice --cov-report=html
# Run specific test class
python -m pytest tests/test_dice.py::TestDiceKeepHighestLowest
```
### Code Quality
```bash
# Format code
black src/ tests/ tools/
# Sort imports
isort src/ tests/ tools/
# Lint code
ruff check src/ tests/ tools/
```
## Contributing
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
### Areas for Contribution
- New features for existing RPG systems
- Performance optimizations
- Additional CLI tools
- Documentation improvements
- Bug fixes and testing
### Continuous Integration
This project uses GitHub Actions for CI/CD:
- **Testing**: Automated tests across Python 3.8-3.12 on Ubuntu, Windows, and macOS
- **Code Quality**: Black formatting, isort import sorting, and Ruff linting
- **Package Validation**: Installation testing and CLI tool verification
All pull requests are automatically tested and must pass all checks before merging.
## License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
## Acknowledgments
- Inspired by the diverse mechanics of tabletop RPG systems
- Thanks to the RPG community for feedback and feature requests
- Built with mathematical precision and gaming passion
Raw data
{
"_id": null,
"home_page": null,
"name": "wyrdbound-dice",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.8",
"maintainer_email": "The Wyrd One <wyrdbound@proton.me>",
"keywords": "dice, tabletop, rpg, ttrpg, gaming, random, probability",
"author": null,
"author_email": "The Wyrd One <wyrdbound@proton.me>",
"download_url": "https://files.pythonhosted.org/packages/b8/45/adf2c72aedaeb6835a014230b5fab3c170be55c3e06aa7f4ea5542b687c6/wyrdbound_dice-0.0.2.tar.gz",
"platform": null,
"description": "# Wyrdbound Dice\n\nA comprehensive dice rolling library for tabletop RPGs, designed to handle complex dice expressions with mathematical precision and extensive system support.\n\nThis library is designed for use in [wyrdbound](https://github.com/wyrdbound), a text-based RPG system that emphasizes narrative and player choice.\n\n[](https://github.com/wyrdbound/wyrdbound-dice/actions/workflows/ci.yml)\n[](https://www.python.org/downloads/)\n[](https://opensource.org/licenses/MIT)\n[](https://github.com/psf/black)\n\n> \ud83d\udce3 This library is experimental and was built with much :heart: and [vibe coding](https://en.wikipedia.org/wiki/Vibe_coding). Please do not launch :rocket: or perform :brain: surgery using it. (Should be :a:-:ok: for your Table-Top application though!)\n\n## Features\n\nWyrdbound Dice supports an extensive range of dice rolling mechanics used across many tabletop RPG systems:\n\n### Basic Dice Rolling\n\n- **Standard polyhedral dice**: `1d4`, `1d6`, `1d8`, `1d10`, `1d12`, `1d20`, `1d100`\n- **Multiple dice**: `3d6`, `4d8`, etc.\n- **Percentile dice**: `1d%` (displays as [tens, ones])\n\n### Mathematical Operations\n\n- **Arithmetic operations**: `2d6 + 3`, `1d20 - 2`, `1d6 \u00d7 4`, `1d10 \u00f7 2`\n- **Complex expressions**: `2d6 + 1d4 \u00d7 2 - 1`\n- **Proper precedence**: Mathematical order of operations (PEMDAS/BODMAS)\n- **Unicode operators**: Support for `\u00d7`, `\u00f7`, `\u2212`, and fullwidth characters\n\n### Keep/Drop Mechanics\n\n- **Keep highest**: `4d6kh3` (ability score generation), `2d20kh1` (advantage)\n- **Keep lowest**: `4d6kl3`, `2d20kl1` (disadvantage)\n- **Drop operations**: `4d6dh1` (drop highest), `4d6dl1` (drop lowest)\n- **Multiple operations**: `5d6kh3kl1` (chain keep/drop operations)\n\n### Reroll Mechanics\n\n- **Unlimited rerolls**: `1d6r<=2` (reroll while \u2264 2)\n- **Limited rerolls**: `1d6r1<=2` (reroll once), `1d6r3<=3` (reroll up to 3 times)\n- **Comparison operators**: `<=`, `<`, `>=`, `>`, `=`\n- **Alternate notation**: `1d6ro<=2` (reroll once)\n- **Combine with keep/drop**: `4d6r<=2kh3` (reroll \u22642, keep highest 3)\n\n### Exploding Dice\n\n- **Simple explosion**: `1d6e` (explode on max value)\n- **Explicit threshold**: `1d6e6`, `1d10e>=8`\n- **Custom conditions**: `1d6e>=5` (explode on 5 or 6)\n- **Multiple explosions**: Dice can explode repeatedly\n\n### Fudge Dice (Fate Core/Accelerated)\n\n- **Single Fudge die**: `1dF` (results: -1, 0, +1)\n- **Standard Fate roll**: `4dF`\n- **Symbol display**: Shows as `-, B, +`\n- **Math operations**: Can be combined with other dice and modifiers\n\n### System Shorthands\n\n- **FUDGE**: `4dF` (Fate Core)\n- **BOON**: `3d6kh2` (Traveller advantage)\n- **BANE**: `3d6kl2` (Traveller disadvantage)\n- **FLUX**: `1d6 - 1d6` (Traveller flux)\n- **GOODFLUX**: Always positive flux (highest 1d6 - lowest 1d6)\n- **BADFLUX**: Always negative flux (lowest 1d6 - highest 1d6)\n- **PERC / PERCENTILE**: `1d%`\n\n### Named Modifiers\n\n- **Static modifiers**: `{\"Strength\": 3, \"Proficiency\": 2}`\n- **Dice modifiers**: `{\"Guidance\": \"1d4\", \"Bane\": \"-1d4\"}`\n- **Mixed modifiers**: Combine static numbers and dice expressions\n\n### Advanced Features\n\n- **Zero dice handling**: `0d6` returns 0\n- **Negative dice**: `-1d6` returns negative result\n- **Thread safety**: Safe for concurrent use\n- **Error handling**: Clear exceptions for invalid conditions\n- **Infinite condition detection**: Prevents impossible reroll/explode scenarios\n\n## Installation\n\n### For End Users\n\n```bash\npip install wyrdbound-dice\n```\n\n> **Note**: This package is currently in development and not yet published to PyPI. For now, please use the development installation method below.\n\n### For Development\n\nIf you want to contribute to the project or use the latest development version:\n\n```bash\n# Clone the repository\ngit clone https://github.com/wyrdbound/wyrdbound-dice.git\ncd wyrdbound-dice\n\n# Install in development mode\npip install -e .\n```\n\n### Optional Dependencies\n\nFor visualization features (graph tool):\n\n```bash\npip install \"wyrdbound-dice[visualization]\"\n```\n\nFor development:\n\n```bash\npip install -e \".[dev]\"\n```\n\nFor both visualization and development:\n\n```bash\npip install -e \".[dev,visualization]\"\n```\n\n## Quick Start\n\n```python\nfrom wyrdbound_dice import Dice\n\n# Basic roll\nresult = Dice.roll(\"1d20\")\nprint(result.total) # 20\nprint(result) # 20 = 20 (1d20: 20)\n\n# Complex expression\nresult = Dice.roll(\"2d6 + 1d4 \u00d7 2 + 3\")\nprint(result) # 17 = 8 (2d6: 6, 2) + 3 (1d4: 3) x 2 + 3\n\n# Advantage roll (D&D 5e)\nresult = Dice.roll(\"2d20kh1\")\nprint(result) # 19 = 19 (2d20kh1: 19, 12)\n\n# Reroll (D&D 5e - Great Weapon Fighting)\nresult = Dice.roll(\"2d6r1<=2\")\nprint(result) # 12 = 12 (2d6r1<=2: 1, 2, 6, 6)\n\n# Exploding dice (Savage Worlds)\nresult = Dice.roll(\"1d6e\")\nprint(result) # 11 = 11 (1d6e6: 6, 5)\n\n# Fate Core\nresult = Dice.roll(\"4dF + 2\")\nprint(result) # 2 = 0 (4dF: +, B, -, B) + 2\n\n# With named modifiers\nmodifiers = {\"Strength\": 3, \"Proficiency\": 2, \"Bless\": \"1d4\"}\nresult = Dice.roll(\"1d20\", modifiers)\nprint(result) # 20 = 12 (1d20: 12) + 3 (Strength) + 2 (Proficiency) + 3 (Bless: 3 = 3 (1d4: 3))\n```\n\n## API Reference\n\n### Main Classes\n\n#### `Dice`\n\nThe main entry point for dice rolling.\n\n**`Dice.roll(expression, modifiers=None)`**\n\n- `expression` (str): Dice expression to evaluate\n- `modifiers` (dict, optional): Named modifiers as `{name: value}` where value can be int or dice expression string\n- Returns: `RollResultSet` object\n\n#### `RollResultSet`\n\nContains the results of a dice roll.\n\n**Properties:**\n\n- `total` (int): Final calculated result\n- `results` (list): List of individual `RollResult` objects\n- `modifiers` (list): List of applied modifiers\n- `__str__()`: Human-readable description of the complete roll\n\n#### `RollResult`\n\nRepresents a single dice expression result.\n\n**Properties:**\n\n- `num` (int): Number of dice rolled\n- `sides` (int/str): Number of sides (or \"F\" for Fudge, \"%\" for percentile)\n- `rolls` (list): Final kept dice values\n- `all_rolls` (list): All dice rolled (including rerolls, explosions)\n- `total` (int): Sum of kept dice\n\n### Exceptions\n\n- **`ParseError`**: Invalid dice expression syntax\n- **`DivisionByZeroError`**: Division by zero in expression\n- **`InfiniteConditionError`**: Impossible reroll/explode condition\n\n## Command Line Tools\n\n### Roll Tool\n\nRoll dice expressions from the command line:\n\n```bash\n# Basic usage\npython tools/roll.py \"1d20 + 5\"\n\n# Multiple rolls\npython tools/roll.py \"2d6\" --count 10\n\n# JSON output (single roll)\npython tools/roll.py \"1d20\" --json\n\n# JSON output (multiple rolls)\npython tools/roll.py \"1d6\" --count 3 --json\n```\n\n**Options:**\n\n- `-v, --verbose`: Show detailed breakdown\n- `-n, --count N`: Roll N times\n- `--json`: Output results as JSON\n\n**JSON Output Format:**\n\nSingle roll returns an object:\n\n```json\n{\n \"result\": 14,\n \"description\": \"14 = 14 (1d20: 14)\"\n}\n```\n\nMultiple rolls return an array:\n\n```json\n[\n {\n \"result\": 4,\n \"description\": \"4 = 4 (1d6: 4)\"\n },\n {\n \"result\": 6,\n \"description\": \"6 = 6 (1d6: 6)\"\n }\n]\n```\n\n### Visualization Tool\n\nGenerate probability distributions and statistics:\n\n```bash\n# Basic distribution graph\npython tools/graph.py \"2d6\"\n\n# Complex expression with more samples\npython tools/graph.py \"1d20 + 5\" --num-rolls 50000\n\n# Specify output file\npython tools/graph.py \"4d6kh3\" --output ability_scores.html\n```\n\n**Features:**\n\n- Probability distribution histograms\n- Statistical analysis (mean, mode, range)\n- Comparison charts for multiple expressions\n- Export to various image formats\n\n## Supported Systems\n\nWyrdBound Dice has been designed to support mechanics from many popular RPG systems:\n\n- **D&D 5e / Pathfinder**: Advantage/disadvantage (`2d20kh1`/`2d20kl1`), ability scores (`4d6kh3`)\n- **Savage Worlds**: Exploding dice (`1d6e`), wild dice, aces\n- **Fate Core/Accelerated**: Fudge dice (`4dF`, `FUDGE`)\n- **Traveller**: Boon/Bane (`BOON`/`BANE`), Flux dice (`FLUX`)\n- **World of Darkness**: Dice pools with success counting (upcoming)\n- **Shadowrun**: Exploding dice, glitch detection (upcoming)\n\n## Examples\n\n### Character Creation\n\n```python\n# D&D 5e ability scores\nstats = []\nfor _ in range(6):\n result = Dice.roll(\"4d6kh3\")\n stats.append(result.total)\n\n# Traveller characteristics with modifiers\ncharacteristics = Dice.roll(\"2d6\", {\"DM\": 1})\n```\n\n### Combat Rolls\n\n```python\n# D&D 5e attack with advantage\nattack = Dice.roll(\"2d20kh1 + 8\") # +8 attack bonus\n\n# Savage Worlds damage with ace\ndamage = Dice.roll(\"1d6e + 2\")\n\n# Fate Core with aspects\nfate_roll = Dice.roll(\"4dF + 3\", {\"Aspect\": 2})\n```\n\n### Complex Expressions\n\n```python\n# Fireball damage (8d6) with Metamagic (reroll 1s)\nfireball = Dice.roll(\"8d6r1<=1\")\n\n# Sneak attack with multiple damage types\nsneak = Dice.roll(\"1d8 + 3d6\") # Rapier + sneak attack\n\n# Mathematical complexity\ncomplex_formula = Dice.roll(\"(2d6 + 3) \u00d7 2 + 1d4 - 1\")\n```\n\n## Debug Logging\n\nWyrdBound Dice includes comprehensive debug logging to help troubleshoot dice rolling issues and understand how expressions are parsed and evaluated.\n\n### Enabling Debug Mode\n\n```python\nfrom wyrdbound_dice import Dice\n\n# Enable debug logging for a roll\nresult = Dice.roll(\"2d6 + 3\", debug=True)\n```\n\n### Debug Output Example\n\nWhen debug mode is enabled, you'll see detailed step-by-step information:\n\n```\nDEBUG: [START] Rolling expression: '2d6 + 3'\nDEBUG: [PROCESSING] Starting expression processing\nDEBUG: NORMALIZED: '2d6 + 3'\nDEBUG: [PARSER_SELECTION] Using precedence parser\nDEBUG: [TOKENIZING] Tokenizing expression: '2d6 + 3'\nDEBUG: Tokens: ['DICE(2d6)@0', 'PLUS(+)@3', 'NUMBER(3)@4']\nDEBUG: [PARSING] Parsing tokens with precedence rules\nDEBUG: [EVALUATING] Evaluating parsed expression\nDEBUG: Rolling 1d6: 5\nDEBUG: Rolling 1d6: 4\nDEBUG: [RESULT] Expression evaluated to: 12\nDEBUG: TOTAL 12 modifiers(0) = 12\nDEBUG: [COMPLETE] Final result: 12\n```\n\n### What Debug Mode Shows\n\nDebug logging provides insights into:\n\n- **Expression normalization**: How input expressions are cleaned and processed\n- **Shorthand expansion**: When shortcuts like \"FUDGE\" are expanded to \"4dF\"\n- **Parser selection**: Whether the precedence parser or original parser is used\n- **Tokenization**: How complex expressions are broken into tokens\n- **Individual dice rolls**: Each die roll with specific results\n- **Keep/drop operations**: Parsed keep/drop operations like \"kh2\"\n- **Mathematical evaluation**: Step-by-step calculation of complex expressions\n- **Modifier processing**: How modifiers are applied to results\n- **Error handling**: Debug information even when errors occur\n\n### Debug Examples\n\n```python\n# Simple dice with debug\nresult = Dice.roll(\"1d20\", debug=True)\n\n# Complex expression with debug\nresult = Dice.roll(\"2d6 * 2 + 1d4\", debug=True)\n\n# Keep operations with debug\nresult = Dice.roll(\"4d6kh3\", debug=True)\n\n# Shorthand expansion with debug\nresult = Dice.roll(\"FUDGE\", debug=True)\n\n# With modifiers and debug\nmodifiers = {\"strength\": 3, \"magic_bonus\": 2}\nresult = Dice.roll(\"1d20\", modifiers=modifiers, debug=True)\n```\n\n### Custom Debug Loggers\n\nYou can inject your own logger to capture debug output using Python's standard logging interface:\n\n```python\nimport logging\nfrom wyrdbound_dice import Dice\nfrom wyrdbound_dice.debug_logger import StringLogger\n\n# Method 1: Use the built-in StringLogger for testing/API purposes\nstring_logger = StringLogger()\nresult = Dice.roll(\"2d6 + 3\", debug=True, logger=string_logger)\n\n# Get all the debug output as a string\ndebug_output = string_logger.get_logs()\nprint(debug_output)\n\n# Clear the logger for reuse\nstring_logger.clear()\n\n# Method 2: Use Python's standard logging module\n# Create a custom logger with your preferred configuration\nlogger = logging.getLogger('my_dice_app')\nlogger.setLevel(logging.DEBUG)\n\n# Add your own handler (file, web service, etc.)\nhandler = logging.FileHandler('dice_debug.log')\nhandler.setFormatter(logging.Formatter('%(asctime)s %(message)s'))\nlogger.addHandler(handler)\n\n# Use with dice rolling\nresult = Dice.roll(\"1d20\", debug=True, logger=logger)\n\n# Method 3: Create a custom logger class\nclass WebAppLogger:\n def debug(self, message):\n # Send to your web app's logging system\n app.logger.debug(message)\n\n def info(self, message):\n app.logger.info(message)\n\n def warning(self, message):\n app.logger.warning(message)\n\n def error(self, message):\n app.logger.error(message)\n\nweb_logger = WebAppLogger()\nresult = Dice.roll(\"1d20\", debug=True, logger=web_logger)\n```\n\n### Logger Interface\n\nCustom loggers should implement Python's standard logging interface methods:\n\n```python\nclass MyCustomLogger:\n def debug(self, message: str) -> None:\n \"\"\"Log a debug message.\"\"\"\n ...\n\n def info(self, message: str) -> None:\n \"\"\"Log an info message.\"\"\"\n ...\n\n def warning(self, message: str) -> None:\n \"\"\"Log a warning message.\"\"\"\n ...\n\n def error(self, message: str) -> None:\n \"\"\"Log an error message.\"\"\"\n ...\nclass MyLogger:\n def log(self, message: str) -> None:\n # Your custom logging implementation\n pass\n```\n\n### Command Line Debug\n\nThe `tools/roll.py` script also supports debug mode:\n\n```bash\n# Basic roll with debug\npython tools/roll.py \"2d6 + 3\" --debug\n\n# Complex expression with debug\npython tools/roll.py \"4d6kh3\" --debug\n\n# Multiple rolls with debug\npython tools/roll.py \"1d6\" -n 3 --debug\n\n# JSON output with debug information included\npython tools/roll.py \"2d6 + 3\" --json --debug\n\n# Help shows all options including debug\npython tools/roll.py --help\n```\n\nWhen using `--json --debug`, the debug output is captured and included in the JSON response under a \"debug\" key:\n\n```json\n{\n \"result\": 11,\n \"description\": \"11 = 8 (2d6: 4, 4) + 3\",\n \"debug\": \"DEBUG: [START] Rolling expression: '2d6 + 3'\\nDEBUG: [PROCESSING] Starting expression processing\\n...\"\n}\n```\n\n### Debug Output Format\n\nDebug messages are prefixed with `DEBUG:` and use structured labels like `[START]`, `[TOKENIZING]`, `[PARSING]`, etc. This makes it easy to follow the progression through the dice rolling engine and identify where issues might occur.\n\n## Development\n\n### Setting Up Development Environment\n\n```bash\n# Install the package with development dependencies\npip install -e \".[dev]\"\n\n# Install with both development and visualization dependencies\npip install -e \".[dev,visualization]\"\n\n# Or install development dependencies separately\npip install pytest pytest-cov black isort ruff\n```\n\n### Running Tests\n\n```bash\n# Run all tests\npython -m pytest tests/\n\n# Run with coverage\npython -m pytest tests/ --cov=wyrdbound_dice\n\n# Run with coverage and generate HTML report\npython -m pytest tests/ --cov=wyrdbound_dice --cov-report=html\n\n# Run specific test class\npython -m pytest tests/test_dice.py::TestDiceKeepHighestLowest\n```\n\n### Code Quality\n\n```bash\n# Format code\nblack src/ tests/ tools/\n\n# Sort imports\nisort src/ tests/ tools/\n\n# Lint code\nruff check src/ tests/ tools/\n```\n\n## Contributing\n\nContributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.\n\n### Areas for Contribution\n\n- New features for existing RPG systems\n- Performance optimizations\n- Additional CLI tools\n- Documentation improvements\n- Bug fixes and testing\n\n### Continuous Integration\n\nThis project uses GitHub Actions for CI/CD:\n\n- **Testing**: Automated tests across Python 3.8-3.12 on Ubuntu, Windows, and macOS\n- **Code Quality**: Black formatting, isort import sorting, and Ruff linting\n- **Package Validation**: Installation testing and CLI tool verification\n\nAll pull requests are automatically tested and must pass all checks before merging.\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n\n## Acknowledgments\n\n- Inspired by the diverse mechanics of tabletop RPG systems\n- Thanks to the RPG community for feedback and feature requests\n- Built with mathematical precision and gaming passion\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "A comprehensive dice rolling library for tabletop RPGs",
"version": "0.0.2",
"project_urls": {
"Bug Tracker": "https://github.com/wyrdbound/wyrdbound-dice/issues",
"Documentation": "https://github.com/wyrdbound/wyrdbound-dice#readme",
"Homepage": "https://github.com/wyrdbound/wyrdbound-dice",
"Repository": "https://github.com/wyrdbound/wyrdbound-dice"
},
"split_keywords": [
"dice",
" tabletop",
" rpg",
" ttrpg",
" gaming",
" random",
" probability"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "5a2f42764efb1e0ed10114354480648e8a5386a0e4609c4f413fd29c49bc1a7f",
"md5": "144527340a5d7ce9a2c80a4dba110d6d",
"sha256": "7290c163c540604cd7457802142095a89ab0afe05cac17b51fb0e836d5dfe08b"
},
"downloads": -1,
"filename": "wyrdbound_dice-0.0.2-py3-none-any.whl",
"has_sig": false,
"md5_digest": "144527340a5d7ce9a2c80a4dba110d6d",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8",
"size": 29141,
"upload_time": "2025-10-14T04:44:56",
"upload_time_iso_8601": "2025-10-14T04:44:56.307686Z",
"url": "https://files.pythonhosted.org/packages/5a/2f/42764efb1e0ed10114354480648e8a5386a0e4609c4f413fd29c49bc1a7f/wyrdbound_dice-0.0.2-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "b845adf2c72aedaeb6835a014230b5fab3c170be55c3e06aa7f4ea5542b687c6",
"md5": "1346a8c634b520db0a6d4990722537e3",
"sha256": "fcecb99538bdd0879624fc9aed8a2887ba6cd0c71163e485a9e35dbb1c9fa6eb"
},
"downloads": -1,
"filename": "wyrdbound_dice-0.0.2.tar.gz",
"has_sig": false,
"md5_digest": "1346a8c634b520db0a6d4990722537e3",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.8",
"size": 45836,
"upload_time": "2025-10-14T04:44:57",
"upload_time_iso_8601": "2025-10-14T04:44:57.654312Z",
"url": "https://files.pythonhosted.org/packages/b8/45/adf2c72aedaeb6835a014230b5fab3c170be55c3e06aa7f4ea5542b687c6/wyrdbound_dice-0.0.2.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-10-14 04:44:57",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "wyrdbound",
"github_project": "wyrdbound-dice",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "wyrdbound-dice"
}