dicetables 4.0.2__py3-none-any.whl

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 (49) hide show
  1. dicetables/__init__.py +13 -0
  2. dicetables/additiveevents.py +71 -0
  3. dicetables/dicepool.py +36 -0
  4. dicetables/dicepool_collection.py +140 -0
  5. dicetables/dicerecord.py +69 -0
  6. dicetables/dicetable.py +111 -0
  7. dicetables/dieevents.py +484 -0
  8. dicetables/eventsbases/__init__.py +0 -0
  9. dicetables/eventsbases/eventerrors.py +13 -0
  10. dicetables/eventsbases/integerevents.py +46 -0
  11. dicetables/eventsbases/protodie.py +74 -0
  12. dicetables/eventsinfo.py +289 -0
  13. dicetables/factory/__init__.py +0 -0
  14. dicetables/factory/errorhandler.py +112 -0
  15. dicetables/factory/eventsfactory.py +181 -0
  16. dicetables/factory/factorytools.py +78 -0
  17. dicetables/factory/warninghandler.py +60 -0
  18. dicetables/parser.py +274 -0
  19. dicetables/roller.py +44 -0
  20. dicetables/tools/__init__.py +0 -0
  21. dicetables/tools/alias_table.py +67 -0
  22. dicetables/tools/dictcombiner.py +210 -0
  23. dicetables/tools/indexedvalues.py +109 -0
  24. dicetables/tools/limit_checker.py +205 -0
  25. dicetables/tools/listtostring.py +47 -0
  26. dicetables/tools/numberforamtter.py +133 -0
  27. dicetables/tools/orderedcombinations.py +50 -0
  28. dicetables-4.0.2.dist-info/LICENSE +21 -0
  29. dicetables-4.0.2.dist-info/METADATA +277 -0
  30. dicetables-4.0.2.dist-info/RECORD +49 -0
  31. dicetables-4.0.2.dist-info/WHEEL +6 -0
  32. dicetables-4.0.2.dist-info/top_level.txt +2 -0
  33. tests/eventsbases/__init__.py +0 -0
  34. tests/eventsbases/test_eventserrors.py +30 -0
  35. tests/eventsbases/test_integerevents.py +119 -0
  36. tests/eventsbases/test_protodie.py +189 -0
  37. tests/factory/__init__.py +0 -0
  38. tests/factory/test_eventsfactory.py +772 -0
  39. tests/factory/test_factoryerrorhandler.py +118 -0
  40. tests/factory/test_factorytools.py +201 -0
  41. tests/factory/test_factorywarninghandler.py +172 -0
  42. tests/tools/__init__.py +0 -0
  43. tests/tools/test_alias_table.py +108 -0
  44. tests/tools/test_dictcombiner.py +374 -0
  45. tests/tools/test_indexedvalues.py +168 -0
  46. tests/tools/test_limit_checker.py +214 -0
  47. tests/tools/test_listtostring.py +77 -0
  48. tests/tools/test_numberformatter.py +340 -0
  49. tests/tools/test_orderedcombinations.py +114 -0
dicetables/__init__.py ADDED
@@ -0,0 +1,13 @@
1
+ from dicetables.additiveevents import AdditiveEvents
2
+ from dicetables.dicepool_collection import BestOfDicePool, WorstOfDicePool, UpperMidOfDicePool, LowerMidOfDicePool
3
+ from dicetables.dicepool import DicePool
4
+ from dicetables.dicerecord import DiceRecord
5
+ from dicetables.dicetable import DiceTable, DetailedDiceTable
6
+ from dicetables.dieevents import Die, ModDie, WeightedDie, ModWeightedDie, StrongDie, Modifier, ExplodingOn, Exploding
7
+ from dicetables.eventsbases.eventerrors import DiceRecordError, InvalidEventsError
8
+ from dicetables.eventsinfo import (EventsCalculations, EventsInformation,
9
+ events_range, mean, stddev, percentage_points, percentage_axes, stats,
10
+ full_table_string)
11
+ from dicetables.parser import Parser, ParseError
12
+ from dicetables.tools.limit_checker import LimitsError
13
+ from dicetables.roller import Roller
@@ -0,0 +1,71 @@
1
+ """
2
+ AdditiveEvents is the general case for DiceTable - an IntegerEvents that can combine with other IntegerEvents.
3
+ """
4
+ from typing import TypeVar, Type, Dict
5
+
6
+ from dicetables.eventsbases.integerevents import IntegerEvents
7
+ from dicetables.factory.eventsfactory import EventsFactory
8
+ from dicetables.tools.dictcombiner import DictCombiner
9
+
10
+
11
+ def scrub_zeroes(dictionary):
12
+ return {key: val for key, val in dictionary.items() if val}
13
+
14
+
15
+ T = TypeVar('T', bound='AdditiveEvents')
16
+
17
+
18
+ class AdditiveEvents(IntegerEvents):
19
+
20
+ def __init__(self, events_dict):
21
+ """
22
+
23
+ :param events_dict: {event: occurrences}\n
24
+ event=int. occurrences=int >=0
25
+ total occurrences > 0
26
+ """
27
+ self._table = scrub_zeroes(events_dict)
28
+ super(AdditiveEvents, self).__init__()
29
+ EventsFactory.check(self.__class__)
30
+
31
+ @classmethod
32
+ def new(cls: Type[T]) -> T:
33
+ return EventsFactory.new(cls)
34
+
35
+ def get_dict(self) -> Dict[int, int]:
36
+ return self._table.copy()
37
+
38
+ def __str__(self):
39
+ min_event = min(self._table.keys())
40
+ max_event = max(self._table.keys())
41
+ return 'table from {} to {}'.format(min_event, max_event)
42
+
43
+ def combine(self: T, events: IntegerEvents, times: int = 1) -> T:
44
+ dictionary = DictCombiner(self.get_dict()).combine_by_fastest(events.get_dict(), times)
45
+ return EventsFactory.from_dictionary(self, dictionary)
46
+
47
+ def combine_by_flattened_list(self: T, events: IntegerEvents, times: int = 1) -> T:
48
+ """
49
+
50
+ :WARNING - UNSAFE METHOD: len(flattened_list) = total occurrences of events.
51
+ if this list is too big, it will raise MemoryError or OverflowError
52
+ """
53
+ dictionary = DictCombiner(self.get_dict()).combine_by_flattened_list(events.get_dict(), times)
54
+ return EventsFactory.from_dictionary(self, dictionary)
55
+
56
+ def combine_by_dictionary(self: T, events: IntegerEvents, times: int = 1) -> T:
57
+ dictionary = DictCombiner(self.get_dict()).combine_by_dictionary(events.get_dict(), times)
58
+ return EventsFactory.from_dictionary(self, dictionary)
59
+
60
+ def combine_by_indexed_values(self: T, events: IntegerEvents, times: int = 1) -> T:
61
+ dictionary = DictCombiner(self.get_dict()).combine_by_indexed_values(events.get_dict(), times)
62
+ return EventsFactory.from_dictionary(self, dictionary)
63
+
64
+ def remove(self: T, events: IntegerEvents, times: int = 1) -> T:
65
+ """
66
+
67
+ :WARNING - UNSAFE METHOD: There is no record of what you added to an AdditiveEvents.
68
+ If you remove what you haven't added, no error will be raised, but you will have bugs.
69
+ """
70
+ dictionary = DictCombiner(self.get_dict()).remove_by_tuple_list(events.get_dict(), times)
71
+ return EventsFactory.from_dictionary(self, dictionary)
dicetables/dicepool.py ADDED
@@ -0,0 +1,36 @@
1
+ from typing import Dict, Tuple
2
+
3
+ from dicetables.eventsbases.protodie import ProtoDie
4
+ from dicetables.tools.orderedcombinations import ordered_combinations_of_events
5
+
6
+
7
+ class DicePool(object):
8
+ def __init__(self, input_die: ProtoDie, pool_size: int):
9
+ if pool_size < 1:
10
+ raise ValueError("Minimum DicePool size is 1")
11
+ self._input_die = input_die
12
+ self._pool_size = pool_size
13
+ self._rolls = ordered_combinations_of_events(input_die, pool_size)
14
+
15
+ @property
16
+ def die(self) -> ProtoDie:
17
+ return self._input_die
18
+
19
+ @property
20
+ def size(self) -> int:
21
+ return self._pool_size
22
+
23
+ @property
24
+ def rolls(self) -> Dict[Tuple[int, ...], int]:
25
+ return self._rolls.copy()
26
+
27
+ def __eq__(self, other):
28
+ if not isinstance(other, DicePool):
29
+ return False
30
+ return (self.die, self.size) == (other.die, other.size)
31
+
32
+ def __hash__(self):
33
+ return hash((self.die, self.size))
34
+
35
+ def __repr__(self):
36
+ return "{}({!r}, {})".format(self.__class__.__name__, self.die, self.size)
@@ -0,0 +1,140 @@
1
+ from typing import Dict
2
+
3
+ from dicetables.dieevents import ProtoDie
4
+ from dicetables.dicepool import DicePool
5
+
6
+
7
+ class DicePoolCollection(ProtoDie):
8
+ """
9
+ The abstract class for all DicePoolCollection objects. A DicePoolCollection creates a new die by selecting from a
10
+ DicePool. Select determines how many rolls are selected from the pool of total rolls.
11
+ Different implementations determine which particular rolls to select.
12
+ """
13
+
14
+ def __init__(self, pool: DicePool, select: int):
15
+ """
16
+
17
+ :param pool: the dice pool to select from
18
+ :param select: int <= `pool.size`. How many dice to select from the pool.
19
+ """
20
+ self._dice_pool = pool
21
+ if select > self._dice_pool.size:
22
+ raise ValueError('you cannot select more dice than the pool_size.')
23
+ self._select = select
24
+ self._dict = self._generate_dict()
25
+ super(DicePoolCollection, self).__init__()
26
+
27
+ def _generate_dict(self):
28
+ raise NotImplementedError
29
+
30
+ def get_pool(self) -> DicePool:
31
+ return self._dice_pool
32
+
33
+ def get_select(self) -> int:
34
+ return self._select
35
+
36
+ def get_dict(self) -> Dict[int, int]:
37
+ return self._dict.copy()
38
+
39
+ def get_size(self) -> int:
40
+ return self._dice_pool.die.get_size() * self._select
41
+
42
+ def get_weight(self) -> int:
43
+ return self._dice_pool.die.get_weight() * self._select
44
+
45
+ def weight_info(self):
46
+ input_die = self._dice_pool.die
47
+ original_weight_info = input_die.weight_info().replace(str(input_die), 'input_die info:')
48
+ return '{}\n{}'.format(self, original_weight_info)
49
+
50
+ def multiply_str(self, number):
51
+ return '{}({})'.format(number, self)
52
+
53
+ def __str__(self):
54
+ descriptor = self.__class__.__name__.replace('OfDicePool', '')
55
+ return '{} {} of {}'.format(descriptor, self._select, self._dice_pool.die.multiply_str(self._dice_pool.size))
56
+
57
+ def __repr__(self):
58
+ return '{}({!r}, {})'.format(
59
+ self.__class__.__name__, self._dice_pool, self._select
60
+ )
61
+
62
+
63
+ class BestOfDicePool(DicePoolCollection):
64
+ """
65
+ Take the best [select] rolls from a DicePool of [pool_size] * [input_die].
66
+ BestOfDicePool(DicePool(Die(6), 4), 3) is the best 3 rolls from four six-sided dice.
67
+ """
68
+
69
+ def __init__(self, pool: DicePool, select: int):
70
+ super().__init__(pool, select)
71
+
72
+ def _generate_dict(self):
73
+ ordered_combinations = self._dice_pool.rolls
74
+ start_at = self._dice_pool.size - self._select
75
+ stop_before = self._dice_pool.size
76
+ return generate_events_dict(ordered_combinations, start_at, stop_before)
77
+
78
+
79
+ class WorstOfDicePool(DicePoolCollection):
80
+ """
81
+ Take the worst [select] rolls from a DicePool of [pool_size] * [input_die].
82
+ WorstOfDicePool(DicePool(Die(6), 4), 3) is the worst 3 rolls from four six-sided dice.
83
+ """
84
+
85
+ def __init__(self, pool: DicePool, select: int):
86
+ super().__init__(pool, select)
87
+
88
+ def _generate_dict(self):
89
+ ordered_combinations = self._dice_pool.rolls
90
+ start_at = 0
91
+ stop_before = self._select
92
+ return generate_events_dict(ordered_combinations, start_at, stop_before)
93
+
94
+
95
+ class UpperMidOfDicePool(DicePoolCollection):
96
+ """
97
+ Take the middle [select] rolls from a DicePool of [pool_size] * [input_die].
98
+ UpperMidOfDicePool(DicePool(Die(6), 5), 3) is the middle 3 rolls from five six-sided dice.
99
+
100
+ If there is no perfect middle, take the higher of two choices. For five dice that roll
101
+ (1, 1, 2, 3, 4), select=3 takes (1, 2, 3) and select=2 takes (2, 3).
102
+ """
103
+
104
+ def __init__(self, pool: DicePool, select: int):
105
+ super().__init__(pool, select)
106
+
107
+ def _generate_dict(self):
108
+ ordered_combinations = self._dice_pool.rolls
109
+ end_slice, extra = divmod(self._dice_pool.size - self._select, 2)
110
+ start_at = end_slice + extra
111
+ stop_before = self._dice_pool.size - end_slice
112
+ return generate_events_dict(ordered_combinations, start_at, stop_before)
113
+
114
+
115
+ class LowerMidOfDicePool(DicePoolCollection):
116
+ """
117
+ Take the middle [select] rolls from a DicePool of [pool_size] * [input_die].
118
+ LowerMidOfDicePool(DicePool(Die(6), 5), 3) is the middle 3 rolls from five six-sided dice.
119
+
120
+ If there is no perfect middle, take the lower of two choices. For five dice that roll
121
+ (1, 1, 2, 3, 4), select=3 takes (1, 2, 3) and select=2 takes (1, 2).
122
+ """
123
+
124
+ def __init__(self, pool: DicePool, select: int):
125
+ super().__init__(pool, select)
126
+
127
+ def _generate_dict(self):
128
+ ordered_combinations = self._dice_pool.rolls
129
+ end_slice, extra = divmod(self._dice_pool.size - self._select, 2)
130
+ start_at = end_slice
131
+ stop_before = self._dice_pool.size - (end_slice + extra)
132
+ return generate_events_dict(ordered_combinations, start_at, stop_before)
133
+
134
+
135
+ def generate_events_dict(ordered_combinations, start_at, stop_before) -> Dict[int, int]:
136
+ master_dict = {}
137
+ for key, val in ordered_combinations.items():
138
+ master_key = sum(key[start_at:stop_before])
139
+ master_dict[master_key] = master_dict.get(master_key, 0) + val
140
+ return master_dict
@@ -0,0 +1,69 @@
1
+ """
2
+ An immutable record of dice added to and removed from DiceTable
3
+ """
4
+ from typing import Dict
5
+
6
+ from dicetables.eventsbases.eventerrors import DiceRecordError
7
+ from dicetables.eventsbases.protodie import ProtoDie
8
+
9
+
10
+ class RecordVerifier(object):
11
+ @staticmethod
12
+ def check_types(die_input):
13
+ if any(not isinstance(die, ProtoDie) or not isinstance(num, int) for die, num in die_input.items()):
14
+ raise DiceRecordError('input must be {ProtoDie: int, ...}')
15
+
16
+ @staticmethod
17
+ def check_negative(die_input):
18
+ for key, val in die_input.items():
19
+ if val < 0:
20
+ raise DiceRecordError('Tried to create a DiceRecord with a negative value at {!r}: {}'.format(key, val))
21
+
22
+ @staticmethod
23
+ def check_number(num):
24
+ if num < 0:
25
+ raise DiceRecordError('Tried to add_die or remove_die with a negative number.')
26
+
27
+
28
+ def scrub_zeroes(input_dict):
29
+ return {key: val for key, val in input_dict.items() if val}
30
+
31
+
32
+ class DiceRecord(object):
33
+ def __init__(self, dice_number_dict: Dict[ProtoDie, int]):
34
+ RecordVerifier.check_types(dice_number_dict)
35
+ RecordVerifier.check_negative(dice_number_dict)
36
+ self._record = scrub_zeroes(dice_number_dict)
37
+
38
+ @classmethod
39
+ def new(cls) -> 'DiceRecord':
40
+ return cls({})
41
+
42
+ def get_dict(self) -> Dict[ProtoDie, int]:
43
+ return self._record.copy()
44
+
45
+ def get_number(self, query_die: ProtoDie) -> int:
46
+ return self._record.get(query_die, 0)
47
+
48
+ def add_die(self, die: ProtoDie, times: int) -> 'DiceRecord':
49
+ RecordVerifier.check_number(times)
50
+ new = self._record.copy()
51
+ new[die] = times + self.get_number(die)
52
+ return DiceRecord(new)
53
+
54
+ def remove_die(self, die: ProtoDie, times: int) -> 'DiceRecord':
55
+ RecordVerifier.check_number(times)
56
+ new = self._record.copy()
57
+ new[die] = self.get_number(die) - times
58
+ return DiceRecord(new)
59
+
60
+ def __eq__(self, other):
61
+ if not isinstance(other, DiceRecord):
62
+ return False
63
+ return self.get_dict() == other.get_dict()
64
+
65
+ def __ne__(self, other):
66
+ return not self == other
67
+
68
+ def __repr__(self):
69
+ return 'DiceRecord({!r})'.format(self._record)
@@ -0,0 +1,111 @@
1
+ """
2
+ DiceTable and DetailedDiceTable compute combination of Die events
3
+ """
4
+
5
+ from typing import TypeVar
6
+
7
+ from dicetables import DiceRecord
8
+ from dicetables.additiveevents import AdditiveEvents
9
+ from dicetables.eventsbases.protodie import ProtoDie
10
+ from dicetables.eventsinfo import EventsCalculations, EventsInformation
11
+ from dicetables.factory.eventsfactory import EventsFactory
12
+ from dicetables.tools.dictcombiner import DictCombiner
13
+
14
+ T = TypeVar('T', bound='DiceTable')
15
+
16
+
17
+ class DiceTable(AdditiveEvents):
18
+ def __init__(self, events_dict: dict, dice_record: DiceRecord):
19
+ self._record = dice_record
20
+ super(DiceTable, self).__init__(events_dict)
21
+
22
+ def dice_data(self) -> DiceRecord:
23
+ return self._record
24
+
25
+ def get_list(self):
26
+ """
27
+
28
+ :return: sorted copy of dice list: [(die, number of dice), ...]
29
+ """
30
+ return sorted(self._record.get_dict().items())
31
+
32
+ def number_of_dice(self, query_die: ProtoDie) -> int:
33
+ return self._record.get_number(query_die)
34
+
35
+ def weights_info(self):
36
+ """
37
+
38
+ :return: str: complete info for all dice
39
+ """
40
+ output = ''
41
+ for die, number in self.get_list():
42
+ output += format_die_info(die, number)
43
+ return output.rstrip('\n')
44
+
45
+ def __str__(self):
46
+ str_list = [die.multiply_str(number) for die, number in self.get_list()]
47
+ return '\n'.join(str_list)
48
+
49
+ def __repr__(self):
50
+ class_name = self.__class__.__name__
51
+ dice = str(self).replace('\n', ', ')
52
+ return '<{} containing [{}]>'.format(class_name, dice)
53
+
54
+ def add_die(self: T, die: ProtoDie, times: int = 1) -> T:
55
+ """
56
+
57
+ :param die: any subclass of ProtoDie: Die, ModDie, WeightedDie, ModWeightedDie, Modifier,
58
+ StrongDie, Exploding, ExplodingOn
59
+ :param times: int>= 0
60
+ """
61
+ dice_data = self._record.add_die(die, times)
62
+ dictionary = DictCombiner(self.get_dict()).combine_by_fastest(die.get_dict(), times)
63
+ return EventsFactory.from_dictionary_and_dice(self, dictionary, dice_data)
64
+
65
+ def remove_die(self: T, die: ProtoDie, times=1) -> T:
66
+ """
67
+
68
+ :param die: any subclass of ProtoDie: Die, ModDie, WeightedDie, ModWeightedDie, Modifier,
69
+ StrongDie, Exploding, ExplodingOn
70
+ :param times: 0 <= int <= number of "die" in table
71
+ """
72
+ dice_data = self._record.remove_die(die, times)
73
+ dictionary = DictCombiner(self.get_dict()).remove_by_tuple_list(die.get_dict(), times)
74
+ return EventsFactory.from_dictionary_and_dice(self, dictionary, dice_data)
75
+
76
+ def __eq__(self, other):
77
+ return super(DiceTable, self).__eq__(other) and self.dice_data() == other.dice_data()
78
+
79
+
80
+ def format_die_info(die, number):
81
+ weight_info = die.weight_info()
82
+ adjusted_info = weight_info.replace(str(die), die.multiply_str(number)) + '\n\n'
83
+ return adjusted_info
84
+
85
+
86
+ class DetailedDiceTable(DiceTable):
87
+ def __init__(self, events_dict: dict, dice_record: DiceRecord, calc_includes_zeroes: bool = True):
88
+ super(DetailedDiceTable, self).__init__(events_dict, dice_record)
89
+ self._calc = EventsCalculations(self, calc_includes_zeroes)
90
+
91
+ @property
92
+ def info(self) -> EventsInformation:
93
+ return self._calc.info
94
+
95
+ @property
96
+ def calc(self) -> EventsCalculations:
97
+ return self._calc
98
+
99
+ @property
100
+ def calc_includes_zeroes(self) -> bool:
101
+ return self._calc.include_zeroes
102
+
103
+ def switch_boolean(self: T) -> T:
104
+ """
105
+
106
+ :return: a new DetailedDiceTable with the `calc_includes_zeroes` boolean switched
107
+ """
108
+ return EventsFactory.from_params(self, {'calc_includes_zeroes': not self.calc_includes_zeroes})
109
+
110
+ def __eq__(self, other):
111
+ return super(DetailedDiceTable, self).__eq__(other) and self.calc_includes_zeroes == other.calc_includes_zeroes