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.
Files changed (35) hide show
  1. wyrdbound_dice-0.0.1/LICENSE +21 -0
  2. wyrdbound_dice-0.0.1/PKG-INFO +625 -0
  3. wyrdbound_dice-0.0.1/README.md +586 -0
  4. wyrdbound_dice-0.0.1/pyproject.toml +85 -0
  5. wyrdbound_dice-0.0.1/setup.cfg +4 -0
  6. wyrdbound_dice-0.0.1/src/wyrdbound_dice/__init__.py +73 -0
  7. wyrdbound_dice-0.0.1/src/wyrdbound_dice/debug_logger.py +124 -0
  8. wyrdbound_dice-0.0.1/src/wyrdbound_dice/dice.py +1076 -0
  9. wyrdbound_dice-0.0.1/src/wyrdbound_dice/errors.py +42 -0
  10. wyrdbound_dice-0.0.1/src/wyrdbound_dice/expression_lexer.py +240 -0
  11. wyrdbound_dice-0.0.1/src/wyrdbound_dice/expression_parser.py +294 -0
  12. wyrdbound_dice-0.0.1/src/wyrdbound_dice/expression_token.py +49 -0
  13. wyrdbound_dice-0.0.1/src/wyrdbound_dice/roll_result.py +382 -0
  14. wyrdbound_dice-0.0.1/src/wyrdbound_dice.egg-info/PKG-INFO +625 -0
  15. wyrdbound_dice-0.0.1/src/wyrdbound_dice.egg-info/SOURCES.txt +33 -0
  16. wyrdbound_dice-0.0.1/src/wyrdbound_dice.egg-info/dependency_links.txt +1 -0
  17. wyrdbound_dice-0.0.1/src/wyrdbound_dice.egg-info/requires.txt +10 -0
  18. wyrdbound_dice-0.0.1/src/wyrdbound_dice.egg-info/top_level.txt +1 -0
  19. wyrdbound_dice-0.0.1/tests/test_base.py +20 -0
  20. wyrdbound_dice-0.0.1/tests/test_convenience_function.py +52 -0
  21. wyrdbound_dice-0.0.1/tests/test_debug_logging.py +292 -0
  22. wyrdbound_dice-0.0.1/tests/test_dice.py +126 -0
  23. wyrdbound_dice-0.0.1/tests/test_dice_chaos_engineering.py +507 -0
  24. wyrdbound_dice-0.0.1/tests/test_dice_drop_highest_lowest.py +36 -0
  25. wyrdbound_dice-0.0.1/tests/test_dice_exploding_rerolls.py +68 -0
  26. wyrdbound_dice-0.0.1/tests/test_dice_fudge.py +38 -0
  27. wyrdbound_dice-0.0.1/tests/test_dice_keep_highest_lowest.py +41 -0
  28. wyrdbound_dice-0.0.1/tests/test_dice_modifiers.py +125 -0
  29. wyrdbound_dice-0.0.1/tests/test_dice_percentile.py +39 -0
  30. wyrdbound_dice-0.0.1/tests/test_dice_reroll.py +35 -0
  31. wyrdbound_dice-0.0.1/tests/test_dice_system_shorthands.py +70 -0
  32. wyrdbound_dice-0.0.1/tests/test_dice_unicode_and_special_characters.py +47 -0
  33. wyrdbound_dice-0.0.1/tests/test_dice_zero_dice_edge_cases.py +30 -0
  34. wyrdbound_dice-0.0.1/tests/test_expression_parsing.py +71 -0
  35. 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
+ [![CI](https://github.com/wyrdbound/wyrdbound-dice/actions/workflows/ci.yml/badge.svg)](https://github.com/wyrdbound/wyrdbound-dice/actions/workflows/ci.yml)
47
+ [![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)
48
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
49
+ [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](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