Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
/d20Public

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

License

NotificationsYou must be signed in to change notification settings

avrae/d20

Repository files navigation

PyPI version shields.ioPyPI licensePyPI pyversionscodecovDocumentation 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 used20.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

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

Documentation

Check out the docs onRead 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.

NameSyntaxDescriptionExamples
literalINT,DECIMALA literal number.1,0.5,3.14
diceINT? "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 to3d6, but(3d6,) is the set containing the one element3d6.

Set Operations

These operations can be performed on dice and sets.

Grammar

NameSyntaxDescriptionExamples
set_opoperation selectorAn operation on a set (see below).kh3,ro<3
selectorseltype INTA 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.

SyntaxNameDescription
kkeepKeeps all matched values.
pdropDrops all matched values.
rrrerollRerolls all matched values until none match. (Dice only)
roreroll onceRerolls all matched values once. (Dice only)
rareroll and addRerolls up to one matched value once, keeping the original roll. (Dice only)
eexplode onRolls another die for each matched value. (Dice only)
miminimumSets the minimum value of each die. (Dice only)
mamaximumSets the maximum value of each die. (Dice only)

Selectors

Selectors select from the remaining kept values in a set.

SyntaxNameDescription
XliteralAll values in this set that are literally this value.
hXhighest XThe highest X values in the set.
lXlowest XThe lowest X values in the set.
>Xgreater than XAll values in this set greater than X.
<Xless than XAll values in this set less than X.

Unary Operations

SyntaxNameDescription
+XpositiveDoes nothing.
-XnegativeThe negative value of X.

Binary Operations

SyntaxName
X * Ymultiplication
X / Ydivision
X // Yint division
X % Ymodulo
X + Yaddition
X - Ysubtraction
X == Yequality
X >= Ygreater/equal
X <= Yless/equal
X > Ygreater than
X < Yless than
X != Yinequality

Examples

>>>fromd20importroll>>>r=roll("4d6kh3")# highest 3 of 4 6-sided dice>>>r.total14>>>str(r)'4d6kh3 (4, 4, **6**, ~~3~~) = `14`'>>>r=roll("2d6ro<3")# roll 2d6s, then reroll any 1s or 2s once>>>r.total9>>>str(r)'2d6ro<3 (**~~1~~**, 3, **6**) = `9`'>>>r=roll("8d6mi2")# roll 8d6s, with each die having a minimum roll of 2>>>r.total33>>>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.total3>>>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 ofd20.Stringifier(ord20.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 theroll() function!

>>>importd20>>>classMyStringifier(d20.SimpleStringifier):...def_stringify(self,node):...ifnotnode.kept:...return'X'...returnsuper()._stringify(node)......def_str_expression(self,node):...returnf"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,

>>>fromd20importroll>>>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, whenallow_comments=True is passed toroll(), the result of the roll may have a comment:

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

Note that whileallow_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 inExpressionobjects, which can be accessed as such:

>>>fromd20importroll>>>result=roll("3d6 + 1d4 + 3")>>>str(result)'3d6 (4, **6**, **6**) + 1d4 (**1**) + 3 = `20`'>>>result.expr<Expressionroll=<BinOpleft=<BinOpleft=<Dicenum=3size=6values=[<Diesize=6values=[<Literal4>]>,<Diesize=6values=[<Literal6>]>,<Diesize=6values=[<Literal6>]>]operations=[]>op=+right=<Dicenum=1size=4values=[<Diesize=4values=[<Literal1>]>]operations=[]>>op=+right=<Literal3>>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 whichmay 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:

>>>fromd20importroll>>>binop=roll("1 + 2 + 3 + 4")>>>left=binop.expr>>>whileleft.children:...left=left.children[0]>>>left<Literal1>>>>right=binop.expr>>>whileright.children:...right=right.children[-1]>>>right<Literal4>>>>fromd20importutils# these patterns are available in the utils submodule:>>>utils.leftmost(binop.expr)<Literal1>>>>utils.rightmost(binop.expr)<Literal4>

Searching for the d4:

>>>fromd20importroll,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,lambdanode:isinstance(node,Dice)andnode.num==1andnode.size==4)>>>result<Dicenum=1size=4values=[<Diesize=4values=[<Literal3>]>]operations=[]>>>>SimpleStringifier().stringify(result)'1d4 (3)'

As a note, even though aDice object is the parent ofDie objects,Dice.children returns an empty list, since it'smore 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 significantspeedup when rolling many of the same kinds of rolls. This caching is disabled whenallow_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

About

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

Topics

Resources

License

Code of conduct

Stars

Watchers

Forks

Packages

No packages published

Languages


[8]ページ先頭

©2009-2025 Movatter.jp