Evaluate dice rolling notation strings

前端 未结 14 1739
甜味超标
甜味超标 2021-01-30 14:55

Rules

Write a function that accepts string as a parameter, returning evaluated value of expression in dice notation, including addition and multiplication.

To

14条回答
  •  暗喜
    暗喜 (楼主)
    2021-01-30 15:33

    Python, 452 bytes in the compressed version

    I'm not sure if this is cool, ugly, or plain stupid, but it was fun writing it.

    What we do is as follows: We use regexes (which is usually not the right tool for this kind of thing) to convert the dice notation string into a list of commands in a small, stack-based language. This language has four commands:

    • mul multiplies the top two numbers on the stack and pushes the result
    • add adds the top two numbers on the stack and pushes the result
    • roll pops the dice size from the stack, then the count, rolls a size-sided dice count times and pushes the result
    • a number just pushes itself onto the stack

    This command list is then evaluated.

    import re, random
    
    def dice_eval(s):
        s = s.replace(" ","")
        s = re.sub(r"(\d+|[d+*])",r"\1 ",s) #seperate tokens by spaces
        s = re.sub(r"(^|[+*] )d",r"\g<1>1 d",s) #e.g. change d 6 to 1 d 6
        while "*" in s:
            s = re.sub(r"([^+]+) \* ([^+]+)",r"\1 \2mul ",s,1)
        while "+" in s:
            s = re.sub(r"(.+) \+ (.+)",r"\1 \2add ",s,1)
        s = re.sub(r"d (\d+) ",r"\1 roll ",s)
    
        stack = []
    
        for token in s.split():
            if token == "mul":
                stack.append(stack.pop() * stack.pop())
            elif token == "add":
                stack.append(stack.pop() + stack.pop())
            elif token == "roll":
                v = 0
                dice = stack.pop()
                for i in xrange(stack.pop()):
                    v += random.randint(1,dice)
                stack.append(v)
            elif token.isdigit():
                stack.append(int(token))
            else:
                raise ValueError
    
        assert len(stack) == 1
    
        return stack.pop() 
    
    print dice_eval("2*d12+3d20*3+d6")
    

    By the way (this was discussed in the question comments), this implementation will allow strings like "2d3d6", understanding this as "roll a d3 twice, then roll a d6 as many times as the result of the two rolls."

    Also, although there is some error checking, it still expects a valid input. Passing "*4" for example will result in an infinite loop.

    Here is the compressed version (not pretty):

    import re, random
    r=re.sub
    def e(s):
     s=r(" ","",s)
     s=r(r"(\d+|[d+*])",r"\1 ",s)
     s=r(r"(^|[+*] )d",r"\g<1>1 d",s)
     while"*"in s:s=r(r"([^+]+) \* ([^+]+)",r"\1 \2M ",s)
     while"+"in s:s=r(r"(.+) \+ (.+)",r"\1 \2A ",s)
     s=r(r"d (\d+)",r"\1 R",s)
     t=[]
     a=t.append
     p=t.pop
     for k in s.split():
      if k=="M":a(p()*p())
      elif k=="A":a(p()+p())
      elif k=="R":
       v=0
       d=p()
       for i in [[]]*p():v+=random.randint(1,d)
       a(v)
      else:a(int(k))
     return p()
    

提交回复
热议问题