All Projects → avrae → d20

avrae / d20

Licence: MIT license
A fast, powerful, and extensible dice engine for D&D, d20 systems, and any other system that needs dice!

Programming Languages

python
139335 projects - #7 most used programming language

Projects that are alternatives of or similar to d20

rollem-telegram-bot
🎲 An RPG dice rolling bot for Telegram.
Stars: ✭ 26 (-66.67%)
Mutual labels:  dice, dungeons-and-dragons, tabletop
DiceRoller
Extensible and well-documented dice roller with a robust set of features. Excellent for play-by-post forums, virtual tabletops, or for integrating into character sheets or encounter managers.
Stars: ✭ 22 (-71.79%)
Mutual labels:  dice, dice-roller, tabletop
tavernbot
D&D Discord Bot - based on Discord.js and Node 8.
Stars: ✭ 21 (-73.08%)
Mutual labels:  dice, dungeons-and-dragons, dice-roller
quick-dice-roller
The flexible, complete and handy Android dice roller for boardgame & RPG geeks.
Stars: ✭ 16 (-79.49%)
Mutual labels:  dice, dice-roller
dice-simulator
A Python simple Dice Simulator just for fun
Stars: ✭ 17 (-78.21%)
Mutual labels:  dice, dice-roller
5e-Framework
A comprehensive 5th Edition Dungeons & Dragons Framework for MapTool.
Stars: ✭ 40 (-48.72%)
Mutual labels:  dungeons-and-dragons, tabletop
dice-roller
a dice roller generator in PHP
Stars: ✭ 13 (-83.33%)
Mutual labels:  dice, dice-roller
roller
Dice roller written in Go and Javascript to run on Google Appengine
Stars: ✭ 26 (-66.67%)
Mutual labels:  dice
dnddata
Weekly updated dataset of D&D characters submitted to https://oganm.com/shiny/printSheetApp and https://oganm.com/shiny/interactiveSheet. A superset of characters used in oganm/dndstats
Stars: ✭ 91 (+16.67%)
Mutual labels:  dungeons-and-dragons
foundry-5e-monster-maker
A D&D 5e monster maker module for the Foundry VTT. Build new monsters with level-appropriate, balanced stats in seconds.
Stars: ✭ 26 (-66.67%)
Mutual labels:  dungeons-and-dragons
SolrConfigExamples
Examples of Solr configuration entries for Solr plugins and Conceptual Search\Semantic Search from Simon Hughes Dice.com
Stars: ✭ 26 (-66.67%)
Mutual labels:  dice
the-black-hack
The Black Hack RPG text and tables, ready to be translated into your language
Stars: ✭ 56 (-28.21%)
Mutual labels:  tabletop
DicePP
基于nonebot和go-cqhttp的骰子机器人
Stars: ✭ 17 (-78.21%)
Mutual labels:  dice
Luminari-Source
Current Source Code for LuminariMUD - A Pathfinder/D20 Multi User Dungeon
Stars: ✭ 37 (-52.56%)
Mutual labels:  dungeons-and-dragons
snd
Sales & Dungeons — Thermal Printer as D&D Utility
Stars: ✭ 55 (-29.49%)
Mutual labels:  dungeons-and-dragons
dungeon-sheets
A tool to create character sheets and GM session notes for Dungeons and Dragons fifth edition (D&D 5e).
Stars: ✭ 121 (+55.13%)
Mutual labels:  dungeons-and-dragons
react-dice-complete
Complete dice rolling functionality and animations
Stars: ✭ 60 (-23.08%)
Mutual labels:  dice
vscode-dmbinder
Visual Studio Code extension for managing campaign documents.
Stars: ✭ 53 (-32.05%)
Mutual labels:  dungeons-and-dragons
fast-loaded-dice-roller
The Fast Loaded Dice Roller: A Near-Optimal Exact Sampler for Discrete Probability Distributions
Stars: ✭ 41 (-47.44%)
Mutual labels:  dice-roller
my spells
Your personal D&D 5e Spellbook.
Stars: ✭ 12 (-84.62%)
Mutual labels:  dungeons-and-dragons

d20

PyPI version shields.io PyPI license PyPI pyversions codecov Documentation Status

A fast, powerful, and extensible dice engine for D&D, d20 systems, and any other system that needs dice!

Key Features

  • Quick to start - just use d20.roll()!
  • Optimized for speed and memory efficiency
  • Highly extensible API for custom behaviour and dice stringification
  • Built-in execution limits against malicious dice expressions
  • Tree-based dice representation for easy traversal

Installing

Requires Python 3.6+.

python3 -m pip install -U d20

Quickstart

>>> import d20
>>> result = d20.roll("1d20+5")
>>> str(result)
'1d20 (10) + 5 = `15`'
>>> result.total
15
>>> result.crit
<CritType.NORMAL: 0>
>>> str(result.ast)
'1d20 + 5'

Documentation

Check out the docs on Read the Docs!

Dice Syntax

This is the grammar supported by the dice parser, roughly ordered in how tightly the grammar binds.

Numbers

These are the atoms used at the base of the syntax tree.

Name Syntax Description Examples
literal INT, DECIMAL A literal number. 1, 0.5, 3.14
dice INT? "d" (INT | "%") A set of die. d20, 3d6
set "(" (num ("," num)* ","?)? ")" A set of expressions. (), (2,), (1, 3+3, 1d20)

Note that (3d6) is equivalent to 3d6, but (3d6,) is the set containing the one element 3d6.

Set Operations

These operations can be performed on dice and sets.

Grammar

Name Syntax Description Examples
set_op operation selector An operation on a set (see below). kh3, ro<3
selector seltype INT A selection on a set (see below). 3, h1, >2

Operators

Operators are always followed by a selector, and operate on the items in the set that match the selector.

Syntax Name Description
k keep Keeps all matched values.
p drop Drops all matched values.
rr reroll Rerolls all matched values until none match. (Dice only)
ro reroll once Rerolls all matched values once. (Dice only)
ra reroll and add Rerolls up to one matched value once, keeping the original roll. (Dice only)
e explode on Rolls another die for each matched value. (Dice only)
mi minimum Sets the minimum value of each die. (Dice only)
ma maximum Sets the maximum value of each die. (Dice only)

Selectors

Selectors select from the remaining kept values in a set.

Syntax Name Description
X literal All values in this set that are literally this value.
hX highest X The highest X values in the set.
lX lowest X The lowest X values in the set.
>X greater than X All values in this set greater than X.
<X less than X All values in this set less than X.

Unary Operations

Syntax Name Description
+X positive Does nothing.
-X negative The negative value of X.

Binary Operations

Syntax Name
X * Y multiplication
X / Y division
X // Y int division
X % Y modulo
X + Y addition
X - Y subtraction
X == Y equality
X >= Y greater/equal
X <= Y less/equal
X > Y greater than
X < Y less than
X != Y inequality

Examples

>>> from d20 import roll
>>> r = roll("4d6kh3")  # highest 3 of 4 6-sided dice
>>> r.total
14
>>> str(r)
'4d6kh3 (4, 4, **6**, ~~3~~) = `14`'

>>> r = roll("2d6ro<3")  # roll 2d6s, then reroll any 1s or 2s once
>>> r.total
9
>>> str(r)
'2d6ro<3 (**~~1~~**, 3, **6**) = `9`'

>>> r = roll("8d6mi2")  # roll 8d6s, with each die having a minimum roll of 2
>>> r.total
33
>>> str(r)
'8d6mi2 (1 -> 2, **6**, 4, 2, **6**, 2, 5, **6**) = `33`'

>>> r = roll("(1d4 + 1, 3, 2d6kl1)kh1")  # the highest of 1d4+1, 3, and the lower of 2 d6s
>>> r.total
3
>>> str(r)
'(1d4 (2) + 1, ~~3~~, ~~2d6kl1 (2, 5)~~)kh1 = `3`'

Custom Stringifier

By default, d20 stringifies the result of each dice roll formatted in Markdown, which may not be useful in your application. To change this behaviour, you can create a subclass of d20.Stringifier (or d20.SimpleStringifier as a starting point), and implement the _str_* methods to customize how your dice tree is stringified.

Then, simply pass an instance of your stringifier into the roll() function!

>>> import d20
>>> class MyStringifier(d20.SimpleStringifier):
...     def _stringify(self, node):
...         if not node.kept:
...             return 'X'
...         return super()._stringify(node)
...
...     def _str_expression(self, node):
...         return f"The result of the roll {self._stringify(node.roll)} was {int(node.total)}"

>>> result = d20.roll("4d6e6kh3", stringifier=MyStringifier())
>>> str(result)
'The result of the roll 4d6e6kh3 (X, 5, 6!, 6!, X, X) was 17'

Annotations and Comments

Each dice node supports value annotations - i.e., a method to "tag" parts of a roll with some indicator. For example,

>>> from d20 import roll
>>> str(roll("3d6 [fire] + 1d4 [piercing]"))
'3d6 (3, 2, 2) [fire] + 1d4 (3) [piercing] = `10`'

>>> str(roll("-(1d8 + 3) [healing]"))
'-(1d8 (7) + 3) [healing] = `-10`'

>>> str(roll("(1 [one], 2 [two], 3 [three])"))
'(1 [one], 2 [two], 3 [three]) = `6`'

are all examples of valid annotations. Annotations are purely visual and do not affect the evaluation of the roll by default.

Additionally, when allow_comments=True is passed to roll(), the result of the roll may have a comment:

>>> from d20 import roll
>>> result = roll("1d20 I rolled a d20", allow_comments=True)
>>> str(result)
'1d20 (13) = `13`'
>>> result.comment
'I rolled a d20'

Note that while allow_comments is enabled, AST caching is disabled, which may lead to slightly worse performance.

Traversing Dice Results

The raw results of dice rolls are returned in Expression objects, which can be accessed as such:

>>> from d20 import roll
>>> result = roll("3d6 + 1d4 + 3")
>>> str(result)
'3d6 (4, **6**, **6**) + 1d4 (**1**) + 3 = `20`'
>>> result.expr
<Expression roll=<BinOp left=<BinOp left=<Dice num=3 size=6 values=[<Die size=6 values=[<Literal 4>]>, <Die size=6 values=[<Literal 6>]>, <Die size=6 values=[<Literal 6>]>] operations=[]> op=+ right=<Dice num=1 size=4 values=[<Die size=4 values=[<Literal 1>]>] operations=[]>> op=+ right=<Literal 3>> comment=None>

or, in a easier-to-read format,

<Expression 
    roll=<BinOp
        left=<BinOp
            left=<Dice
                num=3
                size=6
                values=[
                    <Die size=6 values=[<Literal 4>]>,
                    <Die size=6 values=[<Literal 6>]>,
                    <Die size=6 values=[<Literal 6>]>
                ]
                operations=[]
            >
            op=+
            right=<Dice
                num=1
                size=4
                values=[
                    <Die size=4 values=[<Literal 1>]>
                ]
                operations=[]
            >
        >
        op=+
        right=<Literal 3>
    >
    comment=None
>

From here, Expression.children returns a tree of nodes representing the expression from left to right, each of which may have children of their own. This can be used to easily search for specific dice, look for the left-most operand, or modify the result by adding in resistances or other modifications.

Examples

Finding the left and right-most operands:

>>> from d20 import roll

>>> binop = roll("1 + 2 + 3 + 4")
>>> left = binop.expr
>>> while left.children:
...     left = left.children[0]
>>> left
<Literal 1>

>>> right = binop.expr
>>> while right.children:
...     right = right.children[-1]
>>> right
<Literal 4>

>>> from d20 import utils  # these patterns are available in the utils submodule:
>>> utils.leftmost(binop.expr)
<Literal 1>
>>> utils.rightmost(binop.expr)
<Literal 4>

Searching for the d4:

>>> from d20 import roll, Dice, SimpleStringifier, utils

>>> mixed = roll("-1d8 + 4 - (3, 1d4)kh1")
>>> str(mixed)
'-1d8 (**8**) + 4 - (3, ~~1d4 (3)~~)kh1 = `-7`'
>>> root = mixed.expr
>>> result = utils.dfs(root, lambda node: isinstance(node, Dice) and node.num == 1 and node.size == 4)
>>> result
<Dice num=1 size=4 values=[<Die size=4 values=[<Literal 3>]>] operations=[]>
>>> SimpleStringifier().stringify(result)
'1d4 (3)'

As a note, even though a Dice object is the parent of Die objects, Dice.children returns an empty list, since it's more common to look for the dice, and not each individual component of that dice.

Performance

By default, the parser caches the 256 most frequently used dice expressions in an LFU cache, allowing for a significant speedup when rolling many of the same kinds of rolls. This caching is disabled when allow_comments is True.

With caching:

$ python3 -m timeit -s "from d20 import roll" "roll('1d20')"
10000 loops, best of 5: 21.6 usec per loop
$ python3 -m timeit -s "from d20 import roll" "roll('100d20')"
500 loops, best of 5: 572 usec per loop
$ python3 -m timeit -s "from d20 import roll; expr='1d20+'*50+'1d20'" "roll(expr)"
500 loops, best of 5: 732 usec per loop
$ python3 -m timeit -s "from d20 import roll" "roll('10d20rr<20')"
1000 loops, best of 5: 1.13 msec per loop

Without caching:

$ python3 -m timeit -s "from d20 import roll" "roll('1d20')"
5000 loops, best of 5: 61.6 usec per loop
$ python3 -m timeit -s "from d20 import roll" "roll('100d20')"
500 loops, best of 5: 620 usec per loop
$ python3 -m timeit -s "from d20 import roll; expr='1d20+'*50+'1d20'" "roll(expr)"
500 loops, best of 5: 2.1 msec per loop
$ python3 -m timeit -s "from d20 import roll" "roll('10d20rr<20')"
1000 loops, best of 5: 1.26 msec per loop
Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].