Skip to content

Latest commit

 

History

History
325 lines (238 loc) · 9.76 KB

the_dice.rst

File metadata and controls

325 lines (238 loc) · 9.76 KB

The Dice

A die class is a :pydicetables.eventsbases.protodie.ProtoDie, which is a subclass of :pydicetables.eventsbases.integerevents.IntegerEvents. It is a representation of die.

All dice require implementations of the following methods:

  • get_dict() : The representation of the die rolls as {roll: frequency}.

    >>> import dicetables as dt >>> dt.Die(3).get_dict() == {1: 1, 2: 1, 3: 1} True >>> dt.ModDie(3, -2).get_dict() == {-1: 1, 0: 1, 1: 1} True

  • get_size() : The size of the die. This can occasionally be non-intuitive.

    >>> die = dt.WeightedDie({1: 1, 2: 1, 3: 0}) >>> die.get_size() 3 >>> die.get_dict() == {1: 1, 2: 1} True

  • get_weight(): The total weight of all the die rolls. Used mainly in the __lt__ method to differentiate between dice of equal size.
  • weight_info(): A string detailing the rolls and their weights.
  • multiply_str(number) : The string representation for multiples of the die.

    >>> die = dt.ModDie(6, 1) >>> str(die) 'D6+1' >>> die.multiply_str(5) '5D6+5'

  • __str__()
  • __repr__()

Dice are immutable , hashable and rich-comparable. Multiple names can safely point to the same instance of a Die, they can be used in sets and dictionary keys and they can be sorted with any other kind of die. Comparisons are done by (size, weight, get_dict, __repr__(as a last resort)). So:

>>> dice_list = [ ... dt.ModDie(2, 0), ... dt.WeightedDie({1: 1, 2: 1}), ... dt.Die(2), ... dt.ModWeightedDie({1: 1, 2: 1}, 0), ... dt.StrongDie(dt.Die(2), 1), ... dt.StrongDie(dt.WeightedDie({1: 1, 2: 1}), 1) ... ] >>> [die.get_dict() == {1: 1, 2: 1} for die in dice_list] [True, True, True, True, True, True] >>> sorted(dice_list) [Die(2), ModDie(2, 0), StrongDie(Die(2), 1), ModWeightedDie({1: 1, 2: 1}, 0), StrongDie(WeightedDie({1: 1, 2: 1}), 1), WeightedDie({1: 1, 2: 1})] >>> [die == dt.Die(2) for die in sorted(dice_list)] [True, False, False, False, False, False] >>> my_set = {dt.Die(6)} >>> my_set.add(dt.Die(6)) >>> my_set == {dt.Die(6)} True >>> my_set.add(dt.ModDie(6, 0)) >>> my_set == {dt.Die(6), dt.ModDie(6, 0)} True

Top

Die Classes

dicetables.dieevents

Die

Base methods for all dice:

ModDie

It is 4-sided die with -1 added to each roll (D4-1)

added methods:

WeightedDie

dt.WeightedDie({1:1, 3:3, 4:6}) is a 4-sided die. It rolls 4 six times as often as 1, rolls 3 three times as often as 1 and never rolls 2

added methods:

get_raw_dict() returns something similar to the input dict with keys from 1 to die.get_size() even if they are zero. dt.WeightedDie({1: 1, 3: 3, 4: 6}).get_raw_dict() returns {1: 1, 2: 0, 3: 3, 4: 4}

ModWeightedDie

added methods:

>>> dt.WeightedDie({1: 1, 3: 3, 4: 6}).get_raw_dict() == {1: 1, 2: 0, 3: 3, 4: 6} True >>> dt.ModWeightedDie({1: 1, 3: 3, 4: 6}, -100).get_raw_dict() == {1: 1, 2: 0, 3: 3, 4: 6} True

StrongDie

>>> die = dt.Die(4) >>> die.get_dict() == {1: 1, 2: 1, 3: 1, 4: 1} True >>> dt.StrongDie(die, 5).get_dict() == {5: 1, 10: 1, 15: 1, 20: 1} True >>> example = dt.StrongDie(die, -2) >>> example.get_dict() == {-2: 1, -4: 1, -6: 1, -8: 1} True >>> example.get_input_die() == die True >>> example.get_multiplier() -2

added methods:

Modifier

>>> table = dt.DiceTable.new().add_die(dt.Die(4)) >>> table.get_dict() == {1: 1, 2: 1, 3: 1, 4: 1} True >>> table = table.add_die(dt.Modifier(3)) >>> print(table) +3 1D4 >>> table.get_dict() == {4: 1, 5: 1, 6: 1, 7: 1} True

added methods:

Exploding

Here are the rolls for an exploding D4 that can explode up to 3 times. It rolls 1-3 sixty-four times more often than 13-16.

>>> roll_values = dt.Exploding(dt.Die(4), explosions=3).get_dict() >>> sorted(roll_values.items()) [(1, 64), (2, 64), (3, 64), (5, 16), (6, 16), (7, 16), (9, 4), (10, 4), (11, 4), (13, 1), (14, 1), (15, 1), (16, 1)]

Any modifiers and multipliers are applied to each re-roll. Exploding D6+1 explodes on a 7. On a "7" it rolls 7 + (D6 + 1). On a "14", it rolls 14 + (D6 + 1).

Here are the rolls for an exploding D6+1 that can explode the default times.

>>> roll_values = dt.Exploding(dt.ModDie(6, 1)).get_dict() >>> sorted(roll_values.items()) [(2, 36), (3, 36), (4, 36), (5, 36), (6, 36), (9, 6), (10, 6), (11, 6), (12, 6), (13, 6), (16, 1), (17, 1), (18, 1), (19, 1), (20, 1), (21, 1)]

added methods:

ExplodingOn

Here are the rolls for an exploding D6 that can explode the default times and explodes on 5 and 6.

>>> roll_values = dt.ExplodingOn(dt.Die(6), (5, 6)).get_dict() >>> sorted(roll_values.items()) [(1, 36), (2, 36), (3, 36), (4, 36), (6, 6), (7, 12), (8, 12), (9, 12), (10, 6), (11, 1), (12, 3), (13, 4), (14, 4), (15, 4), (16, 4), (17, 3), (18, 1)]

added methods:

Top

Dice Pools

DicePool s are a pool of a single die. DicePoolCollection s are lightweight wrappers around a DicePool. They are a way to extract rolls from a Dice Pool and cast it as a ProtoDie. DicePool can be expensive to instantiate, which is explained below. They are immutable and a single instance can be passed to many collections.

dicetables.dicepool

DicePool

The collections are treated as one giant Die with very funky rolling behavior. They all follow the basic form: <WhatToSelect>OfDicePool(pool=DicePool(input_die, pool_size), select=<int>). BestOfDicePool(DicePool(Die(6), 4), 3) means: Make a dice pool of 4D6. Roll this and take the best three results from every roll. This object is also an 18-sided "Die" that rolls from 3 to 18.

dicetables.dicepool_collection

DicePoolCollection

BestOfDicePool

WorstOfDicePool

UpperMidOfDicePool

LowerMidOfDicePool

All DicePool objects calculate all the possible combinations of rolls and the frequency of each combination. So, DicePool(Die(3), 3, 2) creates the following dictionary

>>> pool = dt.DicePool(dt.Die(3), 3) >>> pool.rolls == { ... (1, 1, 1): 1, ... (1, 1, 2): 3, ... (1, 1, 3): 3, ... (1, 2, 2): 3, ... (1, 2, 3): 6, ... (1, 3, 3): 3, ... (2, 2, 2): 1, ... (2, 2, 3): 3, ... (2, 3, 3): 3, ... (3, 3, 3): 1 ... } True

This says that, with 3*Die(3), the roll: (1, 1, 1) happens once. The roll: (1, 2, 3) happens 6 times. BestOfDicePool(DicePool(Die(3), 3), 2) looks at the above dictionary and selects the two best rolls in each tuple. so:

>>> best_two = dt.BestOfDicePool(pool, 2) >>> best_two.get_dict() == {2: 1, 3: 3, 4: 7, 5: 9, 6: 7} True

The number of keys in any one of these dictionaries relies on pool_size and dict_size = len(input_die.get_dict()). The formula is (dict_size-1 + pool_size)!/(dict_size-1)! * 1/(pool_size)! and you can calculate it using count_unique_combination_keys. If you have a key_count, you can find the pool_size with largest_permitted_pool_size.

>>> from dicetables.tools.orderedcombinations import count_unique_combination_keys, largest_permitted_pool_size >>> count_unique_combination_keys(dt.Die(3), 3) == 10 # The dictionary demonstrated above True >>> count_unique_combination_keys(dt.Die(6), 10) == 3003 True >>> count_unique_combination_keys(dt.Die(6), 20) == 53130 True >>> count_unique_combination_keys(dt.Die(6), 30) == 324632 True >>> largest_permitted_pool_size(dt.Die(6), 330000) 30

This graph gives an idea of the times to instantiate different DicePools. It is time vs number-of-keys-needed-to-generate-the-pool. The black annotations are the pool sizes. Notice that each of these increases linearly with the underlying dictionary, but closer to exponentially with pool_size. Especially with larger dice, an increase of one in the pool size can have a surprisingly large effect.

image

Some Example Dice

  • WeightedDie({1: 3, 2: 4, 3: 4, 4: 4, 5: 4, 6: 5}) a mildly weighted die that has a 21% chance to roll a "6" (5/24), a 12.5% chance to roll a "1" and the rest are 1 in 6 (4/24).
  • ModWeightedDie({1: 3, 2: 1, 3: 1, 4: 1}, -1) a six-sided die with faces [0, 0, 0, 1, 2, 3].
  • ModDie(2, -1) a coin where "1" is heads, and "0" is tails. The die roll will tell you the number of heads rolled.
  • ModWeightedDie({1: 40, 2: 60}, -1) a cheater's coin that rolls heads 60% of the time.
  • ModWeightedDie({1: 45, 2: 55}. -1) a person who's likely to pick "1" 55% of the time.
  • StrongDie(ModWeightedDie({1: 10, 2: 90}, -1), 1000) a thousand people who will almost certainly choose "1" and will all vote as a block. whatever they choose, they're doing it as a team.
  • BestOfDicePool(DicePool(Die(6), 4), 3) best 3 out of 4D6.
  • 2 Die(6) and Modifier(3) 2D6+3

    >>> import dicetables as dt >>> dt.DiceTable.new().add_die(dt.Die(6), 2).add_die(dt.Modifier(3)) <DiceTable containing [+3, 2D6]>

Top