tags:

views:

243

answers:

5

I'm writing a custom dice rolling parser (snicker if you must) in python. Basically, I want to use standard math evaluation but add the 'd' operator:

#xdy
sum = 0
for each in range(x):
    sum += randInt(1, y)
return sum

So that, for example, 1d6+2d6+2d6-72+4d100 = (5)+(1+1)+(6+2)-72+(5+39+38+59) = 84

I was using regex to replace all 'd's with the sum and then using eval, but my regex fell apart when dealing with parentheses on either side. Is there a faster way to go about this than implementing my own recursive parsing? Perhaps adding an operator to eval?

Edit: I seem to have given a bad example, as the above example works with my current version. What I'm looking for is some way to evaluate, say, (5+(6d6))d(7-2*(1d4)).
By "fell apart", I just meant that my current regex expression failed. I have been too vague about my failure, sorry for the confusion. Here's my current code:

def evalDice(roll_matchgroup):
    roll_split = roll_matchgroup.group('roll').split('d')
    print roll_split
    roll_list = []

    for die in range(int(roll_split[0])):
        roll = random.randint(1,int(roll_split[1]))
        roll_list.append(roll)

def EvalRoll(roll):
    if not roll: return 0
    rollPattern = re.compile('(?P<roll>\d*d\d+)')
    roll_string = rollPattern.sub(evalDice, roll.lower())

for this, "1d6+4d100" works just fine, but "(1d6+4)d100" or even "1d6+4d(100)" fails.

+5  A: 

Python doesn't let you write brand new operators, and you can't do parentheses with a regular language. You'll have to write a recursive descent parser. This should be pretty simple for your dice-rolling language though.

Alternatively, you could coopt an existing Python operator and use Pythons parsing tools to convert the text into an AST.

Marcelo Cantos
I think the parens were just as a demonstrative intermediate result, not part of the DSL itself. Writing `1d6+2d6+2d6-72+4d100 = 84` wouldn't have been all that explanatory. +1 for the rec descent suggestion, though.
Marc Bollinger
Then I'm confused. Why did the OP's "regex [fall] apart when dealing with the parentheses"?
Marcelo Cantos
I was rather more vague than I thought, sorry. The above example works fine. What I meant is that if you put parentheses on either side of the d (like (4+2)d6) the interpreter couldn't handle evaluating that.
taynaron
+6  A: 

You could use a callback function with re.sub. When you follow the link, search down to the paragraph beginning with "If repl is a function..."

import re
import random

def xdy(matchobj):
    x,y=map(int,matchobj.groups())
    s = 0
    for each in range(x):
        s += random.randint(1, y)
    return str(s)
s='1d6+2d6+2d6-72+4d100'
t=re.sub('(\d+)d(\d+)',xdy,s)
print(t)
# 5+10+8-72+197
print(eval(t))
# 148
unutbu
A: 

This uses eval, which is pretty awful really, but here you go

>>> x = '1d6+2d6+2d6-72+4d100'
>>> eval(re.sub(r'(\d+)d(\d+)',r'sum((random.randint(1,x) for x in \1 * [\2]))', x))

Some quick notes:

This replaces, say, 4d6 with sum((random.randint(1,x) for x in 4 * [6])).

4 * [6] yields the list [6,6,6,6].

((random.randint(1,x) for x in [6,6,6,6])) is the generator equivalent of a list comprehension; this particular one will return four random numbers between 1 and 6.

kibibu
+1  A: 

Take a look at the PyParsing library. In particular, the examples page has a sample fairly close to what you want:

dice2.py

A dice roll parser and evaluator for evaluating strings such as "4d20+5.5+4d6.takeHighest(3)".

ars
A: 

In my Supybot dice plugin I parse the expression with

r'(?P<sign>[+-])((?P<dice>\d*)d(?P<sides>\d+)|(?P<mod>\d+))'

then get total numbers of each dice and a total modifier, roll them and get total result (I wanted to show total numbers of each dice).

wRAR