Skip to content

Instantly share code, notes, and snippets.

@mpkocher
Last active September 20, 2019 05:20
Show Gist options
  • Save mpkocher/b2241469a555f499ed03e3f5d9d59f8c to your computer and use it in GitHub Desktop.
Save mpkocher/b2241469a555f499ed03e3f5d9d59f8c to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
"""
https://twitter.com/AlSweigart/status/1170907280285417473
Refactored from https://pastebin.com/raw/hU1m440m by Al Sweigart [email protected]
"""
import re
import operator as op
import sys
from typing import NamedTuple, List, Any
import random
SUPPORTED_OPS = {'+': op.add, '-': op.sub}
class Roll(NamedTuple):
num_sides: int
rolls: List[int]
operator: Any = op.add # this is really Function[Int, Int] -> Int
value: int = 0
@staticmethod
def from_num_rolls(num_sides:int, num_rolls:int, operator=op.add, value:int=0):
rolls = [random.randint(1, num_sides) for _ in range(num_rolls)]
return Roll(num_sides, rolls, operator, value)
def total(self):
return self.operator(sum(self.rolls), self.value)
def to_example_inputs() -> str:
return """Example input:
'3d6' rolls three 6-sided dice
'1d10+2' rolls one 10-sided dice, and adds 2
'2d17-1' rolls two 17-sided dice, and subtracts 1'
'QUIT' quits the program"""
def to_header() -> str:
sx = "DND Dice Roller"
return "\n".join([sx, to_example_inputs()])
def not_none(x):
return x is not None
def parse_raw_roll_str(sx) -> Roll:
"""Convert the raw roll string to a Roll instance or Raise Value Error"""
sx = sx.replace(' ', '') # allow a bit of slop
r0 = r'(^\d*)d(\d*)'
r1 = r'(^\d*)d(\d*)(\+|\-)(\d*)'
err_msg = f"Invalid format `{sx}`. Expected format {r0} or {r1}. \n{to_example_inputs()}"
# translate the match groups to a Roll instance
def f0(g):
if len(g) == 2:
return Roll.from_num_rolls(int(g[1]), int(g[0]))
return None
def f1(g):
if len(g) == 4:
return Roll.from_num_rolls(int(g[1]), int(g[0]), SUPPORTED_OPS[g[2]], int(g[3]))
def match(rx, fx):
m = re.match(rx, sx)
if m is not None:
return fx(m.groups())
return None
# regex and translating func from group match to Roll (or None)
rxs = [(r1, f1), (r0, f0)]
results = list(filter(not_none, (match(rx, fx) for rx, fx in rxs)))
if results:
return results[0]
else:
raise ValueError(err_msg)
def process_roll(sx) -> bool:
roll = parse_raw_roll_str(sx)
print(roll)
print(roll.total())
return True
def process_input(raw_input) -> bool:
if raw_input == "QUIT":
return False
else:
return process_roll(raw_input)
def process_input_loop() -> int:
flag = True
while flag:
raw_input = input('>')
try:
flag = process_input(raw_input)
except Exception as ex:
sys.stderr.write(str(ex) + "\n")
return 0
def main() -> int:
print(to_header())
return process_input_loop()
if __name__ == '__main__':
sys.exit(main())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment