Last active
September 20, 2019 05:20
-
-
Save mpkocher/b2241469a555f499ed03e3f5d9d59f8c to your computer and use it in GitHub Desktop.
Refactor Example from https://twitter.com/AlSweigart/status/1170907280285417473
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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