aboutsummaryrefslogtreecommitdiffstats
path: root/scripts/roll_die.py
blob: 39066982f08047863d3e7f2c1fbf2a79764ab366 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
#!/usr/bin/env python3

import random
import sys
from pathlib import Path
import re


class Roll:
    _pattern = re.compile(
        r"(?P<number>\d+)d(?P<sides>\d+)(?P<modifier>[+-]\d+)?(?P<keep>[><]\d+)?"
    )

    def __init__(self, roll_notation: str):
        match = self._match = self._pattern.match(roll_notation)
        if not match:
            raise ValueError(f"Invalid roll notation: {roll_notation}")
        self.matches = match.groupdict()
        self.number = int(self.matches["number"])
        self.sides = int(self.matches["sides"])
        self.modifier = int(self.matches["modifier"]) if self.matches["modifier"] else 0
        if self.matches["keep"] is None:
            self.keep = 0
        else:
            mult = 1 if self.matches["keep"][0] == ">" else -1
            self.keep = mult * int(self.matches["keep"][1:])

    def roll(self) -> list[int]:
        rolls = roll_die(self.number, self.sides, self.modifier)
        # TODO: keep/drop the highest/lowest
        return rolls

    def __str__(self) -> str:
        s = f"{self.number}d{self.sides}"
        if self.modifier != 0:
            s += "+" if self.modifier > 0 else ""
            s += str(self.modifier)
        if self.keep != 0:
            s += ">" if self.keep > 0 else "<"
            s += f"{abs(self.keep)}"
        return s


# notation
# nDs(+|-)m(>|<)a


def roll_die(num_dice: int, dice_type: int, modifier: int) -> list[int]:
    rolls: list[int] = []
    for _ in range(num_dice):
        rolls.append(random.randint(1, dice_type) + modifier)

    return rolls


def parse_dice_notation(dice_notation: str) -> tuple[int, int, int]:
    # Define the regular expression pattern
    pattern = re.compile(r"(\d+)d(\d+)([+-]\d+)?")

    # Search the pattern in the dice notation string
    match = pattern.match(dice_notation)

    if not match:
        raise ValueError(f"Invalid dice notation: {dice_notation}")

    # Extract the groups from the match
    groups = match.groups()

    # Convert the groups to integers and handle the optional modifier
    num_dice = int(groups[0])
    dice_type = int(groups[1])
    modifier = int(groups[2]) if groups[2] else 0

    return num_dice, dice_type, modifier


def summary(rolls: list[int]) -> None:
    print(rolls)
    print(f"Min: {min(rolls)}")
    print(f"Max: {max(rolls)}")
    if len(rolls) > 1:
        print(f"Total: {sum(rolls)}")
        print(f"Avg: {sum(rolls)/len(rolls):.2f}")


def main():
    if len(sys.argv) != 2:
        path = Path(sys.argv[0]).parts[-1]
        print(f"Usage: {path} xdy[(+|y)z]")
        print("Rolls x number of y sided die, optionally using a modifier z")
        print()
        print("Examples:")
        print("\t2d4+2 Rolls two four sided die adding 2 to each roll")
        print("\t1d20-1 Rolls a single twenty and subtracts 1 from the roll")
        sys.exit(1)

    dice_notation = sys.argv[1]

    try:
        num_die, die_type, modifier = parse_dice_notation(dice_notation)
    except ValueError as e:
        print(e)
        sys.exit(1)

    rolls = roll_die(num_die, die_type, modifier)
    summary(rolls)


if __name__ == "__main__":
    main()