dice-calc


Namedice-calc JSON
Version 0.3.4 PyPI version JSON
download
home_pageNone
SummaryAdvanced Calculator for Dice
upload_time2024-11-10 17:24:02
maintainerAr-Kareem
docs_urlNone
authorAr-Kareem
requires_python>=3.9.0
licenseMIT License Copyright (c) 2024 Ar-Kareem Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
keywords dice calculator probability statistics math dice-notation dice-roller dice-roller-simulator dice-notation-parser dice-notation-compiler dice-notation-interpreter dice-notation-evaluator dice-notation-calculator dice-notation-solver dice-notation-simulator dice-notation-optimizer anydice anydice-compiler anydice-interpreter anydice-evaluator anydice-calculator anydice-solver anydice-simulator python-dice
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # PythonDice

Try it online: https://ar-kareem.github.io/PythonDiceWeb/

This is a python package that includes a simple to use and powerful dice probability engine. 

This package offers features and capabilities to calculate probabilities of arbitrary dice scripts including full capabilities of `anydice.com`. 

Additionaly, this package includes the first* custom built compiler that translates any** valid `anydice.com` code into runnable python code.

This project is still in early development but everything mentioned above is complete. (project started October 2024)

\* as far as we know

\*\* except very rare edge cases (e.g. using numbers >$2^{53}$ causing `anydice` to potentially perform incorrect/different calculations) mentioned at the end for transparency.

# Features

1. Ability to compile and run any `anydice` script **directly and locally in python** with a single function call.
   * Of course, this means that all operations and functions that are available in `anydice` are available in this package.

2. Ability to write scripts that combine the complexity of the `anydice` language with the power of Python (built-in libraries such as `itertools`, custom classes, etc.).

3. Very light-weight pure Python implementation with zero dependencies. Using this package requires no other modules. 

4. Package is actively maintained (as of October 2024) and we welcome all new issues/questions/feedback/suggestions

# Installation

`pip install dice-calc`

This package has no dependencies. Requires `Python >= 3.9`

# Basic Usage

If you are familiar with the `anydice.com` language then you should skip to section `compiling anydice.com code` which automatically converts the provided `anydice` code to this packages code thus teaching you all the nuances of this package.

The examples below are **not comprehensive at all**. They just show very basic use cases of our package.

### Example #1: 
Let's roll a single d20 dice (a d20 is a regular twenty sided-die)


```python
from dice_calc import roll, output
X = roll(20)
output(X)
```

    10.5 ± 5.77
     1:  5.00  ████
     2:  5.00  ████
     3:  5.00  ████
     4:  5.00  ████
     5:  5.00  ████
     6:  5.00  ████
     7:  5.00  ████
     8:  5.00  ████
     9:  5.00  ████
    10:  5.00  ████
    11:  5.00  ████
    12:  5.00  ████
    13:  5.00  ████
    14:  5.00  ████
    15:  5.00  ████
    16:  5.00  ████
    17:  5.00  ████
    18:  5.00  ████
    19:  5.00  ████
    20:  5.00  ████
    ----------------------------------------------------------------------------------------
    

The numbers on the first line are the mean and std.

### Example #2
Let's roll two d6 dice (a d6 is a regular six sided-die)


```python
from dice_calc import roll, output
X = roll(2, 6)  # or roll(6) + roll(6)
output(X)
```

    7.0 ± 2.42
     2:  2.78  ██
     3:  5.56  ████
     4:  8.33  ██████
     5: 11.11  █████████
     6: 13.89  ███████████
     7: 16.67  █████████████
     8: 13.89  ███████████
     9: 11.11  █████████
    10:  8.33  ██████
    11:  5.56  ████
    12:  2.78  ██
    ----------------------------------------------------------------------------------------
    

### Example #3

What about rolling a D20 plus a D6?


```python
from dice_calc import roll, output
X = roll(20) + roll(6)
output(X)
```

    14.0 ± 6.01
     2:  0.83  █
     3:  1.67  █
     4:  2.50  ██
     5:  3.33  ███
     6:  4.17  ███
     7:  5.00  ████
     8:  5.00  ████
     9:  5.00  ████
    10:  5.00  ████
    11:  5.00  ████
    12:  5.00  ████
    13:  5.00  ████
    14:  5.00  ████
    15:  5.00  ████
    16:  5.00  ████
    17:  5.00  ████
    18:  5.00  ████
    19:  5.00  ████
    20:  5.00  ████
    21:  5.00  ████
    22:  4.17  ███
    23:  3.33  ███
    24:  2.50  ██
    25:  1.67  █
    26:  0.83  █
    ----------------------------------------------------------------------------------------
    

### Example #4
What about the probability distribution of rolling two D20's with advantage (i.e. rolling two D20's and taking the highest number)


```python
from dice_calc import roll, output
X = 1 @ roll(2, 20)
output(X)
```

    13.82 ± 4.71
     1:  0.25  
     2:  0.75  █
     3:  1.25  █
     4:  1.75  █
     5:  2.25  ██
     6:  2.75  ██
     7:  3.25  ███
     8:  3.75  ███
     9:  4.25  ███
    10:  4.75  ████
    11:  5.25  ████
    12:  5.75  ████
    13:  6.25  █████
    14:  6.75  █████
    15:  7.25  ██████
    16:  7.75  ██████
    17:  8.25  ██████
    18:  8.75  ███████
    19:  9.25  ███████
    20:  9.75  ████████
    ----------------------------------------------------------------------------------------
    

### Example #5
Let's try a slightly more complex example. 

Rolling a D20 with advantage + two D6's + 5


```python
from dice_calc import roll, output
D20_adv = 1 @ roll(2, 20)
two_D6 = roll(2, 6)
result = D20_adv + two_D6 + 5
output(result)
```

    25.82 ± 5.29
     8:  0.01  
     9:  0.03  
    10:  0.10  
    11:  0.21  
    12:  0.38  
    13:  0.63  
    14:  0.96  █
    15:  1.35  █
    16:  1.78  █
    17:  2.26  ██
    18:  2.75  ██
    19:  3.25  ███
    20:  3.75  ███
    21:  4.25  ███
    22:  4.75  ████
    23:  5.25  ████
    24:  5.75  ████
    25:  6.25  █████
    26:  6.75  █████
    27:  7.25  ██████
    28:  7.47  ██████
    29:  7.38  ██████
    30:  6.99  █████
    31:  6.26  █████
    32:  5.20  ████
    33:  3.78  ███
    34:  2.57  ██
    35:  1.57  █
    36:  0.80  █
    37:  0.27  
    ----------------------------------------------------------------------------------------
    

### getting evaluated results (roller)

Any RV object (dice / random variable) can be rolled using the `rolled` function.


```python
from dice_calc import roll, roller
X = 1 @ roll(2, 20)  # rolls a d20 with advantage
roller(X)  # gets a random value from rolling a d20 with advantage
```




    14



### Complex Example:

Let's try calculating the total damage of the following attack on a boss in an RPG: 
- TO HIT: 1d20 + 7 against 22 AC (less than 22 is a miss. rolling a 20 is a CRITICAL so double the damage die)
- DAMAGE: 2d8 + 4 blunt damange 
- \+ 1d4 thunder damage
- \+ 1d10 + 3 radiant damange (half damage if the target succeeds a 16 DC wisdom saving throw, boss has +5 wis saving throw)


```python
from dice_calc import roll, anydice_casting, T_N, T_S, T_D

# In anydice we have (:N, :S, and :D) which are (T_N, T_S, and T_D) in here
# read: https://anydice.com/docs/functions for more information
@anydice_casting()
def calculate(to_hit_roll: T_N, save_roll: T_N):  # type hinting as T_N REQUIRED!!!
    if to_hit_roll + 7 < 22:  # miss
        return 0
    is_crit = (to_hit_roll == 20)
    dmg_die_mult = 2 if is_crit else 1
    blung_dmg = roll(2 * dmg_die_mult, 8) + 4
    thund_dmg = roll(1 * dmg_die_mult, 4)
    radiant_dmg = roll(1 * dmg_die_mult, 10) + 3
    if save_roll + 5 >= 16:  # save success
        radiant_dmg = radiant_dmg // 2
    return blung_dmg + thund_dmg + radiant_dmg


X = calculate(roll(20), roll(20))

# plotting code
from matplotlib import pyplot as plt
vals, probs = zip(*X.get_vals_probs())
plt.bar(vals, probs); plt.xlabel('Damage'); plt.ylabel('Probability');
```


    
![png](./README_files/./README_18_0.png)
    


Notice how we used the decorator `@anydice_casting` to use `if` conditions on dice inside of a custom function. Typehinting the input to `int` is required, the engine knows that you want to calculate the function many times based on all possible combinations of the input random variable.

The three valid typehints that the decorator `@anydice_casting` looks for are `: int`, `: Seq`, `: RV` which are equivelant to the 3 types `:n`, `:s`, and `:d` respectively in `anydice`. The casting done by `@anydice_casting` is exactly how casting is done in the `anydice` language. For more info on that please read the [documentation `functions -> Parameter types` in the `anydice` docs](https://anydice.com/docs/functions/) .

Note: `Seq` and `RV` are imported from `dice_calc.randvar`

# Getting probabilities as dict 


```python
from dice_calc import roll
X = 1 @ roll(2, 20)  # D20 with advantage
pdf = dict(X.get_vals_probs())
print(pdf)
```

    {1: 0.0025, 2: 0.0075, 3: 0.0125, 4: 0.0175, 5: 0.0225, 6: 0.0275, 7: 0.0325, 8: 0.0375, 9: 0.0425, 10: 0.0475, 11: 0.0525, 12: 0.0575, 13: 0.0625, 14: 0.0675, 15: 0.0725, 16: 0.0775, 17: 0.0825, 18: 0.0875, 19: 0.0925, 20: 0.0975}
    

## plotting using matploblib



```python
from dice_calc import roll
X = 1 @ roll(2, 20) + 8  # D20 with advantage + 8

# plotting code
from matplotlib import pyplot as plt
vals, probs = zip(*X.get_vals_probs())
percent = [p * 100 for p in probs]
plt.bar(vals, percent); plt.xlabel('Roll'); plt.ylabel('Probability %');
```


    
![png](./README_files/./README_23_0.png)
    


# compiling anydice.com code

[anydice](anydice.com) is a powerful and popular online dice calculater which inspired the creation of this package. Any* valid code from anydice can be converted to valid python code using this package in a single function call.

Below we take an example piece of code from the anydice articles [legend of the five rings](https://anydice.com/articles/legend-of-the-five-rings/).

We convert it using the function `compile_anydice` and then execute it using our package (p.s. don't forget to import all the library functions as we do below).


```python
from dice_calc.parser import compile_anydice

EXAMPLE_CODE = """
function: convert SUM:n {
 if SUM >= 1000 {
  TENSROLLED: SUM / 1000
  result: SUM - TENSROLLED * 990 + TENSROLLED d [explode d10]
 }
 result: SUM
}

output [convert [highest 3 of 6d{1..9, 1000}]] named "6k3 exploded after keeping"
"""

code = compile_anydice(EXAMPLE_CODE)
print(code)
```

    @max_func_depth()
    @anydice_casting()
    def convert_X(SUM: T_N):
      if SUM >= 1000:
        TENSROLLED = SUM // 1000
        return SUM - TENSROLLED * 990 + roll(TENSROLLED, explode_X(roll(10)))
      
      return SUM
    
    output(convert_X(highest_X_of_X(3, roll(6, get_seq([myrange(1, 9), 1000])))), named=f"6k3 exploded after keeping")
    
    


```python
# IMPORT EVERYTHING
from dice_calc import *

# EXECUTE CODE FROM COMPILE_ANYDICE
@max_func_depth()
@anydice_casting()
def convert_X(SUM: T_N):
  if SUM >= 1000:
    TENSROLLED = SUM // 1000
    return SUM - TENSROLLED * 990 + roll(TENSROLLED, explode_X(roll(10)))
  
  return SUM

output(convert_X(highest_X_of_X(3, roll(6, Seq([myrange(1, 9), 1000])))), named=f"6k3 exploded after keeping")

```

    6k3 exploded after keeping 26.53 ± 8.32
      3:  0.00  
      4:  0.00  
      5:  0.00  
      6:  0.01  
      7:  0.02  
      8:  0.04  
      9:  0.09  
     10:  0.17  
     11:  0.29  
     12:  0.48  
     13:  0.76  █
     14:  1.12  █
     15:  1.62  █
     16:  2.22  ██
     17:  2.92  ██
     18:  3.71  ███
     19:  4.55  ████
     20:  5.31  ████
     21:  6.00  █████
     22:  6.40  █████
     23:  6.51  █████
     24:  6.20  █████
     25:  5.61  ████
     26:  4.65  ████
     27:  3.78  ███
     28:  3.14  ██
     29:  3.45  ███
     30:  3.40  ███
     31:  3.32  ███
     32:  3.16  ██
     33:  2.93  ██
     34:  2.60  ██
     35:  2.21  ██
     36:  1.78  █
    ... output cropped ...
    

Or you can do it all in one line


```python
from dice_calc.parser import compile_anydice, _get_lib

EXAMPLE_CODE = """
function: convert SUM:n {
 if SUM >= 1000 {
  TENSROLLED: SUM / 1000
  result: SUM - TENSROLLED * 990 + TENSROLLED d [explode d10]
 }
 result: SUM
}

output [convert [highest 3 of 6d{1..9, 1000}]] named "6k3 exploded after keeping"
"""

exec(compile_anydice(EXAMPLE_CODE), _get_lib())
```

    6k3 exploded after keeping 26.53 ± 8.32
      3:  0.00  
      4:  0.00  
      5:  0.00  
      6:  0.01  
      7:  0.02  
      8:  0.04  
      9:  0.09  
     10:  0.17  
     11:  0.29  
     12:  0.48  
     13:  0.76  █
     14:  1.12  █
     15:  1.62  █
     16:  2.22  ██
     17:  2.92  ██
     18:  3.71  ███
     19:  4.55  ████
     20:  5.31  ████
     21:  6.00  █████
     22:  6.40  █████
     23:  6.51  █████
     24:  6.20  █████
     25:  5.61  ████
     26:  4.65  ████
     27:  3.78  ███
     28:  3.14  ██
     29:  3.45  ███
     30:  3.40  ███
     31:  3.32  ███
     32:  3.16  ██
     33:  2.93  ██
     34:  2.60  ██
     35:  2.21  ██
     36:  1.78  █
    ... output cropped ...
    

Note that calls to the built-in python function `exec` executes arbitrary code and could be dangerous if malicious code is run. Only run `exec` on code you trust.

`compile_anydice` was developed to only generate safe code, but no garuntees are made.

## compile_anydice edge cases

The `compile_anydice` function was a large part of this project. Under the hood it is a custom compiler built using Python's implementation of `lex` and `yacc` provided by [`PLY (Python Lex-Yacc)`](https://github.com/dabeaz/ply).

As far as we tested, almost all valid `anydice` code worked perfectly using our compiler, except for extremely rare/intentionally ignored subsets of `anydice` code mentioned below:


1. ~~certain operators on ints and nothing else.~~ **Update : #1 has been correctly implemented** (as an optional compiler flag)

2. ~~Limit on global function depth.~~ **Update : #2 has been correctly implemented**

3. ~~(very rare) Naming a fucntion as an illegal reserved keywords~~ **Update : #3 been correctly implemented ; automatically turns on when a collision is detected by the compiler**

4. **(very rare) MAX_INT is higher in python than in JavaScript**: This is a very rare issue when a number is larger than $2^{53}$. See example we made here: https://anydice.com/program/39567

Note: that in certain cases our **`compile_anydice` code runs while `anydice` crashes**. This is because our compiler allows certain pieces of code (like defining functions within functions or outputs inside of functions) to execute perfectly while `anydice` crashes for the same input. We do not consider this as a failiure since our goal is to make any code that runs on `anydice` to also run using our package, the reverse of that is of no concern. In fact, the descrepancy is considered a positive for us as it means we handle more pieces of code than `anydice`.

If you discover any code that behaves differently when run on `anydice.com` then please report it to us as an issue so we can keep improving this package.

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "dice-calc",
    "maintainer": "Ar-Kareem",
    "docs_url": null,
    "requires_python": ">=3.9.0",
    "maintainer_email": null,
    "keywords": "dice, calculator, probability, statistics, math, dice-notation, dice-roller, dice-roller-simulator, dice-notation-parser, dice-notation-compiler, dice-notation-interpreter, dice-notation-evaluator, dice-notation-calculator, dice-notation-solver, dice-notation-simulator, dice-notation-optimizer, anydice, anydice-compiler, anydice-interpreter, anydice-evaluator, anydice-calculator, anydice-solver, anydice-simulator, python-dice",
    "author": "Ar-Kareem",
    "author_email": null,
    "download_url": "https://files.pythonhosted.org/packages/f9/dc/2d3aa00606bb8fc573804045de2803cdc298594f81545814bfb06029cf8b/dice_calc-0.3.4.tar.gz",
    "platform": null,
    "description": "# PythonDice\n\nTry it online: https://ar-kareem.github.io/PythonDiceWeb/\n\nThis is a python package that includes a simple to use and powerful dice probability engine. \n\nThis package offers features and capabilities to calculate probabilities of arbitrary dice scripts including full capabilities of `anydice.com`. \n\nAdditionaly, this package includes the first* custom built compiler that translates any** valid `anydice.com` code into runnable python code.\n\nThis project is still in early development but everything mentioned above is complete. (project started October 2024)\n\n\\* as far as we know\n\n\\*\\* except very rare edge cases (e.g. using numbers >$2^{53}$ causing `anydice` to potentially perform incorrect/different calculations) mentioned at the end for transparency.\n\n# Features\n\n1. Ability to compile and run any `anydice` script **directly and locally in python** with a single function call.\n   * Of course, this means that all operations and functions that are available in `anydice` are available in this package.\n\n2. Ability to write scripts that combine the complexity of the `anydice` language with the power of Python (built-in libraries such as `itertools`, custom classes, etc.).\n\n3. Very light-weight pure Python implementation with zero dependencies. Using this package requires no other modules. \n\n4. Package is actively maintained (as of October 2024) and we welcome all new issues/questions/feedback/suggestions\n\n# Installation\n\n`pip install dice-calc`\n\nThis package has no dependencies. Requires `Python >= 3.9`\n\n# Basic Usage\n\nIf you are familiar with the `anydice.com` language then you should skip to section `compiling anydice.com code` which automatically converts the provided `anydice` code to this packages code thus teaching you all the nuances of this package.\n\nThe examples below are **not comprehensive at all**. They just show very basic use cases of our package.\n\n### Example #1: \nLet's roll a single d20 dice (a d20 is a regular twenty sided-die)\n\n\n```python\nfrom dice_calc import roll, output\nX = roll(20)\noutput(X)\n```\n\n    10.5 \u00b1 5.77\n     1:  5.00  \u2588\u2588\u2588\u2588\n     2:  5.00  \u2588\u2588\u2588\u2588\n     3:  5.00  \u2588\u2588\u2588\u2588\n     4:  5.00  \u2588\u2588\u2588\u2588\n     5:  5.00  \u2588\u2588\u2588\u2588\n     6:  5.00  \u2588\u2588\u2588\u2588\n     7:  5.00  \u2588\u2588\u2588\u2588\n     8:  5.00  \u2588\u2588\u2588\u2588\n     9:  5.00  \u2588\u2588\u2588\u2588\n    10:  5.00  \u2588\u2588\u2588\u2588\n    11:  5.00  \u2588\u2588\u2588\u2588\n    12:  5.00  \u2588\u2588\u2588\u2588\n    13:  5.00  \u2588\u2588\u2588\u2588\n    14:  5.00  \u2588\u2588\u2588\u2588\n    15:  5.00  \u2588\u2588\u2588\u2588\n    16:  5.00  \u2588\u2588\u2588\u2588\n    17:  5.00  \u2588\u2588\u2588\u2588\n    18:  5.00  \u2588\u2588\u2588\u2588\n    19:  5.00  \u2588\u2588\u2588\u2588\n    20:  5.00  \u2588\u2588\u2588\u2588\n    ----------------------------------------------------------------------------------------\n    \n\nThe numbers on the first line are the mean and std.\n\n### Example #2\nLet's roll two d6 dice (a d6 is a regular six sided-die)\n\n\n```python\nfrom dice_calc import roll, output\nX = roll(2, 6)  # or roll(6) + roll(6)\noutput(X)\n```\n\n    7.0 \u00b1 2.42\n     2:  2.78  \u2588\u2588\n     3:  5.56  \u2588\u2588\u2588\u2588\n     4:  8.33  \u2588\u2588\u2588\u2588\u2588\u2588\n     5: 11.11  \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\n     6: 13.89  \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\n     7: 16.67  \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\n     8: 13.89  \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\n     9: 11.11  \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\n    10:  8.33  \u2588\u2588\u2588\u2588\u2588\u2588\n    11:  5.56  \u2588\u2588\u2588\u2588\n    12:  2.78  \u2588\u2588\n    ----------------------------------------------------------------------------------------\n    \n\n### Example #3\n\nWhat about rolling a D20 plus a D6?\n\n\n```python\nfrom dice_calc import roll, output\nX = roll(20) + roll(6)\noutput(X)\n```\n\n    14.0 \u00b1 6.01\n     2:  0.83  \u2588\n     3:  1.67  \u2588\n     4:  2.50  \u2588\u2588\n     5:  3.33  \u2588\u2588\u2588\n     6:  4.17  \u2588\u2588\u2588\n     7:  5.00  \u2588\u2588\u2588\u2588\n     8:  5.00  \u2588\u2588\u2588\u2588\n     9:  5.00  \u2588\u2588\u2588\u2588\n    10:  5.00  \u2588\u2588\u2588\u2588\n    11:  5.00  \u2588\u2588\u2588\u2588\n    12:  5.00  \u2588\u2588\u2588\u2588\n    13:  5.00  \u2588\u2588\u2588\u2588\n    14:  5.00  \u2588\u2588\u2588\u2588\n    15:  5.00  \u2588\u2588\u2588\u2588\n    16:  5.00  \u2588\u2588\u2588\u2588\n    17:  5.00  \u2588\u2588\u2588\u2588\n    18:  5.00  \u2588\u2588\u2588\u2588\n    19:  5.00  \u2588\u2588\u2588\u2588\n    20:  5.00  \u2588\u2588\u2588\u2588\n    21:  5.00  \u2588\u2588\u2588\u2588\n    22:  4.17  \u2588\u2588\u2588\n    23:  3.33  \u2588\u2588\u2588\n    24:  2.50  \u2588\u2588\n    25:  1.67  \u2588\n    26:  0.83  \u2588\n    ----------------------------------------------------------------------------------------\n    \n\n### Example #4\nWhat about the probability distribution of rolling two D20's with advantage (i.e. rolling two D20's and taking the highest number)\n\n\n```python\nfrom dice_calc import roll, output\nX = 1 @ roll(2, 20)\noutput(X)\n```\n\n    13.82 \u00b1 4.71\n     1:  0.25  \n     2:  0.75  \u2588\n     3:  1.25  \u2588\n     4:  1.75  \u2588\n     5:  2.25  \u2588\u2588\n     6:  2.75  \u2588\u2588\n     7:  3.25  \u2588\u2588\u2588\n     8:  3.75  \u2588\u2588\u2588\n     9:  4.25  \u2588\u2588\u2588\n    10:  4.75  \u2588\u2588\u2588\u2588\n    11:  5.25  \u2588\u2588\u2588\u2588\n    12:  5.75  \u2588\u2588\u2588\u2588\n    13:  6.25  \u2588\u2588\u2588\u2588\u2588\n    14:  6.75  \u2588\u2588\u2588\u2588\u2588\n    15:  7.25  \u2588\u2588\u2588\u2588\u2588\u2588\n    16:  7.75  \u2588\u2588\u2588\u2588\u2588\u2588\n    17:  8.25  \u2588\u2588\u2588\u2588\u2588\u2588\n    18:  8.75  \u2588\u2588\u2588\u2588\u2588\u2588\u2588\n    19:  9.25  \u2588\u2588\u2588\u2588\u2588\u2588\u2588\n    20:  9.75  \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\n    ----------------------------------------------------------------------------------------\n    \n\n### Example #5\nLet's try a slightly more complex example. \n\nRolling a D20 with advantage + two D6's + 5\n\n\n```python\nfrom dice_calc import roll, output\nD20_adv = 1 @ roll(2, 20)\ntwo_D6 = roll(2, 6)\nresult = D20_adv + two_D6 + 5\noutput(result)\n```\n\n    25.82 \u00b1 5.29\n     8:  0.01  \n     9:  0.03  \n    10:  0.10  \n    11:  0.21  \n    12:  0.38  \n    13:  0.63  \n    14:  0.96  \u2588\n    15:  1.35  \u2588\n    16:  1.78  \u2588\n    17:  2.26  \u2588\u2588\n    18:  2.75  \u2588\u2588\n    19:  3.25  \u2588\u2588\u2588\n    20:  3.75  \u2588\u2588\u2588\n    21:  4.25  \u2588\u2588\u2588\n    22:  4.75  \u2588\u2588\u2588\u2588\n    23:  5.25  \u2588\u2588\u2588\u2588\n    24:  5.75  \u2588\u2588\u2588\u2588\n    25:  6.25  \u2588\u2588\u2588\u2588\u2588\n    26:  6.75  \u2588\u2588\u2588\u2588\u2588\n    27:  7.25  \u2588\u2588\u2588\u2588\u2588\u2588\n    28:  7.47  \u2588\u2588\u2588\u2588\u2588\u2588\n    29:  7.38  \u2588\u2588\u2588\u2588\u2588\u2588\n    30:  6.99  \u2588\u2588\u2588\u2588\u2588\n    31:  6.26  \u2588\u2588\u2588\u2588\u2588\n    32:  5.20  \u2588\u2588\u2588\u2588\n    33:  3.78  \u2588\u2588\u2588\n    34:  2.57  \u2588\u2588\n    35:  1.57  \u2588\n    36:  0.80  \u2588\n    37:  0.27  \n    ----------------------------------------------------------------------------------------\n    \n\n### getting evaluated results (roller)\n\nAny RV object (dice / random variable) can be rolled using the `rolled` function.\n\n\n```python\nfrom dice_calc import roll, roller\nX = 1 @ roll(2, 20)  # rolls a d20 with advantage\nroller(X)  # gets a random value from rolling a d20 with advantage\n```\n\n\n\n\n    14\n\n\n\n### Complex Example:\n\nLet's try calculating the total damage of the following attack on a boss in an RPG: \n- TO HIT: 1d20 + 7 against 22 AC (less than 22 is a miss. rolling a 20 is a CRITICAL so double the damage die)\n- DAMAGE: 2d8 + 4 blunt damange \n- \\+ 1d4 thunder damage\n- \\+ 1d10 + 3 radiant damange (half damage if the target succeeds a 16 DC wisdom saving throw, boss has +5 wis saving throw)\n\n\n```python\nfrom dice_calc import roll, anydice_casting, T_N, T_S, T_D\n\n# In anydice we have (:N, :S, and :D) which are (T_N, T_S, and T_D) in here\n# read: https://anydice.com/docs/functions for more information\n@anydice_casting()\ndef calculate(to_hit_roll: T_N, save_roll: T_N):  # type hinting as T_N REQUIRED!!!\n    if to_hit_roll + 7 < 22:  # miss\n        return 0\n    is_crit = (to_hit_roll == 20)\n    dmg_die_mult = 2 if is_crit else 1\n    blung_dmg = roll(2 * dmg_die_mult, 8) + 4\n    thund_dmg = roll(1 * dmg_die_mult, 4)\n    radiant_dmg = roll(1 * dmg_die_mult, 10) + 3\n    if save_roll + 5 >= 16:  # save success\n        radiant_dmg = radiant_dmg // 2\n    return blung_dmg + thund_dmg + radiant_dmg\n\n\nX = calculate(roll(20), roll(20))\n\n# plotting code\nfrom matplotlib import pyplot as plt\nvals, probs = zip(*X.get_vals_probs())\nplt.bar(vals, probs); plt.xlabel('Damage'); plt.ylabel('Probability');\n```\n\n\n    \n![png](./README_files/./README_18_0.png)\n    \n\n\nNotice how we used the decorator `@anydice_casting` to use `if` conditions on dice inside of a custom function. Typehinting the input to `int` is required, the engine knows that you want to calculate the function many times based on all possible combinations of the input random variable.\n\nThe three valid typehints that the decorator `@anydice_casting` looks for are `: int`, `: Seq`, `: RV` which are equivelant to the 3 types `:n`, `:s`, and `:d` respectively in `anydice`. The casting done by `@anydice_casting` is exactly how casting is done in the `anydice` language. For more info on that please read the [documentation `functions -> Parameter types` in the `anydice` docs](https://anydice.com/docs/functions/) .\n\nNote: `Seq` and `RV` are imported from `dice_calc.randvar`\n\n# Getting probabilities as dict \n\n\n```python\nfrom dice_calc import roll\nX = 1 @ roll(2, 20)  # D20 with advantage\npdf = dict(X.get_vals_probs())\nprint(pdf)\n```\n\n    {1: 0.0025, 2: 0.0075, 3: 0.0125, 4: 0.0175, 5: 0.0225, 6: 0.0275, 7: 0.0325, 8: 0.0375, 9: 0.0425, 10: 0.0475, 11: 0.0525, 12: 0.0575, 13: 0.0625, 14: 0.0675, 15: 0.0725, 16: 0.0775, 17: 0.0825, 18: 0.0875, 19: 0.0925, 20: 0.0975}\n    \n\n## plotting using matploblib\n\n\n\n```python\nfrom dice_calc import roll\nX = 1 @ roll(2, 20) + 8  # D20 with advantage + 8\n\n# plotting code\nfrom matplotlib import pyplot as plt\nvals, probs = zip(*X.get_vals_probs())\npercent = [p * 100 for p in probs]\nplt.bar(vals, percent); plt.xlabel('Roll'); plt.ylabel('Probability %');\n```\n\n\n    \n![png](./README_files/./README_23_0.png)\n    \n\n\n# compiling anydice.com code\n\n[anydice](anydice.com) is a powerful and popular online dice calculater which inspired the creation of this package. Any* valid code from anydice can be converted to valid python code using this package in a single function call.\n\nBelow we take an example piece of code from the anydice articles [legend of the five rings](https://anydice.com/articles/legend-of-the-five-rings/).\n\nWe convert it using the function `compile_anydice` and then execute it using our package (p.s. don't forget to import all the library functions as we do below).\n\n\n```python\nfrom dice_calc.parser import compile_anydice\n\nEXAMPLE_CODE = \"\"\"\nfunction: convert SUM:n {\n if SUM >= 1000 {\n  TENSROLLED: SUM / 1000\n  result: SUM - TENSROLLED * 990 + TENSROLLED d [explode d10]\n }\n result: SUM\n}\n\noutput [convert [highest 3 of 6d{1..9, 1000}]] named \"6k3 exploded after keeping\"\n\"\"\"\n\ncode = compile_anydice(EXAMPLE_CODE)\nprint(code)\n```\n\n    @max_func_depth()\n    @anydice_casting()\n    def convert_X(SUM: T_N):\n      if SUM >= 1000:\n        TENSROLLED = SUM // 1000\n        return SUM - TENSROLLED * 990 + roll(TENSROLLED, explode_X(roll(10)))\n      \n      return SUM\n    \n    output(convert_X(highest_X_of_X(3, roll(6, get_seq([myrange(1, 9), 1000])))), named=f\"6k3 exploded after keeping\")\n    \n    \n\n\n```python\n# IMPORT EVERYTHING\nfrom dice_calc import *\n\n# EXECUTE CODE FROM COMPILE_ANYDICE\n@max_func_depth()\n@anydice_casting()\ndef convert_X(SUM: T_N):\n  if SUM >= 1000:\n    TENSROLLED = SUM // 1000\n    return SUM - TENSROLLED * 990 + roll(TENSROLLED, explode_X(roll(10)))\n  \n  return SUM\n\noutput(convert_X(highest_X_of_X(3, roll(6, Seq([myrange(1, 9), 1000])))), named=f\"6k3 exploded after keeping\")\n\n```\n\n    6k3 exploded after keeping 26.53 \u00b1 8.32\n      3:  0.00  \n      4:  0.00  \n      5:  0.00  \n      6:  0.01  \n      7:  0.02  \n      8:  0.04  \n      9:  0.09  \n     10:  0.17  \n     11:  0.29  \n     12:  0.48  \n     13:  0.76  \u2588\n     14:  1.12  \u2588\n     15:  1.62  \u2588\n     16:  2.22  \u2588\u2588\n     17:  2.92  \u2588\u2588\n     18:  3.71  \u2588\u2588\u2588\n     19:  4.55  \u2588\u2588\u2588\u2588\n     20:  5.31  \u2588\u2588\u2588\u2588\n     21:  6.00  \u2588\u2588\u2588\u2588\u2588\n     22:  6.40  \u2588\u2588\u2588\u2588\u2588\n     23:  6.51  \u2588\u2588\u2588\u2588\u2588\n     24:  6.20  \u2588\u2588\u2588\u2588\u2588\n     25:  5.61  \u2588\u2588\u2588\u2588\n     26:  4.65  \u2588\u2588\u2588\u2588\n     27:  3.78  \u2588\u2588\u2588\n     28:  3.14  \u2588\u2588\n     29:  3.45  \u2588\u2588\u2588\n     30:  3.40  \u2588\u2588\u2588\n     31:  3.32  \u2588\u2588\u2588\n     32:  3.16  \u2588\u2588\n     33:  2.93  \u2588\u2588\n     34:  2.60  \u2588\u2588\n     35:  2.21  \u2588\u2588\n     36:  1.78  \u2588\n    ... output cropped ...\n    \n\nOr you can do it all in one line\n\n\n```python\nfrom dice_calc.parser import compile_anydice, _get_lib\n\nEXAMPLE_CODE = \"\"\"\nfunction: convert SUM:n {\n if SUM >= 1000 {\n  TENSROLLED: SUM / 1000\n  result: SUM - TENSROLLED * 990 + TENSROLLED d [explode d10]\n }\n result: SUM\n}\n\noutput [convert [highest 3 of 6d{1..9, 1000}]] named \"6k3 exploded after keeping\"\n\"\"\"\n\nexec(compile_anydice(EXAMPLE_CODE), _get_lib())\n```\n\n    6k3 exploded after keeping 26.53 \u00b1 8.32\n      3:  0.00  \n      4:  0.00  \n      5:  0.00  \n      6:  0.01  \n      7:  0.02  \n      8:  0.04  \n      9:  0.09  \n     10:  0.17  \n     11:  0.29  \n     12:  0.48  \n     13:  0.76  \u2588\n     14:  1.12  \u2588\n     15:  1.62  \u2588\n     16:  2.22  \u2588\u2588\n     17:  2.92  \u2588\u2588\n     18:  3.71  \u2588\u2588\u2588\n     19:  4.55  \u2588\u2588\u2588\u2588\n     20:  5.31  \u2588\u2588\u2588\u2588\n     21:  6.00  \u2588\u2588\u2588\u2588\u2588\n     22:  6.40  \u2588\u2588\u2588\u2588\u2588\n     23:  6.51  \u2588\u2588\u2588\u2588\u2588\n     24:  6.20  \u2588\u2588\u2588\u2588\u2588\n     25:  5.61  \u2588\u2588\u2588\u2588\n     26:  4.65  \u2588\u2588\u2588\u2588\n     27:  3.78  \u2588\u2588\u2588\n     28:  3.14  \u2588\u2588\n     29:  3.45  \u2588\u2588\u2588\n     30:  3.40  \u2588\u2588\u2588\n     31:  3.32  \u2588\u2588\u2588\n     32:  3.16  \u2588\u2588\n     33:  2.93  \u2588\u2588\n     34:  2.60  \u2588\u2588\n     35:  2.21  \u2588\u2588\n     36:  1.78  \u2588\n    ... output cropped ...\n    \n\nNote that calls to the built-in python function `exec` executes arbitrary code and could be dangerous if malicious code is run. Only run `exec` on code you trust.\n\n`compile_anydice` was developed to only generate safe code, but no garuntees are made.\n\n## compile_anydice edge cases\n\nThe `compile_anydice` function was a large part of this project. Under the hood it is a custom compiler built using Python's implementation of `lex` and `yacc` provided by [`PLY (Python Lex-Yacc)`](https://github.com/dabeaz/ply).\n\nAs far as we tested, almost all valid `anydice` code worked perfectly using our compiler, except for extremely rare/intentionally ignored subsets of `anydice` code mentioned below:\n\n\n1. ~~certain operators on ints and nothing else.~~ **Update : #1 has been correctly implemented** (as an optional compiler flag)\n\n2. ~~Limit on global function depth.~~ **Update : #2 has been correctly implemented**\n\n3. ~~(very rare) Naming a fucntion as an illegal reserved keywords~~ **Update : #3 been correctly implemented ; automatically turns on when a collision is detected by the compiler**\n\n4. **(very rare) MAX_INT is higher in python than in JavaScript**: This is a very rare issue when a number is larger than $2^{53}$. See example we made here: https://anydice.com/program/39567\n\nNote: that in certain cases our **`compile_anydice` code runs while `anydice` crashes**. This is because our compiler allows certain pieces of code (like defining functions within functions or outputs inside of functions) to execute perfectly while `anydice` crashes for the same input. We do not consider this as a failiure since our goal is to make any code that runs on `anydice` to also run using our package, the reverse of that is of no concern. In fact, the descrepancy is considered a positive for us as it means we handle more pieces of code than `anydice`.\n\nIf you discover any code that behaves differently when run on `anydice.com` then please report it to us as an issue so we can keep improving this package.\n",
    "bugtrack_url": null,
    "license": "MIT License  Copyright (c) 2024 Ar-Kareem  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.  THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ",
    "summary": "Advanced Calculator for Dice",
    "version": "0.3.4",
    "project_urls": {
        "Homepage": "https://github.com/Ar-Kareem/PythonDice/",
        "Issues": "https://github.com/Ar-Kareem/PythonDice/issues",
        "Repository": "https://github.com/Ar-Kareem/PythonDice/"
    },
    "split_keywords": [
        "dice",
        " calculator",
        " probability",
        " statistics",
        " math",
        " dice-notation",
        " dice-roller",
        " dice-roller-simulator",
        " dice-notation-parser",
        " dice-notation-compiler",
        " dice-notation-interpreter",
        " dice-notation-evaluator",
        " dice-notation-calculator",
        " dice-notation-solver",
        " dice-notation-simulator",
        " dice-notation-optimizer",
        " anydice",
        " anydice-compiler",
        " anydice-interpreter",
        " anydice-evaluator",
        " anydice-calculator",
        " anydice-solver",
        " anydice-simulator",
        " python-dice"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "4c1410a748dbb68d9174ccf653746bd1716a5a2bc2b3bd8ff2c6fa5c31b78cd5",
                "md5": "37e002bef9c7196cd227cb4eb310aa70",
                "sha256": "252a916028efc9108952b6f79378e80bb4ae3e383f89b5d3f8d76bdf04d00ff4"
            },
            "downloads": -1,
            "filename": "dice_calc-0.3.4-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "37e002bef9c7196cd227cb4eb310aa70",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.9.0",
            "size": 71687,
            "upload_time": "2024-11-10T17:24:01",
            "upload_time_iso_8601": "2024-11-10T17:24:01.325152Z",
            "url": "https://files.pythonhosted.org/packages/4c/14/10a748dbb68d9174ccf653746bd1716a5a2bc2b3bd8ff2c6fa5c31b78cd5/dice_calc-0.3.4-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "f9dc2d3aa00606bb8fc573804045de2803cdc298594f81545814bfb06029cf8b",
                "md5": "4c4f97bbfa13e6b85f8acae15b713e4e",
                "sha256": "784341a7adae37739946f20151d5e484aaa2b5b313b2cd9afb5b83a371750f23"
            },
            "downloads": -1,
            "filename": "dice_calc-0.3.4.tar.gz",
            "has_sig": false,
            "md5_digest": "4c4f97bbfa13e6b85f8acae15b713e4e",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.9.0",
            "size": 68126,
            "upload_time": "2024-11-10T17:24:02",
            "upload_time_iso_8601": "2024-11-10T17:24:02.707994Z",
            "url": "https://files.pythonhosted.org/packages/f9/dc/2d3aa00606bb8fc573804045de2803cdc298594f81545814bfb06029cf8b/dice_calc-0.3.4.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-11-10 17:24:02",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "Ar-Kareem",
    "github_project": "PythonDice",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "requirements": [],
    "lcname": "dice-calc"
}
        
Elapsed time: 1.48834s