wyrdbound-dice 0.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- wyrdbound_dice-0.0.1/LICENSE +21 -0
- wyrdbound_dice-0.0.1/PKG-INFO +625 -0
- wyrdbound_dice-0.0.1/README.md +586 -0
- wyrdbound_dice-0.0.1/pyproject.toml +85 -0
- wyrdbound_dice-0.0.1/setup.cfg +4 -0
- wyrdbound_dice-0.0.1/src/wyrdbound_dice/__init__.py +73 -0
- wyrdbound_dice-0.0.1/src/wyrdbound_dice/debug_logger.py +124 -0
- wyrdbound_dice-0.0.1/src/wyrdbound_dice/dice.py +1076 -0
- wyrdbound_dice-0.0.1/src/wyrdbound_dice/errors.py +42 -0
- wyrdbound_dice-0.0.1/src/wyrdbound_dice/expression_lexer.py +240 -0
- wyrdbound_dice-0.0.1/src/wyrdbound_dice/expression_parser.py +294 -0
- wyrdbound_dice-0.0.1/src/wyrdbound_dice/expression_token.py +49 -0
- wyrdbound_dice-0.0.1/src/wyrdbound_dice/roll_result.py +382 -0
- wyrdbound_dice-0.0.1/src/wyrdbound_dice.egg-info/PKG-INFO +625 -0
- wyrdbound_dice-0.0.1/src/wyrdbound_dice.egg-info/SOURCES.txt +33 -0
- wyrdbound_dice-0.0.1/src/wyrdbound_dice.egg-info/dependency_links.txt +1 -0
- wyrdbound_dice-0.0.1/src/wyrdbound_dice.egg-info/requires.txt +10 -0
- wyrdbound_dice-0.0.1/src/wyrdbound_dice.egg-info/top_level.txt +1 -0
- wyrdbound_dice-0.0.1/tests/test_base.py +20 -0
- wyrdbound_dice-0.0.1/tests/test_convenience_function.py +52 -0
- wyrdbound_dice-0.0.1/tests/test_debug_logging.py +292 -0
- wyrdbound_dice-0.0.1/tests/test_dice.py +126 -0
- wyrdbound_dice-0.0.1/tests/test_dice_chaos_engineering.py +507 -0
- wyrdbound_dice-0.0.1/tests/test_dice_drop_highest_lowest.py +36 -0
- wyrdbound_dice-0.0.1/tests/test_dice_exploding_rerolls.py +68 -0
- wyrdbound_dice-0.0.1/tests/test_dice_fudge.py +38 -0
- wyrdbound_dice-0.0.1/tests/test_dice_keep_highest_lowest.py +41 -0
- wyrdbound_dice-0.0.1/tests/test_dice_modifiers.py +125 -0
- wyrdbound_dice-0.0.1/tests/test_dice_percentile.py +39 -0
- wyrdbound_dice-0.0.1/tests/test_dice_reroll.py +35 -0
- wyrdbound_dice-0.0.1/tests/test_dice_system_shorthands.py +70 -0
- wyrdbound_dice-0.0.1/tests/test_dice_unicode_and_special_characters.py +47 -0
- wyrdbound_dice-0.0.1/tests/test_dice_zero_dice_edge_cases.py +30 -0
- wyrdbound_dice-0.0.1/tests/test_expression_parsing.py +71 -0
- wyrdbound_dice-0.0.1/tests/test_infinite_conditions.py +73 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 The Wyrd One
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,625 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: wyrdbound-dice
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: A comprehensive dice rolling library for tabletop RPGs
|
|
5
|
+
Author-email: The Wyrd One <wyrdbound@proton.me>
|
|
6
|
+
Maintainer-email: The Wyrd One <wyrdbound@proton.me>
|
|
7
|
+
License: MIT
|
|
8
|
+
Project-URL: Homepage, https://github.com/yourusername/wyrdbound-dice
|
|
9
|
+
Project-URL: Repository, https://github.com/yourusername/wyrdbound-dice
|
|
10
|
+
Project-URL: Documentation, https://github.com/yourusername/wyrdbound-dice#readme
|
|
11
|
+
Project-URL: Bug Tracker, https://github.com/yourusername/wyrdbound-dice/issues
|
|
12
|
+
Keywords: dice,tabletop,rpg,ttrpg,gaming,random,probability
|
|
13
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
24
|
+
Classifier: Topic :: Games/Entertainment :: Role-Playing
|
|
25
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
26
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
27
|
+
Requires-Python: >=3.8
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
License-File: LICENSE
|
|
30
|
+
Provides-Extra: visualization
|
|
31
|
+
Requires-Dist: matplotlib>=3.7.0; extra == "visualization"
|
|
32
|
+
Provides-Extra: dev
|
|
33
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
34
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
35
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
36
|
+
Requires-Dist: isort>=5.12.0; extra == "dev"
|
|
37
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
38
|
+
Dynamic: license-file
|
|
39
|
+
|
|
40
|
+
# Wyrdbound Dice
|
|
41
|
+
|
|
42
|
+
A comprehensive dice rolling library for tabletop RPGs, designed to handle complex dice expressions with mathematical precision and extensive system support.
|
|
43
|
+
|
|
44
|
+
This library is designed for use in [wyrdbound](https://github.com/wyrdbound), a text-based RPG system that emphasizes narrative and player choice.
|
|
45
|
+
|
|
46
|
+
[](https://github.com/wyrdbound/wyrdbound-dice/actions/workflows/ci.yml)
|
|
47
|
+
[](https://www.python.org/downloads/)
|
|
48
|
+
[](https://opensource.org/licenses/MIT)
|
|
49
|
+
[](https://github.com/psf/black)
|
|
50
|
+
|
|
51
|
+
> 📣 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!)
|
|
52
|
+
|
|
53
|
+
## Features
|
|
54
|
+
|
|
55
|
+
Wyrdbound Dice supports an extensive range of dice rolling mechanics used across many tabletop RPG systems:
|
|
56
|
+
|
|
57
|
+
### Basic Dice Rolling
|
|
58
|
+
|
|
59
|
+
- **Standard polyhedral dice**: `1d4`, `1d6`, `1d8`, `1d10`, `1d12`, `1d20`, `1d100`
|
|
60
|
+
- **Multiple dice**: `3d6`, `4d8`, etc.
|
|
61
|
+
- **Percentile dice**: `1d%` (displays as [tens, ones])
|
|
62
|
+
|
|
63
|
+
### Mathematical Operations
|
|
64
|
+
|
|
65
|
+
- **Arithmetic operations**: `2d6 + 3`, `1d20 - 2`, `1d6 × 4`, `1d10 ÷ 2`
|
|
66
|
+
- **Complex expressions**: `2d6 + 1d4 × 2 - 1`
|
|
67
|
+
- **Proper precedence**: Mathematical order of operations (PEMDAS/BODMAS)
|
|
68
|
+
- **Unicode operators**: Support for `×`, `÷`, `−`, and fullwidth characters
|
|
69
|
+
|
|
70
|
+
### Keep/Drop Mechanics
|
|
71
|
+
|
|
72
|
+
- **Keep highest**: `4d6kh3` (ability score generation), `2d20kh1` (advantage)
|
|
73
|
+
- **Keep lowest**: `4d6kl3`, `2d20kl1` (disadvantage)
|
|
74
|
+
- **Drop operations**: `4d6dh1` (drop highest), `4d6dl1` (drop lowest)
|
|
75
|
+
- **Multiple operations**: `5d6kh3kl1` (chain keep/drop operations)
|
|
76
|
+
|
|
77
|
+
### Reroll Mechanics
|
|
78
|
+
|
|
79
|
+
- **Unlimited rerolls**: `1d6r<=2` (reroll while ≤ 2)
|
|
80
|
+
- **Limited rerolls**: `1d6r1<=2` (reroll once), `1d6r3<=3` (reroll up to 3 times)
|
|
81
|
+
- **Comparison operators**: `<=`, `<`, `>=`, `>`, `=`
|
|
82
|
+
- **Alternate notation**: `1d6ro<=2` (reroll once)
|
|
83
|
+
|
|
84
|
+
### Exploding Dice
|
|
85
|
+
|
|
86
|
+
- **Simple explosion**: `1d6e` (explode on max value)
|
|
87
|
+
- **Explicit threshold**: `1d6e6`, `1d10e>=8`
|
|
88
|
+
- **Custom conditions**: `1d6e>=5` (explode on 5 or 6)
|
|
89
|
+
- **Multiple explosions**: Dice can explode repeatedly
|
|
90
|
+
|
|
91
|
+
### Fudge Dice (Fate Core/Accelerated)
|
|
92
|
+
|
|
93
|
+
- **Single Fudge die**: `1dF` (results: -1, 0, +1)
|
|
94
|
+
- **Standard Fate roll**: `4dF`
|
|
95
|
+
- **Symbol display**: Shows as `-, B, +`
|
|
96
|
+
- **Math operations**: Can be combined with other dice and modifiers
|
|
97
|
+
|
|
98
|
+
### System Shorthands
|
|
99
|
+
|
|
100
|
+
- **FUDGE**: `4dF` (Fate Core)
|
|
101
|
+
- **BOON**: `3d6kh2` (Traveller advantage)
|
|
102
|
+
- **BANE**: `3d6kl2` (Traveller disadvantage)
|
|
103
|
+
- **FLUX**: `1d6 - 1d6` (Traveller flux)
|
|
104
|
+
- **GOODFLUX**: Always positive flux (highest 1d6 - lowest 1d6)
|
|
105
|
+
- **BADFLUX**: Always negative flux (lowest 1d6 - highest 1d6)
|
|
106
|
+
- **PERC / PERCENTILE**: `1d%`
|
|
107
|
+
|
|
108
|
+
### Named Modifiers
|
|
109
|
+
|
|
110
|
+
- **Static modifiers**: `{"Strength": 3, "Proficiency": 2}`
|
|
111
|
+
- **Dice modifiers**: `{"Guidance": "1d4", "Bane": "-1d4"}`
|
|
112
|
+
- **Mixed modifiers**: Combine static numbers and dice expressions
|
|
113
|
+
|
|
114
|
+
### Advanced Features
|
|
115
|
+
|
|
116
|
+
- **Zero dice handling**: `0d6` returns 0
|
|
117
|
+
- **Negative dice**: `-1d6` returns negative result
|
|
118
|
+
- **Thread safety**: Safe for concurrent use
|
|
119
|
+
- **Error handling**: Clear exceptions for invalid conditions
|
|
120
|
+
- **Infinite condition detection**: Prevents impossible reroll/explode scenarios
|
|
121
|
+
|
|
122
|
+
## Installation
|
|
123
|
+
|
|
124
|
+
### For End Users
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
pip install wyrdbound-dice
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
> **Note**: This package is currently in development and not yet published to PyPI. For now, please use the development installation method below.
|
|
131
|
+
|
|
132
|
+
### For Development
|
|
133
|
+
|
|
134
|
+
If you want to contribute to the project or use the latest development version:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
# Clone the repository
|
|
138
|
+
git clone https://github.com/wyrdbound/wyrdbound-dice.git
|
|
139
|
+
cd wyrdbound-dice
|
|
140
|
+
|
|
141
|
+
# Install in development mode
|
|
142
|
+
pip install -e .
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Optional Dependencies
|
|
146
|
+
|
|
147
|
+
For visualization features (graph tool):
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
pip install "wyrdbound-dice[visualization]"
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
For development:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
pip install -e ".[dev]"
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
For both visualization and development:
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
pip install -e ".[dev,visualization]"
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Quick Start
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
from wyrdbound_dice import Dice
|
|
169
|
+
|
|
170
|
+
# Basic roll
|
|
171
|
+
result = Dice.roll("1d20")
|
|
172
|
+
print(result.total) # 20
|
|
173
|
+
print(result) # 20 = 20 (1d20: 20)
|
|
174
|
+
|
|
175
|
+
# Complex expression
|
|
176
|
+
result = Dice.roll("2d6 + 1d4 × 2 + 3")
|
|
177
|
+
print(result) # 17 = 8 (2d6: 6, 2) + 3 (1d4: 3) x 2 + 3
|
|
178
|
+
|
|
179
|
+
# Advantage roll (D&D 5e)
|
|
180
|
+
result = Dice.roll("2d20kh1")
|
|
181
|
+
print(result) # 19 = 19 (2d20kh1: 19, 12)
|
|
182
|
+
|
|
183
|
+
# Reroll (D&D 5e - Great Weapon Fighting)
|
|
184
|
+
result = Dice.roll("2d6r1<=2")
|
|
185
|
+
print(result) # 12 = 12 (2d6r1<=2: 1, 2, 6, 6)
|
|
186
|
+
|
|
187
|
+
# Exploding dice (Savage Worlds)
|
|
188
|
+
result = Dice.roll("1d6e")
|
|
189
|
+
print(result) # 11 = 11 (1d6e6: 6, 5)
|
|
190
|
+
|
|
191
|
+
# Fate Core
|
|
192
|
+
result = Dice.roll("4dF + 2")
|
|
193
|
+
print(result) # 2 = 0 (4dF: +, B, -, B) + 2
|
|
194
|
+
|
|
195
|
+
# With named modifiers
|
|
196
|
+
modifiers = {"Strength": 3, "Proficiency": 2, "Bless": "1d4"}
|
|
197
|
+
result = Dice.roll("1d20", modifiers)
|
|
198
|
+
print(result) # 20 = 12 (1d20: 12) + 3 (Strength) + 2 (Proficiency) + 3 (Bless: 3 = 3 (1d4: 3))
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## API Reference
|
|
202
|
+
|
|
203
|
+
### Main Classes
|
|
204
|
+
|
|
205
|
+
#### `Dice`
|
|
206
|
+
|
|
207
|
+
The main entry point for dice rolling.
|
|
208
|
+
|
|
209
|
+
**`Dice.roll(expression, modifiers=None)`**
|
|
210
|
+
|
|
211
|
+
- `expression` (str): Dice expression to evaluate
|
|
212
|
+
- `modifiers` (dict, optional): Named modifiers as `{name: value}` where value can be int or dice expression string
|
|
213
|
+
- Returns: `RollResultSet` object
|
|
214
|
+
|
|
215
|
+
#### `RollResultSet`
|
|
216
|
+
|
|
217
|
+
Contains the results of a dice roll.
|
|
218
|
+
|
|
219
|
+
**Properties:**
|
|
220
|
+
|
|
221
|
+
- `total` (int): Final calculated result
|
|
222
|
+
- `results` (list): List of individual `RollResult` objects
|
|
223
|
+
- `modifiers` (list): List of applied modifiers
|
|
224
|
+
- `__str__()`: Human-readable description of the complete roll
|
|
225
|
+
|
|
226
|
+
#### `RollResult`
|
|
227
|
+
|
|
228
|
+
Represents a single dice expression result.
|
|
229
|
+
|
|
230
|
+
**Properties:**
|
|
231
|
+
|
|
232
|
+
- `num` (int): Number of dice rolled
|
|
233
|
+
- `sides` (int/str): Number of sides (or "F" for Fudge, "%" for percentile)
|
|
234
|
+
- `rolls` (list): Final kept dice values
|
|
235
|
+
- `all_rolls` (list): All dice rolled (including rerolls, explosions)
|
|
236
|
+
- `total` (int): Sum of kept dice
|
|
237
|
+
|
|
238
|
+
### Exceptions
|
|
239
|
+
|
|
240
|
+
- **`ParseError`**: Invalid dice expression syntax
|
|
241
|
+
- **`DivisionByZeroError`**: Division by zero in expression
|
|
242
|
+
- **`InfiniteConditionError`**: Impossible reroll/explode condition
|
|
243
|
+
|
|
244
|
+
## Command Line Tools
|
|
245
|
+
|
|
246
|
+
### Roll Tool
|
|
247
|
+
|
|
248
|
+
Roll dice expressions from the command line:
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
# Basic usage
|
|
252
|
+
python tools/roll.py "1d20 + 5"
|
|
253
|
+
|
|
254
|
+
# Multiple rolls
|
|
255
|
+
python tools/roll.py "2d6" --count 10
|
|
256
|
+
|
|
257
|
+
# JSON output (single roll)
|
|
258
|
+
python tools/roll.py "1d20" --json
|
|
259
|
+
|
|
260
|
+
# JSON output (multiple rolls)
|
|
261
|
+
python tools/roll.py "1d6" --count 3 --json
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
**Options:**
|
|
265
|
+
|
|
266
|
+
- `-v, --verbose`: Show detailed breakdown
|
|
267
|
+
- `-n, --count N`: Roll N times
|
|
268
|
+
- `--json`: Output results as JSON
|
|
269
|
+
|
|
270
|
+
**JSON Output Format:**
|
|
271
|
+
|
|
272
|
+
Single roll returns an object:
|
|
273
|
+
|
|
274
|
+
```json
|
|
275
|
+
{
|
|
276
|
+
"result": 14,
|
|
277
|
+
"description": "14 = 14 (1d20: 14)"
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Multiple rolls return an array:
|
|
282
|
+
|
|
283
|
+
```json
|
|
284
|
+
[
|
|
285
|
+
{
|
|
286
|
+
"result": 4,
|
|
287
|
+
"description": "4 = 4 (1d6: 4)"
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
"result": 6,
|
|
291
|
+
"description": "6 = 6 (1d6: 6)"
|
|
292
|
+
}
|
|
293
|
+
]
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Visualization Tool
|
|
297
|
+
|
|
298
|
+
Generate probability distributions and statistics:
|
|
299
|
+
|
|
300
|
+
```bash
|
|
301
|
+
# Basic distribution graph
|
|
302
|
+
python tools/graph.py "2d6"
|
|
303
|
+
|
|
304
|
+
# Complex expression with more samples
|
|
305
|
+
python tools/graph.py "1d20 + 5" --num-rolls 50000
|
|
306
|
+
|
|
307
|
+
# Specify output file
|
|
308
|
+
python tools/graph.py "4d6kh3" --output ability_scores.html
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
**Features:**
|
|
312
|
+
|
|
313
|
+
- Probability distribution histograms
|
|
314
|
+
- Statistical analysis (mean, mode, range)
|
|
315
|
+
- Comparison charts for multiple expressions
|
|
316
|
+
- Export to various image formats
|
|
317
|
+
|
|
318
|
+
## Supported Systems
|
|
319
|
+
|
|
320
|
+
WyrdBound Dice has been designed to support mechanics from many popular RPG systems:
|
|
321
|
+
|
|
322
|
+
- **D&D 5e / Pathfinder**: Advantage/disadvantage (`2d20kh1`/`2d20kl1`), ability scores (`4d6kh3`)
|
|
323
|
+
- **Savage Worlds**: Exploding dice (`1d6e`), wild dice, aces
|
|
324
|
+
- **Fate Core/Accelerated**: Fudge dice (`4dF`, `FUDGE`)
|
|
325
|
+
- **Traveller**: Boon/Bane (`BOON`/`BANE`), Flux dice (`FLUX`)
|
|
326
|
+
- **World of Darkness**: Dice pools with success counting (upcoming)
|
|
327
|
+
- **Shadowrun**: Exploding dice, glitch detection (upcoming)
|
|
328
|
+
|
|
329
|
+
## Examples
|
|
330
|
+
|
|
331
|
+
### Character Creation
|
|
332
|
+
|
|
333
|
+
```python
|
|
334
|
+
# D&D 5e ability scores
|
|
335
|
+
stats = []
|
|
336
|
+
for _ in range(6):
|
|
337
|
+
result = Dice.roll("4d6kh3")
|
|
338
|
+
stats.append(result.total)
|
|
339
|
+
|
|
340
|
+
# Traveller characteristics with modifiers
|
|
341
|
+
characteristics = Dice.roll("2d6", {"DM": 1})
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
### Combat Rolls
|
|
345
|
+
|
|
346
|
+
```python
|
|
347
|
+
# D&D 5e attack with advantage
|
|
348
|
+
attack = Dice.roll("2d20kh1 + 8") # +8 attack bonus
|
|
349
|
+
|
|
350
|
+
# Savage Worlds damage with ace
|
|
351
|
+
damage = Dice.roll("1d6e + 2")
|
|
352
|
+
|
|
353
|
+
# Fate Core with aspects
|
|
354
|
+
fate_roll = Dice.roll("4dF + 3", {"Aspect": 2})
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### Complex Expressions
|
|
358
|
+
|
|
359
|
+
```python
|
|
360
|
+
# Fireball damage (8d6) with Metamagic (reroll 1s)
|
|
361
|
+
fireball = Dice.roll("8d6r1<=1")
|
|
362
|
+
|
|
363
|
+
# Sneak attack with multiple damage types
|
|
364
|
+
sneak = Dice.roll("1d8 + 3d6") # Rapier + sneak attack
|
|
365
|
+
|
|
366
|
+
# Mathematical complexity
|
|
367
|
+
complex_formula = Dice.roll("(2d6 + 3) × 2 + 1d4 - 1")
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
## Debug Logging
|
|
371
|
+
|
|
372
|
+
WyrdBound Dice includes comprehensive debug logging to help troubleshoot dice rolling issues and understand how expressions are parsed and evaluated.
|
|
373
|
+
|
|
374
|
+
### Enabling Debug Mode
|
|
375
|
+
|
|
376
|
+
```python
|
|
377
|
+
from wyrdbound_dice import Dice
|
|
378
|
+
|
|
379
|
+
# Enable debug logging for a roll
|
|
380
|
+
result = Dice.roll("2d6 + 3", debug=True)
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### Debug Output Example
|
|
384
|
+
|
|
385
|
+
When debug mode is enabled, you'll see detailed step-by-step information:
|
|
386
|
+
|
|
387
|
+
```
|
|
388
|
+
DEBUG: [START] Rolling expression: '2d6 + 3'
|
|
389
|
+
DEBUG: [PROCESSING] Starting expression processing
|
|
390
|
+
DEBUG: NORMALIZED: '2d6 + 3'
|
|
391
|
+
DEBUG: [PARSER_SELECTION] Using precedence parser
|
|
392
|
+
DEBUG: [TOKENIZING] Tokenizing expression: '2d6 + 3'
|
|
393
|
+
DEBUG: Tokens: ['DICE(2d6)@0', 'PLUS(+)@3', 'NUMBER(3)@4']
|
|
394
|
+
DEBUG: [PARSING] Parsing tokens with precedence rules
|
|
395
|
+
DEBUG: [EVALUATING] Evaluating parsed expression
|
|
396
|
+
DEBUG: Rolling 1d6: 5
|
|
397
|
+
DEBUG: Rolling 1d6: 4
|
|
398
|
+
DEBUG: [RESULT] Expression evaluated to: 12
|
|
399
|
+
DEBUG: TOTAL 12 modifiers(0) = 12
|
|
400
|
+
DEBUG: [COMPLETE] Final result: 12
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
### What Debug Mode Shows
|
|
404
|
+
|
|
405
|
+
Debug logging provides insights into:
|
|
406
|
+
|
|
407
|
+
- **Expression normalization**: How input expressions are cleaned and processed
|
|
408
|
+
- **Shorthand expansion**: When shortcuts like "FUDGE" are expanded to "4dF"
|
|
409
|
+
- **Parser selection**: Whether the precedence parser or original parser is used
|
|
410
|
+
- **Tokenization**: How complex expressions are broken into tokens
|
|
411
|
+
- **Individual dice rolls**: Each die roll with specific results
|
|
412
|
+
- **Keep/drop operations**: Parsed keep/drop operations like "kh2"
|
|
413
|
+
- **Mathematical evaluation**: Step-by-step calculation of complex expressions
|
|
414
|
+
- **Modifier processing**: How modifiers are applied to results
|
|
415
|
+
- **Error handling**: Debug information even when errors occur
|
|
416
|
+
|
|
417
|
+
### Debug Examples
|
|
418
|
+
|
|
419
|
+
```python
|
|
420
|
+
# Simple dice with debug
|
|
421
|
+
result = Dice.roll("1d20", debug=True)
|
|
422
|
+
|
|
423
|
+
# Complex expression with debug
|
|
424
|
+
result = Dice.roll("2d6 * 2 + 1d4", debug=True)
|
|
425
|
+
|
|
426
|
+
# Keep operations with debug
|
|
427
|
+
result = Dice.roll("4d6kh3", debug=True)
|
|
428
|
+
|
|
429
|
+
# Shorthand expansion with debug
|
|
430
|
+
result = Dice.roll("FUDGE", debug=True)
|
|
431
|
+
|
|
432
|
+
# With modifiers and debug
|
|
433
|
+
modifiers = {"strength": 3, "magic_bonus": 2}
|
|
434
|
+
result = Dice.roll("1d20", modifiers=modifiers, debug=True)
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
### Custom Debug Loggers
|
|
438
|
+
|
|
439
|
+
You can inject your own logger to capture debug output using Python's standard logging interface:
|
|
440
|
+
|
|
441
|
+
```python
|
|
442
|
+
import logging
|
|
443
|
+
from wyrdbound_dice import Dice
|
|
444
|
+
from wyrdbound_dice.debug_logger import StringLogger
|
|
445
|
+
|
|
446
|
+
# Method 1: Use the built-in StringLogger for testing/API purposes
|
|
447
|
+
string_logger = StringLogger()
|
|
448
|
+
result = Dice.roll("2d6 + 3", debug=True, logger=string_logger)
|
|
449
|
+
|
|
450
|
+
# Get all the debug output as a string
|
|
451
|
+
debug_output = string_logger.get_logs()
|
|
452
|
+
print(debug_output)
|
|
453
|
+
|
|
454
|
+
# Clear the logger for reuse
|
|
455
|
+
string_logger.clear()
|
|
456
|
+
|
|
457
|
+
# Method 2: Use Python's standard logging module
|
|
458
|
+
# Create a custom logger with your preferred configuration
|
|
459
|
+
logger = logging.getLogger('my_dice_app')
|
|
460
|
+
logger.setLevel(logging.DEBUG)
|
|
461
|
+
|
|
462
|
+
# Add your own handler (file, web service, etc.)
|
|
463
|
+
handler = logging.FileHandler('dice_debug.log')
|
|
464
|
+
handler.setFormatter(logging.Formatter('%(asctime)s %(message)s'))
|
|
465
|
+
logger.addHandler(handler)
|
|
466
|
+
|
|
467
|
+
# Use with dice rolling
|
|
468
|
+
result = Dice.roll("1d20", debug=True, logger=logger)
|
|
469
|
+
|
|
470
|
+
# Method 3: Create a custom logger class
|
|
471
|
+
class WebAppLogger:
|
|
472
|
+
def debug(self, message):
|
|
473
|
+
# Send to your web app's logging system
|
|
474
|
+
app.logger.debug(message)
|
|
475
|
+
|
|
476
|
+
def info(self, message):
|
|
477
|
+
app.logger.info(message)
|
|
478
|
+
|
|
479
|
+
def warning(self, message):
|
|
480
|
+
app.logger.warning(message)
|
|
481
|
+
|
|
482
|
+
def error(self, message):
|
|
483
|
+
app.logger.error(message)
|
|
484
|
+
|
|
485
|
+
web_logger = WebAppLogger()
|
|
486
|
+
result = Dice.roll("1d20", debug=True, logger=web_logger)
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
### Logger Interface
|
|
490
|
+
|
|
491
|
+
Custom loggers should implement Python's standard logging interface methods:
|
|
492
|
+
|
|
493
|
+
```python
|
|
494
|
+
class MyCustomLogger:
|
|
495
|
+
def debug(self, message: str) -> None:
|
|
496
|
+
"""Log a debug message."""
|
|
497
|
+
...
|
|
498
|
+
|
|
499
|
+
def info(self, message: str) -> None:
|
|
500
|
+
"""Log an info message."""
|
|
501
|
+
...
|
|
502
|
+
|
|
503
|
+
def warning(self, message: str) -> None:
|
|
504
|
+
"""Log a warning message."""
|
|
505
|
+
...
|
|
506
|
+
|
|
507
|
+
def error(self, message: str) -> None:
|
|
508
|
+
"""Log an error message."""
|
|
509
|
+
...
|
|
510
|
+
class MyLogger:
|
|
511
|
+
def log(self, message: str) -> None:
|
|
512
|
+
# Your custom logging implementation
|
|
513
|
+
pass
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
### Command Line Debug
|
|
517
|
+
|
|
518
|
+
The `tools/roll.py` script also supports debug mode:
|
|
519
|
+
|
|
520
|
+
```bash
|
|
521
|
+
# Basic roll with debug
|
|
522
|
+
python tools/roll.py "2d6 + 3" --debug
|
|
523
|
+
|
|
524
|
+
# Complex expression with debug
|
|
525
|
+
python tools/roll.py "4d6kh3" --debug
|
|
526
|
+
|
|
527
|
+
# Multiple rolls with debug
|
|
528
|
+
python tools/roll.py "1d6" -n 3 --debug
|
|
529
|
+
|
|
530
|
+
# JSON output with debug information included
|
|
531
|
+
python tools/roll.py "2d6 + 3" --json --debug
|
|
532
|
+
|
|
533
|
+
# Help shows all options including debug
|
|
534
|
+
python tools/roll.py --help
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
When using `--json --debug`, the debug output is captured and included in the JSON response under a "debug" key:
|
|
538
|
+
|
|
539
|
+
```json
|
|
540
|
+
{
|
|
541
|
+
"result": 11,
|
|
542
|
+
"description": "11 = 8 (2d6: 4, 4) + 3",
|
|
543
|
+
"debug": "DEBUG: [START] Rolling expression: '2d6 + 3'\nDEBUG: [PROCESSING] Starting expression processing\n..."
|
|
544
|
+
}
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
### Debug Output Format
|
|
548
|
+
|
|
549
|
+
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.
|
|
550
|
+
|
|
551
|
+
## Development
|
|
552
|
+
|
|
553
|
+
### Setting Up Development Environment
|
|
554
|
+
|
|
555
|
+
```bash
|
|
556
|
+
# Install the package with development dependencies
|
|
557
|
+
pip install -e ".[dev]"
|
|
558
|
+
|
|
559
|
+
# Install with both development and visualization dependencies
|
|
560
|
+
pip install -e ".[dev,visualization]"
|
|
561
|
+
|
|
562
|
+
# Or install development dependencies separately
|
|
563
|
+
pip install pytest pytest-cov black isort ruff
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
### Running Tests
|
|
567
|
+
|
|
568
|
+
```bash
|
|
569
|
+
# Run all tests
|
|
570
|
+
python -m pytest tests/
|
|
571
|
+
|
|
572
|
+
# Run with coverage
|
|
573
|
+
python -m pytest tests/ --cov=wyrdbound_dice
|
|
574
|
+
|
|
575
|
+
# Run with coverage and generate HTML report
|
|
576
|
+
python -m pytest tests/ --cov=wyrdbound_dice --cov-report=html
|
|
577
|
+
|
|
578
|
+
# Run specific test class
|
|
579
|
+
python -m pytest tests/test_dice.py::TestDiceKeepHighestLowest
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
### Code Quality
|
|
583
|
+
|
|
584
|
+
```bash
|
|
585
|
+
# Format code
|
|
586
|
+
black src/ tests/ tools/
|
|
587
|
+
|
|
588
|
+
# Sort imports
|
|
589
|
+
isort src/ tests/ tools/
|
|
590
|
+
|
|
591
|
+
# Lint code
|
|
592
|
+
ruff check src/ tests/ tools/
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
## Contributing
|
|
596
|
+
|
|
597
|
+
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.
|
|
598
|
+
|
|
599
|
+
### Areas for Contribution
|
|
600
|
+
|
|
601
|
+
- New features for existing RPG systems
|
|
602
|
+
- Performance optimizations
|
|
603
|
+
- Additional CLI tools
|
|
604
|
+
- Documentation improvements
|
|
605
|
+
- Bug fixes and testing
|
|
606
|
+
|
|
607
|
+
### Continuous Integration
|
|
608
|
+
|
|
609
|
+
This project uses GitHub Actions for CI/CD:
|
|
610
|
+
|
|
611
|
+
- **Testing**: Automated tests across Python 3.8-3.12 on Ubuntu, Windows, and macOS
|
|
612
|
+
- **Code Quality**: Black formatting, isort import sorting, and Ruff linting
|
|
613
|
+
- **Package Validation**: Installation testing and CLI tool verification
|
|
614
|
+
|
|
615
|
+
All pull requests are automatically tested and must pass all checks before merging.
|
|
616
|
+
|
|
617
|
+
## License
|
|
618
|
+
|
|
619
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
620
|
+
|
|
621
|
+
## Acknowledgments
|
|
622
|
+
|
|
623
|
+
- Inspired by the diverse mechanics of tabletop RPG systems
|
|
624
|
+
- Thanks to the RPG community for feedback and feature requests
|
|
625
|
+
- Built with mathematical precision and gaming passion
|