"""
die and dice submodule
"""
import random
from gamble.errors import GambleException
[docs]
class Die:
"""
a single die object
Args:
sides: the number of sides to this die
"""
def __init__(self, sides: int = 6) -> None:
if abs(int(sides)) < 2:
raise GambleException("A die must have at least 2 sides")
self.sides = abs(int(sides))
self.negative = sides <= 0
self.multiplier = -1 if self.negative else 1
self.rolls = 0
[docs]
def __str__(self) -> str:
"""
dunder str method
Returns:
the string representation of this die
"""
return f"<{'-' if self.negative else ''}d{self.sides} Die>"
[docs]
def __repr__(self) -> str:
"""
dunder repr method
Returns:
the repr representation of this die
"""
return self.__str__()
[docs]
def __lt__(self, other: "Die") -> bool:
"""
dunder less than method
Args:
other: another Die instance
Returns:
true if this die is smaller than the other die
"""
return self.net_sides < other.net_sides
[docs]
def __gt__(self, other: "Die") -> bool:
"""
dunder greater than method
Args:
other: another Die instance
Returns:
true if this die is bigger than the other die
"""
return self.net_sides > other.net_sides
[docs]
def __le__(self, other: "Die") -> bool:
"""
dunder less than or equal to method
Args:
other: another Die instance
Returns:
true if this die is smaller than or equal to the other die
"""
return self < other or self == other
[docs]
def __ge__(self, other: "Die") -> bool:
"""
dunder greater than or equal to method
Args:
other: another Die instance
Returns:
true if this die is bigger than or equal to the other die
"""
return self > other or self == other
@property
def net_sides(self) -> int:
"""
the raw max sides * multiplier
Returns:
the non-absolute value of this die's sides
"""
return self.sides * self.multiplier
@property
def max(self) -> int:
"""
the max value this die can roll
Returns:
the max for this die
"""
return -1 if self.negative else self.sides
@property
def min(self) -> int:
"""
the min value this die can roll
Returns:
the min for this die
"""
return self.sides if self.negative else 1
[docs]
def roll(self) -> int:
"""
roll the die
Returns:
the value rolled by this die
"""
value = random.randrange(self.sides) + 1 # nosec
self.rolls += 1
return value * self.multiplier
[docs]
class RiggedDie(Die):
"""
a die with a modifier to roll in the top 3 per the percentage passed in
Args:
sides: the number of sides to this die
rigged_factor: int from 0-100 to manipulate the die into a high roll
"""
def __init__(self, sides: int = 6, rigged_factor: int = 50) -> None:
self.rigged_factor = rigged_factor
if rigged_factor < 0 or rigged_factor > 100:
raise GambleException("The rigged factor must be between 0 and 100")
if sides < 2:
raise GambleException("the die must have at least 2 sides")
super().__init__(sides)
[docs]
def roll(self) -> int:
"""
sometime override supers die roll depending on the rigged_factor
Returns:
the value rolled by this die
"""
if random.randrange(101) <= self.rigged_factor: # nosec
value = [self.sides, self.sides - 1, self.sides - 2][random.randrange(3)] # nosec
self.rolls += 1
return value * self.multiplier
return super().roll()
[docs]
class Dice:
"""
a group of die objects
Args:
init_string: a d-notation string representing a set of dice
rigged_factor: int from 0-100 to manipulate the die into a high roll
"""
def __init__(self, init_string: str = "2d6", rigged_factor: int = -1) -> None:
self.__d_string = init_string.strip().lower().replace("-", "+-")
self.d_strings = [x.strip() for x in self.__d_string.split("+")]
self.dice: list[Die | RiggedDie] = []
self.bonuses: list[int] = []
for d_string in self.d_strings:
if "d" in d_string:
die_settings = [int(x) for x in d_string.split("d") if x]
if len(die_settings) == 1:
self.dice.append(self.create_die(die_settings[0], rigged_factor=rigged_factor))
elif len(die_settings) > 1:
num, value = die_settings
negative = -1 if num < 0 else 1
self.dice.extend(
[self.create_die(value * negative, rigged_factor=rigged_factor)] * abs(num)
)
else:
raise GambleException("cannot create a die with no value!")
else:
self.bonuses.append(int(d_string))
self.dice = sorted(self.dice)
self.bonuses = sorted(self.bonuses)
self.rolls = 0
[docs]
def __str__(self) -> str:
"""
dunder str method
Returns:
the string representation of this set of dice
"""
dice_string = "\n".join([str(x) for x in self.parts])
return f"{{\n{dice_string}\n}}"
[docs]
def __repr__(self) -> str:
"""
dunder repr method
Returns:
the repr representation of this set of dice
"""
return self.__str__()
@property
def parts(self) -> list[Die | int]:
"""
listing of the parts of this roll + bonuses
Returns:
a list of the parts of this dice calculation
"""
return [*self.dice, *self.bonuses]
@property
def max(self) -> int:
"""
the max value these dice can roll
Returns:
the max for these dice + bonuses
"""
return sum([*[x.max for x in self.dice], *self.bonuses])
@property
def min(self) -> int:
"""
the min value these dice can roll
Returns:
the min for these dice + bonuses
"""
return sum([*[x.min for x in self.dice], *self.bonuses])
[docs]
def create_die(self, sides: int, rigged_factor: int = -1) -> Die | RiggedDie:
"""
helper to create dice
Args:
sides: the number of sides on a die
rigged_factor: int from 0-100 to manipulate the die into a high roll
Returns:
A Die object that can be rigged
"""
if rigged_factor != -1:
return RiggedDie(sides, rigged_factor)
return Die(sides)
[docs]
def roll(self) -> int:
"""
roll the dice
Returns:
the value rolled by this dice
"""
self.rolls += 1
rolls = [x.roll() for x in self.dice]
return sum([*rolls, *self.bonuses])
[docs]
def roll_many(self, num_rolls: int = 2) -> list[int]:
"""
roll dice multiple times
Args:
num_rolls: the number of times to roll the dice
Returns:
list of values rolled by these dice
"""
return [self.roll() for _ in range(num_rolls)]
[docs]
def max_of(self, num_rolls: int = 2) -> tuple[int, list[int]]:
"""
roll dice multiple times
Args:
num_rolls: the number of times to roll the dice
Returns:
a tuple with the max value rolled by the dice, and the dice rolls
"""
rolls = self.roll_many(num_rolls)
max_roll = max(rolls)
return max_roll, rolls
[docs]
def min_of(self, num_rolls: int = 2) -> tuple[int, list[int]]:
"""
roll dice multiple times
Args:
num_rolls: the number of times to roll the dice
Returns:
a tuple with the min value rolled by the dice, and the dice rolls
"""
rolls = self.roll_many(num_rolls)
min_roll = min(rolls)
return min_roll, rolls