-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdice.py
More file actions
68 lines (56 loc) · 2.02 KB
/
Copy pathdice.py
File metadata and controls
68 lines (56 loc) · 2.02 KB
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
import re
import secrets
from dataclasses import dataclass
MAX_DICE = 100
MAX_SIDES = 1000
ROLL_PATTERN = re.compile(
r"^\s*(?P<count>\d{0,3})d(?P<sides>\d{1,4})"
r"(?P<keep>kh1|kl1)?(?P<modifier>[+-]\d{1,5})?\s*$",
re.IGNORECASE,
)
class DiceError(ValueError):
pass
@dataclass(frozen=True)
class RollResult:
expression: str
rolls: tuple[int, ...]
kept: tuple[int, ...]
modifier: int
total: int
def format(self) -> str:
rolls = ", ".join(str(value) for value in self.rolls)
modifier = f" {self.modifier:+d}" if self.modifier else ""
return f"🎲 `{self.expression}` → [{rolls}]{modifier} = **{self.total}**"
def parse_roll(expression: str) -> tuple[int, int, str | None, int]:
match = ROLL_PATTERN.fullmatch(expression)
if not match:
raise DiceError("Geçersiz zar ifadesi. Örnek: `2d6+3`, `2d20kh1+5`.")
count = int(match.group("count") or 1)
sides = int(match.group("sides"))
keep = match.group("keep")
modifier = int(match.group("modifier") or 0)
if not 1 <= count <= MAX_DICE:
raise DiceError(f"Zar sayısı 1-{MAX_DICE} arasında olmalıdır.")
if not 2 <= sides <= MAX_SIDES:
raise DiceError(f"Zar yüzü 2-{MAX_SIDES} arasında olmalıdır.")
return count, sides, keep.lower() if keep else None, modifier
def roll(expression: str) -> RollResult:
count, sides, keep, modifier = parse_roll(expression)
values = tuple(secrets.randbelow(sides) + 1 for _ in range(count))
if keep == "kh1":
kept = (max(values),)
elif keep == "kl1":
kept = (min(values),)
else:
kept = values
normalized = f"{count}d{sides}{keep or ''}{modifier:+d}".removesuffix("+0")
return RollResult(
expression=normalized,
rolls=values,
kept=kept,
modifier=modifier,
total=sum(kept) + modifier,
)
def roll_command(text: str) -> str | None:
match = re.fullmatch(r"\s*/(?:roll|r)\s+(.+?)\s*", text, re.IGNORECASE)
return match.group(1) if match else None