J, 16 11 bytes
(+$:)^:=1+?
Try it online!
Explanation
TL;DR 1+?
performs the die roll, (+$:)^:=
reiterates only when it equals the input.
The function is a train of 4 verbs:
┌─ +
┌───┴─ $:
┌─ ^: ─┴─ =
│
──┤ ┌─ 1
└──────┼─ +
└─ ?
A train is when 2 or more verbs are concatenated. Here, the answer is of the form f g h j
:
(+$:)^:= 1 + ?
f g h j
A so-called "4-train" is parsed as a hook and a fork:
f g h j ⇔ f (g h j)
Thus, the answer is equivalent to:
(+$:)^:= (1 + ?)
Hooks: (f g) x
and x (f g) y
A monadic (one-argument) hook of two verbs, given an argument x
, the following equivalence holds:
(f g) x ⇔ x f (g x)
For example, (* -) 5
evaluates to 5 * (- 5)
, which evaluates to _25
.
This means that our 4-train, a hook of f
and (g h j)
, is equivalent to:
(f (g h j)) x ⇔ x f ((g h j) x)
But what does f
do here? (+$:)^:=
is a conjunction of two verbs using the Power conjunction ^:
: another hook ((+$:)
) and a verb (=
). Note here that f
is dyadic—it has two arguments (x
and (g h j) x
). So we have to look at how ^:
behaves. The power conjunction f^:o
takes a verb f
and either a verb or a noun o
(a noun is just a piece of data) and applies f
o
times. For example, take o = 3
. The following equivalences holds:
(f^:3) x ⇔ f (f (f x))
x (f^:3) y ⇔ x f (x f (x f y))
If o
is a verb, the power conjunction will simply evaluate o
over the arguments and use the noun result as the repeat count.
For our verb, o
is =
, the equality verb. It evaluates to 0
for differing arguments and to 1
for equal arguments. We repeat the hook (+$:)
once for equal arguments and no times for differing ones. For ease of notation for the explanation, let y ⇔ ((g h j) x)
. Remember that our initial hook is equivalent to this:
x (+$:)^:= ((g h j) x)
x (+$:)^:= y
Expanding the conjunction, this becomes:
x ((+$:)^:(x = y)) y
If x
and y
are the same, this becomes:
x (+$:)^:1 y ⇔ x (+$:) y
Otherwise, this becomes:
x (+$:)^:0 y ⇔ y
Now, we've seen monadic forks. Here, we have a dyadic fork:
x (f g) y ⇔ x f (g y)
So, when x
and y
are the same, we get:
x (+$:) y ⇔ x + ($: y)
What is $:
? It refers to the entire verb itself and allows for recursion. This means that, when x
and yare the same, we apply the verb to
yand add
x` to it.
Forks: (g h j) x
Now, what does the inner fork do? This was y
in our last example. For a monadic fork of three verbs, given an argument x
, the following equivalence hold:
(g h j) x ⇔ (g x) h (j x)
For this next example, suppose we have verbs named SUM
, DIVIDE
, and LENGTH
, which do what you suppose they might. If we concatenate the three into a fork, we get:
(SUM DIVIDE LENGTH) x ⇔ (SUM x) DIVIDE (LENGTH x)
This fork evaluates to the average of x
(assuming x
is a list of numbers). In J, we'd actually write this as example as +/ % #
.
One last thing about forks. When the leftmost "tine" (in our symbolic case above, g
) is a noun, it is treated as a constant function returning that value.
With all this in place, we can now understand the above fork:
(1 + ?) x ⇔ (1 x) + (? x)
⇔ 1 + (? x)
?
here gives a random integer in the range \$[0,x)\$, so we need to transform the range to represent dice; incrementing yields the range \$[1, x]\$.
Putting it all together
Given all these things, our verb is equivalent to:
((+$:)^:=1+?) x ⇔ ((+$:)^:= 1 + ?) x
⇔ ((+$:)^:= (1 + ?)) x
⇔ x ((+$:)^:=) (1 + ?) x
⇔ x ((+$:)^:=) (1 + (? x))
⇔ x (+$:)^:(x = (1 + (? x))
(let y = 1 + (? x))
if x = y ⇒ x + $: y
otherwise ⇒ y
This expresses the desired functionality.