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
    Commented Apr 28, 2017 at 21:50
  • \$\begingroup\$ @Thumbnail sure, added it. \$\endgroup\$
    – ammut
    Commented Apr 29, 2017 at 21:06


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Browse other questions tagged or ask your own question.