Simulating Exploding Dice

Your task is to make a program that takes in an integer n > 1, and outputs the roll of a single n-sided die. However, this dice follows the rules for exploding dice.

When you roll the die, check what value you rolled. If you got the maximum for that kind of die (on a standard d4 that would be 4, or 6 on a d6, etc.), roll again and add the new roll to that total. Each roll continues adding to the total, until you don't roll the max number anymore. That final number is still added though.

Your program should take in a single integer n, and roll the exploding n-sided die. Here's an example distribution to show what it should look like for n=4. Note that you should never output any multiples of n, since they will always explode.

You can assume the stack size for any recursion you do is infinite, and your random function must meet our standards for randomness (built-in random generator or time/date). Your random function should also be as uniform as possible, vs. something like a geometric distribution, since these are dice we're talking about.

• does the program have to be perfect? Like can its distribution be off by some extremely low amount? Apr 12 '19 at 14:38
• To: Riker; RE: @Maltysen's comment above; or extremely high amount? Apr 12 '19 at 15:09
• @ArtemisFowl See our standards for randomness. Also, here. Apr 12 '19 at 22:46

Excel VBA, 46 bytes

Thanks to @TaylorScott

Do:v=-Int(-[A1]*Rnd):t=t+v:Loop While[A1]=v:?t

Executed in the command window.

As a user-defined function.

Excel VBA, 108 67 bytes

Function z(i)
Do
v=Int((i*Rnd)+1)
z=z+v
Loop While v=i
End Function
• You can get this down quite a bit by using a do..loop while loop, and converting the function into an immediate window function. - Do:v=-Int(-[A1]*Rnd):t=t+v:Loop While[A1]=v:?t - 46 Bytes Apr 15 '19 at 19:03
• @TaylorScott Thanks, I forgot that Do x While y existed in Excel VBA. Apr 15 '19 at 20:23

Excel (pure), 43 bytes

=A1*INT(-LOG(RAND(),A1))-INT(RAND()*(1-A1))

Takes input from A1, outputs in the cell you put this formula.

Explanation

This uses the observation from Robin Ryder's R solution that the requested distribution is the sum of n times a Geom(1-1/n) and an independent Uniform(1..n-1) distribution. The clever bit is that

INT(-LOG(RAND(),A1))

uses a logarithm to scale a Uniform(0,1) distribution exactly to the desired geometric distribution: for instance, with A1=6, the LOG maps the interval (1/6,1) to the interval (-1,0), the interval (1/36,1/6) to the interval (-2, -1), and so on. I also save a +1 on the uniform distribution by scaling a random number "backwards" to (1-A1, 0), then subtracting the floor of this negative number.

import System.Random
f n=do i<-randomRIO(1,n);last$((i+)<$>f n):[return i|i<n]

Try it online!

Original

Quite similar to @dfeuer's, but using do notation.

• -13 bytes by removing whitespace, thanks to @dfeuer
• -3 bytes thanks to this
• 81 bytes Apr 13 '19 at 2:13
• Wuups, fixed the typo! And thanks for the suggestion :)
– bugs
Apr 13 '19 at 10:16

Octave/MATLAB with Statistics Package/Toolbox, 30 bytes

@(n)geornd(1-1/n)*n+randi(n-1)

Try it online!

How it works

This is an anonymous function which takes n as input and produces a number obtained as follows. The function generates a geometric random variable with parameter 1-1/n (this models the number of rolls that produce n), multiplies by n, and adds a random variable uniformly distributed from 1 to n-1 (this models the last roll).

Vyxal, 25 10 bytes

λ?:ɾ℅~=[x+

Try it Online!

-15 thanks to Aaron Miller. This is a fully functional program as lambdas are automatically called at the end.

• recursion would probably make this simpler. May 8 '21 at 13:46
• @Razetime Yes probably, I'll try May 8 '21 at 20:21
• 11 bytes with recursion Jun 22 '21 at 20:28
• @AaronMiller 10 as you can close the if in the footer. Jun 22 '21 at 20:41
• Ok, I wasn't sure. I haven't really done many submissions that were just functions. Thanks for saying so! Jun 22 '21 at 20:43

><>, 90 bytes

0&  v
v:::<             <
v/1>{1-:?!v}
>x0^v10~  <
^
5=?v>:@}}*{+{2*l
&n;>,*:1%-1+:&+&=?^

Try it online!

The whitespace on the second line is bugging me. I'll work on golfing that out.

><> doesn't have a nice method for producing uniform random integers. This approach generates, for input $$\N\$$, a random number produced by generating $$\N\$$ random bits, then taking the resulting binary integer and dividing it by $$\2^N\$$. This process is repeated until $$\N\$$ is not generated by this process.

• – Jo King
Apr 13 '19 at 0:07

37 bytes: ${NestWhile[{_+Random[1,x]},{x|_},0]} Perl 6, 26 bytes {[+] {^$_ .roll+1}...$_>*} Try it online! Python 3, 80 bytes This is pretty much just a tail-call recursive version of @Artemis Fowl's answer, but I liked doing it without unrolling into a while loop. Uses an accumulator parameter to return the total rolled value once the exploding stops. from random import* def r(n,a=0):v=randint(1,n);a+=v;return r(n,a)if v==n else a • I don’t think python does tail-call optimization, does it? Regardless, good answer anyways! Apr 15 '19 at 17:21 • Unfortunately not. I've heard that Guido doesn't want python to turn into a functional language 😜 Apr 15 '19 at 17:23 Charcoal, 17 bytes ＮθＷ⁼Ｌυ№υθ⊞υ⊕‽θＩΣυ Try it online! Link is to verbose version of code. Explanation: Ｎθ Input n. Ｗ⁼Ｌυ№υθ Repeat while the predefined empty list only contains ns (i.e. its length equals its count of ns)... ⊞υ⊕‽θ ... push a random integer from 1 to n to the list. ＩΣυ Print the total. C# (Visual C# Interactive Compiler), 60 bytes int f(int n,int k=1)=>(k=new Random().Next(n)+1)<n?k:k+f(n); Saved 4 bytes thanks to @ExpiredData! Try it online! • 60 bytes Apr 12 '19 at 19:48 • @ExpiredData Using new Random() gives it the same seed if you call it again, which doesn't make it truly random Apr 12 '19 at 20:11 • Nah it's a new seed, run my tio a few times. But even if it was same seed random() isn't true random anyway it's a prng.... Apr 12 '19 at 20:40 • I've noticed when running on TIO, you either get values less than n or way, way, higher. I think creating a new Random in a tight loop causes the same seed to be used? In any case, this might save a byte? int f(int n,int k=1)=>(k+=new Random().Next(n))<n?k:k+f(n); – dana Apr 13 '19 at 5:38 • This is longer, but seems to give better results? var r=new Random();int k;int f(int n)=>(k=r.Next(n)+1)<n?k:k+f(n); – dana Apr 13 '19 at 5:42 Perl 6, 25 bytes {sum {.rand+|0+1}...$_>*}

Try it online!

F#, 83 bytes

let r=System.Random()
let e d=
let mutable t=0
while t%d=0 do t<-t+r.Next(d)+1
t

Try it online! The first argument in the TIO program is the number of sides on the die, the second is the amount of tests to make. The output is printed to the console and shows each total and the number of times it has been rolled.

The random number generator r is initialised outside of the function. Initialising it within the function, combined with calling this function many times in succession, will cause it to return the same random numbers over and over again (try it for yourself!)

For the life of me I could not figure out how to write this without using mutable, especially since the last roll must be counted (Seq.takeWhile would not include the terminating element). I thought about using Seq.mapFold but it seems like it evaluates the input sequence first, which was a no-go for me.

• You're implementing so many extra things. They don't appear to be in the code you're counting, though, so...okay. Apr 17 '19 at 8:18
• What do you mean by extra things? Apr 17 '19 at 20:04
• "shows ...the number of times it has been rolled", not a requirement. Apr 17 '19 at 21:14
• ..You need to roll one exploding die. Not 10,000. I'm not sure this is valid...? Apr 17 '19 at 21:22
• The submission does only roll one die. The TIO program demonstrates the function being run 10000 times to show that its results are distributed similar to the example distribution. It's common to have a TIO to show the function being used and that its output is valid. And that is not included in the byte count. Apr 18 '19 at 21:34

C (gcc), 36 32 bytes

-4 bytes thanks to Ben Voigt

f(n,x){x=rand()%n;x=x?x:n+f(n);}

Try it online!

Here a Test with 100k d4

• Shouldn't srand(time(0)) be included in the byte count? AFAIK rand() will always return the same value if it was never seeded Apr 12 '19 at 16:42
• I'm not entirely sure, but I remember the meta consensus being that using an unseed PRNG is acceptable @Tau Apr 12 '19 at 16:50
• This is not even close to being C code. Your recursive call has the wrong number of parameters, and you are missing a return. Apr 13 '19 at 18:46
• Even on gcc it returns 0 all the time (if compiled with -O3) or total garbage (if compiled with -O1). If you have optimization-unstable code that depends on particular compile settings, that should be mentioned. Apr 15 '19 at 14:32
• @BenVoigt unless mentioned in the post, it is assumed compilers use default options. The default for gcc is -O0, which works perfectly fine for this code. The purpose of codegolfing is to write code in as few bytes as possible, not write functional, production-ready code. If it works, it works. Apr 15 '19 at 14:55

Ruby, 28 bytes

f=->n{(a=rand n)>0?a:n+f[n]}

Try it online!

Java (JDK), 61 bytes

int f(int n){int r=1;r+=Math.random()*n;return r<n?r:r+f(n);}

Try it online!

Funky, 38 bytes

f=n=>ifn==x=math.random(n)f(n)+x elsex

Try it online!

C# (.NET Core), 155 153 145 144 bytes

using C=System.Console;class A{static void Main(){int i=int.Parse(C.ReadLine()),j=0;while((j+=new System.Random().Next(i)+1)%i<1){}C.Write(j);}}

Try it online!

This code instantiates a new RNG every time, but that uses time as a seed anyway so it should still satisfy randomness requirements.

I wonder if console input is cheaper in the long run.

• Saved 2 bytes by using [0,i)+1 rather than [1,i+1) Apr 17 '19 at 19:35
• Saved 8 bytes by forgoing principles about RNG and just making a new one in the loop. Apr 17 '19 at 21:19
• Saved one byte w/ <1 instead of ==0 Apr 20 '19 at 7:35

Clojure, 63 bytes

#(loop[n 0](let[r(+ n(rand-int %)1)](if(<(- r n)%)r(recur r))))

A naive solution to the problem.

Expanded:

(defn exploding-die [sides]
(loop [total 0]
(let [new-total (+ total (rand-int sides) 1)]
(if (< (- new-total total) sides) new-total
(recur new-total)))))

VTL-2, 5453 51 bytes

1 A=?
2 C=C+B
3 B='/A*0+%+1
4 #=B=A*2
5 ?=C+B

Line 1 takes input into variable A, this will be our die's sidedness. Line 2 does what it appears to, though it's important to note that in VTL-2, referencing a variable that hasn't been initialized assumes 0, so for the first pass, this is C=0+0. Line 3 divides a random number by our dice sides (' is the system variable for a random number) and then turns this into a roll - % is the remainder of the last division operation; add 1 and put this in B. Line 4 is a little cryptic: B=A is evaluated first. It evaluates to 1 if the two are equal (if our die roll is the same as its number of sides), otherwise it evaluates to 0. This result is multiplied by two (*2), and then the final value here is handed to #=, which is equivalent to a GOTO. If this is given a zero, it ignores it; otherwise we GOTO 2, adding the roll to the total and rolling again. Line 6 prints the total of C+B.

Had an epiphany right after posting this, and golfed off one byte. I was doing 3 B='/A and 4 B=%+1 because % is a system variable with the remainder of the last division operation; you need to do a division operation to get that value. But it occurred to me that I could do the division and then multiply that by zero since I don't need it. But since I've done it, the remainder is now in % and so I can add that to the zero I just made, and add 1 to get the die roll. This is long, but still shorter than two lines - line numbers always take two bytes in VTL-2, plus a space to separate the line number, plus a CR.

Second edit, did my math wildly wrong on byte count both times. Eesh. Third, golfed off two leftover parens.

• Is this the language from the Altair systems? Google seems to imply so. But this is really cool! May 4 '19 at 20:55
• @Rɪᴋᴇʀ Yup! Stumbled across it recently while looking for some totally unrelated 680 info. I'm running the interpreter in an 8800 emulator. It's a very interesting language, a lot of fun design decisions were made to fit it in ~1KB (VTL-1 was closer to ~700B!). May 4 '19 at 21:29
• Huh, that's pretty cool. I'm glad you decided to answer my challenge in that language, of all things. May 4 '19 at 23:38

MMIX, 40 bytes (10 instrs)

Assumes TRAP 82,78,71¹ gets a randomly-generated uint64_t, by some method the OS knows about, but we don't have to.

(jxd -T)

00000000: e3010000 f7010000 00524e47 1effff00  ẉ¢¡¡ẋ¢¡¡¡RNGœ””¡
00000010: feff0006 220101ff 22010100 43fffffb  “”¡©"¢¢”"¢¢¡C””»
00000020: 26000100 f8010000                    &¡¢¡ẏ¢¡¡

Disassembled:

expl    SETL $1,0 PUT rD,0 0H TRAP 82,78,71 // loop: tmp = sysrand() DIVU$255,$255,0 GET$255,rR        // tmp = tmp % n
ADDU $1,$1,$255 ADDU$1,$1,$0       // t += tmp + n
BZ   $255,0B // if(!tmp) goto loop SUBU$0,$1,$0
POP  1,0            // return t - n

The main hack here is to treat every roll as being in the range $$\[n,2n-1]\$$, then subtracting $$\n\$$ once after everything is over.

¹ Yes, those numbers were carefully picked.

Ly, 16 bytes

00ns[p+1l?l=]p+u

Try it online!

This one is pretty much a direct translations of the rules of the challenge.

00n              # Push "0", "0" and the input number on the stack
s             # Save the input number in the backup cell
[       ]    # Loop, quits when the random number doesn't match the die size
p+          # Delete loop iterator check, add the remain two entries
# (The stack holds the accumulated value and the last roll)
1l?       # Generate a random number between 1 and die size, inclusive
l=     # Compare the random roll to the saved die size
p+u # Add the last role and print as a number