My target was to write a function that takes a String as input, interprets it as a series of dice rolls, evaluates it and returns a series of integers.

The input is in a form that is typical in table top RPGs, slightly extended:

  • 1d20; d20; 20 - Roll a single twenty sided die, result would be e.g. 12
  • 2d6 + 4 - Roll two six sided dice and add 4 to the sum, result would be e.g. 12 (5 [d6] + 3 [d6] + 4)
  • d20, d20; 20, (note the comma) - Roll two twenty sided dice and display the results separately, result would be e.g. 6 // 14

I handled the parsing with instaparse. The function I am interested in feedback for, which you'll find below, is the one that then transforms a single "roll".

According to my specifications, I can add any number of commas (,) to a roll (a series of dice and constant modifiers). I opted to have those additional trailing commas parsed as being part of the preceding roll, syntactically, being added as :repeat flags to the end of the syntax tree. Because these rolls can be repeated, I had to pass functions instead of values, so the repeated rolls would actually yield different results. That means example arguments to this function can look like this:

[#(d 4 12) #(+ 5) :repeat :repeat] - which would correspond to "4d12 + 5,,", where d is defined as follows:

(defn d
    ([count faces]
     (reduce + (map d (repeat count faces))))
    ([faces] (inc (rand-int faces))))

Without further ado:

(defn roll [& dice-info]
    (let [until-repeat (complement {:repeat true})
          [dice repeat] (split-with until-repeat dice-info)
          eval #(%)
          eval-dice #(map eval dice)
          eval-roll (fn [_] (apply + (eval-dice)))]
        (map eval-roll (cons :repeat repeat))))

I am particularly interested in how idiomatic this is, and whether the approach I've chosen is reasonable, but of course any input is welcome.

Here is the grammar text:

Series = Roll ( <','> Roll )*

Roll = <S?> ( QuickDie | ( Die ( <S?> Modifier)* ) ) <S?> Repeat*
Repeat = <',' S?>

Die = Integer? <'d'> Integer
QuickDie = Integer
Integer = #"([1-9][0-9]*|0)"

<Modifier> = ( Plus | Minus )
Plus = <'+'> <S?> Addend
Minus = <'-'> <S?> Addend
<Addend> = ( Integer | Die )

S = #"\s+"

And for completeness' sake, the whole code: DiceRoll.clj and DiceRoll.eval.clj.

  • \$\begingroup\$ Can we see your Instaparse grammar text, please? \$\endgroup\$ – Thumbnail Apr 28 '17 at 21:50
  • \$\begingroup\$ @Thumbnail sure, added it. \$\endgroup\$ – ammut Apr 29 '17 at 21:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.